Compare commits

..
186 changed files with 1965 additions and 1802 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.14
FROM mcr.microsoft.com/devcontainers/python:1-3.13
ENV \
DEBIAN_FRONTEND=noninteractive \
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Setup lint cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with:
path: |
node_modules/.cache/prettier
+3 -3
View File
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
uses: github/codeql-action/init@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
uses: github/codeql-action/autobuild@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
uses: github/codeql-action/analyze@19b2f06db2b6f5108140aeb04014ef02b648f789 # v4.31.11
+1 -1
View File
@@ -6,7 +6,7 @@ on:
- cron: "0 1 * * *"
env:
PYTHON_VERSION: "3.14"
PYTHON_VERSION: "3.13"
NODE_OPTIONS: --max_old_space_size=6144
permissions:
+2 -2
View File
@@ -6,7 +6,7 @@ on:
- published
env:
PYTHON_VERSION: "3.14"
PYTHON_VERSION: "3.13"
NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions
@@ -84,7 +84,7 @@ jobs:
- name: Build wheels
uses: home-assistant/[email protected]
with:
abi: cp314
abi: cp313
tag: musllinux_1_2
arch: amd64
wheels-key: ${{ secrets.WHEELS_KEY }}
-6
View File
@@ -9,14 +9,11 @@ import { selectedDemoConfig } from "./configs/demo-configs";
import { mockAreaRegistry } from "./stubs/area_registry";
import { mockAuth } from "./stubs/auth";
import { mockConfigEntries } from "./stubs/config_entries";
import { mockDeviceRegistry } from "./stubs/device_registry";
import { mockEnergy } from "./stubs/energy";
import { energyEntities } from "./stubs/entities";
import { mockEntityRegistry } from "./stubs/entity_registry";
import { mockEvents } from "./stubs/events";
import { mockFloorRegistry } from "./stubs/floor_registry";
import { mockFrontend } from "./stubs/frontend";
import { mockLabelRegistry } from "./stubs/label_registry";
import { mockIcons } from "./stubs/icons";
import { mockHistory } from "./stubs/history";
import { mockLovelace } from "./stubs/lovelace";
@@ -63,9 +60,6 @@ export class HaDemo extends HomeAssistantAppEl {
mockPersistentNotification(hass);
mockConfigEntries(hass);
mockAreaRegistry(hass);
mockDeviceRegistry(hass);
mockFloorRegistry(hass);
mockLabelRegistry(hass);
mockEntityRegistry(hass, [
{
config_entry_id: "co2signal",
-21
View File
@@ -27,25 +27,4 @@ export const mockFrontend = (hass: MockHomeAssistant) => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return () => {};
});
hass.mockWS(
"frontend/subscribe_system_data",
(_msg, currentHass, onChange) => {
onChange?.({
value: currentHass.systemData,
});
// eslint-disable-next-line @typescript-eslint/no-empty-function
return () => {};
}
);
hass.mockWS("labs/subscribe", (_msg, _currentHass, onChange) => {
onChange?.({
preview_feature: _msg.preview_feature,
domain: _msg.domain,
enabled: false,
is_built_in: true,
});
// eslint-disable-next-line @typescript-eslint/no-empty-function
return () => {};
});
hass.mockWS("repairs/list_issues", () => ({ issues: [] }));
};
+1 -11
View File
@@ -7,18 +7,8 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
})
);
hass.mockWS("render_template", (msg, _hass, onChange) => {
let result = msg.template;
// Simple variable substitution for demo purposes
if (msg.variables) {
for (const [key, value] of Object.entries(msg.variables)) {
result = result.replace(
new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, "g"),
String(value)
);
}
}
onChange!({
result,
result: msg.template,
listeners: { all: false, domains: [], entities: [], time: false },
});
// eslint-disable-next-line @typescript-eslint/no-empty-function
+9 -9
View File
@@ -27,14 +27,14 @@
"type": "module",
"dependencies": {
"@babel/runtime": "7.28.6",
"@braintree/sanitize-url": "7.1.2",
"@braintree/sanitize-url": "7.1.1",
"@codemirror/autocomplete": "6.20.0",
"@codemirror/commands": "6.10.1",
"@codemirror/language": "6.12.1",
"@codemirror/legacy-modes": "6.5.2",
"@codemirror/search": "6.6.0",
"@codemirror/state": "6.5.4",
"@codemirror/view": "6.39.12",
"@codemirror/view": "6.39.11",
"@date-fns/tz": "1.4.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "7.2.0",
@@ -89,7 +89,7 @@
"@thomasloven/round-slider": "0.6.0",
"@tsparticles/engine": "3.9.1",
"@tsparticles/preset-links": "3.2.0",
"@vibrant/color": "4.0.4",
"@vibrant/color": "4.0.0",
"@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.10",
"@webcomponents/webcomponentsjs": "2.8.0",
@@ -122,7 +122,7 @@
"luxon": "3.7.2",
"marked": "17.0.1",
"memoize-one": "6.0.0",
"node-vibrant": "4.0.4",
"node-vibrant": "4.0.3",
"object-hash": "3.0.0",
"punycode": "2.3.1",
"qr-scanner": "1.4.2",
@@ -151,12 +151,12 @@
"@babel/plugin-transform-runtime": "7.28.5",
"@babel/preset-env": "7.28.6",
"@bundle-stats/plugin-webpack-filter": "4.21.9",
"@lokalise/node-api": "15.6.1",
"@lokalise/node-api": "15.6.0",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.0.3",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.5.1",
"@rspack/core": "1.7.4",
"@rsdoctor/rspack-plugin": "1.5.0",
"@rspack/core": "1.7.3",
"@rspack/dev-server": "1.2.1",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.25",
@@ -211,7 +211,7 @@
"rspack-manifest-plugin": "5.2.1",
"serve": "14.2.5",
"sinon": "21.0.1",
"tar": "7.5.7",
"tar": "7.5.6",
"terser-webpack-plugin": "5.3.16",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.3",
@@ -229,7 +229,7 @@
"clean-css": "5.3.3",
"@lit/reactive-element": "2.1.2",
"@fullcalendar/daygrid": "6.1.20",
"globals": "17.2.0",
"globals": "17.1.0",
"tslib": "2.8.1",
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
"glob@^10.2.2": "^10.5.0"
+1 -1
View File
@@ -12,7 +12,7 @@ readme = "README.md"
authors = [
{name = "The Home Assistant Authors", email = "[email protected]"}
]
requires-python = ">=3.14.0"
requires-python = ">=3.13.0"
[project.urls]
"Homepage" = "https://github.com/home-assistant/frontend"
+1 -13
View File
@@ -1,15 +1,3 @@
let isViewTransitionDisabled = false;
try {
isViewTransitionDisabled =
window.localStorage.getItem("disableViewTransition") === "true";
} catch {
// ignore
}
export const setViewTransitionDisabled = (disabled: boolean): void => {
isViewTransitionDisabled = disabled;
};
/**
* Executes a synchronous callback within a View Transition if supported, otherwise runs it directly.
*
@@ -26,7 +14,7 @@ export const setViewTransitionDisabled = (disabled: boolean): void => {
export const withViewTransition = (
callback: (viewTransitionAvailable: boolean) => void
): Promise<void> => {
if (!document.startViewTransition || isViewTransitionDisabled) {
if (!document.startViewTransition) {
callback(false);
return Promise.resolve();
}
+1 -1
View File
@@ -58,7 +58,7 @@ export class HaSankeyChart extends LitElement {
@property({ type: Boolean }) public vertical = false;
@property({ attribute: false }) public valueFormatter?: (
@property({ type: String, attribute: false }) public valueFormatter?: (
value: number
) => string;
+1 -1
View File
@@ -29,7 +29,7 @@ export class HaSunburstChart extends LitElement {
@property({ attribute: false }) public data?: SunburstNode;
@property({ attribute: false }) public valueFormatter?: (
@property({ type: String, attribute: false }) public valueFormatter?: (
value: number
) => string;
@@ -50,16 +50,16 @@ export class StateHistoryChartLine extends LitElement {
@property({ attribute: false }) public endTime!: Date;
@property({ attribute: false }) public paddingYAxis = 0;
@property({ attribute: false, type: Number }) public paddingYAxis = 0;
@property({ attribute: false }) public chartIndex?;
@property({ attribute: false, type: Number }) public chartIndex?;
@property({ attribute: "logarithmic-scale", type: Boolean })
public logarithmicScale = false;
@property({ attribute: false }) public minYAxis?: number;
@property({ attribute: false, type: Number }) public minYAxis?: number;
@property({ attribute: false }) public maxYAxis?: number;
@property({ attribute: false, type: Number }) public maxYAxis?: number;
@property({ attribute: "fit-y-data", type: Boolean }) public fitYData = false;
@@ -716,18 +716,6 @@ export class StateHistoryChartLine extends LitElement {
// Add an entry for final values
pushData(endTime, prevValues);
// For sensors, append current state if viewing recent data
const now = new Date();
// allow 1s of leeway for "now"
const isUpToNow = now.getTime() - endTime.getTime() <= 1000;
if (domain === "sensor" && isUpToNow && data.length === 1) {
const stateObj = this.hass.states[states.entity_id];
const currentValue = stateObj ? safeParseFloat(stateObj.state) : null;
if (currentValue !== null) {
data[0].data!.push([now, currentValue]);
}
}
// Concat two arrays
Array.prototype.push.apply(datasets, data);
});
@@ -47,9 +47,9 @@ export class StateHistoryChartTimeline extends LitElement {
@property({ attribute: false }) public endTime!: Date;
@property({ attribute: false }) public paddingYAxis = 0;
@property({ attribute: false, type: Number }) public paddingYAxis = 0;
@property({ attribute: false }) public chartIndex?;
@property({ attribute: false, type: Number }) public chartIndex?;
@property({ attribute: "hide-reset-button", type: Boolean })
public hideResetButton?: boolean;
+3 -3
View File
@@ -60,7 +60,7 @@ export class StateHistoryCharts extends LitElement {
@property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false;
@property({ attribute: false }) public hoursToShow?: number;
@property({ attribute: false, type: Number }) public hoursToShow?: number;
@property({ attribute: "show-names", type: Boolean }) public showNames = true;
@@ -73,9 +73,9 @@ export class StateHistoryCharts extends LitElement {
@property({ attribute: "logarithmic-scale", type: Boolean })
public logarithmicScale = false;
@property({ attribute: false }) public minYAxis?: number;
@property({ attribute: false, type: Number }) public minYAxis?: number;
@property({ attribute: false }) public maxYAxis?: number;
@property({ attribute: false, type: Number }) public maxYAxis?: number;
@property({ attribute: "fit-y-data", type: Boolean }) public fitYData = false;
+3 -54
View File
@@ -62,14 +62,14 @@ export class StatisticsChart extends LitElement {
@property({ attribute: false }) public endTime?: Date;
@property({ attribute: false })
@property({ attribute: false, type: Array })
public statTypes: StatisticType[] = ["sum", "min", "mean", "max"];
@property({ attribute: false }) public chartType: "line" | "bar" = "line";
@property({ attribute: false }) public minYAxis?: number;
@property({ attribute: false, type: Number }) public minYAxis?: number;
@property({ attribute: false }) public maxYAxis?: number;
@property({ attribute: false, type: Number }) public maxYAxis?: number;
@property({ attribute: "fit-y-data", type: Boolean }) public fitYData = false;
@@ -605,57 +605,6 @@ export class StatisticsChart extends LitElement {
}
});
// Append current state if viewing recent data
const now = new Date();
// allow 10m of leeway for "now", because stats are 5 minute aggregated
const isUpToNow = now.getTime() - endTime.getTime() <= 600000;
if (isUpToNow) {
// Skip external statistics (they have ":" in the ID)
if (!statistic_id.includes(":")) {
const stateObj = this.hass.states[statistic_id];
if (stateObj) {
const currentValue = parseFloat(stateObj.state);
if (
isFinite(currentValue) &&
!this._hiddenStats.has(statistic_id)
) {
// First, close out the last stat segment at prevEndTime
const lastEndTime = prevEndTime;
const lastValues = prevValues;
if (lastEndTime && lastValues) {
statDataSets.forEach((d, i) => {
d.data!.push(
this._transformDataValue([lastEndTime, ...lastValues[i]!])
);
});
}
// Then push the current state at now
statTypes.forEach((type, i) => {
const val: (number | null)[] = [];
if (type === "sum" || type === "change") {
// Skip cumulative types - need special calculation
val.push(null);
} else if (
type === bandTop &&
this.chartType === "line" &&
drawBands &&
!this._hiddenStats.has(`${statistic_id}-${bandBottom}`)
) {
// For band chart, current value is both min and max, so diff is 0
val.push(0);
val.push(currentValue);
} else {
val.push(currentValue);
}
statDataSets[i].data!.push(
this._transformDataValue([now, ...val])
);
});
}
}
}
}
// Concat two arrays
Array.prototype.push.apply(totalDataSets, statDataSets);
Array.prototype.push.apply(legendData, statLegendData);
+2 -2
View File
@@ -130,9 +130,9 @@ export class HaDataTable extends LitElement {
// eslint-disable-next-line lit/no-native-attributes
@property({ type: String }) public id = "id";
@property({ attribute: false }) public noDataText?: string;
@property({ attribute: false, type: String }) public noDataText?: string;
@property({ attribute: false }) public searchLabel?: string;
@property({ attribute: false, type: String }) public searchLabel?: string;
@property({ type: Boolean, attribute: "no-label-float" })
public noLabelFloat? = false;
+1 -1
View File
@@ -48,7 +48,7 @@ export class HaDevicePicker extends LitElement {
@property({ type: String, attribute: "search-label" })
public searchLabel?: string;
@property({ attribute: false }) public createDomains?: string[];
@property({ attribute: false, type: Array }) public createDomains?: string[];
/**
* Show only devices with entities from specific domains.
+1 -1
View File
@@ -76,7 +76,7 @@ class HaEntitiesPicker extends LitElement {
@property({ attribute: false })
public entityFilter?: HaEntityPickerEntityFilterFunc;
@property({ attribute: false }) public createDomains?: string[];
@property({ attribute: false, type: Array }) public createDomains?: string[];
@property({ type: Boolean })
public reorder = false;
+1 -1
View File
@@ -58,7 +58,7 @@ export class HaEntityPicker extends LitElement {
@property({ type: String, attribute: "search-label" })
public searchLabel?: string;
@property({ attribute: false }) public createDomains?: string[];
@property({ attribute: false, type: Array }) public createDomains?: string[];
/**
* Show entities from specific domains.
+1 -1
View File
@@ -78,7 +78,7 @@ export class HaStatisticPicker extends LitElement {
@property({ type: Boolean, attribute: "allow-custom-entity" })
public allowCustomEntity;
@property({ attribute: false })
@property({ attribute: false, type: Array })
public statisticIds?: StatisticsMetaData[];
@property({ attribute: false }) public helpMissingEntityUrl =
@@ -11,7 +11,7 @@ class HaStatisticsPicker extends LitElement {
@property({ type: Array }) public value?: string[];
@property({ attribute: false }) public statisticIds?: string[];
@property({ attribute: false, type: Array }) public statisticIds?: string[];
@property({ attribute: "statistic-types" })
public statisticTypes?: "mean" | "sum";
+1 -1
View File
@@ -36,7 +36,7 @@ export class HaAssistChat extends LitElement {
@property({ type: Boolean, attribute: "disable-speech" })
public disableSpeech = false;
@property({ attribute: false })
@property({ type: Boolean, attribute: false })
public startListening?: boolean;
@query("#message-input") private _messageInput!: HaTextField;
-2
View File
@@ -51,7 +51,6 @@ export class HaCard extends LitElement {
font-weight: var(--ha-font-weight-normal);
}
/* clean-css ignore:start */
:host
::slotted(
.card-content:not(:nth-child(1 of .card-content, .card-header))
@@ -60,7 +59,6 @@ export class HaCard extends LitElement {
padding-top: 0;
margin-top: calc(var(--ha-space-2) * -1);
}
/* clean-css ignore:end */
:host ::slotted(.card-content) {
padding: var(--ha-space-4);
+122 -136
View File
@@ -1,31 +1,24 @@
import { SelectBase } from "@material/mwc-select/mwc-select-base";
import { mdiMenuDown } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import type { PropertyValues } from "lit";
import { css, html, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import type { HomeAssistant } from "../types";
import "./ha-attribute-icon";
import "./ha-dropdown";
import "./ha-dropdown-item";
import { ifDefined } from "lit/directives/if-defined";
import { debounce } from "../common/util/debounce";
import { nextRender } from "../common/util/render-status";
import "./ha-icon";
import type { HaIcon } from "./ha-icon";
import "./ha-ripple";
import "./ha-svg-icon";
export interface SelectOption {
label: string;
value: string;
iconPath?: string;
icon?: string;
attributeIcon?: {
stateObj: HassEntity;
attribute: string;
attributeValue?: string;
};
}
import type { HaSvgIcon } from "./ha-svg-icon";
import "./ha-menu";
@customElement("ha-control-select-menu")
export class HaControlSelectMenu extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
export class HaControlSelectMenu extends SelectBase {
@query(".select") protected mdcRoot!: HTMLElement;
@query(".select-anchor") protected anchorElement!: HTMLDivElement | null;
@property({ type: Boolean, attribute: "show-arrow" })
public showArrow = false;
@@ -33,83 +26,95 @@ export class HaControlSelectMenu extends LitElement {
@property({ type: Boolean, attribute: "hide-label" })
public hideLabel = false;
@property({ type: Boolean })
public disabled = false;
@property() public options;
@property({ type: Boolean })
public required = false;
@property()
public label?: string;
@property()
public value?: string;
@property({ attribute: false }) public options: SelectOption[] = [];
@query("button") private _triggerButton!: HTMLButtonElement;
public override render() {
if (this.disabled) {
return this._renderTrigger();
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.get("options")) {
this.layoutOptions();
this.selectByValue(this.value);
}
return html`
<ha-dropdown placement="bottom" @wa-show=${this._showDropdown}>
${this._renderTrigger()} ${this.options.map(this._renderOption)}
</ha-dropdown>
`;
}
private _renderTrigger() {
const selectedText = this.getValueObject(this.options, this.value)?.label;
public override render() {
const classes = {
"select-disabled": this.disabled,
"select-required": this.required,
"select-no-value": !selectedText,
"select-invalid": !this.isUiValid,
"select-no-value": !this.selectedText,
};
return html`<button
?disabled=${this.disabled}
slot="trigger"
class="select-anchor ${classMap(classes)}"
>
${this._renderIcon()}
<div class="content">
${this.hideLabel
? nothing
: html`<p id="label" class="label">${this.label}</p>`}
${selectedText ? html`<p class="value">${selectedText}</p>` : nothing}
const labelledby = this.label && !this.hideLabel ? "label" : undefined;
const labelAttribute =
this.label && this.hideLabel ? this.label : undefined;
return html`
<div class="select ${classMap(classes)}">
<input
class="formElement"
.name=${this.name}
.value=${this.value}
hidden
?disabled=${this.disabled}
?required=${this.required}
/>
<!-- @ts-ignore -->
<div
class="select-anchor"
aria-autocomplete="none"
role="combobox"
aria-expanded=${this.menuOpen}
aria-invalid=${!this.isUiValid}
aria-haspopup="listbox"
aria-labelledby=${ifDefined(labelledby)}
aria-label=${ifDefined(labelAttribute)}
aria-required=${this.required}
aria-controls="listbox"
@focus=${this.onFocus}
@blur=${this.onBlur}
@click=${this.onClick}
@keydown=${this.onKeydown}
>
${this._renderIcon()}
<div class="content">
${this.hideLabel
? nothing
: html`<p id="label" class="label">${this.label}</p>`}
${this.selectedText
? html`<p class="value">${this.selectedText}</p>`
: nothing}
</div>
${this._renderArrow()}
<ha-ripple .disabled=${this.disabled}></ha-ripple>
</div>
${this.renderMenu()}
</div>
${this._renderArrow()}
</button>`;
`;
}
private _renderOption = (option: SelectOption) =>
html`<ha-dropdown-item
.value=${option.value}
class=${this.value === option.value ? "selected" : ""}
>${option.iconPath
? html`<ha-svg-icon slot="icon" .path=${option.iconPath}></ha-svg-icon>`
: option.icon
? html`<ha-icon slot="icon" .icon=${option.icon}></ha-icon>`
: option.attributeIcon
? html`<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${option.attributeIcon.stateObj}
.attribute=${option.attributeIcon.attribute}
.attributeValue=${option.attributeIcon.attributeValue}
></ha-attribute-icon>`
: nothing}
${option.label}</ha-dropdown-item
>`;
protected override renderMenu() {
const classes = this.getMenuClasses();
return html`<ha-menu
innerRole="listbox"
wrapFocus
class=${classMap(classes)}
activatable
.fullwidth=${this.fixedMenuPosition ? false : !this.naturalMenuWidth}
.open=${this.menuOpen}
.anchor=${this.anchorElement}
.fixed=${this.fixedMenuPosition}
@selected=${this.onSelected}
@opened=${this.onOpened}
@closed=${this.onClosed}
@items-updated=${this.onItemsUpdated}
@keydown=${this.handleTypeahead}
>
${this.renderMenuContent()}
</ha-menu>`;
}
private _renderArrow() {
if (!this.showArrow) {
return nothing;
}
if (!this.showArrow) return nothing;
return html`
<div class="icon">
@@ -119,42 +124,47 @@ export class HaControlSelectMenu extends LitElement {
}
private _renderIcon() {
const { iconPath, icon, attributeIcon } =
this.getValueObject(this.options, this.value) ?? {};
const index = this.mdcFoundation?.getSelectedIndex();
const items = this.menuElement?.items ?? [];
const item = index != null ? items[index] : undefined;
const defaultIcon = this.querySelector("[slot='icon']");
const icon = (item?.querySelector("[slot='graphic']") ?? null) as
| HaSvgIcon
| HaIcon
| null;
if (!defaultIcon && !icon) {
return null;
}
return html`
<div class="icon">
${iconPath
? html`<ha-svg-icon slot="icon" .path=${iconPath}></ha-svg-icon>`
: icon
? html`<ha-icon slot="icon" .icon=${icon}></ha-icon>`
: attributeIcon
? html`<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${attributeIcon.stateObj}
.attribute=${attributeIcon.attribute}
.attributeValue=${attributeIcon.attributeValue}
></ha-attribute-icon>`
: defaultIcon
? html`<slot name="icon"></slot>`
: nothing}
${icon && icon.localName === "ha-svg-icon" && "path" in icon
? html`<ha-svg-icon .path=${icon.path}></ha-svg-icon>`
: icon && icon.localName === "ha-icon" && "icon" in icon
? html`<ha-icon .path=${icon.icon}></ha-icon>`
: html`<slot name="icon"></slot>`}
</div>
`;
}
private _showDropdown() {
this.style.setProperty(
"--control-select-menu-width",
`${this._triggerButton.offsetWidth}px`
connectedCallback() {
super.connectedCallback();
window.addEventListener("translations-updated", this._translationsUpdated);
}
disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener(
"translations-updated",
this._translationsUpdated
);
}
private getValueObject = memoizeOne(
(options: SelectOption[], value?: string) =>
options.find((option) => option.value === value)
);
private _translationsUpdated = debounce(async () => {
await nextRender();
this.layoutOptions();
}, 500);
static override styles = [
css`
@@ -176,8 +186,6 @@ export class HaControlSelectMenu extends LitElement {
-webkit-tap-highlight-color: transparent;
}
.select-anchor {
border: none;
text-align: left;
height: var(--control-select-menu-height);
padding: var(--control-select-menu-padding);
overflow: hidden;
@@ -203,12 +211,6 @@ export class HaControlSelectMenu extends LitElement {
font-weight: var(--ha-font-weight-normal);
letter-spacing: 0.25px;
}
.select-anchor:hover {
--control-select-menu-background-color: var(
--ha-color-on-neutral-quiet
);
}
.content {
display: flex;
flex-direction: column;
@@ -228,19 +230,16 @@ export class HaControlSelectMenu extends LitElement {
}
.label {
font-size: var(--ha-font-size-s);
font-size: 0.85em;
letter-spacing: 0.4px;
}
.select-no-value .label {
font-size: inherit;
line-height: inherit;
letter-spacing: inherit;
}
.content .value {
font-size: var(--ha-font-size-m);
}
.select-anchor:focus-visible {
box-shadow: 0 0 0 2px var(--control-select-menu-focus-color);
}
@@ -259,23 +258,10 @@ export class HaControlSelectMenu extends LitElement {
opacity: var(--control-select-menu-background-opacity);
}
.select-disabled.select-anchor {
.select-disabled .select-anchor {
cursor: not-allowed;
color: var(--disabled-color);
}
ha-dropdown-item.selected {
font-weight: var(--ha-font-weight-medium);
color: var(--primary-color);
background-color: var(--ha-color-fill-primary-quiet-resting);
--icon-primary-color: var(--primary-color);
}
ha-dropdown-item.selected:hover {
background-color: var(--ha-color-fill-primary-quiet-hover);
}
ha-dropdown::part(menu) {
min-width: var(--control-select-menu-width);
}
`,
];
}
+2 -2
View File
@@ -24,10 +24,10 @@ export class HaControlSwitch extends LitElement {
@property({ type: Boolean }) public checked = false;
// SVG icon path (if you need a non SVG icon instead, use the provided on icon slot to pass an <ha-icon slot="icon-on"> in)
@property({ attribute: false }) pathOn?: string;
@property({ attribute: false, type: String }) pathOn?: string;
// SVG icon path (if you need a non SVG icon instead, use the provided off icon slot to pass an <ha-icon slot="icon-off"> in)
@property({ attribute: false }) pathOff?: string;
@property({ attribute: false, type: String }) pathOff?: string;
@property({ type: String })
public label?: string;
-3
View File
@@ -88,9 +88,6 @@ export class HaDialog extends DialogBase {
--mdc-typography-headline6-font-weight: var(--ha-font-weight-normal);
--mdc-typography-headline6-font-size: 1.574rem;
}
.mdc-dialog .mdc-dialog__scrim {
background-color: var(--mdc-dialog-scrim-color, none);
}
.mdc-dialog__actions {
justify-content: var(--justify-action-buttons, flex-end);
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
+1 -1
View File
@@ -1,9 +1,9 @@
import DropdownItem from "@home-assistant/webawesome/dist/components/dropdown-item/dropdown-item";
import "@home-assistant/webawesome/dist/components/icon/icon";
import { mdiCheckboxBlankOutline, mdiCheckboxMarked } from "@mdi/js";
import { css, type CSSResultGroup, html } from "lit";
import { customElement } from "lit/decorators";
import "./ha-svg-icon";
import { mdiCheckboxBlankOutline, mdiCheckboxMarked } from "@mdi/js";
/**
* Home Assistant dropdown item component
-3
View File
@@ -1,9 +1,6 @@
import Dropdown from "@home-assistant/webawesome/dist/components/dropdown/dropdown";
import { css, type CSSResultGroup } from "lit";
import { customElement, property } from "lit/decorators";
import type { HaDropdownItem } from "./ha-dropdown-item";
export type HaDropdownSelectEvent = CustomEvent<{ item: HaDropdownItem }>;
/**
* Home Assistant dropdown component
+1 -2
View File
@@ -31,7 +31,6 @@ import "./ha-expansion-panel";
import "./ha-icon";
import "./ha-list";
import "./ha-list-item";
import type { HaDropdownSelectEvent } from "./ha-dropdown";
@customElement("ha-filter-categories")
export class HaFilterCategories extends SubscribeMixin(LitElement) {
@@ -175,7 +174,7 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
}
}
private _handleAction(ev: HaDropdownSelectEvent) {
private _handleAction(ev: CustomEvent<{ item: { value: string } }>) {
const categoryId = (ev.currentTarget as any).categoryId;
const action = ev.detail.item.value;
switch (action) {
@@ -11,7 +11,8 @@ import "../ha-formfield";
import "../ha-icon-button";
import "../ha-picker-field";
import type { HaDropdown, HaDropdownSelectEvent } from "../ha-dropdown";
import type { HaDropdown } from "../ha-dropdown";
import type { HaDropdownItem } from "../ha-dropdown-item";
import type {
HaFormElement,
HaFormMultiSelectData,
@@ -107,7 +108,7 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
`;
}
protected _toggleItem(ev: HaDropdownSelectEvent) {
protected _toggleItem(ev: CustomEvent<{ item: HaDropdownItem }>) {
ev.preventDefault(); // keep the dropdown open
const value = ev.detail.item.value;
const action = (ev.detail.item as any).action;
@@ -17,7 +17,6 @@ import type {
HaFormOptionalActionsSchema,
HaFormSchema,
} from "./types";
import type { HaDropdownSelectEvent } from "../ha-dropdown";
const NO_ACTIONS = [];
@@ -143,7 +142,7 @@ export class HaFormOptionalActions extends LitElement implements HaFormElement {
`;
}
private _handleAddAction(ev: HaDropdownSelectEvent) {
private _handleAddAction(ev: CustomEvent<{ item: { value: string } }>) {
const action = ev.detail.item.value;
this._displayActions = [...(this._displayActions ?? []), action];
}
+1 -1
View File
@@ -30,7 +30,7 @@ export class HaGauge extends LitElement {
@property({ attribute: false })
public formatOptions?: Intl.NumberFormatOptions;
@property({ attribute: false }) public valueText?: string;
@property({ attribute: false, type: String }) public valueText?: string;
@property({ attribute: false }) public locale!: FrontendLocaleData;
+1 -1
View File
@@ -48,7 +48,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
section?: string
) => (PickerComboBoxItem | string)[] | undefined;
@property({ attribute: false })
@property({ attribute: false, type: Array })
public getAdditionalItems?: (searchString?: string) => PickerComboBoxItem[];
@property({ attribute: false })
+4 -4
View File
@@ -113,13 +113,13 @@ class HaHsColorPicker extends LitElement {
@property({ type: Boolean, reflect: true })
public disabled = false;
@property({ attribute: false })
@property({ type: Number, attribute: false })
public renderSize?: number;
@property({ type: Array })
public value?: [number, number];
@property({ attribute: false })
@property({ attribute: false, type: Number })
public colorBrightness?: number;
@property({ type: Number })
@@ -131,10 +131,10 @@ class HaHsColorPicker extends LitElement {
@property({ type: Number })
public ww?: number;
@property({ attribute: false })
@property({ attribute: false, type: Number })
public minKelvin?: number;
@property({ attribute: false })
@property({ attribute: false, type: Number })
public maxKelvin?: number;
@query("#canvas") private _canvas!: HTMLCanvasElement;
+1 -1
View File
@@ -19,7 +19,7 @@ export interface HaIconButtonToolbarItem {
@customElement("ha-icon-button-toolbar")
export class HaIconButtonToolbar extends LitElement {
@property({ attribute: false })
@property({ type: Array, attribute: false })
public items: (HaIconButtonToolbarItem | string)[] = [];
@queryAll("ha-icon-button") private _buttons?: HaIconButton[];
+4 -39
View File
@@ -134,50 +134,15 @@ export class HaMarkdown extends LitElement {
}
table[role="presentation"] {
--markdown-table-border-collapse: separate;
--markdown-table-border-width: 0;
--markdown-table-border-width: attr(border, 0);
--markdown-table-padding-inline: 0;
--markdown-table-padding-block: 0;
th,
th {
vertical-align: attr(valign, middle);
}
td {
vertical-align: middle;
}
}
table[role="presentation"] td[valign="top"],
table[role="presentation"] th[valign="top"] {
vertical-align: top;
}
table[role="presentation"] td[valign="middle"],
table[role="presentation"] th[valign="middle"] {
vertical-align: middle;
}
table[role="presentation"] td[valign="bottom"],
table[role="presentation"] th[valign="bottom"] {
vertical-align: bottom;
}
table[role="presentation"] td[valign="baseline"],
table[role="presentation"] th[valign="baseline"] {
vertical-align: baseline;
}
@supports (border-width: attr(border px, 0)) {
table[role="presentation"] {
--markdown-table-border-width: attr(border px, 0);
}
table[role="presentation"] th,
table[role="presentation"] td {
vertical-align: attr(valign, middle);
}
}
table[role="presentation"][border="0"] {
--markdown-table-border-width: 0;
}
table[role="presentation"][border="1"] {
--markdown-table-border-width: 1px;
}
table[role="presentation"][border="2"] {
--markdown-table-border-width: 2px;
}
table[role="presentation"][border="3"] {
--markdown-table-border-width: 3px;
}
table {
border-collapse: var(--markdown-table-border-collapse, collapse);
+3 -26
View File
@@ -11,7 +11,6 @@ import { fetchConfig } from "../data/lovelace/config/types";
import { getPanelIcon, getPanelTitle } from "../data/panel";
import { findRelated, type RelatedResult } from "../data/search";
import { PANEL_DASHBOARDS } from "../panels/config/lovelace/dashboards/ha-config-lovelace-dashboards";
import { computeAreaPath } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper";
import { multiTermSortedSearch } from "../resources/fuseMultiTerm";
import type { HomeAssistant, ValueChangedEvent } from "../types";
import type { ActionRelatedContext } from "../panels/lovelace/components/hui-action-editor";
@@ -26,9 +25,8 @@ import {
type NavigationGroup = "related" | "dashboards" | "views" | "other_routes";
const RELATED_SORT_PREFIX = {
area_view: "0_area_view",
area: "1_area_settings",
device: "2_device",
area: "0_area",
device: "1_device",
} as const;
interface NavigationItem extends PickerComboBoxItem {
@@ -439,31 +437,10 @@ export class HaNavigationPicker extends LitElement {
for (const areaId of relatedAreaIds) {
const area = this.hass.areas[areaId];
const primary = area?.name ?? areaId;
// Area dashboard view
const viewPath = `/home/${computeAreaPath(areaId)}`;
relatedItems.push({
id: viewPath,
primary,
secondary: viewPath,
icon: area?.icon ?? undefined,
icon_path: area?.icon ? undefined : mdiTextureBox,
sorting_label: createSortingLabel(
RELATED_SORT_PREFIX.area_view,
primary,
viewPath
),
group: "related",
});
// Area settings
const path = `/config/areas/area/${areaId}`;
relatedItems.push({
id: path,
primary: this.hass.localize(
"ui.components.navigation-picker.area_settings",
{ area: primary }
),
primary,
secondary: path,
icon: area?.icon ?? undefined,
icon_path: area?.icon ? undefined : mdiTextureBox,
+1 -1
View File
@@ -89,7 +89,7 @@ export class HaPasswordField extends LitElement {
@property({ type: Boolean }) readOnly = false;
// eslint-disable-next-line lit/no-native-attributes
@property({ attribute: false }) autocapitalize = "";
@property({ attribute: false, type: String }) autocapitalize = "";
@state() private _unmaskedPassword = false;
+1 -7
View File
@@ -22,7 +22,6 @@ import {
import { haStyleScrollbar } from "../resources/styles";
import { loadVirtualizer } from "../resources/virtualizer";
import type { HomeAssistant } from "../types";
import { isTouch } from "../util/is_touch";
import "./chips/ha-chip-set";
import "./chips/ha-filter-chip";
import "./ha-combo-box-item";
@@ -119,7 +118,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
section?: string
) => PickerComboBoxItem[] | undefined;
@property({ attribute: false })
@property({ attribute: false, type: Array })
public getAdditionalItems?: (searchString?: string) => PickerComboBoxItem[];
@property({ attribute: false })
@@ -285,7 +284,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
section === "separator"
? html`<div class="separator"></div>`
: html`<ha-filter-chip
@mousedown=${isTouch ? undefined : this._preventBlur}
@click=${this._toggleSection}
.section-id=${section.id}
.selected=${this._selectedSection === section.id}
@@ -508,10 +506,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
this._valuePinned = true;
};
private _preventBlur(ev: Event) {
ev.preventDefault();
}
private _toggleSection(ev: Event) {
ev.stopPropagation();
this._resetSelectedItem();
+1 -1
View File
@@ -22,7 +22,7 @@ export class HaQrCode extends LitElement {
@property({ type: Number })
public margin = 4;
@property({ attribute: false })
@property({ attribute: false, type: Number })
public maskPattern?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
@property({ attribute: "center-image" }) public centerImage?: string;
+2 -2
View File
@@ -14,8 +14,8 @@ import type { HomeAssistant } from "../types";
import "./ha-alert";
import "./ha-button";
import "./ha-dropdown";
import type { HaDropdownSelectEvent } from "./ha-dropdown";
import "./ha-dropdown-item";
import type { HaDropdownItem } from "./ha-dropdown-item";
import "./ha-spinner";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
@@ -259,7 +259,7 @@ class HaQrScanner extends LitElement {
this._qrCodeScanned(this._manualInput!.value);
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const cameraId = ev.detail?.item?.value;
if (cameraId) {
this._selectedCamera = cameraId;
@@ -224,7 +224,6 @@ ${typeof this._templateResult.result === "object"
margin-bottom: 0;
direction: ltr;
border-radius: var(--ha-border-radius-sm);
overflow-wrap: break-word;
}
`;
}
+1 -1
View File
@@ -76,7 +76,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@property({ type: Boolean, reflect: true }) public compact = false;
@property({ attribute: false }) public createDomains?: string[];
@property({ attribute: false, type: Array }) public createDomains?: string[];
/**
* Show only targets with entities from specific domains.
@@ -48,7 +48,7 @@ export class TopAppBarBaseBase extends BaseElement {
@query(".pane .ha-scrollbar") private _paneElement?: HTMLElement;
@property({ attribute: false })
@property({ attribute: false, type: Object })
get scrollTarget() {
return this._scrollTarget || window;
}
@@ -21,8 +21,8 @@ import { haStyleDialog, haStyleDialogFixedTop } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import "../ha-dialog-header";
import "../ha-dropdown";
import type { HaDropdownSelectEvent } from "../ha-dropdown";
import "../ha-dropdown-item";
import type { HaDropdownItem } from "../ha-dropdown-item";
import "../ha-icon-button-arrow-prev";
import "../ha-wa-dialog";
import "./ha-media-manage-button";
@@ -177,7 +177,7 @@ class DialogMediaPlayerBrowse extends LitElement {
this.classList.add("opened");
}
private async _handleMenuAction(ev: HaDropdownSelectEvent) {
private async _handleMenuAction(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
switch (action) {
case "auto":
+1 -1
View File
@@ -15,7 +15,7 @@ export class HaTileContainer extends LitElement {
@property({ type: Boolean })
public vertical = false;
@property({ attribute: false })
@property({ type: Boolean, attribute: false })
public interactive = false;
@property({ attribute: false })
-1
View File
@@ -53,7 +53,6 @@ export type ClimateEntity = HassEntityBase & {
current_humidity?: number;
target_humidity_low?: number;
target_humidity_high?: number;
target_humidity_step?: number;
min_humidity?: number;
max_humidity?: number;
fan_mode?: string;
+2 -2
View File
@@ -35,7 +35,7 @@ export const deserializeFilters = (value: DataTableFilters) => {
// returns true when this filter has *selected* options and the filter's name
// equals the given filterName
export const isFilterUsed = (
export const isUsedFilter = (
key: string,
filter: DataTableFilter,
filterName: string
@@ -49,7 +49,7 @@ export const isFilterUsed = (
// which has resulted in a list of items that match these selected opions
// (this list can be empty),
// and the filter's name equals (one of) the given filterName(s)
export const isRelatedItemsFilterUsed = (
export const isUsedRelatedItemsFilter = (
key: string,
filter: DataTableFilter,
filterName: string | string[]
-42
View File
@@ -511,15 +511,6 @@ const getEnergyData = async (
"mean",
])
: {};
// If power stats 5 minute data is selected, then also fetch hourly data which
// will be used to back-fill any missing data points in the 5 minute data when
// the requested range is beyond the limit of short term statistics.
const _powerStatsHour: Statistics | Promise<Statistics> =
powerStatIds.length && finePeriod === "5minute"
? fetchStatistics(hass!, start, end, powerStatIds, "hour", powerUnits, [
"mean",
])
: {};
const _waterStats: Statistics | Promise<Statistics> = waterStatIds.length
? fetchStatistics(hass!, start, end, waterStatIds, period, waterUnits, [
@@ -628,7 +619,6 @@ const getEnergyData = async (
const [
energyStats,
powerStats,
powerStatsHour,
waterStats,
energyStatsCompare,
waterStatsCompare,
@@ -637,44 +627,12 @@ const getEnergyData = async (
] = await Promise.all([
_energyStats,
_powerStats,
_powerStatsHour,
_waterStats,
_energyStatsCompare,
_waterStatsCompare,
_fossilEnergyConsumption,
_fossilEnergyConsumptionCompare,
]);
// Back-fill any missing power statistics from hourly data if present
if (Object.keys(powerStatsHour).length) {
powerStatIds.forEach((powerId) => {
if (powerId in powerStatsHour) {
// If we have extra hourly power statistics for an ID, we may need to
// insert data into statistics
if (powerId in powerStats && powerStats[powerId].length) {
// We have 5-minute data. Only insert hourly values for time periods
// before the first 5-minute value.
const powerStatFirst = powerStats[powerId][0];
const powerStatHour = powerStatsHour[powerId];
let powerStatHourLast = 0;
for (const powerStat of powerStatHour) {
if (powerStat.end > powerStatFirst.start) {
break;
}
powerStatHourLast++;
}
powerStats[powerId] = [
...powerStatHour.slice(0, powerStatHourLast),
...powerStats[powerId],
];
} else {
// There was no 5-minute data, so simply insert full hourly data
powerStats[powerId] = powerStatsHour[powerId];
}
}
});
}
const stats = { ...energyStats, ...waterStats, ...powerStats };
if (compare) {
statsCompare = { ...energyStatsCompare, ...waterStatsCompare };
@@ -91,11 +91,9 @@ export const showConfigFlowDialog = (
renderShowFormStepFieldLabel(hass, step, field, options) {
if (field.type === "expandable") {
return (
hass.localize(
`component.${step.handler}.config.step.${step.step_id}.sections.${field.name}.name`,
step.description_placeholders
) || field.name
return hass.localize(
`component.${step.handler}.config.step.${step.step_id}.sections.${field.name}.name`,
step.description_placeholders
);
}
@@ -95,11 +95,9 @@ export const showOptionsFlowDialog = (
renderShowFormStepFieldLabel(hass, step, field, options) {
if (field.type === "expandable") {
return (
hass.localize(
`component.${configEntry.domain}.options.step.${step.step_id}.sections.${field.name}.name`,
step.description_placeholders
) || field.name
return hass.localize(
`component.${configEntry.domain}.options.step.${step.step_id}.sections.${field.name}.name`,
step.description_placeholders
);
}
@@ -86,11 +86,9 @@ export const showSubConfigFlowDialog = (
renderShowFormStepFieldLabel(hass, step, field, options) {
if (field.type === "expandable") {
return (
hass.localize(
`component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.sections.${field.name}.name`,
step.description_placeholders
) || field.name
return hass.localize(
`component.${configEntry.domain}.config_subentries.${flowType}.step.${step.step_id}.sections.${field.name}.name`,
step.description_placeholders
);
}
@@ -8,7 +8,9 @@ import {
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle";
@@ -27,7 +29,6 @@ import "../../../state-control/climate/ha-state-control-climate-temperature";
import type { HomeAssistant } from "../../../types";
import "../components/ha-more-info-control-select-container";
import { moreInfoControlStyle } from "../components/more-info-control-style";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
type MainControl = "temperature" | "humidity";
@@ -168,112 +169,182 @@ class MoreInfoClimate extends LitElement {
</div>
<ha-more-info-control-select-container>
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.localize("ui.card.climate.mode")}
.value=${stateObj.state}
.disabled=${this.stateObj.state === UNAVAILABLE}
.options=${stateObj.attributes.hvac_modes
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleOperationModeChanged}
@closed=${stopPropagation}
>
${html`
<ha-svg-icon
slot="icon"
.path=${climateHvacModeIcon(stateObj.state)}
></ha-svg-icon>
`}
${stateObj.attributes.hvac_modes
.concat()
.sort(compareClimateHvacModes)
.map((mode) => ({
value: mode,
iconPath: climateHvacModeIcon(mode),
label: this.hass.formatEntityState(stateObj, mode),
}))}
@wa-select=${this._handleOperationModeChanged}
>
<ha-svg-icon
slot="icon"
.path=${climateHvacModeIcon(stateObj.state)}
></ha-svg-icon>
.map(
(mode) => html`
<ha-list-item .value=${mode} graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${climateHvacModeIcon(mode)}
></ha-svg-icon>
${this.hass.formatEntityState(stateObj, mode)}
</ha-list-item>
`
)}
</ha-control-select-menu>
${supportPresetMode && stateObj.attributes.preset_modes
? html`
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.formatEntityAttributeName(
stateObj,
"preset_mode"
)}
.value=${stateObj.attributes.preset_mode}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handlePresetmodeChanged}
.options=${stateObj.attributes.preset_modes.map((mode) => ({
value: mode,
label: this.hass.formatEntityAttributeValue(
stateObj,
"preset_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "preset_mode",
attributeValue: mode,
},
}))}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handlePresetmodeChanged}
@closed=${stopPropagation}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
${stateObj.attributes.preset_mode
? html`
<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="preset_mode"
.attributeValue=${stateObj.attributes.preset_mode}
></ha-attribute-icon>
`
: html`
<ha-svg-icon
slot="icon"
.path=${mdiTuneVariant}
></ha-svg-icon>
`}
${stateObj.attributes.preset_modes!.map(
(mode) => html`
<ha-list-item .value=${mode} graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="preset_mode"
.attributeValue=${mode}
></ha-attribute-icon>
${this.hass.formatEntityAttributeValue(
stateObj,
"preset_mode",
mode
)}
</ha-list-item>
`
)}
</ha-control-select-menu>
`
: nothing}
${supportFanMode && stateObj.attributes.fan_modes
? html`
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.formatEntityAttributeName(
stateObj,
"fan_mode"
)}
.value=${stateObj.attributes.fan_mode}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handleFanModeChanged}
.options=${stateObj.attributes.fan_modes.map((mode) => ({
value: mode,
label: this.hass.formatEntityAttributeValue(
stateObj,
"fan_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "fan_mode",
attributeValue: mode,
},
}))}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleFanModeChanged}
@closed=${stopPropagation}
>
<ha-svg-icon slot="icon" .path=${mdiFan}></ha-svg-icon>
${stateObj.attributes.fan_mode
? html`
<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="fan_mode"
.attributeValue=${stateObj.attributes.fan_mode}
></ha-attribute-icon>
`
: html`
<ha-svg-icon slot="icon" .path=${mdiFan}></ha-svg-icon>
`}
${stateObj.attributes.fan_modes!.map(
(mode) => html`
<ha-list-item .value=${mode} graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="fan_mode"
.attributeValue=${mode}
></ha-attribute-icon>
${this.hass.formatEntityAttributeValue(
stateObj,
"fan_mode",
mode
)}
</ha-list-item>
`
)}
</ha-control-select-menu>
`
: nothing}
${supportSwingMode && stateObj.attributes.swing_modes
? html`
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.formatEntityAttributeName(
stateObj,
"swing_mode"
)}
.value=${stateObj.attributes.swing_mode}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handleSwingmodeChanged}
.options=${stateObj.attributes.swing_modes.map((mode) => ({
value: mode,
label: this.hass.formatEntityAttributeValue(
stateObj,
"swing_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "swing_mode",
attributeValue: mode,
},
}))}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleSwingmodeChanged}
@closed=${stopPropagation}
>
<ha-svg-icon
slot="icon"
.path=${mdiArrowOscillating}
></ha-svg-icon>
${stateObj.attributes.swing_mode
? html`
<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="swing_mode"
.attributeValue=${stateObj.attributes.swing_mode}
></ha-attribute-icon>
`
: html`
<ha-svg-icon
slot="icon"
.path=${mdiArrowOscillating}
></ha-svg-icon>
`}
${stateObj.attributes.swing_modes!.map(
(mode) => html`
<ha-list-item .value=${mode} graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="swing_mode"
.attributeValue=${mode}
></ha-attribute-icon>
${this.hass.formatEntityAttributeValue(
stateObj,
"swing_mode",
mode
)}
</ha-list-item>
`
)}
</ha-control-select-menu>
`
: nothing}
@@ -281,34 +352,52 @@ class MoreInfoClimate extends LitElement {
stateObj.attributes.swing_horizontal_modes
? html`
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.formatEntityAttributeName(
stateObj,
"swing_horizontal_mode"
)}
.value=${stateObj.attributes.swing_horizontal_mode}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handleSwingHorizontalmodeChanged}
.options=${stateObj.attributes.swing_horizontal_modes.map(
(mode) => ({
value: mode,
label: this.hass.formatEntityAttributeValue(
stateObj,
"swing_horizontal_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "swing_horizontal_mode",
attributeValue: mode,
},
})
)}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleSwingHorizontalmodeChanged}
@closed=${stopPropagation}
>
<ha-svg-icon
slot="icon"
.path=${mdiArrowOscillating}
></ha-svg-icon>
${stateObj.attributes.swing_horizontal_mode
? html`
<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="swing_horizontal_mode"
.attributeValue=${stateObj.attributes
.swing_horizontal_mode}
></ha-attribute-icon>
`
: html`
<ha-svg-icon
slot="icon"
.path=${mdiArrowOscillating}
></ha-svg-icon>
`}
${stateObj.attributes.swing_horizontal_modes!.map(
(mode) => html`
<ha-list-item .value=${mode} graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="swing_horizontal_mode"
.attributeValue=${mode}
></ha-attribute-icon>
${this.hass.formatEntityAttributeValue(
stateObj,
"swing_horizontal_mode",
mode
)}
</ha-list-item>
`
)}
</ha-control-select-menu>
`
: nothing}
@@ -321,8 +410,8 @@ class MoreInfoClimate extends LitElement {
this._mainControl = ev.currentTarget.control;
}
private _handleFanModeChanged(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value;
private _handleFanModeChanged(ev) {
const newVal = ev.target.value;
this._callServiceHelper(
this.stateObj!.attributes.fan_mode,
newVal,
@@ -331,15 +420,15 @@ class MoreInfoClimate extends LitElement {
);
}
private _handleOperationModeChanged(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value;
private _handleOperationModeChanged(ev) {
const newVal = ev.target.value;
this._callServiceHelper(this.stateObj!.state, newVal, "set_hvac_mode", {
hvac_mode: newVal,
});
}
private _handleSwingmodeChanged(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value;
private _handleSwingmodeChanged(ev) {
const newVal = ev.target.value;
this._callServiceHelper(
this.stateObj!.attributes.swing_mode,
newVal,
@@ -348,8 +437,8 @@ class MoreInfoClimate extends LitElement {
);
}
private _handleSwingHorizontalmodeChanged(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value;
private _handleSwingHorizontalmodeChanged(ev) {
const newVal = ev.target.value;
this._callServiceHelper(
this.stateObj!.attributes.swing_horizontal_mode,
newVal,
@@ -358,8 +447,8 @@ class MoreInfoClimate extends LitElement {
);
}
private _handlePresetmodeChanged(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value || null;
private _handlePresetmodeChanged(ev) {
const newVal = ev.target.value || null;
if (newVal) {
this._callServiceHelper(
this.stateObj!.attributes.preset_mode,
+101 -62
View File
@@ -9,6 +9,7 @@ import {
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { stateActive } from "../../../common/entity/state_active";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
@@ -30,7 +31,6 @@ import type { HomeAssistant } from "../../../types";
import "../components/ha-more-info-control-select-container";
import "../components/ha-more-info-state-header";
import { moreInfoControlStyle } from "../components/more-info-control-style";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
@customElement("more-info-fan")
class MoreInfoFan extends LitElement {
@@ -48,8 +48,8 @@ class MoreInfoFan extends LitElement {
});
};
private _handleDirection(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value;
private _handleDirection(ev) {
const newVal = ev.target.value;
const oldVal = this.stateObj?.attributes.direction;
if (!newVal || oldVal === newVal) return;
@@ -60,8 +60,8 @@ class MoreInfoFan extends LitElement {
});
}
private _handlePresetMode(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value;
private _handlePresetMode(ev) {
const newVal = ev.target.value;
const oldVal = this._presetMode;
if (!newVal || oldVal === newVal) return;
@@ -73,8 +73,8 @@ class MoreInfoFan extends LitElement {
});
}
private _handleOscillating(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value === "true";
private _handleOscillating(ev) {
const newVal = ev.target.value === "true";
const oldVal = this.stateObj?.attributes.oscillating;
if (oldVal === newVal) return;
@@ -176,64 +176,65 @@ class MoreInfoFan extends LitElement {
${supportsPresetMode && this.stateObj.attributes.preset_modes
? html`
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.formatEntityAttributeName(
this.stateObj,
"preset_mode"
)}
.value=${this.stateObj.attributes.preset_mode}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handlePresetMode}
.options=${this.stateObj.attributes.preset_modes.map(
(mode) => ({
value: mode,
label: this.hass.formatEntityAttributeValue(
this.stateObj!,
"preset_mode",
mode
),
attributeIcon: this.stateObj
? {
stateObj: this.stateObj,
attribute: "preset_mode",
attributeValue: mode,
}
: undefined,
})
)}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handlePresetMode}
@closed=${stopPropagation}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
${this.stateObj.attributes.preset_mode
? html`<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="preset_mode"
.attributeValue=${this.stateObj.attributes.preset_mode}
></ha-attribute-icon>`
: html`
<ha-svg-icon
slot="icon"
.path=${mdiTuneVariant}
></ha-svg-icon>
`}
${this.stateObj.attributes.preset_modes?.map(
(mode) => html`
<ha-list-item .value=${mode} graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="preset_mode"
.attributeValue=${mode}
></ha-attribute-icon>
${this.hass.formatEntityAttributeValue(
this.stateObj!,
"preset_mode",
mode
)}
</ha-list-item>
`
)}
</ha-control-select-menu>
`
: nothing}
${supportsDirection
? html`
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.formatEntityAttributeName(
this.stateObj,
"direction"
)}
.value=${this.stateObj.attributes.direction}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handleDirection}
.options=${["forward", "reverse"].map((direction) => ({
value: direction,
label: this.stateObj
? this.hass.formatEntityAttributeValue(
this.stateObj,
"direction",
direction
)
: direction,
attributeIcon: this.stateObj
? {
stateObj: this.stateObj,
attribute: "direction",
attributeValue: direction,
}
: undefined,
}))}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleDirection}
@closed=${stopPropagation}
>
<ha-attribute-icon
slot="icon"
@@ -242,13 +243,40 @@ class MoreInfoFan extends LitElement {
attribute="direction"
.attributeValue=${this.stateObj.attributes.direction}
></ha-attribute-icon>
<ha-list-item value="forward" graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="direction"
attributeValue="forward"
></ha-attribute-icon>
${this.hass.formatEntityAttributeValue(
this.stateObj,
"direction",
"forward"
)}
</ha-list-item>
<ha-list-item value="reverse" graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="direction"
attributeValue="reverse"
></ha-attribute-icon>
${this.hass.formatEntityAttributeValue(
this.stateObj,
"direction",
"reverse"
)}
</ha-list-item>
</ha-control-select-menu>
`
: nothing}
${supportsOscillate
? html`
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.formatEntityAttributeName(
this.stateObj,
"oscillating"
@@ -257,26 +285,37 @@ class MoreInfoFan extends LitElement {
? "true"
: "false"}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handleOscillating}
.options=${["true", "false"].map((val) => ({
value: val,
iconPath:
val === "true"
? mdiArrowOscillating
: mdiArrowOscillatingOff,
label: this.stateObj
? this.hass.formatEntityAttributeValue(
this.stateObj,
"oscillating",
val === "true"
)
: val,
}))}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleOscillating}
@closed=${stopPropagation}
>
<ha-svg-icon
slot="icon"
.path=${mdiArrowOscillatingOff}
></ha-svg-icon>
<ha-list-item value="true" graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiArrowOscillating}
></ha-svg-icon>
${this.hass.formatEntityAttributeValue(
this.stateObj,
"oscillating",
true
)}
</ha-list-item>
<ha-list-item value="false" graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiArrowOscillatingOff}
></ha-svg-icon>
${this.hass.formatEntityAttributeValue(
this.stateObj,
"oscillating",
false
)}
</ha-list-item>
</ha-control-select-menu>
`
: nothing}
@@ -2,6 +2,7 @@ import { mdiPower, mdiTuneVariant } from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select-menu";
@@ -13,7 +14,6 @@ import "../../../state-control/humidifier/ha-state-control-humidifier-humidity";
import type { HomeAssistant } from "../../../types";
import "../components/ha-more-info-control-select-container";
import { moreInfoControlStyle } from "../components/more-info-control-style";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
@customElement("more-info-humidifier")
class MoreInfoHumidifier extends LitElement {
@@ -74,48 +74,68 @@ class MoreInfoHumidifier extends LitElement {
<ha-more-info-control-select-container>
<ha-control-select-menu
.hass=${hass}
.label=${this.hass.localize("ui.card.humidifier.state")}
.value=${this.stateObj.state}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handleStateChanged}
.options=${["off", "on"].map((fanState) => ({
value: fanState,
label: this.stateObj
? this.hass.formatEntityState(this.stateObj, fanState)
: fanState,
}))}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleStateChanged}
@closed=${stopPropagation}
>
<ha-svg-icon slot="icon" .path=${mdiPower}></ha-svg-icon>
<ha-list-item value="off">
${this.hass.formatEntityState(this.stateObj, "off")}
</ha-list-item>
<ha-list-item value="on">
${this.hass.formatEntityState(this.stateObj, "on")}
</ha-list-item>
</ha-control-select-menu>
${supportModes
? html`
<ha-control-select-menu
.hass=${hass}
.label=${hass.localize("ui.card.humidifier.mode")}
.value=${stateObj.attributes.mode}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handleModeChanged}
.options=${stateObj.attributes.available_modes?.map((mode) => ({
value: mode,
label: stateObj
? this.hass.formatEntityAttributeValue(
stateObj,
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleModeChanged}
@closed=${stopPropagation}
>
${stateObj.attributes.mode
? html`
<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="mode"
.attributeValue=${stateObj.attributes.mode}
></ha-attribute-icon>
`
: html`
<ha-svg-icon
slot="icon"
.path=${mdiTuneVariant}
></ha-svg-icon>
`}
${stateObj.attributes.available_modes!.map(
(mode) => html`
<ha-list-item .value=${mode} graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="mode"
.attributeValue=${mode}
></ha-attribute-icon>
${this.hass.formatEntityAttributeValue(
stateObj!,
"mode",
mode
)
: mode,
attributeIcon: stateObj
? {
stateObj,
attribute: "mode",
attributeValue: mode,
}
: undefined,
})) || []}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
)}
</ha-list-item>
`
)}
</ha-control-select-menu>
`
: nothing}
@@ -123,8 +143,8 @@ class MoreInfoHumidifier extends LitElement {
`;
}
private _handleStateChanged(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value || null;
private _handleStateChanged(ev) {
const newVal = ev.target.value || null;
this._callServiceHelper(
this.stateObj!.state,
newVal,
@@ -133,8 +153,8 @@ class MoreInfoHumidifier extends LitElement {
);
}
private _handleModeChanged(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value;
private _handleModeChanged(ev) {
const newVal = ev.target.value || null;
this._mode = newVal;
this._callServiceHelper(
this.stateObj!.attributes.mode,
@@ -9,6 +9,7 @@ import {
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select-menu";
@@ -37,7 +38,6 @@ import "../components/lights/ha-more-info-light-favorite-colors";
import "../components/lights/light-color-rgb-picker";
import "../components/lights/light-color-temp-picker";
import { moreInfoControlStyle } from "../components/more-info-control-style";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
type MainControl = "brightness" | "color_temp" | "color";
@@ -253,35 +253,47 @@ class MoreInfoLight extends LitElement {
${supportsEffects && this.stateObj.attributes.effect_list
? html`
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.formatEntityAttributeName(
this.stateObj,
"effect"
)}
.value=${this.stateObj.attributes.effect}
.disabled=${this.stateObj.state === UNAVAILABLE}
@wa-select=${this._handleEffect}
.options=${this.stateObj.attributes.effect_list.map(
(effect) => ({
value: effect,
label: this.stateObj
? this.hass.formatEntityAttributeValue(
this.stateObj,
"effect",
effect
)
: effect,
attributeIcon: this.stateObj
? {
stateObj: this.stateObj,
attribute: "effect",
attributeValue: effect,
}
: undefined,
})
)}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleEffect}
@closed=${stopPropagation}
>
<ha-svg-icon slot="icon" .path=${mdiCreation}></ha-svg-icon>
${this.stateObj.attributes.effect
? html`<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="effect"
.attributeValue=${this.stateObj.attributes.effect}
></ha-attribute-icon>`
: html`<ha-svg-icon
slot="icon"
.path=${mdiCreation}
></ha-svg-icon>`}
${this.stateObj.attributes.effect_list?.map(
(effect) => html`
<ha-list-item .value=${effect} graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="effect"
.attributeValue=${effect}
></ha-attribute-icon>
${this.hass.formatEntityAttributeValue(
this.stateObj!,
"effect",
effect
)}
</ha-list-item>
`
)}
</ha-control-select-menu>
`
: nothing}
@@ -305,8 +317,8 @@ class MoreInfoLight extends LitElement {
});
};
private _handleEffect(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value;
private _handleEffect(ev) {
const newVal = ev.target.value;
const oldVal = this._effect;
if (!newVal || oldVal === newVal) return;
@@ -25,8 +25,8 @@ import { VolumeSliderController } from "../../../common/util/volume-slider";
import "../../../components/chips/ha-assist-chip";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-marquee-text";
@@ -743,7 +743,7 @@ class MoreInfoMediaPlayer extends LitElement {
});
}
private _handleSourceChange(e: HaDropdownSelectEvent) {
private _handleSourceChange(e: CustomEvent<{ item: HaDropdownItem }>) {
const source = e.detail.item.value;
if (!source || this.stateObj!.attributes.source === source) {
return;
@@ -755,7 +755,7 @@ class MoreInfoMediaPlayer extends LitElement {
});
}
private _handleSoundModeChange(ev: HaDropdownSelectEvent) {
private _handleSoundModeChange(ev: CustomEvent<{ item: HaDropdownItem }>) {
const soundMode = ev.detail.item.value;
if (!soundMode || this.stateObj!.attributes.sound_mode === soundMode) {
return;
@@ -2,10 +2,9 @@ import { mdiAccount, mdiAccountArrowRight, mdiWaterBoiler } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select-menu";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-list-item";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { WaterHeaterEntity } from "../../../data/water_heater";
@@ -17,6 +16,7 @@ import "../../../state-control/water_heater/ha-state-control-water_heater-temper
import type { HomeAssistant } from "../../../types";
import "../components/ha-more-info-control-select-container";
import { moreInfoControlStyle } from "../components/more-info-control-style";
import "../../../components/ha-attribute-icon";
@customElement("more-info-water_heater")
class MoreInfoWaterHeater extends LitElement {
@@ -74,25 +74,32 @@ class MoreInfoWaterHeater extends LitElement {
${supportOperationMode && stateObj.attributes.operation_list
? html`
<ha-control-select-menu
.hass=${this.hass}
.label=${this.hass.localize("ui.card.water_heater.mode")}
.value=${stateObj.state}
.disabled=${stateObj.state === UNAVAILABLE}
@wa-select=${this._handleOperationModeChanged}
.options=${stateObj.attributes.operation_list
.concat()
.sort(compareWaterHeaterOperationMode)
.map((mode) => ({
value: mode,
label: this.hass.formatEntityState(stateObj, mode),
attributeIcon: {
stateObj,
attribute: "operation_mode",
attributeValue: mode,
},
}))}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleOperationModeChanged}
@closed=${stopPropagation}
>
<ha-svg-icon slot="icon" .path=${mdiWaterBoiler}></ha-svg-icon>
${stateObj.attributes.operation_list
.concat()
.sort(compareWaterHeaterOperationMode)
.map(
(mode) => html`
<ha-list-item .value=${mode} graphic="icon">
<ha-attribute-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${stateObj}
attribute="operation_mode"
.attributeValue=${mode}
></ha-attribute-icon>
${this.hass.formatEntityState(stateObj, mode)}
</ha-list-item>
`
)}
</ha-control-select-menu>
`
: nothing}
@@ -105,18 +112,31 @@ class MoreInfoWaterHeater extends LitElement {
)}
.value=${stateObj.attributes.away_mode}
.disabled=${stateObj.state === UNAVAILABLE}
@wa-select=${this._handleAwayModeChanged}
.options=${["on", "off"].map((mode) => ({
value: mode,
label: this.hass.formatEntityAttributeValue(
stateObj,
"away_mode",
mode
),
iconPath: mode === "on" ? mdiAccountArrowRight : mdiAccount,
}))}
fixedMenuPosition
naturalMenuWidth
@selected=${this._handleAwayModeChanged}
@closed=${stopPropagation}
>
<ha-svg-icon slot="icon" .path=${mdiAccount}></ha-svg-icon>
<ha-list-item value="on" graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiAccountArrowRight}
></ha-svg-icon>
${this.hass.formatEntityAttributeValue(
stateObj,
"away_mode",
"on"
)}
</ha-list-item>
<ha-list-item value="off" graphic="icon">
<ha-svg-icon slot="graphic" .path=${mdiAccount}></ha-svg-icon>
${this.hass.formatEntityAttributeValue(
stateObj,
"away_mode",
"off"
)}
</ha-list-item>
</ha-control-select-menu>
`
: nothing}
@@ -124,8 +144,8 @@ class MoreInfoWaterHeater extends LitElement {
`;
}
private _handleOperationModeChanged(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value;
private _handleOperationModeChanged(ev) {
const newVal = ev.target.value;
this._callServiceHelper(
this.stateObj!.state,
newVal,
@@ -136,8 +156,8 @@ class MoreInfoWaterHeater extends LitElement {
);
}
private _handleAwayModeChanged(ev: HaDropdownSelectEvent) {
const newVal = ev.detail.item.value === "on";
private _handleAwayModeChanged(ev) {
const newVal = ev.target.value === "on";
const oldVal = this.stateObj!.attributes.away_mode === "on";
this._callServiceHelper(oldVal, newVal, "set_away_mode", {
+4 -7
View File
@@ -25,11 +25,11 @@ import { stopPropagation } from "../../common/dom/stop_propagation";
import { computeAreaName } from "../../common/entity/compute_area_name";
import { computeDeviceName } from "../../common/entity/compute_device_name";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import {
computeEntityEntryName,
computeEntityName,
} from "../../common/entity/compute_entity_name";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import {
getEntityContext,
getEntityEntryContext,
@@ -37,12 +37,11 @@ import {
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
import { navigate } from "../../common/navigate";
import { computeRTL } from "../../common/util/compute_rtl";
import { withViewTransition } from "../../common/util/view-transition";
import "../../components/ha-dialog";
import "../../components/ha-dialog-header";
import "../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../components/ha-dropdown";
import "../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../components/ha-dropdown-item";
import "../../components/ha-icon-button";
import "../../components/ha-icon-button-prev";
import "../../components/ha-related-items";
@@ -318,7 +317,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
this._setView("related");
}
private _handleMenuAction(ev: HaDropdownSelectEvent) {
private _handleMenuAction(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
switch (action) {
case "device":
@@ -771,9 +770,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
}
private _enlarge() {
withViewTransition(() => {
this.large = !this.large;
});
this.large = !this.large;
}
private _handleOpened() {
+41 -72
View File
@@ -1,10 +1,8 @@
import { mdiDevices } from "@mdi/js";
import Fuse from "fuse.js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import type { NavigationFilterOptions } from "../../common/config/filter_navigation_pages";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event";
import { navigate } from "../../common/navigate";
@@ -47,14 +45,13 @@ import {
type ActionCommandComboBoxItem,
type NavigationComboBoxItem,
} from "../../data/quick_bar";
import type { NavigationFilterOptions } from "../../common/config/filter_navigation_pages";
import {
multiTermSortedSearch,
type FuseWeightedKey,
} from "../../resources/fuseMultiTerm";
import { buttonLinkStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import { isIosApp } from "../../util/is_ios";
import { isMac } from "../../util/is_mac";
import { showConfirmationDialog } from "../generic/show-dialog-box";
import { showShortcutsDialog } from "../shortcuts/show-shortcuts-dialog";
import type { QuickBarParams, QuickBarSection } from "./show-dialog-quick-bar";
@@ -69,7 +66,7 @@ export class QuickBar extends LitElement {
@state() private _loading = true;
@state() private _showHint = false;
@state() private _hint?: string;
@state() private _selectedSection?: QuickBarSection;
@@ -89,8 +86,6 @@ export class QuickBar extends LitElement {
private _translationsLoaded = false;
private _itemSelected = false;
// #region lifecycle
public async showDialog(params: QuickBarParams) {
if (!this._translationsLoaded) {
@@ -99,7 +94,7 @@ export class QuickBar extends LitElement {
}
this._initialize();
this._selectedSection = params.mode;
this._showHint = params.showHint ?? false;
this._hint = params.hint;
this._open = true;
}
@@ -167,7 +162,6 @@ export class QuickBar extends LitElement {
this._selectedSection = undefined;
this._opened = false;
this._open = false;
this._itemSelected = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
};
@@ -259,17 +253,9 @@ export class QuickBar extends LitElement {
clearable
></ha-picker-combo-box>`
: nothing}
${this._showHint
${this._hint
? html`<ha-tip slot="footer" .hass=${this.hass}
>${this.hass.localize("ui.tips.key_shortcut_quick_search", {
keyboard_shortcut: html`<button
class="link"
@click=${this._openShortcutDialog}
>
${this.hass.localize("ui.tips.keyboard_shortcut")}
</button>`,
modifier: isMac ? "⌘" : "Ctrl",
})}</ha-tip
>${this._hint}</ha-tip
>`
: nothing}
</ha-adaptive-dialog>
@@ -669,18 +655,12 @@ export class QuickBar extends LitElement {
private async _handleItemSelected(
ev: CustomEvent<PickerComboBoxIndexSelectedDetail>
) {
if (
!this._itemSelected &&
this._comboBox &&
this._comboBox.virtualizerElement
) {
if (this._comboBox && this._comboBox.virtualizerElement) {
const { index, newTab } = ev.detail;
const item = this._comboBox.virtualizerElement.items[
index
] as PickerComboBoxItem;
this._itemSelected = true;
// entity selected
if (item && "stateObj" in item) {
this.closeDialog();
@@ -762,59 +742,48 @@ export class QuickBar extends LitElement {
}
}
private _openShortcutDialog(ev: Event): void {
ev.preventDefault();
showShortcutsDialog(this);
this.closeDialog();
}
// #endregion interaction
// #region styles
static get styles(): CSSResultGroup {
return [
buttonLinkStyle,
css`
:host {
--dialog-surface-margin-top: var(--ha-space-10);
--ha-dialog-min-height: 620px;
--ha-bottom-sheet-height: calc(
100vh - max(var(--safe-area-inset-top), 48px)
);
--ha-bottom-sheet-height: calc(
100dvh - max(var(--safe-area-inset-top), 48px)
);
--ha-bottom-sheet-max-height: calc(
100vh - max(var(--safe-area-inset-top), 48px)
);
--ha-bottom-sheet-max-height: calc(
100dvh - max(var(--safe-area-inset-top), 48px)
);
--dialog-content-padding: 0;
--safe-area-inset-bottom: 0px;
}
static styles = css`
:host {
--dialog-surface-margin-top: var(--ha-space-10);
--ha-dialog-min-height: 620px;
--ha-bottom-sheet-height: calc(
100vh - max(var(--safe-area-inset-top), 48px)
);
--ha-bottom-sheet-height: calc(
100dvh - max(var(--safe-area-inset-top), 48px)
);
--ha-bottom-sheet-max-height: calc(
100vh - max(var(--safe-area-inset-top), 48px)
);
--ha-bottom-sheet-max-height: calc(
100dvh - max(var(--safe-area-inset-top), 48px)
);
--dialog-content-padding: 0;
--safe-area-inset-bottom: 0px;
}
ha-tip {
display: flex;
justify-content: center;
align-items: center;
color: var(--secondary-text-color);
gap: var(--ha-space-1);
}
ha-tip {
display: flex;
justify-content: center;
align-items: center;
color: var(--secondary-text-color);
gap: var(--ha-space-1);
}
ha-tip a {
color: var(--primary-color);
}
ha-tip a {
color: var(--primary-color);
}
@media all and (max-width: 450px), all and (max-height: 690px) {
ha-tip {
display: none;
}
}
`,
];
}
@media all and (max-width: 450px), all and (max-height: 690px) {
ha-tip {
display: none;
}
}
`;
// #endregion styles
}
@@ -11,7 +11,7 @@ export type QuickBarSection =
export interface QuickBarParams {
entityFilter?: string;
mode?: QuickBarSection;
showHint?: boolean;
hint?: string;
}
export const loadQuickBar = () => import("./ha-quick-bar");
@@ -9,8 +9,8 @@ import { formatLanguageCode } from "../../common/language/format_language";
import "../../components/chips/ha-assist-chip";
import "../../components/ha-dialog";
import "../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../components/ha-dropdown";
import "../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../components/ha-dropdown-item";
import { getLanguageOptions } from "../../components/ha-language-picker";
import type { AssistSatelliteConfiguration } from "../../data/assist_satellite";
import { fetchAssistSatelliteConfiguration } from "../../data/assist_satellite";
@@ -328,7 +328,7 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
}
}
private _handlePickLanguage(ev: HaDropdownSelectEvent) {
private _handlePickLanguage(ev: CustomEvent<{ item: HaDropdownItem }>) {
this._language = ev.detail.item.value;
}
@@ -17,8 +17,8 @@ import "../../components/ha-button";
import "../../components/ha-dialog";
import "../../components/ha-dialog-header";
import "../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../components/ha-dropdown";
import "../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../components/ha-dropdown-item";
import "../../components/ha-icon-button";
import "../../components/ha-icon-next";
import "../../components/ha-spinner";
@@ -215,7 +215,7 @@ export class HaVoiceCommandDialog extends LitElement {
this._preferredPipeline = preferred_pipeline || undefined;
}
private async _selectPipeline(ev: HaDropdownSelectEvent) {
private async _selectPipeline(ev: CustomEvent<{ item: HaDropdownItem }>) {
const pipelineId = ev.detail?.item?.value;
if (pipelineId) {
this._pipelineId = pipelineId;
-8
View File
@@ -8,14 +8,6 @@ export const demoPanels: Panels = {
config: { mode: "storage" },
url_path: "lovelace",
},
home: {
component_name: "home",
icon: "mdi:home",
title: "home",
default_visible: false,
config: null,
url_path: "home",
},
"dev-state": {
component_name: "dev-state",
icon: null,
+2 -4
View File
@@ -7,6 +7,7 @@ import { fireEvent } from "../common/dom/fire_event";
import { computeFormatFunctions } from "../common/translations/entity-state";
import { computeLocalize } from "../common/translations/localize";
import type { EntityRegistryDisplayEntry } from "../data/entity/entity_registry";
import { DEFAULT_PANEL } from "../data/panel";
import {
DateFormat,
FirstWeekday,
@@ -267,9 +268,7 @@ export const provideHass = (
name: "Demo User",
},
panelUrl: "lovelace",
systemData: {
default_panel: "lovelace",
},
defaultPanel: DEFAULT_PANEL,
language: localLanguage,
selectedLanguage: localLanguage,
locale: {
@@ -368,7 +367,6 @@ export const provideHass = (
areas: {},
devices: {},
entities: {},
floors: {},
formatEntityState: (stateObj, state) =>
(state !== null ? state : stateObj.state) ?? "",
formatEntityAttributeName: (_stateObj, attribute) => attribute,
+5 -5
View File
@@ -30,8 +30,8 @@ import { showDataTableSettingsDialog } from "../components/data-table/show-dialo
import "../components/ha-dialog";
import "../components/ha-dialog-header";
import "../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../components/ha-dropdown";
import "../components/ha-dropdown-item";
import type { HaDropdownItem } from "../components/ha-dropdown-item";
import "../components/search-input-outlined";
import { KeyboardShortcutMixin } from "../mixins/keyboard-shortcut-mixin";
import type { HomeAssistant, Route } from "../types";
@@ -134,7 +134,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
* String to show when there are no records in the data table.
* @type {String}
*/
@property({ attribute: false }) public noDataText?: string;
@property({ attribute: false, type: String }) public noDataText?: string;
/**
* Hides the data table and show an empty message.
@@ -590,7 +590,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
this._sortColumn = this._sortDirection ? ev.detail.column : undefined;
}
private _handleSortBy(ev: HaDropdownSelectEvent) {
private _handleSortBy(ev: CustomEvent<{ item: HaDropdownItem }>) {
ev.preventDefault(); // keep the dropdown open
const columnId = ev.detail.item.value;
@@ -609,7 +609,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
});
}
private _handleGroupBy(ev: HaDropdownSelectEvent) {
private _handleGroupBy(ev: CustomEvent<{ item: HaDropdownItem }>) {
const group = ev.detail.item.value;
if (group === "reset") {
@@ -662,7 +662,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
this._selectMode = true;
}
private _handleSelect(ev: HaDropdownSelectEvent) {
private _handleSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail.item.value;
if (!action) {
+1 -1
View File
@@ -13,7 +13,7 @@ class IntegrationBadge extends LitElement {
@property({ attribute: "dark-optimized-icon", type: Boolean })
public darkOptimizedIcon = false;
@property({ attribute: false })
@property({ attribute: false, type: Boolean, reflect: true })
public clickable = false;
protected render(): TemplateResult {
@@ -9,8 +9,8 @@ import "../../../../../components/buttons/ha-progress-button";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-card";
import "../../../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../../../components/ha-dropdown";
import "../../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../../components/ha-dropdown-item";
import "../../../../../components/ha-form/ha-form";
import type {
HaFormDataContainer,
@@ -347,7 +347,7 @@ class SupervisorAppConfig extends LitElement {
}
}
private _handleAction(ev: HaDropdownSelectEvent) {
private _handleAction(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail.item.value;
if (!action) {
@@ -7,8 +7,8 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { extractSearchParam } from "../../../common/url/search-params";
import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/search-input";
import type {
@@ -202,7 +202,7 @@ export class HaConfigAppsAvailable extends LitElement {
})
);
private _handleAction(ev: HaDropdownSelectEvent) {
private _handleAction(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail.item.value;
if (!action) {
@@ -82,7 +82,7 @@ class DialogAreaDetail
@state() private _params?: AreaRegistryDetailDialogParams;
@state() private _submitting = false;
@state() private _submitting?: boolean;
@state() private _open = false;
@@ -378,7 +378,7 @@ class DialogAreaDetail
<ha-button
slot="primaryAction"
@click=${this._updateEntry}
.disabled=${nameInvalid || this._submitting}
.disabled=${nameInvalid || !!this._submitting}
>
${entry
? this.hass.localize("ui.common.save")
@@ -19,6 +19,7 @@ import "../../../components/ha-button";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-list";
@@ -52,7 +53,6 @@ import {
loadAreaRegistryDetailDialog,
showAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
declare interface NameAndEntity<EntityType extends HassEntity> {
name: string;
@@ -239,8 +239,9 @@ class HaConfigAreaPage extends LitElement {
${this.hass.localize("ui.panel.config.areas.edit_settings")}
</ha-dropdown-item>
<ha-dropdown-item value="delete" variant="danger">
<ha-svg-icon slot="icon" .path=${mdiDelete}> </ha-svg-icon>
<ha-dropdown-item value="delete">
<ha-svg-icon class="warning" slot="icon" .path=${mdiDelete}>
</ha-svg-icon>
${this.hass.localize("ui.panel.config.areas.editor.delete")}
</ha-dropdown-item>
</ha-dropdown>
@@ -605,7 +606,7 @@ class HaConfigAreaPage extends LitElement {
this._related = await findRelated(this.hass, "area", this.areaId);
}
private _handleMenuAction(ev: HaDropdownSelectEvent) {
private _handleMenuAction(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
const entry = (ev.detail?.item as any)?.data as AreaRegistryEntry;
switch (action) {
@@ -58,7 +58,6 @@ import {
} from "./show-dialog-area-registry-detail";
import { showAreasFloorsOrderDialog } from "./show-dialog-areas-floors-order";
import { showFloorRegistryDetailDialog } from "./show-dialog-floor-registry-detail";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
const UNASSIGNED_FLOOR = "__unassigned__";
@@ -225,8 +224,9 @@ export class HaConfigAreasDashboard extends LitElement {
"ui.panel.config.areas.picker.floor.edit_floor"
)}</ha-dropdown-item
>
<ha-dropdown-item value="delete" variant="danger"
<ha-dropdown-item value="delete" class="warning"
><ha-svg-icon
class="warning"
.path=${mdiDelete}
slot="icon"
></ha-svg-icon
@@ -535,7 +535,7 @@ export class HaConfigAreasDashboard extends LitElement {
}, time);
}
private _handleFloorAction(ev: HaDropdownSelectEvent) {
private _handleFloorAction(ev: CustomEvent<{ item: { value: string } }>) {
const floor = (ev.currentTarget as any).floor;
const action = ev.detail.item.value;
switch (action) {
@@ -551,7 +551,9 @@ export class HaConfigAreasDashboard extends LitElement {
}
}
private _handleUnassignedAreasAction(ev: HaDropdownSelectEvent) {
private _handleUnassignedAreasAction(
ev: CustomEvent<{ item: { value: string } }>
) {
const action = ev.detail.item.value;
if (action === "reorder") {
this._showReorderDialog();
@@ -725,6 +727,9 @@ export class HaConfigAreasDashboard extends LitElement {
align-items: center;
overflow-wrap: anywhere;
}
.warning {
color: var(--error-color);
}
`;
}
@@ -36,6 +36,7 @@ import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-service-icon";
@@ -91,7 +92,6 @@ import "./types/ha-automation-action-set_conversation_response";
import "./types/ha-automation-action-stop";
import "./types/ha-automation-action-wait_for_trigger";
import "./types/ha-automation-action-wait_template";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
export const getAutomationActionType = memoizeOne(
(action: Action | undefined) => {
@@ -184,6 +184,8 @@ export default class HaAutomationActionRow extends LitElement {
@state() private _warnings?: string[];
@state() private _uiSupported = false;
@query("ha-automation-action-editor")
private _actionEditor?: HaAutomationActionEditor;
@@ -215,6 +217,25 @@ export default class HaAutomationActionRow extends LitElement {
if (!this._uiModeAvailable && !this._yamlMode) {
this._yamlMode = true;
}
if (changedProperties.has("action") || !this.hasUpdated) {
const uiSupported = this._checkUiSupport(type);
if (uiSupported !== this._uiSupported || !this.hasUpdated) {
this._uiSupported = uiSupported;
}
}
}
protected override updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (
changedProps.has("_uiSupported") &&
this._selected &&
this.optionsInSidebar
) {
// update sidebar if uiSupported changed
this.openSidebar();
}
}
private _renderOverflowLabel(label: string, shortcut?: TemplateResult) {
@@ -467,7 +488,7 @@ export default class HaAutomationActionRow extends LitElement {
.disabled=${this.disabled}
.yamlMode=${this._yamlMode}
.narrow=${this.narrow}
.uiSupported=${this._uiSupported(type!)}
.uiSupported=${this._uiSupported}
@ui-mode-not-available=${this._handleUiModeNotAvailable}
></ha-automation-action-editor>`
: nothing}
@@ -539,7 +560,7 @@ export default class HaAutomationActionRow extends LitElement {
.action=${this.action}
.narrow=${this.narrow}
.disabled=${this.disabled}
.uiSupported=${this._uiSupported(type!)}
.uiSupported=${this._uiSupported}
indent
.selected=${this._selected}
@value-changed=${this._onValueChange}
@@ -769,7 +790,6 @@ export default class HaAutomationActionRow extends LitElement {
public openSidebar(action?: Action): void {
const sidebarAction = action ?? this.action;
const actionType = getAutomationActionType(sidebarAction);
fireEvent(this, "open-sidebar", {
save: (value) => {
@@ -799,7 +819,7 @@ export default class HaAutomationActionRow extends LitElement {
config: {
action: sidebarAction,
},
uiSupported: actionType ? this._uiSupported(actionType) : false,
uiSupported: this._uiSupported,
yamlMode: this._yamlMode,
} satisfies ActionSidebarConfig);
this._selected = true;
@@ -849,9 +869,10 @@ export default class HaAutomationActionRow extends LitElement {
this._actionEditor?.collapseAll();
}
private _uiSupported = memoizeOne(
(type: string) =>
customElements.get(`ha-automation-action-${type}`) !== undefined
private _checkUiSupport = memoizeOne((type?: string) =>
type
? customElements.get(`ha-automation-action-${type}`) !== undefined
: false
);
private _toggleCollapse() {
@@ -862,7 +883,7 @@ export default class HaAutomationActionRow extends LitElement {
this._automationRowElement?.focus();
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
ev.stopPropagation();
const action = ev.detail?.item?.value;
@@ -37,6 +37,7 @@ import "../../../../components/ha-card";
import "../../../../components/ha-condition-icon";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import type {
@@ -76,7 +77,6 @@ import "./types/ha-automation-condition-template";
import "./types/ha-automation-condition-time";
import "./types/ha-automation-condition-trigger";
import "./types/ha-automation-condition-zone";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
export interface ConditionElement extends LitElement {
condition: Condition;
@@ -84,6 +84,31 @@ export interface ConditionElement extends LitElement {
collapseAll?: () => void;
}
export const handleChangeEvent = (
element: ConditionElement,
ev: CustomEvent
) => {
ev.stopPropagation();
const name = (ev.currentTarget as any)?.name;
if (!name) {
return;
}
const newVal = ev.detail?.value || (ev.currentTarget as any)?.value;
if ((element.condition[name] || "") === newVal) {
return;
}
let newCondition: Condition;
if (!newVal) {
newCondition = { ...element.condition };
delete newCondition[name];
} else {
newCondition = { ...element.condition, [name]: newVal };
}
fireEvent(element, "value-changed", { value: newCondition });
};
@customElement("ha-automation-condition-row")
export default class HaAutomationConditionRow extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -131,6 +156,8 @@ export default class HaAutomationConditionRow extends LitElement {
@state() private _selected = false;
@state() private _uiSupported = false;
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg!: EntityRegistryEntry[];
@@ -370,9 +397,7 @@ export default class HaAutomationConditionRow extends LitElement {
]}
.disabled=${this.disabled}
.yamlMode=${this._yamlMode}
.uiSupported=${this._uiSupported(
this._getType(this.condition, this.conditionDescriptions)
)}
.uiSupported=${this._uiSupported}
.narrow=${this.narrow}
@ui-mode-not-available=${this._handleUiModeNotAvailable}
></ha-automation-condition-editor>`
@@ -451,9 +476,7 @@ export default class HaAutomationConditionRow extends LitElement {
.hass=${this.hass}
.condition=${this.condition}
.disabled=${this.disabled}
.uiSupported=${this._uiSupported(
this._getType(this.condition, this.conditionDescriptions)
)}
.uiSupported=${this._uiSupported}
indent
.selected=${this._selected}
.narrow=${this.narrow}
@@ -484,6 +507,27 @@ export default class HaAutomationConditionRow extends LitElement {
if (changedProperties.has("yamlMode")) {
this._warnings = undefined;
}
if (changedProperties.has("condition") || !this.hasUpdated) {
const type = this._getType(this.condition, this.conditionDescriptions);
const uiSupported = this._checkUiSupport(type);
if (uiSupported !== this._uiSupported || !this.hasUpdated) {
this._uiSupported = uiSupported;
}
}
}
protected override updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (
changedProps.has("_uiSupported") &&
this._selected &&
this.optionsInSidebar
) {
// update sidebar if uiSupported changed
this.openSidebar();
}
}
private _onValueChange(event: CustomEvent) {
@@ -771,9 +815,7 @@ export default class HaAutomationConditionRow extends LitElement {
cut: this._cutCondition,
test: this._testCondition,
config: sidebarCondition,
uiSupported: this._uiSupported(
this._getType(sidebarCondition, this.conditionDescriptions)
),
uiSupported: this._uiSupported,
description: this.conditionDescriptions[sidebarCondition.condition],
yamlMode: this._yamlMode,
} satisfies ConditionSidebarConfig);
@@ -800,7 +842,7 @@ export default class HaAutomationConditionRow extends LitElement {
}
);
private _uiSupported = memoizeOne(
private _checkUiSupport = memoizeOne(
(type: string) =>
customElements.get(`ha-automation-condition-${type}`) !== undefined
);
@@ -813,7 +855,7 @@ export default class HaAutomationConditionRow extends LitElement {
this._automationRowElement?.focus();
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
ev.stopPropagation();
const action = ev.detail?.item?.value;
@@ -1,15 +1,9 @@
import { html, LitElement } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../../../components/ha-textarea";
import type { TemplateCondition } from "../../../../../data/automation";
import type { HomeAssistant } from "../../../../../types";
import type { SchemaUnion } from "../../../../../components/ha-form/types";
import "../../../../../components/ha-form/ha-form";
import { fireEvent } from "../../../../../common/dom/fire_event";
const SCHEMA = [
{ name: "value_template", required: true, selector: { template: {} } },
] as const;
import { handleChangeEvent } from "../ha-automation-condition-row";
@customElement("ha-automation-condition-template")
export class HaTemplateCondition extends LitElement {
@@ -24,30 +18,36 @@ export class HaTemplateCondition extends LitElement {
}
protected render() {
const { value_template } = this.condition;
return html`
<ha-form
<p>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.template.value_template"
)}
*
</p>
<ha-code-editor
.name=${"value_template"}
mode="jinja2"
.hass=${this.hass}
.data=${this.condition}
.schema=${SCHEMA}
.value=${value_template}
.readOnly=${this.disabled}
autocomplete-entities
@value-changed=${this._valueChanged}
.computeLabel=${this._computeLabelCallback}
.disabled=${this.disabled}
></ha-form>
dir="ltr"
></ha-code-editor>
`;
}
private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation();
const newCondition = ev.detail.value;
fireEvent(this, "value-changed", { value: newCondition });
handleChangeEvent(this, ev);
}
private _computeLabelCallback = (
schema: SchemaUnion<typeof SCHEMA>
): string =>
this.hass.localize(
`ui.panel.config.automation.editor.conditions.type.template.${schema.name}`
);
static styles = css`
p {
margin-top: 0;
}
`;
}
declare global {
@@ -35,6 +35,7 @@ import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-fab";
import "../../../components/ha-fade-in";
import "../../../components/ha-icon";
@@ -90,7 +91,6 @@ import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialo
import "./blueprint-automation-editor";
import "./manual-automation-editor";
import type { HaManualAutomationEditor } from "./manual-automation-editor";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
declare global {
interface HTMLElementTagNameMap {
@@ -1212,7 +1212,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
this._undoRedoController.redo();
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
@@ -52,6 +52,7 @@ import "../../../components/data-table/ha-data-table-labels";
import "../../../components/entity/ha-entity-toggle";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-fab";
import "../../../components/ha-filter-blueprints";
import "../../../components/ha-filter-categories";
@@ -88,9 +89,9 @@ import { fullEntitiesContext } from "../../../data/context";
import type { DataTableFilters } from "../../../data/data_table_filters";
import {
deserializeFilters,
isUsedFilter as isFilterUsed,
isUsedRelatedItemsFilter as isRelatedItemsFilterUsed,
serializeFilters,
isFilterUsed,
isRelatedItemsFilterUsed,
} from "../../../data/data_table_filters";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type {
@@ -126,7 +127,6 @@ import {
} from "../voice-assistants/expose/assistants-table-column";
import { getAvailableAssistants } from "../voice-assistants/expose/available-assistants";
import { showNewAutomationDialog } from "./show-dialog-new-automation";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
type AutomationItem = AutomationEntity & {
name: string;
@@ -884,9 +884,9 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
);
// the filters below only expose the selected options (as filter.value);
// category filter only allows a single selected option
// applying the filter must be done here
} else if (isFilterUsed(key, filter, "ha-filter-categories")) {
// category filter only allows a single selected option
filteredEntityIds = filteredEntityIds.filter(
(entityId) =>
filter.value![0] ===
@@ -1140,7 +1140,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
}
}
private _handleBulkCategory = (ev: HaDropdownSelectEvent) => {
private _handleBulkCategory = (ev: CustomEvent<{ item: HaDropdownItem }>) => {
const value = ev.detail.item.value;
if (value === "category_create") {
this._bulkCreateCategory();
@@ -1528,10 +1528,6 @@ ${rejected
ha-assist-chip {
--ha-assist-chip-container-shape: 10px;
}
ha-dropdown::part(menu),
ha-dropdown::part(submenu) {
--auto-size-available-width: calc(50vw - var(--ha-space-4));
}
ha-dropdown ha-assist-chip {
--md-assist-chip-trailing-space: 8px;
}
@@ -21,6 +21,7 @@ import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/trace/ha-trace-blueprint-config";
import "../../../components/trace/ha-trace-config";
@@ -46,7 +47,6 @@ import "../../../layouts/hass-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
const TABS = ["details", "timeline", "logbook", "automation_config"] as const;
@@ -515,7 +515,7 @@ export class HaAutomationTrace extends LitElement {
}
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
@@ -22,6 +22,7 @@ import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-svg-icon";
@@ -47,7 +48,6 @@ import {
overflowStyles,
rowStyles,
} from "../styles";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@customElement("ha-automation-option-row")
export default class HaAutomationOptionRow extends LitElement {
@@ -349,7 +349,7 @@ export default class HaAutomationOptionRow extends LitElement {
fireEvent(this, "move-down");
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
ev.stopPropagation();
const action = ev.detail?.item?.value;
@@ -18,6 +18,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
import type { ActionSidebarConfig } from "../../../../data/automation";
import { domainToName } from "../../../../data/integration";
@@ -29,7 +30,6 @@ import { getAutomationActionType } from "../action/ha-automation-action-row";
import { getRepeatType } from "../action/types/ha-automation-action-repeat";
import { overflowStyles, sidebarEditorStyles } from "../styles";
import "./ha-automation-sidebar-card";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@customElement("ha-automation-sidebar-action")
export default class HaAutomationSidebarAction extends LitElement {
@@ -334,7 +334,7 @@ export default class HaAutomationSidebarAction extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
@@ -18,6 +18,7 @@ import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type {
ConditionSidebarConfig,
LegacyCondition,
@@ -36,7 +37,6 @@ import "../condition/ha-automation-condition-editor";
import type HaAutomationConditionEditor from "../condition/ha-automation-condition-editor";
import { overflowStyles, sidebarEditorStyles } from "../styles";
import "./ha-automation-sidebar-card";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@customElement("ha-automation-sidebar-condition")
export default class HaAutomationSidebarCondition extends LitElement {
@@ -413,7 +413,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
@@ -9,6 +9,7 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-svg-icon";
import type { OptionSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types";
@@ -16,7 +17,6 @@ import { isMac } from "../../../../util/is_mac";
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
import { overflowStyles, sidebarEditorStyles } from "../styles";
import "./ha-automation-sidebar-card";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@customElement("ha-automation-sidebar-option")
export default class HaAutomationSidebarOption extends LitElement {
@@ -129,7 +129,7 @@ export default class HaAutomationSidebarOption extends LitElement {
</ha-automation-sidebar-card>`;
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
@@ -5,6 +5,7 @@ import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
@@ -12,7 +13,6 @@ import "../../script/ha-script-field-selector-editor";
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
import { sidebarEditorStyles } from "../styles";
import "./ha-automation-sidebar-card";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@customElement("ha-automation-sidebar-script-field-selector")
export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
@@ -162,7 +162,7 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
@@ -4,6 +4,7 @@ import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
import type { HomeAssistant } from "../../../../types";
import { isMac } from "../../../../util/is_mac";
@@ -11,7 +12,6 @@ import "../../script/ha-script-field-editor";
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
import { overflowStyles, sidebarEditorStyles } from "../styles";
import "./ha-automation-sidebar-card";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@customElement("ha-automation-sidebar-script-field")
export default class HaAutomationSidebarScriptField extends LitElement {
@@ -156,7 +156,7 @@ export default class HaAutomationSidebarScriptField extends LitElement {
fireEvent(this, "toggle-yaml-mode");
};
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
@@ -17,6 +17,7 @@ import { keyed } from "lit/directives/keyed";
import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import type {
LegacyTrigger,
TriggerSidebarConfig,
@@ -32,7 +33,6 @@ import { overflowStyles, sidebarEditorStyles } from "../styles";
import "../trigger/ha-automation-trigger-editor";
import type HaAutomationTriggerEditor from "../trigger/ha-automation-trigger-editor";
import "./ha-automation-sidebar-card";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@customElement("ha-automation-sidebar-trigger")
export default class HaAutomationSidebarTrigger extends LitElement {
@@ -328,7 +328,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
this._requestShowId = true;
};
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (!action) {
@@ -40,6 +40,7 @@ import type { HaAutomationRow } from "../../../../components/ha-automation-row";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-svg-icon";
@@ -89,7 +90,6 @@ import "./types/ha-automation-trigger-time";
import "./types/ha-automation-trigger-time_pattern";
import "./types/ha-automation-trigger-webhook";
import "./types/ha-automation-trigger-zone";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
export interface TriggerElement extends LitElement {
trigger: Trigger;
@@ -147,6 +147,8 @@ export default class HaAutomationTriggerRow extends LitElement {
@state() private _warnings?: string[];
@state() private _uiSupported = false;
@property({ attribute: false })
public triggerDescriptions: TriggerDescriptions = {};
@@ -193,9 +195,7 @@ export default class HaAutomationTriggerRow extends LitElement {
private _renderRow() {
const type = this._getType(this.trigger, this.triggerDescriptions);
const supported = this._uiSupported(type);
const yamlMode = this._yamlMode || !supported;
const yamlMode = this._yamlMode || !this._uiSupported;
const target =
type === "platform" &&
@@ -333,7 +333,7 @@ export default class HaAutomationTriggerRow extends LitElement {
<ha-dropdown-item
value="toggle_yaml_mode"
.disabled=${!supported || !!this._warnings}
.disabled=${!this._uiSupported || !!this._warnings}
>
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
${this._renderOverflowLabel(
@@ -412,7 +412,7 @@ export default class HaAutomationTriggerRow extends LitElement {
: undefined}
.disabled=${this.disabled}
.yamlMode=${this._yamlMode}
.uiSupported=${supported}
.uiSupported=${this._uiSupported}
@ui-mode-not-available=${this._handleUiModeNotAvailable}
></ha-automation-trigger-editor>`
: nothing}
@@ -479,13 +479,30 @@ export default class HaAutomationTriggerRow extends LitElement {
if (changedProperties.has("yamlMode")) {
this._warnings = undefined;
}
if (changedProperties.has("trigger") || !this.hasUpdated) {
const type = this._getType(this.trigger, this.triggerDescriptions);
const uiSupported = this._checkUiSupport(type);
if (uiSupported !== this._uiSupported || !this.hasUpdated) {
this._uiSupported = uiSupported;
}
}
}
protected override updated(changedProps: PropertyValues<this>): void {
protected override updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (changedProps.has("trigger")) {
this._subscribeTrigger();
}
if (
changedProps.has("_uiSupported") &&
this._selected &&
this.optionsInSidebar
) {
// update sidebar if uiSupported changed
this.openSidebar();
}
}
public connectedCallback(): void {
@@ -603,9 +620,7 @@ export default class HaAutomationTriggerRow extends LitElement {
cut: this._cutTrigger,
insertAfter: this._insertAfter,
config: trigger,
uiSupported: this._uiSupported(
this._getType(trigger, this.triggerDescriptions)
),
uiSupported: this._uiSupported,
description:
"trigger" in trigger
? this.triggerDescriptions[trigger.trigger]
@@ -805,7 +820,7 @@ export default class HaAutomationTriggerRow extends LitElement {
}
);
private _uiSupported = memoizeOne(
private _checkUiSupport = memoizeOne(
(type: string) =>
customElements.get(`ha-automation-trigger-${type}`) !== undefined
);
@@ -814,7 +829,7 @@ export default class HaAutomationTriggerRow extends LitElement {
this._automationRowElement?.focus();
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
ev.stopPropagation();
const action = ev.detail?.item?.value;
@@ -9,6 +9,7 @@ import { slugify } from "../../../../../common/string/slugify";
import { copyToClipboard } from "../../../../../common/util/copy-clipboard";
import "../../../../../components/ha-dropdown";
import "../../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../../components/ha-dropdown-item";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-textfield";
import type { HaTextField } from "../../../../../components/ha-textfield";
@@ -19,7 +20,6 @@ import type {
import type { HomeAssistant } from "../../../../../types";
import { showToast } from "../../../../../util/toast";
import { handleChangeEvent } from "../ha-automation-trigger-row";
import type { HaDropdownSelectEvent } from "../../../../../components/ha-dropdown";
const SUPPORTED_METHODS = ["GET", "HEAD", "POST", "PUT"];
const DEFAULT_METHODS = ["POST", "PUT"];
@@ -208,7 +208,7 @@ export class HaWebhookTrigger extends LitElement {
});
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
ev.preventDefault(); // don't close the dropdown to select multiple options
const action = ev.detail?.item?.value;
@@ -61,7 +61,7 @@ export class HaBackupDataPicker extends LitElement {
| "page-onboarding.restore"
| "config.backup" = "config.backup";
@property({ attribute: false }) public addonsDisabled = false;
@property({ type: Boolean, attribute: false }) public addonsDisabled = false;
@state() public _addonIcons: Record<string, boolean> = {};
@@ -27,6 +27,7 @@ import type {
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-fab";
import "../../../components/ha-filter-states";
import "../../../components/ha-icon";
@@ -73,7 +74,6 @@ import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup"
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
import { downloadBackup } from "./helper/download_backup";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
interface BackupRow extends DataTableRowData, BackupContent {
formatted_type: string;
@@ -631,7 +631,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
this._dataTable.clearSelection();
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item.value;
if (action === "upload_backup") {
@@ -43,7 +43,6 @@ import "./components/ha-backup-details-restore";
import "./components/ha-backup-details-summary";
import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup";
import { downloadBackup } from "./helper/download_backup";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
interface Agent extends BackupContentAgent {
id: string;
@@ -124,7 +123,7 @@ class HaConfigBackupDetails extends LitElement {
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
${this.hass.localize("ui.common.download")}
</ha-dropdown-item>
<ha-dropdown-item value="delete" variant="danger">
<ha-dropdown-item value="delete" class="warning">
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
${this.hass.localize("ui.common.delete")}
</ha-dropdown-item>
@@ -312,7 +311,7 @@ class HaConfigBackupDetails extends LitElement {
}
}
private _handleAction(ev: HaDropdownSelectEvent) {
private _handleAction(ev: CustomEvent<{ item: { value: string } }>) {
const action = ev.detail.item.value;
switch (action) {
case "download":
@@ -324,7 +323,7 @@ class HaConfigBackupDetails extends LitElement {
}
}
private _handleAgentAction(ev: HaDropdownSelectEvent) {
private _handleAgentAction(ev: CustomEvent<{ item: { value: string } }>) {
const button = ev.currentTarget;
const agentId = (button as any).agent;
this._downloadBackup(agentId);
@@ -386,6 +385,12 @@ class HaConfigBackupDetails extends LitElement {
--mdc-icon-size: 48px;
color: var(--primary-text-color);
}
.warning {
color: var(--error-color);
}
.warning ha-svg-icon {
color: var(--error-color);
}
ha-button.danger {
--mdc-theme-primary: var(--error-color);
}
@@ -7,6 +7,7 @@ import "../../../components/ha-button";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-fab";
import "../../../components/ha-icon";
import "../../../components/ha-icon-next";
@@ -39,7 +40,6 @@ import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboard
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
@customElement("ha-config-backup-overview")
class HaConfigBackupOverview extends LitElement {
@@ -240,7 +240,7 @@ class HaConfigBackupOverview extends LitElement {
`;
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item.value;
if (action === "upload_backup") {
@@ -11,6 +11,7 @@ import "../../../components/ha-button";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-password-field";
@@ -35,7 +36,6 @@ import "./components/config/ha-backup-config-encryption-key";
import "./components/config/ha-backup-config-schedule";
import type { BackupConfigSchedule } from "./components/config/ha-backup-config-schedule";
import { showLocalBackupLocationDialog } from "./dialogs/show-dialog-local-backup-location";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
@customElement("ha-config-backup-settings")
class HaConfigBackupSettings extends LitElement {
@@ -492,7 +492,7 @@ class HaConfigBackupSettings extends LitElement {
fireEvent(this, "ha-refresh-backup-config");
}
private _handleDropdownSelect(ev: HaDropdownSelectEvent) {
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const action = ev.detail?.item?.value;
if (action === "change_local_location") {
@@ -9,6 +9,7 @@ import "../../../../components/ha-button";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-list-item";
import "../../../../components/ha-svg-icon";
import "../../../../components/ha-tip";
@@ -35,7 +36,6 @@ import "./cloud-remote-pref";
import "./cloud-tts-pref";
import "./cloud-webhooks";
import { showSupportPackageDialog } from "./show-dialog-cloud-support-package";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@customElement("cloud-account")
export class CloudAccount extends SubscribeMixin(LitElement) {
@@ -300,7 +300,7 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
fireEvent(this, "ha-refresh-cloud-status");
}
private _handleMenuAction(ev: HaDropdownSelectEvent) {
private _handleMenuAction(ev: CustomEvent<{ item: HaDropdownItem }>) {
const value = ev.detail.item.value;
switch (value) {
case "reset":
@@ -8,6 +8,7 @@ import "../../../../components/ha-alert";
import "../../../../components/ha-card";
import "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
import "../../../../components/ha-icon-next";
import "../../../../components/ha-list";
import "../../../../components/ha-list-item";
@@ -24,7 +25,6 @@ import "../../ha-config-section";
import { showSupportPackageDialog } from "../account/show-dialog-cloud-support-package";
import "./cloud-login";
import type { CloudLogin } from "./cloud-login";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@customElement("cloud-login-panel")
export class CloudLoginPanel extends LitElement {
@@ -167,7 +167,7 @@ export class CloudLoginPanel extends LitElement {
fireEvent(this, "flash-message-changed", { value: "" });
}
private _handleMenuAction(ev: HaDropdownSelectEvent) {
private _handleMenuAction(ev: CustomEvent<{ item: HaDropdownItem }>) {
const value = ev.detail.item.value;
switch (value) {
case "reset":
@@ -11,7 +11,6 @@ import "../../../layouts/hass-subpage";
import type { HomeAssistant, Route } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import "./ha-config-analytics";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
@customElement("ha-config-section-analytics")
class HaConfigSectionAnalytics extends LitElement {
@@ -50,7 +49,7 @@ class HaConfigSectionAnalytics extends LitElement {
}
private async _handleOverflowAction(
ev: HaDropdownSelectEvent
ev: CustomEvent<{ item: { value: string } }>
): Promise<void> {
if (ev.detail.item.value === "download_device_info") {
const signedPath = await getSignedPath(
@@ -1,3 +1,4 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import {
mdiDotsVertical,
mdiLocationEnter,
@@ -11,6 +12,8 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-card";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import type {
HassioSupervisorInfo,
@@ -30,10 +33,6 @@ import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types";
import "../dashboard/ha-config-updates";
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "@home-assistant/webawesome/dist/components/divider/divider";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
@customElement("ha-config-section-updates")
class HaConfigSectionUpdates extends LitElement {
@@ -162,7 +161,9 @@ class HaConfigSectionUpdates extends LitElement {
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
}
private _handleOverflowAction(ev: HaDropdownSelectEvent): void {
private _handleOverflowAction(
ev: CustomEvent<{ item: { value: string } }>
): void {
if (ev.detail.item.value === "toggle_beta") {
if (this._supervisorInfo!.channel === "stable") {
showJoinBetaDialog(this, {
@@ -39,14 +39,12 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { isMac } from "../../../util/is_mac";
import { isMobileClient } from "../../../util/is_mobile";
import "../ha-config-section";
import { configSections } from "../ha-panel-config";
import "../repairs/ha-config-repairs";
import "./ha-config-navigation";
import "./ha-config-updates";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
const randomTip = (openFn: any, hass: HomeAssistant, narrow: boolean) => {
const weighted: string[] = [];
@@ -125,14 +123,6 @@ const randomTip = (openFn: any, hass: HomeAssistant, narrow: boolean) => {
content: hass.localize("ui.tips.key_a_tip", localizeParam),
weight: 1,
narrow: false,
},
{
content: hass.localize("ui.tips.key_shortcut_quick_search", {
...localizeParam,
modifier: isMac ? "⌘" : "Ctrl",
}),
weight: 1,
narrow: false,
}
);
}
@@ -374,10 +364,22 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
);
private _showQuickBar(): void {
showQuickBar(this, { showHint: this.hass.enableShortcuts });
const params = {
keyboard_shortcut: html`<a href="#" @click=${this._openShortcutDialog}
>${this.hass.localize("ui.tips.keyboard_shortcut")}</a
>`,
};
showQuickBar(this, {
hint: this.hass.enableShortcuts
? this.hass.localize("ui.dialogs.quick-bar.key_c_tip", params)
: undefined,
});
}
private async _handleMenuAction(ev: HaDropdownSelectEvent) {
private async _handleMenuAction(
ev: CustomEvent<{ item: { value: string } }>
) {
const action = ev.detail.item.value;
switch (action) {
case "check-updates":
@@ -7,7 +7,6 @@ import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import "./ha-debug-connection-row";
import "./ha-debug-disable-view-transition-row";
import {
getStatisticMetadata,
validateStatistics,
@@ -16,7 +15,6 @@ import { computeDomain } from "../../../../common/entity/compute_domain";
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
import { showToast } from "../../../../util/toast";
import { getExtendedEntityRegistryEntry } from "../../../../data/entity/entity_registry";
import type { ExtEntityRegistryEntry } from "../../../../data/entity/entity_registry";
@customElement("developer-tools-debug")
class HaPanelDevDebug extends SubscribeMixin(LitElement) {
@@ -38,10 +36,6 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
.hass=${this.hass}
.narrow=${this.narrow}
></ha-debug-connection-row>
<ha-debug-disable-view-transition-row
.hass=${this.hass}
.narrow=${this.narrow}
></ha-debug-disable-view-transition-row>
</ha-card>
<ha-card
.header=${this.hass.localize(
@@ -88,12 +82,8 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
};
}
}
let entity: ExtEntityRegistryEntry | undefined;
try {
entity = await getExtendedEntityRegistryEntry(this.hass, id);
} catch {
// not in the registry
}
const entity = await getExtendedEntityRegistryEntry(this.hass, id);
const device = entity?.device_id && this.hass.devices[entity.device_id];
const data = {

Some files were not shown because too many files have changed in this diff Show More