mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-05 01:39:43 +00:00
Compare commits
37 Commits
config-lab
...
dashboard_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2e1d914305 | ||
![]() |
c26775c0bd | ||
![]() |
a5db0708f0 | ||
![]() |
fb6683065c | ||
![]() |
3b0fa6d50b | ||
![]() |
83b372d951 | ||
![]() |
c1ef064e8f | ||
![]() |
028b395cad | ||
![]() |
05fe855147 | ||
![]() |
c02fa81e64 | ||
![]() |
ad7a032c6a | ||
![]() |
91920036ca | ||
![]() |
efffc3001e | ||
![]() |
6b7b77c3b1 | ||
![]() |
d0ab4ea9c5 | ||
![]() |
ac0cf1f9b8 | ||
![]() |
a127fd4637 | ||
![]() |
343fb3e7c5 | ||
![]() |
b49b522c2e | ||
![]() |
d526ff7a66 | ||
![]() |
246100809d | ||
![]() |
6efca93186 | ||
![]() |
6280647b9a | ||
![]() |
2ff52c6c29 | ||
![]() |
d038e11170 | ||
![]() |
8925b39fe5 | ||
![]() |
beeef65506 | ||
![]() |
994c1b5751 | ||
![]() |
6823c647b6 | ||
![]() |
866b478dc0 | ||
![]() |
d746dc5752 | ||
![]() |
5f53e1e71c | ||
![]() |
3da82df093 | ||
![]() |
4cedfffb71 | ||
![]() |
1e1514e7da | ||
![]() |
60e07075bc | ||
![]() |
c998086474 |
@@ -52,7 +52,7 @@
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/luxon3": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@home-assistant/webawesome": "3.0.0-beta.4.ha.3",
|
||||
"@home-assistant/webawesome": "3.0.0-beta.6.ha.0",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lit-labs/motion": "1.0.9",
|
||||
"@lit-labs/observers": "2.0.6",
|
||||
@@ -157,7 +157,7 @@
|
||||
"@octokit/auth-oauth-device": "8.0.1",
|
||||
"@octokit/plugin-retry": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@rsdoctor/rspack-plugin": "1.3.0",
|
||||
"@rsdoctor/rspack-plugin": "1.3.1",
|
||||
"@rspack/core": "1.5.7",
|
||||
"@rspack/dev-server": "1.1.4",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
@@ -203,7 +203,7 @@
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "27.0.0",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "16.2.1",
|
||||
"lint-staged": "16.2.2",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
|
@@ -18,6 +18,7 @@ export const FIXED_DOMAIN_STATES = {
|
||||
"pending",
|
||||
"triggered",
|
||||
],
|
||||
alert: ["on", "off", "idle"],
|
||||
assist_satellite: ["idle", "listening", "responding", "processing"],
|
||||
automation: ["on", "off"],
|
||||
binary_sensor: ["on", "off"],
|
||||
|
@@ -40,6 +40,7 @@ const STATE_COLORED_DOMAIN = new Set([
|
||||
"vacuum",
|
||||
"valve",
|
||||
"water_heater",
|
||||
"weather",
|
||||
]);
|
||||
|
||||
export const stateColorCss = (stateObj: HassEntity, state?: string) => {
|
||||
|
@@ -32,6 +32,8 @@ export const numberFormatToLocale = (
|
||||
return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
|
||||
case NumberFormat.space_comma:
|
||||
return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
|
||||
case NumberFormat.quote_decimal:
|
||||
return ["de-CH"]; // Use German (Switzerland) formatting 1'234'567.89
|
||||
case NumberFormat.system:
|
||||
return undefined;
|
||||
default:
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import type { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
||||
import type { FrontendLocaleData } from "../../data/translation";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { LocalizeFunc } from "./localize";
|
||||
import { computeEntityName } from "../entity/compute_entity_name";
|
||||
import { computeDeviceName } from "../entity/compute_device_name";
|
||||
import { getEntityContext } from "../entity/context/get_entity_context";
|
||||
import { computeAreaName } from "../entity/compute_area_name";
|
||||
import { computeFloorName } from "../entity/compute_floor_name";
|
||||
import { ensureArray } from "../array/ensure-array";
|
||||
import { computeAreaName } from "../entity/compute_area_name";
|
||||
import { computeDeviceName } from "../entity/compute_device_name";
|
||||
import { computeEntityName } from "../entity/compute_entity_name";
|
||||
import { computeFloorName } from "../entity/compute_floor_name";
|
||||
import { getEntityContext } from "../entity/context/get_entity_context";
|
||||
import type { LocalizeFunc } from "./localize";
|
||||
|
||||
export type FormatEntityStateFunc = (
|
||||
stateObj: HassEntity,
|
||||
@@ -77,7 +77,6 @@ export const computeFormatFunctions = async (
|
||||
computeAttributeNameDisplay(localize, stateObj, entities, attribute),
|
||||
formatEntityName: (stateObj, type, separator = " ") => {
|
||||
const types = ensureArray(type);
|
||||
const namesList: (string | undefined)[] = [];
|
||||
|
||||
const { device, area, floor } = getEntityContext(
|
||||
stateObj,
|
||||
@@ -87,33 +86,24 @@ export const computeFormatFunctions = async (
|
||||
floors
|
||||
);
|
||||
|
||||
for (const t of types) {
|
||||
const names = types
|
||||
.map((t) => {
|
||||
switch (t) {
|
||||
case "entity": {
|
||||
namesList.push(computeEntityName(stateObj, entities, devices));
|
||||
break;
|
||||
case "entity":
|
||||
return computeEntityName(stateObj, entities, devices);
|
||||
case "device":
|
||||
return device ? computeDeviceName(device) : undefined;
|
||||
case "area":
|
||||
return area ? computeAreaName(area) : undefined;
|
||||
case "floor":
|
||||
return floor ? computeFloorName(floor) : undefined;
|
||||
default:
|
||||
return t;
|
||||
}
|
||||
case "device": {
|
||||
if (device) {
|
||||
namesList.push(computeDeviceName(device));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "area": {
|
||||
if (area) {
|
||||
namesList.push(computeAreaName(area));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "floor": {
|
||||
if (floor) {
|
||||
namesList.push(computeFloorName(floor));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return namesList.filter((name) => name !== undefined).join(separator);
|
||||
})
|
||||
.filter((name) => name !== undefined);
|
||||
|
||||
return names.join(separator);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@@ -6,6 +6,8 @@ import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { stateColorProperties } from "../../common/entity/state_color";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { computeCssValue } from "../../resources/css-variables";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
|
||||
|
||||
const DOMAIN_STATE_SHADES: Record<string, Record<string, number>> = {
|
||||
media_player: {
|
||||
@@ -51,6 +53,28 @@ function computeTimelineStateColor(
|
||||
let colorIndex = 0;
|
||||
const stateColorMap = new Map<string, string>();
|
||||
|
||||
function computeTimelineEnumColor(
|
||||
state: string,
|
||||
computedStyles: CSSStyleDeclaration,
|
||||
stateObj?: HassEntity
|
||||
): string | undefined {
|
||||
if (!stateObj) {
|
||||
return undefined;
|
||||
}
|
||||
const domain = computeStateDomain(stateObj);
|
||||
const states =
|
||||
FIXED_DOMAIN_STATES[domain] ||
|
||||
(domain === "sensor" &&
|
||||
stateObj.attributes.device_class === "enum" &&
|
||||
stateObj.attributes.options) ||
|
||||
[];
|
||||
const idx = states.indexOf(state);
|
||||
if (idx === -1) {
|
||||
return undefined;
|
||||
}
|
||||
return getGraphColorByIndex(idx, computedStyles);
|
||||
}
|
||||
|
||||
function computeTimeLineGenericColor(
|
||||
state: string,
|
||||
computedStyles: CSSStyleDeclaration
|
||||
@@ -71,6 +95,7 @@ export function computeTimelineColor(
|
||||
): string {
|
||||
return (
|
||||
computeTimelineStateColor(state, computedStyles, stateObj) ||
|
||||
computeTimelineEnumColor(state, computedStyles, stateObj) ||
|
||||
computeTimeLineGenericColor(state, computedStyles)
|
||||
);
|
||||
}
|
||||
|
473
src/components/entity/ha-entity-name-picker.ts
Normal file
473
src/components/entity/ha-entity-name-picker.ts
Normal file
@@ -0,0 +1,473 @@
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { getEntityContext } from "../../common/entity/context/get_entity_context";
|
||||
import type { EntityNameType } from "../../common/translations/entity-state";
|
||||
import {
|
||||
ensureEntityNameItems,
|
||||
formatEntityDisplayName,
|
||||
type EntityNameConfig,
|
||||
type EntityNameItem,
|
||||
} from "../../panels/lovelace/common/entity/entity-display-name";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../chips/ha-assist-chip";
|
||||
import "../chips/ha-chip-set";
|
||||
import "../chips/ha-filter-chip";
|
||||
import "../chips/ha-input-chip";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-list-item";
|
||||
import "../ha-select";
|
||||
import "../ha-sortable";
|
||||
import "../ha-textfield";
|
||||
|
||||
interface EntityNameOption {
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<EntityNameOption> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">${item.primary}</span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
const KNOWN_TYPES = new Set(["entity", "device", "area", "floor"]);
|
||||
|
||||
const UNIQUE_TYPES = new Set(["entity", "device", "area", "floor"]);
|
||||
|
||||
@customElement("ha-entity-name-picker")
|
||||
export class HaEntityNamePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId?: string;
|
||||
|
||||
@property() public value?: EntityNameConfig;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@query(".container", true) private _container?: HTMLDivElement;
|
||||
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
private _editIndex?: number;
|
||||
|
||||
private _validOptions = memoizeOne((entityId: string) => {
|
||||
const stateObj = this.hass.states[entityId];
|
||||
if (!stateObj) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const context = getEntityContext(
|
||||
stateObj,
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.floors
|
||||
);
|
||||
const options: EntityNameType[] = ["entity"];
|
||||
if (context.device) options.push("device");
|
||||
if (context.area) options.push("area");
|
||||
if (context.floor) options.push("floor");
|
||||
return options;
|
||||
});
|
||||
|
||||
private _getOptions = memoizeOne((entityId?: string) => {
|
||||
if (!entityId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const items = this._validOptions(entityId).map<EntityNameOption>(
|
||||
(name) => ({
|
||||
primary: this.hass.localize(
|
||||
`ui.components.entity.entity-name-picker.types.${name}`
|
||||
),
|
||||
secondary: formatEntityDisplayName(
|
||||
this.hass,
|
||||
this.hass.states[entityId],
|
||||
name
|
||||
),
|
||||
value: name,
|
||||
})
|
||||
);
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
private _formatItem = (item: EntityNameItem) => {
|
||||
if (item.type === "text") {
|
||||
return `"${item.text}"`;
|
||||
}
|
||||
if (KNOWN_TYPES.has(item.type)) {
|
||||
return this.hass.localize(
|
||||
`ui.components.entity.entity-name-picker.types.${item.type as EntityNameType}`
|
||||
);
|
||||
}
|
||||
return item.type;
|
||||
};
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const value = this._value;
|
||||
const options = this._getOptions(this.entityId);
|
||||
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<div class="container">
|
||||
<ha-sortable
|
||||
no-style
|
||||
@item-moved=${this._moveItem}
|
||||
.disabled=${this.disabled}
|
||||
handle-selector="button.primary.action"
|
||||
filter=".add"
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
this._value,
|
||||
(item) => item,
|
||||
(item: EntityNameItem, idx) => {
|
||||
const label = this._formatItem(item);
|
||||
return html`
|
||||
<ha-input-chip
|
||||
data-idx=${idx}
|
||||
@remove=${this._removeItem}
|
||||
@click=${this._editItem}
|
||||
.label=${label}
|
||||
.selected=${!this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDrag}></ha-svg-icon>
|
||||
<span>${label}</span>
|
||||
</ha-input-chip>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
${this.disabled
|
||||
? nothing
|
||||
: html`
|
||||
<ha-assist-chip
|
||||
@click=${this._addItem}
|
||||
.disabled=${this.disabled}
|
||||
label=${this.hass.localize(
|
||||
"ui.components.entity.entity-name-picker.add"
|
||||
)}
|
||||
class="add"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
`}
|
||||
</ha-chip-set>
|
||||
</ha-sortable>
|
||||
|
||||
<mwc-menu-surface
|
||||
.open=${this._opened}
|
||||
@closed=${this._onClosed}
|
||||
@opened=${this._onOpened}
|
||||
@input=${stopPropagation}
|
||||
.anchor=${this._container}
|
||||
>
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.value=${""}
|
||||
.autofocus=${this.autofocus}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
.required=${this.required && !value.length}
|
||||
.helper=${this.helper}
|
||||
.items=${options}
|
||||
allow-custom-value
|
||||
item-id-path="value"
|
||||
item-value-path="value"
|
||||
item-label-path="primary"
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
>
|
||||
</ha-combo-box>
|
||||
</mwc-menu-surface>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onClosed(ev) {
|
||||
ev.stopPropagation();
|
||||
this._opened = false;
|
||||
this._editIndex = undefined;
|
||||
}
|
||||
|
||||
private async _onOpened(ev) {
|
||||
if (!this._opened) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
this._opened = true;
|
||||
await this._comboBox?.focus();
|
||||
await this._comboBox?.open();
|
||||
}
|
||||
|
||||
private async _addItem(ev) {
|
||||
ev.stopPropagation();
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private async _editItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const idx = parseInt(ev.currentTarget.dataset.idx, 10);
|
||||
this._editIndex = idx;
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private get _value(): EntityNameItem[] {
|
||||
return this._toItems(this.value);
|
||||
}
|
||||
|
||||
private _toItems = memoizeOne((value?: EntityNameConfig) =>
|
||||
value ? ensureEntityNameItems(value) : []
|
||||
);
|
||||
|
||||
private _toValue = memoizeOne((items: EntityNameItem[]): EntityNameConfig => {
|
||||
const compactItems = items.map((item) => {
|
||||
if (item.type === "text") {
|
||||
return item.text;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return compactItems.length === 0
|
||||
? []
|
||||
: compactItems.length === 1
|
||||
? compactItems[0]
|
||||
: compactItems;
|
||||
});
|
||||
|
||||
private _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||
const open = ev.detail.value;
|
||||
if (open) {
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const initialItem =
|
||||
this._editIndex != null ? this._value[this._editIndex] : undefined;
|
||||
|
||||
const initialValue = initialItem
|
||||
? initialItem.type === "text"
|
||||
? initialItem.text
|
||||
: initialItem.type
|
||||
: "";
|
||||
|
||||
const filteredItems = this._filterSelectedOptions(options, initialValue);
|
||||
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
this._comboBox.setInputValue(initialValue);
|
||||
} else {
|
||||
this._opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _filterSelectedOptions = (
|
||||
options: EntityNameOption[],
|
||||
current?: string
|
||||
) => {
|
||||
const value = this._value;
|
||||
|
||||
const types = value.map((item) => item.type) as string[];
|
||||
|
||||
const filteredOptions = options.filter(
|
||||
(option) =>
|
||||
!UNIQUE_TYPES.has(option.value) ||
|
||||
!types.includes(option.value) ||
|
||||
option.value === current
|
||||
);
|
||||
return filteredOptions;
|
||||
};
|
||||
|
||||
private _filterChanged(ev: ValueChangedEvent<string>) {
|
||||
const input = ev.detail.value;
|
||||
const filter = input?.toLowerCase() || "";
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const currentItem =
|
||||
this._editIndex != null ? this._value[this._editIndex] : undefined;
|
||||
|
||||
const currentValue = currentItem
|
||||
? currentItem.type === "text"
|
||||
? currentItem.text
|
||||
: currentItem.type
|
||||
: "";
|
||||
|
||||
this._comboBox.filteredItems = this._filterSelectedOptions(
|
||||
options,
|
||||
currentValue
|
||||
);
|
||||
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fuseOptions: IFuseOptions<EntityNameOption> = {
|
||||
keys: ["primary", "secondary", "value"],
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: Math.min(filter.length, 2),
|
||||
threshold: 0.2,
|
||||
ignoreDiacritics: true,
|
||||
};
|
||||
|
||||
const fuse = new Fuse(this._comboBox.filteredItems, fuseOptions);
|
||||
const filteredItems = fuse.search(filter).map((result) => result.item);
|
||||
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
}
|
||||
|
||||
private async _moveItem(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
const value = this._value;
|
||||
const newValue = value.concat();
|
||||
const element = newValue.splice(oldIndex, 1)[0];
|
||||
newValue.splice(newIndex, 0, element);
|
||||
this._setValue(newValue);
|
||||
await this.updateComplete;
|
||||
this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>);
|
||||
}
|
||||
|
||||
private async _removeItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const value = [...this._value];
|
||||
const idx = parseInt(ev.target.dataset.idx, 10);
|
||||
value.splice(idx, 1);
|
||||
this._setValue(value);
|
||||
await this.updateComplete;
|
||||
this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>);
|
||||
}
|
||||
|
||||
private _comboBoxValueChanged(ev: ValueChangedEvent<string>): void {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (this.disabled || value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
const item: EntityNameItem = KNOWN_TYPES.has(value as any)
|
||||
? { type: value as EntityNameType }
|
||||
: { type: "text", text: value };
|
||||
|
||||
const newValue = [...this._value];
|
||||
|
||||
if (this._editIndex != null) {
|
||||
newValue[this._editIndex] = item;
|
||||
} else {
|
||||
newValue.push(item);
|
||||
}
|
||||
|
||||
this._setValue(newValue);
|
||||
}
|
||||
|
||||
private _setValue(value: EntityNameItem[]) {
|
||||
const newValue = this._toValue(value);
|
||||
this.value = newValue;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
background-color: var(--mdc-text-field-fill-color, whitesmoke);
|
||||
border-radius: 4px;
|
||||
border-end-end-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
}
|
||||
.container:after {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: var(
|
||||
--mdc-text-field-idle-line-color,
|
||||
rgba(0, 0, 0, 0.42)
|
||||
);
|
||||
transform:
|
||||
height 180ms ease-in-out,
|
||||
background-color 180ms ease-in-out;
|
||||
}
|
||||
:host([disabled]) .container:after {
|
||||
background-color: var(
|
||||
--mdc-text-field-disabled-line-color,
|
||||
rgba(0, 0, 0, 0.42)
|
||||
);
|
||||
}
|
||||
.container:focus-within:after {
|
||||
height: 2px;
|
||||
background-color: var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.add {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
mwc-menu-surface {
|
||||
--mdc-menu-min-width: 100%;
|
||||
}
|
||||
|
||||
ha-chip-set {
|
||||
padding: 8px 8px;
|
||||
}
|
||||
|
||||
.sortable-fallback {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.sortable-drag {
|
||||
cursor: grabbing;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-name-picker": HaEntityNamePicker;
|
||||
}
|
||||
}
|
@@ -112,7 +112,7 @@ export class HaEntityToggle extends LitElement {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
const stateDomain = computeStateDomain(this.stateObj);
|
||||
let serviceDomain;
|
||||
let service;
|
||||
|
50
src/components/ha-selector/ha-selector-entity-name.ts
Normal file
50
src/components/ha-selector/ha-selector-entity-name.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { EntityNameSelector } from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../entity/ha-entity-name-picker";
|
||||
|
||||
@customElement("ha-selector-entity_name")
|
||||
export class HaSelectorEntityName extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: EntityNameSelector;
|
||||
|
||||
@property() public value?: string | string[];
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@property({ attribute: false }) public context?: {
|
||||
entity?: string;
|
||||
};
|
||||
|
||||
protected render() {
|
||||
const value = this.value ?? this.selector.entity_name?.default_name;
|
||||
|
||||
return html`
|
||||
<ha-entity-name-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.selector.entity_name?.entity_id ||
|
||||
this.context?.entity}
|
||||
.value=${value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-entity-name-picker>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-entity_name": HaSelectorEntityName;
|
||||
}
|
||||
}
|
@@ -29,6 +29,7 @@ const LOAD_ELEMENTS = {
|
||||
device: () => import("./ha-selector-device"),
|
||||
duration: () => import("./ha-selector-duration"),
|
||||
entity: () => import("./ha-selector-entity"),
|
||||
entity_name: () => import("./ha-selector-entity-name"),
|
||||
statistic: () => import("./ha-selector-statistic"),
|
||||
file: () => import("./ha-selector-file"),
|
||||
floor: () => import("./ha-selector-floor"),
|
||||
|
@@ -15,7 +15,7 @@ export class HaSwitch extends SwitchBase {
|
||||
super.firstUpdated();
|
||||
this.addEventListener("change", () => {
|
||||
if (this.haptic) {
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -28,6 +28,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export const forwardHaptic = (hapticType: HapticType) => {
|
||||
fireEvent(window, "haptic", hapticType);
|
||||
export const forwardHaptic = (node: HTMLElement, hapticType: HapticType) => {
|
||||
fireEvent(node, "haptic", hapticType);
|
||||
};
|
||||
|
@@ -7,6 +7,7 @@ import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
||||
import { isHelperDomain } from "../panels/config/helpers/const";
|
||||
import type { EntityNameConfig } from "../panels/lovelace/common/entity/entity-display-name";
|
||||
import type { UiAction } from "../panels/lovelace/components/hui-action-editor";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import {
|
||||
@@ -41,6 +42,7 @@ export type Selector =
|
||||
| LegacyDeviceSelector
|
||||
| DurationSelector
|
||||
| EntitySelector
|
||||
| EntityNameSelector
|
||||
| LegacyEntitySelector
|
||||
| FileSelector
|
||||
| IconSelector
|
||||
@@ -499,6 +501,13 @@ export interface UiStateContentSelector {
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface EntityNameSelector {
|
||||
entity_name: {
|
||||
entity_id?: string;
|
||||
default_name?: EntityNameConfig | EntityNameConfig[];
|
||||
} | null;
|
||||
}
|
||||
|
||||
export const expandLabelTarget = (
|
||||
hass: HomeAssistant,
|
||||
labelId: string,
|
||||
|
@@ -6,6 +6,7 @@ export enum NumberFormat {
|
||||
system = "system",
|
||||
comma_decimal = "comma_decimal",
|
||||
decimal_comma = "decimal_comma",
|
||||
quote_decimal = "quote_decimal",
|
||||
space_comma = "space_comma",
|
||||
none = "none",
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ class MoreInfoFan extends LitElement {
|
||||
|
||||
private _toggle = () => {
|
||||
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
this.hass.callService("fan", service, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
|
@@ -310,7 +310,7 @@ class MoreInfoLight extends LitElement {
|
||||
|
||||
private _toggle = () => {
|
||||
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
this.hass.callService("light", service, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
|
@@ -258,7 +258,10 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
|
||||
const stateObj = this.stateObj;
|
||||
const controls = computeMediaControls(stateObj, true);
|
||||
const coverUrl = stateObj.attributes.entity_picture || "";
|
||||
const coverUrl =
|
||||
stateObj.attributes.entity_picture_local ||
|
||||
stateObj.attributes.entity_picture ||
|
||||
"";
|
||||
const playerObj = new HassMediaPlayerEntity(this.hass, this.stateObj);
|
||||
|
||||
const position = Math.max(Math.floor(playerObj.currentProgress || 0), 0);
|
||||
|
@@ -210,10 +210,10 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
this._readingAttribute = true;
|
||||
try {
|
||||
this._attributeValue = await readAttributeValue(this.hass, data);
|
||||
forwardHaptic("success");
|
||||
forwardHaptic(this, "success");
|
||||
button.actionSuccess();
|
||||
} catch (_err: any) {
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(this, "failure");
|
||||
button.actionError();
|
||||
} finally {
|
||||
this._readingAttribute = false;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiDevices,
|
||||
mdiDotsVertical,
|
||||
mdiHelpCircle,
|
||||
mdiPlus,
|
||||
mdiRobot,
|
||||
@@ -9,8 +8,9 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-overflow-menu";
|
||||
import "../../../components/ha-relative-time";
|
||||
import type {
|
||||
LabelRegistryEntry,
|
||||
@@ -42,7 +43,6 @@ import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showLabelDetailDialog } from "./show-dialog-label-detail";
|
||||
import type { HaMdMenu } from "../../../components/ha-md-menu";
|
||||
|
||||
@customElement("ha-config-labels")
|
||||
export class HaConfigLabels extends LitElement {
|
||||
@@ -86,10 +86,6 @@ export class HaConfigLabels extends LitElement {
|
||||
})
|
||||
private _activeHiddenColumns?: string[];
|
||||
|
||||
@query("#overflow-menu") private _overflowMenu?: HaMdMenu;
|
||||
|
||||
private _overflowLabel!: LabelRegistryEntry;
|
||||
|
||||
private _columns = memoizeOne((localize: LocalizeFunc, narrow: boolean) => {
|
||||
const columns: DataTableColumnContainer<LabelRegistryEntry> = {
|
||||
icon: {
|
||||
@@ -109,13 +105,14 @@ export class HaConfigLabels extends LitElement {
|
||||
template: (label) =>
|
||||
label.color
|
||||
? html`<div
|
||||
style="
|
||||
background-color: ${computeCssColor(label.color)};
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--outline-color);
|
||||
box-sizing: border-box;
|
||||
width: 20px;
|
||||
height: 20px;"
|
||||
style=${styleMap({
|
||||
backgroundColor: computeCssColor(label.color),
|
||||
borderRadius: "10px",
|
||||
border: "1px solid var(--outline-color)",
|
||||
boxSizing: "border-box",
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
})}
|
||||
></div>`
|
||||
: nothing,
|
||||
},
|
||||
@@ -176,12 +173,34 @@ export class HaConfigLabels extends LitElement {
|
||||
hideable: false,
|
||||
type: "overflow-menu",
|
||||
template: (label) => html`
|
||||
<ha-icon-button
|
||||
.selected=${label}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
@click=${this._toggleOverflowMenu}
|
||||
></ha-icon-button>
|
||||
<ha-icon-overflow-menu
|
||||
.hass=${this.hass}
|
||||
narrow
|
||||
.items=${[
|
||||
{
|
||||
label: this.hass.localize("ui.panel.config.entities.caption"),
|
||||
path: mdiShape,
|
||||
action: () => this._navigateEntities(label),
|
||||
},
|
||||
{
|
||||
label: this.hass.localize("ui.panel.config.devices.caption"),
|
||||
path: mdiDevices,
|
||||
action: () => this._navigateDevices(label),
|
||||
},
|
||||
{
|
||||
label: this.hass.localize("ui.panel.config.automation.caption"),
|
||||
path: mdiRobot,
|
||||
action: () => this._navigateAutomations(label),
|
||||
},
|
||||
{
|
||||
label: this.hass.localize("ui.common.delete"),
|
||||
path: mdiDelete,
|
||||
action: () => this._removeLabel(label),
|
||||
warning: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
</ha-icon-overflow-menu>
|
||||
`,
|
||||
},
|
||||
};
|
||||
@@ -195,20 +214,6 @@ export class HaConfigLabels extends LitElement {
|
||||
}))
|
||||
);
|
||||
|
||||
private _toggleOverflowMenu = (ev) => {
|
||||
if (!this._overflowMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._overflowMenu.open) {
|
||||
this._overflowMenu.close();
|
||||
return;
|
||||
}
|
||||
this._overflowLabel = ev.target.selected;
|
||||
this._overflowMenu.anchorElement = ev.target;
|
||||
this._overflowMenu.show();
|
||||
};
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this._fetchLabels();
|
||||
@@ -252,32 +257,6 @@ export class HaConfigLabels extends LitElement {
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-md-menu id="overflow-menu" positioning="fixed">
|
||||
<ha-md-menu-item .clickAction=${this._navigateEntities}>
|
||||
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.entities.caption")}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item .clickAction=${this._navigateDevices}>
|
||||
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.devices.caption")}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item .clickAction=${this._navigateAutomations}>
|
||||
<ha-svg-icon slot="start" .path=${mdiRobot}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.automation.caption")}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._handleRemoveLabelClick}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
class="warning"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize("ui.common.delete")}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -338,10 +317,6 @@ export class HaConfigLabels extends LitElement {
|
||||
return updated;
|
||||
}
|
||||
|
||||
private _handleRemoveLabelClick = () => {
|
||||
this._removeLabel(this._overflowLabel);
|
||||
};
|
||||
|
||||
private async _removeLabel(selectedLabel: LabelRegistryEntry) {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
@@ -369,23 +344,19 @@ export class HaConfigLabels extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _navigateEntities = () => {
|
||||
navigate(
|
||||
`/config/entities?historyBack=1&label=${this._overflowLabel.label_id}`
|
||||
);
|
||||
};
|
||||
private _navigateEntities(label: LabelRegistryEntry) {
|
||||
navigate(`/config/entities?historyBack=1&label=${label.label_id}`);
|
||||
}
|
||||
|
||||
private _navigateDevices = () => {
|
||||
navigate(
|
||||
`/config/devices/dashboard?historyBack=1&label=${this._overflowLabel.label_id}`
|
||||
);
|
||||
};
|
||||
private _navigateDevices(label: LabelRegistryEntry) {
|
||||
navigate(`/config/devices/dashboard?historyBack=1&label=${label.label_id}`);
|
||||
}
|
||||
|
||||
private _navigateAutomations = () => {
|
||||
private _navigateAutomations(label: LabelRegistryEntry) {
|
||||
navigate(
|
||||
`/config/automation/dashboard?historyBack=1&label=${this._overflowLabel.label_id}`
|
||||
`/config/automation/dashboard?historyBack=1&label=${label.label_id}`
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
private _handleSortingChanged(ev: CustomEvent) {
|
||||
this._activeSorting = ev.detail;
|
||||
|
@@ -1087,7 +1087,7 @@ ${rejected
|
||||
name: computeStateName(scene),
|
||||
}),
|
||||
});
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
};
|
||||
|
||||
private _deleteConfirm(scene: SceneEntity): void {
|
||||
|
@@ -443,7 +443,7 @@ class HaPanelDevAction extends LitElement {
|
||||
const button = ev.currentTarget as HaProgressButton;
|
||||
|
||||
if (this._yamlMode && !this._yamlValid) {
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(this, "failure");
|
||||
button.actionError();
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.actions.errors.yaml.invalid_yaml"
|
||||
@@ -465,7 +465,7 @@ class HaPanelDevAction extends LitElement {
|
||||
);
|
||||
|
||||
if (this._error !== undefined) {
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(this, "failure");
|
||||
button.actionError();
|
||||
return;
|
||||
}
|
||||
@@ -534,7 +534,7 @@ class HaPanelDevAction extends LitElement {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(this, "failure");
|
||||
button.actionError();
|
||||
|
||||
let localizedErrorMessage: string | undefined;
|
||||
|
@@ -177,7 +177,7 @@ class HuiAreaControlsCardFeature
|
||||
.map((entityId) => this.hass!.states[entityId] as HassEntity | undefined)
|
||||
.filter((v): v is HassEntity => Boolean(v));
|
||||
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
toggleGroupEntities(this.hass, entities);
|
||||
}
|
||||
|
||||
|
@@ -69,7 +69,7 @@ class HuiLockCommandsCardFeature
|
||||
if (!this.hass || !this._stateObj || !service) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
callProtectedLockService(this, this.hass, this._stateObj, service);
|
||||
}
|
||||
|
||||
|
@@ -112,7 +112,7 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
|
||||
if (!this.hass || !this._stateObj) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
const stateDomain = computeDomain(this._stateObj.entity_id);
|
||||
const serviceDomain = stateDomain;
|
||||
const service = turnOn ? "turn_on" : "turn_off";
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { getColorByIndex } from "../../../common/color/colors";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
@@ -16,7 +17,11 @@ import type {
|
||||
import "../../calendar/ha-full-calendar";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import "../components/hui-warning";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type {
|
||||
LovelaceCard,
|
||||
LovelaceCardEditor,
|
||||
LovelaceGridOptions,
|
||||
} from "../types";
|
||||
import type { CalendarCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-calendar-card")
|
||||
@@ -48,6 +53,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public layout?: string;
|
||||
|
||||
@state() private _events: CalendarEvent[] = [];
|
||||
|
||||
@state() private _config?: CalendarCardConfig;
|
||||
@@ -88,7 +95,16 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return this._config?.header ? 1 : 0 + 11;
|
||||
return 12;
|
||||
}
|
||||
|
||||
public getGridOptions(): LovelaceGridOptions {
|
||||
return {
|
||||
rows: 6,
|
||||
columns: 12,
|
||||
min_columns: 4,
|
||||
min_rows: 4,
|
||||
};
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
@@ -118,6 +134,10 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
<ha-card>
|
||||
<div class="header">${this._config.title}</div>
|
||||
<ha-full-calendar
|
||||
class=${classMap({
|
||||
"is-grid": this.layout === "grid",
|
||||
"is-panel": this.layout === "panel",
|
||||
})}
|
||||
.narrow=${this._narrow}
|
||||
.events=${this._events}
|
||||
.hass=${this.hass}
|
||||
@@ -224,6 +244,11 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
ha-full-calendar {
|
||||
--calendar-height: 400px;
|
||||
}
|
||||
|
||||
ha-full-calendar.is-grid,
|
||||
ha-full-calendar.is-panel {
|
||||
height: calc(100% - 16px);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -231,6 +231,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
|
||||
static styles = css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
ha-alert {
|
||||
margin-bottom: 8px;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
@@ -9,7 +9,6 @@ import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-card";
|
||||
@@ -26,6 +25,10 @@ import type { HomeAssistant } from "../../../types";
|
||||
import "../card-features/hui-card-features";
|
||||
import type { LovelaceCardFeatureContext } from "../card-features/types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import {
|
||||
formatEntityDisplayName,
|
||||
type EntityNameConfig,
|
||||
} from "../common/entity/entity-display-name";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
@@ -47,6 +50,11 @@ export const getEntityDefaultTileIconAction = (entityId: string) => {
|
||||
return supportsIconAction ? "toggle" : "none";
|
||||
};
|
||||
|
||||
export const DEFAULT_NAME = [
|
||||
{ type: "device" },
|
||||
{ type: "entity" },
|
||||
] satisfies EntityNameConfig;
|
||||
|
||||
@customElement("hui-tile-card")
|
||||
export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
@@ -255,7 +263,12 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
|
||||
const contentClasses = { vertical: Boolean(this._config.vertical) };
|
||||
|
||||
const name = this._config.name || computeStateName(stateObj);
|
||||
const name = formatEntityDisplayName(
|
||||
this.hass,
|
||||
stateObj,
|
||||
this._config.name || DEFAULT_NAME
|
||||
);
|
||||
|
||||
const active = stateActive(stateObj);
|
||||
const color = this._computeStateColor(stateObj, this._config.color);
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
|
@@ -3,6 +3,7 @@ import type { HaDurationData } from "../../../components/ha-duration-input";
|
||||
import type { ActionConfig } from "../../../data/lovelace/config/action";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type { Statistic, StatisticType } from "../../../data/recorder";
|
||||
import type { TimeFormat } from "../../../data/translation";
|
||||
import type { ForecastType } from "../../../data/weather";
|
||||
import type {
|
||||
FullCalendarView,
|
||||
@@ -13,6 +14,7 @@ import type {
|
||||
LovelaceCardFeatureConfig,
|
||||
LovelaceCardFeaturePosition,
|
||||
} from "../card-features/types";
|
||||
import type { EntityNameConfig } from "../common/entity/entity-display-name";
|
||||
import type { LegacyStateFilter } from "../common/evaluate-filter";
|
||||
import type { Condition, LegacyCondition } from "../common/validate-condition";
|
||||
import type { HuiImage } from "../components/hui-image";
|
||||
@@ -25,7 +27,6 @@ import type {
|
||||
} from "../entity-rows/types";
|
||||
import type { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
||||
import type { LovelaceHeadingBadgeConfig } from "../heading-badges/types";
|
||||
import type { TimeFormat } from "../../../data/translation";
|
||||
import type { HomeSummary } from "../strategies/home/helpers/home-summaries";
|
||||
|
||||
export type AlarmPanelCardConfigState =
|
||||
@@ -565,7 +566,7 @@ export interface WeatherForecastCardConfig extends LovelaceCardConfig {
|
||||
|
||||
export interface TileCardConfig extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
name?: string;
|
||||
name?: EntityNameConfig;
|
||||
hide_state?: boolean;
|
||||
state_content?: string | string[];
|
||||
icon?: string;
|
||||
|
106
src/panels/lovelace/common/entity/entity-display-name.ts
Normal file
106
src/panels/lovelace/common/entity/entity-display-name.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
import { computeEntityName } from "../../../../common/entity/compute_entity_name";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import type { EntityNameType } from "../../../../common/translations/entity-state";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { getEntityContext } from "../../../../common/entity/context/get_entity_context";
|
||||
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
|
||||
import { computeAreaName } from "../../../../common/entity/compute_area_name";
|
||||
import { computeFloorName } from "../../../../common/entity/compute_floor_name";
|
||||
|
||||
interface TextName {
|
||||
type: "text";
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface ContextType {
|
||||
type: EntityNameType;
|
||||
}
|
||||
|
||||
export type EntityNameItem = TextName | ContextType;
|
||||
|
||||
export const ensureEntityNameItems = (
|
||||
name: EntityNameConfig
|
||||
): EntityNameItem[] =>
|
||||
ensureArray(name).map((n) =>
|
||||
typeof n === "string" ? { type: "text", text: n } : n
|
||||
);
|
||||
|
||||
export type EntityNameConfig =
|
||||
| (EntityNameItem | string)[]
|
||||
| EntityNameItem
|
||||
| string;
|
||||
|
||||
export const formatEntityDisplayName = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: HassEntity,
|
||||
name?: EntityNameConfig
|
||||
) => {
|
||||
if (typeof name === "string") {
|
||||
return name;
|
||||
}
|
||||
if (!name) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
|
||||
let items = ensureEntityNameItems(name);
|
||||
|
||||
// If custom name does not include any of the known types, just join and return
|
||||
if (!items.some((n) => n.type !== "text")) {
|
||||
return items.join(" ");
|
||||
}
|
||||
|
||||
const entityUseDeviceName = !computeEntityName(
|
||||
stateObj,
|
||||
hass.entities,
|
||||
hass.devices
|
||||
);
|
||||
|
||||
// If entity has no custom name, use device name instead of entity name
|
||||
if (entityUseDeviceName) {
|
||||
items = items.map((n) => (n.type === "entity" ? { type: "device" } : n));
|
||||
}
|
||||
|
||||
// Remove duplicates type while preserving order (only if they are known types)
|
||||
if (items.length > 1) {
|
||||
items = items.filter(
|
||||
(n, i) =>
|
||||
!(
|
||||
n.type !== "text" &&
|
||||
items.findIndex((item) => item.type === n.type) < i
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { device, area, floor } = getEntityContext(
|
||||
stateObj,
|
||||
hass.entities,
|
||||
hass.devices,
|
||||
hass.areas,
|
||||
hass.floors
|
||||
);
|
||||
|
||||
const formattedName = items
|
||||
.map((item) => {
|
||||
switch (item.type) {
|
||||
case "entity":
|
||||
return computeEntityName(stateObj, hass.entities, hass.devices);
|
||||
case "device":
|
||||
return device ? computeDeviceName(device) : undefined;
|
||||
case "area":
|
||||
return area ? computeAreaName(area) : undefined;
|
||||
case "floor":
|
||||
return floor ? computeFloorName(floor) : undefined;
|
||||
case "text":
|
||||
return item.text;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
})
|
||||
.filter((n) => n)
|
||||
.join(" ");
|
||||
|
||||
// Fallback to state name (friendly name) if no name could be computed
|
||||
return formattedName || computeStateName(stateObj);
|
||||
};
|
@@ -53,7 +53,7 @@ export const handleAction = async (
|
||||
(e) => e.user === hass!.user?.id
|
||||
))
|
||||
) {
|
||||
forwardHaptic("warning");
|
||||
forwardHaptic(node, "warning");
|
||||
|
||||
let serviceName;
|
||||
if (
|
||||
@@ -107,7 +107,7 @@ export const handleAction = async (
|
||||
"ui.panel.lovelace.cards.actions.no_entity_more_info"
|
||||
),
|
||||
});
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(node, "failure");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -122,7 +122,7 @@ export const handleAction = async (
|
||||
"ui.panel.lovelace.cards.actions.no_navigation_path"
|
||||
),
|
||||
});
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(node, "failure");
|
||||
}
|
||||
break;
|
||||
case "url": {
|
||||
@@ -132,21 +132,21 @@ export const handleAction = async (
|
||||
showToast(node, {
|
||||
message: hass.localize("ui.panel.lovelace.cards.actions.no_url"),
|
||||
});
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(node, "failure");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "toggle": {
|
||||
if (config.entity) {
|
||||
toggleEntity(hass, config.entity!);
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(node, "light");
|
||||
} else {
|
||||
showToast(node, {
|
||||
message: hass.localize(
|
||||
"ui.panel.lovelace.cards.actions.no_entity_toggle"
|
||||
),
|
||||
});
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(node, "failure");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export const handleAction = async (
|
||||
showToast(node, {
|
||||
message: hass.localize("ui.panel.lovelace.cards.actions.no_action"),
|
||||
});
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(node, "failure");
|
||||
return;
|
||||
}
|
||||
const [domain, service] = (actionConfig.perform_action ||
|
||||
@@ -167,7 +167,7 @@ export const handleAction = async (
|
||||
actionConfig.data ?? actionConfig.service_data,
|
||||
actionConfig.target
|
||||
);
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(node, "light");
|
||||
break;
|
||||
}
|
||||
case "assist": {
|
||||
|
@@ -58,7 +58,7 @@ class HuiEntitiesToggle extends LitElement {
|
||||
`;
|
||||
|
||||
private _callService(ev: MouseEvent): void {
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
const turnOn = (ev.target as HaSwitch).checked;
|
||||
turnOnOffEntities(this.hass!, this._toggleEntities!, turnOn!);
|
||||
}
|
||||
|
@@ -30,11 +30,15 @@ import type {
|
||||
LovelaceCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
} from "../../card-features/types";
|
||||
import { getEntityDefaultTileIconAction } from "../../cards/hui-tile-card";
|
||||
import {
|
||||
DEFAULT_NAME,
|
||||
getEntityDefaultTileIconAction,
|
||||
} from "../../cards/hui-tile-card";
|
||||
import type { TileCardConfig } from "../../cards/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { actionConfigStruct } from "../structs/action-struct";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { entityNameStruct } from "../structs/entity-name-struct";
|
||||
import type { EditDetailElementEvent, EditSubElementEvent } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { getSupportedFeaturesType } from "./hui-card-features-editor";
|
||||
@@ -43,7 +47,7 @@ const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
object({
|
||||
entity: optional(string()),
|
||||
name: optional(string()),
|
||||
name: optional(entityNameStruct),
|
||||
icon: optional(string()),
|
||||
color: optional(string()),
|
||||
show_entity_picture: optional(boolean()),
|
||||
@@ -97,11 +101,19 @@ export class HuiTileCardEditor
|
||||
type: "expandable",
|
||||
iconPath: mdiTextShort,
|
||||
schema: [
|
||||
{
|
||||
name: "name",
|
||||
selector: {
|
||||
entity_name: {
|
||||
default_name: DEFAULT_NAME,
|
||||
},
|
||||
},
|
||||
context: { entity: "entity" },
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{ name: "name", selector: { text: {} } },
|
||||
{
|
||||
name: "icon",
|
||||
selector: {
|
||||
|
22
src/panels/lovelace/editor/structs/entity-name-struct.ts
Normal file
22
src/panels/lovelace/editor/structs/entity-name-struct.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { array, literal, object, string, union } from "superstruct";
|
||||
|
||||
const entityNameItemStruct = union([
|
||||
object({
|
||||
type: literal("text"),
|
||||
text: string(),
|
||||
}),
|
||||
object({
|
||||
type: union([
|
||||
literal("entity"),
|
||||
literal("device"),
|
||||
literal("area"),
|
||||
literal("floor"),
|
||||
]),
|
||||
}),
|
||||
string(),
|
||||
]);
|
||||
|
||||
export const entityNameStruct = union([
|
||||
entityNameItemStruct,
|
||||
array(entityNameItemStruct),
|
||||
]);
|
@@ -103,7 +103,7 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow {
|
||||
return;
|
||||
}
|
||||
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
|
||||
setInputSelectOption(this.hass!, stateObj.entity_id, option);
|
||||
}
|
||||
|
@@ -104,7 +104,7 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
|
||||
return;
|
||||
}
|
||||
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
|
||||
setSelectOption(this.hass!, stateObj.entity_id, option);
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import {
|
||||
mdiAndroid,
|
||||
mdiApple,
|
||||
@@ -15,7 +14,8 @@ import memoizeOne from "memoize-one";
|
||||
import { relativeTime } from "../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-button-menu";
|
||||
import "../../components/ha-md-button-menu";
|
||||
import "../../components/ha-md-menu-item";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-label";
|
||||
@@ -146,20 +146,19 @@ class HaRefreshTokens extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_END"
|
||||
menu-corner="END"
|
||||
@action=${this._handleAction}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-md-button-menu positioning="popover">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon">
|
||||
<ha-md-menu-item
|
||||
graphic="icon"
|
||||
@click=${this._toggleTokenExpiration}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="start"
|
||||
.path=${token.expire_at
|
||||
? mdiClockRemoveOutline
|
||||
: mdiClockCheckOutline}
|
||||
@@ -171,20 +170,24 @@ class HaRefreshTokens extends LitElement {
|
||||
: this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.enable_token_expiration"
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
graphic="icon"
|
||||
class="warning"
|
||||
.disabled=${token.is_current}
|
||||
@click=${this._deleteToken}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.common.delete")}
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</div>
|
||||
</ha-settings-row>
|
||||
`
|
||||
@@ -207,19 +210,8 @@ class HaRefreshTokens extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
const token = (ev.currentTarget as any).token;
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._toggleTokenExpiration(token);
|
||||
break;
|
||||
case 1:
|
||||
this._deleteToken(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async _toggleTokenExpiration(token: RefreshToken): Promise<void> {
|
||||
private async _toggleTokenExpiration(ev): Promise<void> {
|
||||
const token = (ev.currentTarget as any).token as RefreshToken;
|
||||
const enable = !token.expire_at;
|
||||
if (!enable) {
|
||||
if (
|
||||
@@ -260,7 +252,8 @@ class HaRefreshTokens extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteToken(token: RefreshToken): Promise<void> {
|
||||
private async _deleteToken(ev): Promise<void> {
|
||||
const token = (ev.currentTarget as any).token as RefreshToken;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
@@ -332,8 +325,8 @@ class HaRefreshTokens extends LitElement {
|
||||
ha-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-list-item[disabled],
|
||||
ha-list-item[disabled] ha-svg-icon {
|
||||
ha-md-list-item[disabled],
|
||||
ha-md-list-item[disabled] ha-svg-icon {
|
||||
color: var(--disabled-text-color) !important;
|
||||
}
|
||||
ha-settings-row .current-session {
|
||||
|
@@ -39,7 +39,7 @@ class HaSetVibrateRow extends LitElement {
|
||||
fireEvent(this, "hass-vibrate", {
|
||||
vibrate,
|
||||
});
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -14,7 +14,15 @@ import {
|
||||
polyfillTimeZoneData,
|
||||
} from "./locale-data-polyfill";
|
||||
|
||||
let polyfilled = false;
|
||||
|
||||
const _polyfillTimeZoneData = polyfillTimeZoneData;
|
||||
|
||||
const polyfillIntl = async () => {
|
||||
if (polyfilled) {
|
||||
return;
|
||||
}
|
||||
polyfilled = true;
|
||||
const locale = getLocalLanguage();
|
||||
const polyfills: Promise<unknown>[] = [];
|
||||
if (shouldPolyfillGetCanonicalLocales()) {
|
||||
@@ -26,7 +34,7 @@ const polyfillIntl = async () => {
|
||||
if (shouldPolyfillDateTimeFormat(locale)) {
|
||||
polyfills.push(
|
||||
import("@formatjs/intl-datetimeformat/polyfill-force").then(() =>
|
||||
polyfillTimeZoneData()
|
||||
_polyfillTimeZoneData()
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -58,7 +66,7 @@ const polyfillIntl = async () => {
|
||||
if (polyfills.length === 0) {
|
||||
return;
|
||||
}
|
||||
await Promise.all(polyfills).then(() =>
|
||||
await Promise.allSettled(polyfills).then(() =>
|
||||
// Load the default language
|
||||
polyfillLocaleData(locale)
|
||||
);
|
||||
|
@@ -184,6 +184,21 @@ export const colorStyles = css`
|
||||
--state-water_heater-heat_pump-color: var(--orange-color);
|
||||
--state-water_heater-high_demand-color: var(--deep-orange-color);
|
||||
--state-water_heater-performance-color: var(--deep-orange-color);
|
||||
--state-weather-clear_night-color: var(--deep-purple-color);
|
||||
--state-weather-cloudy-color: var(--light-grey-color);
|
||||
--state-weather-exceptional-color: var(--red-color);
|
||||
--state-weather-fog-color: var(--grey-color);
|
||||
--state-weather-hail-color: var(--cyan-color);
|
||||
--state-weather-lightning_rainy-color: var(--lime-color);
|
||||
--state-weather-lightning-color: var(--yellow-color);
|
||||
--state-weather-partlycloudy-color: var(--blue-grey-color);
|
||||
--state-weather-pouring-color: var(--indigo-color);
|
||||
--state-weather-rainy-color: var(--blue-color);
|
||||
--state-weather-snowy_rainy-color: var(--light-blue-color);
|
||||
--state-weather-snowy-color: #c0e0ff;
|
||||
--state-weather-sunny-color: var(--amber-color);
|
||||
--state-weather-windy_variant-color: var(--green-color);
|
||||
--state-weather-windy-color: var(--green-color);
|
||||
|
||||
/* history colors */
|
||||
--history-unknown-color: var(--dark-grey-color);
|
||||
|
@@ -20,7 +20,7 @@ export const coreStyles = css`
|
||||
--ha-border-radius-circle: 50%;
|
||||
--ha-border-radius-square: 0;
|
||||
|
||||
# Spacing
|
||||
/* Spacing */
|
||||
--ha-space-0: 0px;
|
||||
--ha-space-1: 4px;
|
||||
--ha-space-2: 8px;
|
||||
@@ -34,7 +34,7 @@ export const coreStyles = css`
|
||||
--ha-space-10: 40px;
|
||||
--ha-space-11: 44px;
|
||||
--ha-space-12: 48px;
|
||||
--ha-space-13: 44px;
|
||||
--ha-space-13: 52px;
|
||||
--ha-space-14: 56px;
|
||||
--ha-space-15: 60px;
|
||||
--ha-space-16: 64px;
|
||||
|
@@ -40,7 +40,7 @@ export class HaStateControlCoverToggle extends LitElement {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
|
||||
await this.hass.callService(
|
||||
"cover",
|
||||
|
@@ -46,7 +46,7 @@ export class HaStateControlToggle extends LitElement {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
const stateDomain = computeDomain(this.stateObj.entity_id);
|
||||
let serviceDomain;
|
||||
let service;
|
||||
|
@@ -68,7 +68,7 @@ export class HaStateControlLockToggle extends LitElement {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
fireEvent(this, "lock-service-called");
|
||||
callProtectedLockService(
|
||||
this,
|
||||
|
@@ -40,7 +40,7 @@ export class HaStateControlValveToggle extends LitElement {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("light");
|
||||
forwardHaptic(this, "light");
|
||||
|
||||
await this.hass.callService(
|
||||
"valve",
|
||||
|
@@ -127,7 +127,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
);
|
||||
}
|
||||
if (notifyOnError) {
|
||||
forwardHaptic("failure");
|
||||
forwardHaptic(this, "failure");
|
||||
const lokalize = await this.hass!.loadBackendTranslation(
|
||||
"exceptions",
|
||||
err.translation_domain
|
||||
|
@@ -654,6 +654,15 @@
|
||||
"placeholder": "Select an entity",
|
||||
"create_helper": "Create a new {domain, select, \n undefined {} \n other {{domain} }\n } helper."
|
||||
},
|
||||
"entity-name-picker": {
|
||||
"types": {
|
||||
"floor": "Floor",
|
||||
"area": "Area",
|
||||
"device": "Device",
|
||||
"entity": "Entity"
|
||||
},
|
||||
"add": "Add"
|
||||
},
|
||||
"entity-attribute-picker": {
|
||||
"attribute": "Attribute",
|
||||
"show_attributes": "Show attributes"
|
||||
@@ -8440,6 +8449,7 @@
|
||||
"system": "Use system locale",
|
||||
"comma_decimal": "1,234,567.89",
|
||||
"decimal_comma": "1.234.567,89",
|
||||
"quote_decimal": "1'234'567.89",
|
||||
"space_comma": "1 234 567,89",
|
||||
"none": "None"
|
||||
}
|
||||
|
141
yarn.lock
141
yarn.lock
@@ -1351,10 +1351,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ctrl/tinycolor@npm:^4.1.0":
|
||||
version: 4.2.0
|
||||
resolution: "@ctrl/tinycolor@npm:4.2.0"
|
||||
checksum: 10/1be14de7d7e8184c0bc5c8d7e3486cc8186e6702e8ca899c7239f328bb1df9a15d1575e2af7b4c6ba020727fa78f5a9f887555971f30a2890cece9e4253a9d3a
|
||||
"@ctrl/tinycolor@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@ctrl/tinycolor@npm:4.1.0"
|
||||
checksum: 10/e64569399139ef0abd2eb0ec9fb7267dfd7820f7ad7d4567a63e5fc35e5cfdcb8ecdb3bad65cb9244b47ba6c77bc51085826c00e981acf263a3221dc89343adc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1940,11 +1940,11 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3":
|
||||
version: 3.0.0-beta.4.ha.3
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3"
|
||||
"@home-assistant/webawesome@npm:3.0.0-beta.6.ha.0":
|
||||
version: 3.0.0-beta.6.ha.0
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.6.ha.0"
|
||||
dependencies:
|
||||
"@ctrl/tinycolor": "npm:^4.1.0"
|
||||
"@ctrl/tinycolor": "npm:4.1.0"
|
||||
"@floating-ui/dom": "npm:^1.6.13"
|
||||
"@lit/react": "npm:^1.0.8"
|
||||
"@shoelace-style/animations": "npm:^1.2.0"
|
||||
@@ -1953,8 +1953,7 @@ __metadata:
|
||||
lit: "npm:^3.2.1"
|
||||
nanoid: "npm:^5.1.5"
|
||||
qr-creator: "npm:^1.0.0"
|
||||
style-observer: "npm:^0.0.7"
|
||||
checksum: 10/b9241821ed471ccbad86b0ea4697a2d41395f05fdc26f46e5edbc7f6b5eeab5d248251ef702326312ded00d5bf850ce0dcdcf7cd5e2e542b9d9cb9a84f3726da
|
||||
checksum: 10/ec9d74585b544e5755f7b2644a0d7f9318b5776bedf51430c8f8729918fddb6e54cce46acace674960383385362846cc4c0f2da5245fa622bce8c54733a31865
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3899,81 +3898,82 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/client@npm:1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "@rsdoctor/client@npm:1.3.0"
|
||||
checksum: 10/dca67aa80613ddd36b7bc43456e15633b632df91c7aca6ab05653b3fbb1d57887ffe9fa3a792a3812efc2e0d2c6f50cec9b5567ed182fd125c675cc9a4fa8ceb
|
||||
"@rsdoctor/client@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@rsdoctor/client@npm:1.3.1"
|
||||
checksum: 10/6885dd7e16f2172ddf5d4c901275af77640402f1b5cfd41b0eab6e695cad423bda2909a5b13dc68e864e9d9df4440587b8a3403138437c20b6e3bb15d0c83b04
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/core@npm:1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "@rsdoctor/core@npm:1.3.0"
|
||||
"@rsdoctor/core@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@rsdoctor/core@npm:1.3.1"
|
||||
dependencies:
|
||||
"@rsdoctor/graph": "npm:1.3.0"
|
||||
"@rsdoctor/sdk": "npm:1.3.0"
|
||||
"@rsdoctor/types": "npm:1.3.0"
|
||||
"@rsdoctor/utils": "npm:1.3.0"
|
||||
"@rsdoctor/graph": "npm:1.3.1"
|
||||
"@rsdoctor/sdk": "npm:1.3.1"
|
||||
"@rsdoctor/types": "npm:1.3.1"
|
||||
"@rsdoctor/utils": "npm:1.3.1"
|
||||
browserslist-load-config: "npm:^1.0.1"
|
||||
enhanced-resolve: "npm:5.12.0"
|
||||
filesize: "npm:^10.1.6"
|
||||
fs-extra: "npm:^11.1.1"
|
||||
lodash-es: "npm:^4.17.21"
|
||||
semver: "npm:^7.7.2"
|
||||
source-map: "npm:^0.7.6"
|
||||
checksum: 10/e990358b82a260242a27af9d93fa6f673c68fb0896b74888aa121b4060069e8b47079f929403a237a532da65f4566d6bfb1d824dae2266735e44294d2521b1e6
|
||||
checksum: 10/40f4de3680202487ff094cd97664035c19c8bd802ff9adbd4c3947c53b08e738eac65e22b45514ca1cd2640305451c53d1efd23a0097674d4af0391698eff9a7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/graph@npm:1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "@rsdoctor/graph@npm:1.3.0"
|
||||
"@rsdoctor/graph@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@rsdoctor/graph@npm:1.3.1"
|
||||
dependencies:
|
||||
"@rsdoctor/types": "npm:1.3.0"
|
||||
"@rsdoctor/utils": "npm:1.3.0"
|
||||
"@rsdoctor/types": "npm:1.3.1"
|
||||
"@rsdoctor/utils": "npm:1.3.1"
|
||||
lodash.unionby: "npm:^4.8.0"
|
||||
path-browserify: "npm:1.0.1"
|
||||
source-map: "npm:^0.7.6"
|
||||
checksum: 10/64dd610819fd38beb0bb96cf7f2b85c47d2b40134150a8f07e71bc94ae1ad38b7be23a453b42d3692303f1c7b07c3f7ec78bca6b7e8b7f41c961e8b7dc479495
|
||||
checksum: 10/7ae4abd2bd630e2589975df3e34d029921c2ff34c9f62961aff73c384dbb7e94d24faf2bf3f5118860f56b9bab2a5cd4b5185c178ce91f8a0852a258a854602c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/rspack-plugin@npm:1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "@rsdoctor/rspack-plugin@npm:1.3.0"
|
||||
"@rsdoctor/rspack-plugin@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@rsdoctor/rspack-plugin@npm:1.3.1"
|
||||
dependencies:
|
||||
"@rsdoctor/core": "npm:1.3.0"
|
||||
"@rsdoctor/graph": "npm:1.3.0"
|
||||
"@rsdoctor/sdk": "npm:1.3.0"
|
||||
"@rsdoctor/types": "npm:1.3.0"
|
||||
"@rsdoctor/utils": "npm:1.3.0"
|
||||
"@rsdoctor/core": "npm:1.3.1"
|
||||
"@rsdoctor/graph": "npm:1.3.1"
|
||||
"@rsdoctor/sdk": "npm:1.3.1"
|
||||
"@rsdoctor/types": "npm:1.3.1"
|
||||
"@rsdoctor/utils": "npm:1.3.1"
|
||||
lodash-es: "npm:^4.17.21"
|
||||
peerDependencies:
|
||||
"@rspack/core": "*"
|
||||
peerDependenciesMeta:
|
||||
"@rspack/core":
|
||||
optional: true
|
||||
checksum: 10/8f9126f6ed8c0bc350e899da5a9104cbf74808ac51a98a0f6a95fddf6dfdcbfb9b338c696095c9b7960bbc378947e90895bdf531da959426f47f3d00e5402409
|
||||
checksum: 10/94759bf214102e8acffeaaeb89d8274301f0b420274bf6f26afa736ac915f029e02e33cbc4f9f977d208e20a5e38bf3d812a1147be830dcd25a49755ff111d6d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/sdk@npm:1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "@rsdoctor/sdk@npm:1.3.0"
|
||||
"@rsdoctor/sdk@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@rsdoctor/sdk@npm:1.3.1"
|
||||
dependencies:
|
||||
"@rsdoctor/client": "npm:1.3.0"
|
||||
"@rsdoctor/graph": "npm:1.3.0"
|
||||
"@rsdoctor/types": "npm:1.3.0"
|
||||
"@rsdoctor/utils": "npm:1.3.0"
|
||||
"@rsdoctor/client": "npm:1.3.1"
|
||||
"@rsdoctor/graph": "npm:1.3.1"
|
||||
"@rsdoctor/types": "npm:1.3.1"
|
||||
"@rsdoctor/utils": "npm:1.3.1"
|
||||
safer-buffer: "npm:2.1.2"
|
||||
socket.io: "npm:4.8.1"
|
||||
tapable: "npm:2.2.3"
|
||||
checksum: 10/c6183047ce5e7db1240b89c3d3890def9fc7ae2c1dcab8621ee3c0652b174e3facae6358cbd706317ae103af748d41e9c3187a519b082e1d249dcc18a9938a58
|
||||
checksum: 10/194efba86d15e86d81de3b1a747c3e82874f69c4e3f1f96e9f36f8a83cabbcc6371729498e2ab82724550f376dd2630849c435841031a0c139406aeb4b472d06
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/types@npm:1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "@rsdoctor/types@npm:1.3.0"
|
||||
"@rsdoctor/types@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@rsdoctor/types@npm:1.3.1"
|
||||
dependencies:
|
||||
"@types/connect": "npm:3.4.38"
|
||||
"@types/estree": "npm:1.0.5"
|
||||
@@ -3987,16 +3987,16 @@ __metadata:
|
||||
optional: true
|
||||
webpack:
|
||||
optional: true
|
||||
checksum: 10/bc077915aa616844ee1d58b17ac34250f2174a0f5d2b8c83e2983a4c435855c85ec8317e7d2800ce62dcba225c2b493bca95c6cfa51c4f232a019e3390dd0ca4
|
||||
checksum: 10/e058017b77b4b58c22c39a0f1177e6cabdedbdebc355f936bbc6be3ace51279d0cd078e2cab19543a5fe2d4cff3e9980f076c4d18bd70ab3d393d5ce0dd1eb89
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/utils@npm:1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "@rsdoctor/utils@npm:1.3.0"
|
||||
"@rsdoctor/utils@npm:1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "@rsdoctor/utils@npm:1.3.1"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:7.26.2"
|
||||
"@rsdoctor/types": "npm:1.3.0"
|
||||
"@rsdoctor/types": "npm:1.3.1"
|
||||
"@types/estree": "npm:1.0.5"
|
||||
acorn: "npm:^8.10.0"
|
||||
acorn-import-attributes: "npm:^1.9.5"
|
||||
@@ -4010,7 +4010,7 @@ __metadata:
|
||||
picocolors: "npm:^1.1.1"
|
||||
rslog: "npm:^1.2.11"
|
||||
strip-ansi: "npm:^6.0.1"
|
||||
checksum: 10/d11ab54328d719542965af87fb843627850e26a48e49378ab4b0b02553cbf37b2ec185caa622f745bc27735738112f29b1d1f00656e8d0a8b4bd23103a320a9a
|
||||
checksum: 10/ebe1a7233179bf9be0272959c16fc2fc89c37c2cc2553973002889ab8432697f2bee6308dc1c82208ddb1d13d875be6341b9a985d9fe18536af381989200dc48
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7528,6 +7528,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"enhanced-resolve@npm:5.12.0":
|
||||
version: 5.12.0
|
||||
resolution: "enhanced-resolve@npm:5.12.0"
|
||||
dependencies:
|
||||
graceful-fs: "npm:^4.2.4"
|
||||
tapable: "npm:^2.2.0"
|
||||
checksum: 10/ea5b49a0641827c6a083eaa3a625f953f4bd4e8f015bf70b9fb8cf60a35aaeb44e567df2da91ed28efaea3882845016e1d22a3152c2fdf773ea14f39cbe3d8a9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"enhanced-resolve@npm:^0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "enhanced-resolve@npm:0.9.1"
|
||||
@@ -8969,7 +8979,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.8":
|
||||
"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.8":
|
||||
version: 4.2.11
|
||||
resolution: "graceful-fs@npm:4.2.11"
|
||||
checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2
|
||||
@@ -9195,7 +9205,7 @@ __metadata:
|
||||
"@fullcalendar/list": "npm:6.1.19"
|
||||
"@fullcalendar/luxon3": "npm:6.1.19"
|
||||
"@fullcalendar/timegrid": "npm:6.1.19"
|
||||
"@home-assistant/webawesome": "npm:3.0.0-beta.4.ha.3"
|
||||
"@home-assistant/webawesome": "npm:3.0.0-beta.6.ha.0"
|
||||
"@lezer/highlight": "npm:1.2.1"
|
||||
"@lit-labs/motion": "npm:1.0.9"
|
||||
"@lit-labs/observers": "npm:2.0.6"
|
||||
@@ -9232,7 +9242,7 @@ __metadata:
|
||||
"@octokit/plugin-retry": "npm:8.0.1"
|
||||
"@octokit/rest": "npm:22.0.0"
|
||||
"@replit/codemirror-indentation-markers": "npm:6.5.3"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.3.0"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.3.1"
|
||||
"@rspack/core": "npm:1.5.7"
|
||||
"@rspack/dev-server": "npm:1.1.4"
|
||||
"@swc/helpers": "npm:0.5.17"
|
||||
@@ -9312,7 +9322,7 @@ __metadata:
|
||||
leaflet: "npm:1.9.4"
|
||||
leaflet-draw: "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
leaflet.markercluster: "npm:1.5.3"
|
||||
lint-staged: "npm:16.2.1"
|
||||
lint-staged: "npm:16.2.2"
|
||||
lit: "npm:3.3.1"
|
||||
lit-analyzer: "npm:2.0.3"
|
||||
lit-html: "npm:3.3.1"
|
||||
@@ -10647,9 +10657,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lint-staged@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "lint-staged@npm:16.2.1"
|
||||
"lint-staged@npm:16.2.2":
|
||||
version: 16.2.2
|
||||
resolution: "lint-staged@npm:16.2.2"
|
||||
dependencies:
|
||||
commander: "npm:^14.0.1"
|
||||
listr2: "npm:^9.0.4"
|
||||
@@ -10660,7 +10670,7 @@ __metadata:
|
||||
yaml: "npm:^2.8.1"
|
||||
bin:
|
||||
lint-staged: bin/lint-staged.js
|
||||
checksum: 10/b604de3ca27a067e45c5f0e0780ca46f5617e9f6ac3895297dee087d62742bbcd9f9e910300c15c599e1f06900666469b73e036e3fe3153ccedef314ce791dd5
|
||||
checksum: 10/8a6dc0a54fabd095c99ce5962b50df6cee3c8b1191b5e4ff345037a721284cc77532c359e125a0877cf83314a7847b361879da6608e142b22aad2fb32ff66e3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13725,13 +13735,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"style-observer@npm:^0.0.7":
|
||||
version: 0.0.7
|
||||
resolution: "style-observer@npm:0.0.7"
|
||||
checksum: 10/bb57f98bae4463c1e1b57234f8ffe72ec0de27fb08b032c1919910129c210aacd1ddd615432b9453d491e10d3b719cf6c2a68a97165ca55d6fc9b86c0fca37fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"style-observer@npm:^0.0.8":
|
||||
version: 0.0.8
|
||||
resolution: "style-observer@npm:0.0.8"
|
||||
|
Reference in New Issue
Block a user