Compare commits

...

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] 55e23d03f5 Fix Prettier formatting in zha-options-page.ts and zwave_js-custom-param.ts
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
2026-08-03 14:10:13 +00:00
copilot-swe-agent[bot] c39b89f6e0 Fix event handler element typings
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
2026-08-03 12:59:55 +00:00
Aidan Timson c8c61a1b36 Type configuration event handlers 2026-08-03 12:37:02 +01:00
Aidan Timson cee1c91ed8 Home Assistant event handler types skill (#53452)
* Document Home Assistant event handler types

* Expand event handling guidance

* Prefer bottom event declarations

* Split event guidance into dedicated skill
2026-08-03 13:51:24 +03:00
29 changed files with 303 additions and 87 deletions
@@ -7,6 +7,8 @@ description: Home Assistant frontend component patterns. Use when implementing o
Use this skill when creating or reviewing Home Assistant UI components and common interaction patterns.
Cross-load `ha-frontend-events` when component work includes event listener typing, custom event dispatch, or event-map declarations.
## Dialogs
Open dialogs through the fire-event pattern:
+105
View File
@@ -0,0 +1,105 @@
---
name: ha-frontend-events
description: Home Assistant frontend event patterns. Use when typing event handlers, using HASSDomEvent types, dispatching with fireEvent, or declaring HASSDomEvents and event maps.
---
# HA Frontend Events
Use this skill when implementing or reviewing event listeners and custom event contracts. Cross-load `ha-frontend-components` when the work also involves dialogs, forms, alerts, shortcuts, tooltips, panels, Lovelace cards, or buttons.
## Event Handling
Use the event types from `src/common/dom/fire_event.ts` instead of plain `Event`, generic `CustomEvent`, or element casts when they express the handler contract:
- Use `HASSDomCurrentTargetEvent<T>` to read the element on which the listener was registered through `ev.currentTarget`.
- Use `HASSDomTargetEvent<T>` only to read the element that originated the event through `ev.target`.
- Use `HASSDomEvent<T>` to read a custom event payload through `ev.detail`.
- Use `ValueChangedEvent<T>` from `src/types.ts` for the standard `value-changed` event.
- Prefer an event type exported by the component being listened to, such as `HaSelectSelectEvent<T, Clearable>` or `HaDropdownSelectEvent<TValue, TData>`, over reconstructing its detail type.
- Use `ActionHandlerEvent` from `src/data/lovelace/action_handler.ts` for Lovelace tap, hold, and double-tap handlers.
Import event and element types with `import type`:
```ts
import type {
HASSDomCurrentTargetEvent,
HASSDomEvent,
HASSDomTargetEvent,
} from "../common/dom/fire_event";
import type { HaCheckbox } from "../components/ha-checkbox";
import type { HaEntityPicker } from "../components/entity/ha-entity-picker";
import type { HaRadioGroup } from "../components/radio/ha-radio-group";
import type { ValueChangedEvent } from "../types";
```
Type the handler so the selected property can be read directly. Do not cast `ev.currentTarget` or assign it to a single-use variable:
```ts
private _scopeChanged(ev: HASSDomCurrentTargetEvent<HaRadioGroup>): void {
this._scope = ev.currentTarget.value;
}
private _checkedChanged(ev: HASSDomTargetEvent<HaCheckbox>): void {
this._checked = ev.target.checked;
}
private _valueChanged(ev: ValueChangedEvent<string>): void {
this._value = ev.detail.value;
}
private _itemSelected(ev: HASSDomEvent<{ id: string }>): void {
this._selectedId = ev.detail.id;
}
```
Use intersections when a handler needs more than one facet of an event. Keep the native event type when the handler reads native fields such as `key`, modifier keys, `dataTransfer`, or focus relationships:
```ts
private _entityChanged(
ev: ValueChangedEvent<string> & HASSDomCurrentTargetEvent<HaEntityPicker>
): void {
ev.currentTarget.value = ev.detail.value;
}
private _keyDown(
ev: KeyboardEvent & HASSDomCurrentTargetEvent<HTMLInputElement>
): void {
if (ev.key === "Enter") {
this._submit(ev.currentTarget.value);
}
}
```
Dispatch Home Assistant component events with `fireEvent()` instead of constructing `Event` or `CustomEvent` directly. Register the event name and detail type by augmenting `HASSDomEvents`; use `undefined` when an event has no detail. `fireEvent()` constrains event names and supplied detail, and events bubble and cross shadow boundaries by default:
```ts
fireEvent(this, "item-selected", { id: item.id });
fireEvent(this, "refresh-requested");
```
When an event is already registered, derive handler and listener types from its registration rather than repeating the payload shape:
```ts
private _itemSelected(
ev: HASSDomEvent<HASSDomEvents["item-selected"]>
): void {
this._selectedId = ev.detail.id;
}
```
`HASSDomEvents` types `fireEvent()` calls. Augment `HTMLElementEventMap` for typed listeners on HTML elements, or `GlobalEventHandlersEventMap` when the event is handled on global event targets.
In component files, prefer placing global event declarations after the class at the bottom of the file. Preserve the existing placement when editing established files; foundational type, helper, and mixin files commonly keep declarations near the top before their consumers.
```ts
declare global {
interface HASSDomEvents {
"item-selected": { id: string };
"refresh-requested": undefined;
}
interface HTMLElementEventMap {
"item-selected": HASSDomEvent<HASSDomEvents["item-selected"]>;
}
}
```
+1 -1
View File
@@ -70,4 +70,4 @@ Configuration and props:
- Identify behavioral regressions, bugs, accessibility issues, and missing tests first.
- Keep style-only comments secondary unless they affect maintainability or user experience.
- Prefer small, direct fixes over large refactors during review follow-up.
- Cross-load `ha-frontend-contexts`, `ha-frontend-components`, `ha-frontend-styling`, `ha-frontend-testing`, or `ha-frontend-user-facing-text` when a finding falls in that area.
- Cross-load `ha-frontend-contexts`, `ha-frontend-components`, `ha-frontend-events`, `ha-frontend-styling`, `ha-frontend-testing`, or `ha-frontend-user-facing-text` when a finding falls in that area.
+1
View File
@@ -40,6 +40,7 @@ Detailed guidance lives in project skills under `.agents/skills/`. Load the matc
- `ha-frontend-contexts`: Lit contexts, `hass` migration, and rerender-sensitive state access.
- `ha-frontend-components`: dialogs, forms, alerts, shortcuts, tooltips, panels, and Lovelace cards.
- `ha-frontend-events`: event handler typing, custom event dispatch, and event-map declarations.
- `ha-frontend-styling`: theme variables, spacing tokens, responsive layout, RTL, and view transitions.
- `ha-frontend-testing`: lint, typecheck, Vitest, Playwright e2e dev servers, and benchmarks.
- `ha-frontend-user-facing-text`: localization, terminology, sentence case, and Home Assistant text style.
@@ -3,6 +3,7 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import type { HASSDomTargetEvent } from "../../../common/dom/fire_event";
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
@@ -455,7 +456,7 @@ class DialogAreaDetail
return deviceReg && deviceReg.area_id === areaId;
};
private _nameChanged(ev: InputEvent) {
private _nameChanged(ev: InputEvent & HASSDomTargetEvent<HaInput>) {
this._error = undefined;
this._name = (ev.target as HaInput).value ?? "";
this._updateDirtyState(this._currentState());
@@ -479,7 +480,9 @@ class DialogAreaDetail
this._updateDirtyState(this._currentState());
}
private _pictureChanged(ev: ValueChangedEvent<string | null>) {
private _pictureChanged(
ev: ValueChangedEvent<string | null> & HASSDomTargetEvent<HaPictureUpload>
) {
this._error = undefined;
this._picture = (ev.target as HaPictureUpload).value;
this._updateDirtyState(this._currentState());
@@ -490,7 +493,9 @@ class DialogAreaDetail
this._updateDirtyState(this._currentState());
}
private _sensorChanged(ev: CustomEvent): void {
private _sensorChanged(
ev: ValueChangedEvent<string> & HASSDomTargetEvent<HaEntityPicker>
): void {
const deviceClass = (ev.target as HaEntityPicker).includeDeviceClasses![0];
const key = `_${deviceClass}Entity`;
this[key] = ev.detail.value || null;
@@ -5,6 +5,7 @@ import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import type { HASSDomTargetEvent } from "../../../common/dom/fire_event";
import "../../../components/chips/ha-chip-set";
import "../../../components/chips/ha-input-chip";
import "../../../components/ha-alert";
@@ -336,13 +337,13 @@ class DialogFloorDetail extends DirtyStateProviderMixin<FloorFormState>()(
this._updateDirtyState(this._currentState());
}
private _nameChanged(ev: InputEvent) {
private _nameChanged(ev: InputEvent & HASSDomTargetEvent<HaInput>) {
this._error = undefined;
this._name = (ev.target as HaInput).value ?? "";
this._updateDirtyState(this._currentState());
}
private _levelChanged(ev: InputEvent) {
private _levelChanged(ev: InputEvent & HASSDomTargetEvent<HaInput>) {
this._error = undefined;
this._level =
(ev.target as HaInput).value === ""
@@ -2,6 +2,7 @@ import { mdiClose, mdiOpenInNew } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import type { HASSDomTargetEvent } from "../../../common/dom/fire_event";
import { withViewTransition } from "../../../common/util/view-transition";
import "../../../components/ha-alert";
import "../../../components/ha-button";
@@ -279,7 +280,7 @@ class DialogImportBlueprint extends DirtyStateProviderMixin<BlueprintImportState
});
}
private _inputChanged(ev: Event) {
private _inputChanged(ev: HASSDomTargetEvent<HaInput>) {
this._updateDirtyState({
value: (ev.target as HaInput).value ?? "",
hasResult: !!this._result,
@@ -13,7 +13,10 @@ import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import type {
HASSDomCurrentTargetEvent,
HASSDomEvent,
} from "../../../common/dom/fire_event";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
@@ -483,7 +486,7 @@ class HaBlueprintOverview extends LitElement {
this._createNew(blueprint);
}
private _handleUsageClick = (ev: Event) => {
private _handleUsageClick = (ev: HASSDomCurrentTargetEvent<HTMLElement>) => {
ev.stopPropagation();
ev.preventDefault();
const target = ev.currentTarget as HTMLElement | null;
@@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-statistic-picker";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog";
@@ -340,7 +341,7 @@ export class DialogEnergyBatterySettings
}
private _handlePowerConfigChanged(
ev: CustomEvent<{ powerType: PowerType; powerConfig: PowerConfig }>
ev: HASSDomEvent<HASSDomEvents["power-config-changed"]>
) {
this._powerType = ev.detail.powerType;
this._powerConfig = ev.detail.powerConfig;
@@ -3,6 +3,7 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../../common/dom/fire_event";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
@@ -214,7 +215,7 @@ export class DialogEnergyCustomise
`;
}
private _toggleCard = (ev: Event): void => {
private _toggleCard = (ev: HASSDomCurrentTargetEvent<HaSwitch>): void => {
const target = ev.currentTarget as HaSwitch;
const cardKey = target.dataset.cardKey;
if (!cardKey) {
@@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/entity/ha-statistic-picker";
import "../../../../components/ha-button";
@@ -353,7 +354,7 @@ export class DialogEnergyGasSettings
`;
}
private _handleCostChanged(ev: Event) {
private _handleCostChanged(ev: HASSDomCurrentTargetEvent<HaRadioGroup>) {
this._costs = (ev.currentTarget as HaRadioGroup).value as CostType;
this._updateFormDirtyState();
}
@@ -2,6 +2,10 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import type {
HASSDomCurrentTargetEvent,
HASSDomEvent,
} from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/entity/ha-statistic-picker";
import "../../../../components/ha-button";
@@ -593,7 +597,9 @@ export class DialogEnergyGridSettings
this._updateFormDirtyState();
}
private _handleImportCostTypeChanged(ev: Event) {
private _handleImportCostTypeChanged(
ev: HASSDomCurrentTargetEvent<HaRadioGroup>
) {
this._importCostType = (ev.currentTarget as HaRadioGroup).value as CostType;
// Clear other cost fields when switching types
this._source = {
@@ -605,7 +611,9 @@ export class DialogEnergyGridSettings
this._updateFormDirtyState();
}
private _handleExportCostTypeChanged(ev: Event) {
private _handleExportCostTypeChanged(
ev: HASSDomCurrentTargetEvent<HaRadioGroup>
) {
this._exportCostType = (ev.currentTarget as HaRadioGroup).value as CostType;
// Clear other cost fields when switching types
this._source = {
@@ -630,7 +638,7 @@ export class DialogEnergyGridSettings
this._updateFormDirtyState();
}
private _numberCostChanged(ev: Event) {
private _numberCostChanged(ev: HASSDomCurrentTargetEvent<HTMLInputElement>) {
const input = ev.currentTarget as HTMLInputElement;
const value = input.value ? parseFloat(input.value) : null;
this._source = { ...this._source!, number_energy_price: value };
@@ -653,7 +661,9 @@ export class DialogEnergyGridSettings
this._updateFormDirtyState();
}
private _numberCompensationChanged(ev: Event) {
private _numberCompensationChanged(
ev: HASSDomCurrentTargetEvent<HTMLInputElement>
) {
const input = ev.currentTarget as HTMLInputElement;
const value = input.value ? parseFloat(input.value) : null;
this._source = { ...this._source!, number_energy_price_export: value };
@@ -661,7 +671,7 @@ export class DialogEnergyGridSettings
}
private _handlePowerConfigChanged(
ev: CustomEvent<{ powerType: PowerType; powerConfig: PowerConfig }>
ev: HASSDomEvent<HASSDomEvents["power-config-changed"]>
) {
this._powerType = ev.detail.powerType;
this._powerConfig = ev.detail.powerConfig;
@@ -3,6 +3,7 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-statistic-picker";
import "../../../../components/ha-button";
import "../../../../components/ha-checkbox";
@@ -290,7 +291,7 @@ export class DialogEnergySolarSettings
);
}
private _handleForecastChanged(ev: Event) {
private _handleForecastChanged(ev: HASSDomCurrentTargetEvent<HaRadioGroup>) {
this._forecast = (ev.currentTarget as HaRadioGroup).value === "true";
this._updateFormDirtyState();
}
@@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/entity/ha-statistic-picker";
import "../../../../components/ha-button";
@@ -289,7 +290,7 @@ export class DialogEnergyWaterSettings
`;
}
private _handleCostChanged(ev: Event) {
private _handleCostChanged(ev: HASSDomCurrentTargetEvent<HaRadioGroup>) {
this._costs = (ev.currentTarget as HaRadioGroup).value as CostType;
this._updateFormDirtyState();
}
@@ -3,6 +3,7 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import "../../../../components/entity/ha-statistic-picker";
@@ -234,7 +235,7 @@ export class HaEnergyPowerConfig extends LitElement {
fireEvent(this, "hass-more-info", { entityId: this.helperEntityId! });
}
private _handlePowerTypeChanged(ev: Event) {
private _handlePowerTypeChanged(ev: HASSDomCurrentTargetEvent<HaRadioGroup>) {
const newPowerType = (ev.currentTarget as HaRadioGroup).value as PowerType;
// Clear power config when switching types
fireEvent(this, "power-config-changed", {
@@ -3,6 +3,7 @@ import type { CSSResultGroup, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
import { computeDeviceName } from "../../../../../common/entity/compute_device_name";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-button";
@@ -451,7 +452,9 @@ export class BluetoothAdapterInfoPage extends LitElement {
return this._formatModeLabel(scannerState.current_mode);
}
private async _handleEnable(ev: Event) {
private async _handleEnable(
ev: HASSDomCurrentTargetEvent<HTMLElement & { entry: ConfigEntry }>
) {
const button = ev.currentTarget as HTMLElement & { entry: ConfigEntry };
const entryId = button.entry.entry_id;
try {
@@ -474,7 +477,9 @@ export class BluetoothAdapterInfoPage extends LitElement {
}
}
private _openOptionFlow(ev: Event) {
private _openOptionFlow(
ev: HASSDomCurrentTargetEvent<HTMLElement & { entry: ConfigEntry }>
) {
ev.preventDefault();
ev.stopPropagation();
const button = ev.currentTarget as HTMLElement & { entry: ConfigEntry };
@@ -3,6 +3,7 @@ import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-button";
import "../../../../../components/ha-dialog-footer";
@@ -211,7 +212,9 @@ class DialogMatterLockManage extends LitElement {
`;
}
private _handleUserClick(ev: Event): void {
private _handleUserClick(
ev: HASSDomCurrentTargetEvent<HTMLElement & { user: MatterLockUser }>
): void {
// Ignore clicks that originated from the delete button
const path = ev.composedPath();
if (path.some((el) => (el as HTMLElement).tagName === "HA-ICON-BUTTON")) {
@@ -221,7 +224,9 @@ class DialogMatterLockManage extends LitElement {
this._editUser(user);
}
private _handleDeleteUserClick(ev: Event): void {
private _handleDeleteUserClick(
ev: HASSDomCurrentTargetEvent<HTMLElement & { user: MatterLockUser }>
): void {
ev.preventDefault();
ev.stopPropagation();
const user = (ev.currentTarget as any).user as MatterLockUser;
@@ -11,10 +11,7 @@ import { addGroup, fetchGroupableDevices } from "../../../../../data/zha";
import "../../../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../../../types";
import "./zha-device-endpoint-list";
import type {
DeviceEndpointSelectionChangedEvent,
ZHADeviceEndpointList,
} from "./zha-device-endpoint-list";
import type { ZHADeviceEndpointList } from "./zha-device-endpoint-list";
@customElement("zha-add-group-page")
export class ZHAAddGroupPage extends LitElement {
@@ -131,7 +128,7 @@ export class ZHAAddGroupPage extends LitElement {
}
private _handleAddSelectionChanged(
ev: HASSDomEvent<DeviceEndpointSelectionChangedEvent>
ev: HASSDomEvent<HASSDomEvents["selection-changed"]>
): void {
this._selectedDevicesToAdd = ev.detail.value;
}
@@ -4,10 +4,15 @@ import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import type {
HASSDomCurrentTargetEvent,
HASSDomEvent,
} from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-list";
import "../../../../../components/input/ha-input-search";
import type { HaInputSearch } from "../../../../../components/input/ha-input-search";
import "../../../../../components/item/ha-list-item-base";
import "../../../../../components/item/ha-list-item-option";
import type { HaListItemOption } from "../../../../../components/item/ha-list-item-option";
@@ -269,11 +274,16 @@ export class ZHADeviceEndpointList extends LitElement {
.join(" · ");
}
private _handleFilterChanged(ev: Event): void {
this._filter = (ev.currentTarget as HTMLInputElement).value;
private _handleFilterChanged(
ev: HASSDomCurrentTargetEvent<HaInputSearch>
): void {
this._filter = ev.currentTarget.value ?? "";
}
private _handleItemSelected(ev: CustomEvent<number>): void {
private _handleItemSelected(
ev: HASSDomEvent<HASSDomEvents["ha-list-item-selected"]> &
HASSDomCurrentTargetEvent<HaListSelectable>
): void {
const list = ev.currentTarget as HaListSelectable;
let selectedDeviceIds = this._selectedDeviceIds;
@@ -290,7 +300,10 @@ export class ZHADeviceEndpointList extends LitElement {
}
}
private _handleItemDeselected(ev: CustomEvent<number>): void {
private _handleItemDeselected(
ev: HASSDomEvent<HASSDomEvents["ha-list-item-deselected"]> &
HASSDomCurrentTargetEvent<HaListSelectable>
): void {
const list = ev.currentTarget as HaListSelectable;
let selectedDeviceIds = this._selectedDeviceIds;
@@ -19,10 +19,7 @@ import "../../../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../../../types";
import { formatAsPaddedHex } from "./functions";
import "./zha-device-endpoint-list";
import type {
DeviceEndpointSelectionChangedEvent,
ZHADeviceEndpointList,
} from "./zha-device-endpoint-list";
import type { ZHADeviceEndpointList } from "./zha-device-endpoint-list";
import { showZHAAddGroupMembersDialog } from "./show-dialog-zha-add-group-members";
@customElement("zha-group-page")
@@ -202,7 +199,7 @@ export class ZHAGroupPage extends LitElement {
}
private _handleRemoveSelectionChanged(
ev: HASSDomEvent<DeviceEndpointSelectionChangedEvent>
ev: HASSDomEvent<HASSDomEvents["selection-changed"]>
): void {
this._selectedDevicesToRemove = ev.detail.value;
}
@@ -1,13 +1,16 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/buttons/ha-progress-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-select";
import "../../../../../components/input/ha-input";
import type { HaInput } from "../../../../../components/input/ha-input";
import "../../../../../components/ha-switch";
import type { HaSwitch } from "../../../../../components/ha-switch";
import type { ZHAConfiguration } from "../../../../../data/zha";
import {
fetchZHAConfiguration,
@@ -18,6 +21,8 @@ import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
const PREDEFINED_TIMEOUTS = [1800, 3600, 7200, 21600, 43200, 86400];
type ZHASwitchChangeEvent = HASSDomCurrentTargetEvent<HaSwitch>;
type ZHAInputChangeEvent = HASSDomCurrentTargetEvent<HaInput>;
@customElement("zha-options-page")
class ZHAOptionsPage extends LitElement {
@@ -345,53 +350,54 @@ class ZHAOptionsPage extends LitElement {
`;
}
private _enableIdentifyOnJoinChanged(ev: Event): void {
const checked = (ev.target as HTMLInputElement).checked;
this._configuration!.data.zha_options.enable_identify_on_join = checked;
private _enableIdentifyOnJoinChanged(ev: ZHASwitchChangeEvent): void {
this._configuration!.data.zha_options.enable_identify_on_join =
ev.currentTarget.checked;
this.requestUpdate();
}
private _enhancedLightTransitionChanged(ev: Event): void {
const checked = (ev.target as HTMLInputElement).checked;
this._configuration!.data.zha_options.enhanced_light_transition = checked;
private _enhancedLightTransitionChanged(ev: ZHASwitchChangeEvent): void {
this._configuration!.data.zha_options.enhanced_light_transition =
ev.currentTarget.checked;
this.requestUpdate();
}
private _lightTransitioningFlagChanged(ev: Event): void {
const checked = (ev.target as HTMLInputElement).checked;
this._configuration!.data.zha_options.light_transitioning_flag = checked;
private _lightTransitioningFlagChanged(ev: ZHASwitchChangeEvent): void {
this._configuration!.data.zha_options.light_transitioning_flag =
ev.currentTarget.checked;
this.requestUpdate();
}
private _groupMembersAssumeStateChanged(ev: Event): void {
const checked = (ev.target as HTMLInputElement).checked;
this._configuration!.data.zha_options.group_members_assume_state = checked;
private _groupMembersAssumeStateChanged(ev: ZHASwitchChangeEvent): void {
this._configuration!.data.zha_options.group_members_assume_state =
ev.currentTarget.checked;
this.requestUpdate();
}
private _enableMainsStartupPollingChanged(ev: Event): void {
const checked = (ev.target as HTMLInputElement).checked;
private _enableMainsStartupPollingChanged(ev: ZHASwitchChangeEvent): void {
this._configuration!.data.zha_options.enable_mains_startup_polling =
checked;
ev.currentTarget.checked;
this.requestUpdate();
}
private _defaultLightTransitionChanged(ev: Event): void {
const value = Number((ev.target as HTMLInputElement).value);
this._configuration!.data.zha_options.default_light_transition = value;
private _defaultLightTransitionChanged(ev: ZHAInputChangeEvent): void {
this._configuration!.data.zha_options.default_light_transition = Number(
ev.currentTarget.value
);
this.requestUpdate();
}
private _customMainsSecondsChanged(ev: Event): void {
const seconds = Number((ev.target as HTMLInputElement).value);
this._configuration!.data.zha_options.consider_unavailable_mains = seconds;
private _customMainsSecondsChanged(ev: ZHAInputChangeEvent): void {
this._configuration!.data.zha_options.consider_unavailable_mains = Number(
ev.currentTarget.value
);
this.requestUpdate();
}
private _customBatterySecondsChanged(ev: Event): void {
const seconds = Number((ev.target as HTMLInputElement).value);
this._configuration!.data.zha_options.consider_unavailable_battery =
seconds;
private _customBatterySecondsChanged(ev: ZHAInputChangeEvent): void {
this._configuration!.data.zha_options.consider_unavailable_battery = Number(
ev.currentTarget.value
);
this.requestUpdate();
}
@@ -419,7 +425,15 @@ class ZHAOptionsPage extends LitElement {
this.requestUpdate();
}
private async _updateConfiguration(ev: Event): Promise<void> {
private async _updateConfiguration(
ev: HASSDomCurrentTargetEvent<
HTMLElement & {
progress: boolean;
actionSuccess: () => void;
actionError: () => void;
}
>
): Promise<void> {
const button = ev.currentTarget as HTMLElement & {
progress: boolean;
actionSuccess: () => void;
@@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
import type { LocalizeKeys } from "../../../../../common/translations/localize";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-button";
@@ -478,7 +479,9 @@ class DialogZwaveCredentialUserEdit extends DirtyStateProviderMixin<CredentialFo
}
}
private _handleCredentialTypeChanged(ev: Event): void {
private _handleCredentialTypeChanged(
ev: HASSDomCurrentTargetEvent<HaRadioGroup>
): void {
this._credentialType = (ev.currentTarget as HaRadioGroup)
.value as ZwaveCredentialType;
// Switching types invalidates any data tied to the previous type's
@@ -2,11 +2,13 @@ import { mdiCloseCircle } from "@mdi/js";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-button";
import "../../../../../components/ha-select";
import type { HaSelectSelectEvent } from "../../../../../components/ha-select";
import "../../../../../components/ha-spinner";
import "../../../../../components/input/ha-input";
import type { HaInput } from "../../../../../components/input/ha-input";
import {
getZwaveNodeRawConfigParameter,
setZwaveNodeRawConfigParameter,
@@ -129,9 +131,9 @@ class ZWaveJSCustomParam extends LitElement {
return parsed;
}
private _customParamNumberChanged(ev: Event) {
private _customParamNumberChanged(ev: HASSDomCurrentTargetEvent<HaInput>) {
this._customParamNumber = this._tryParseNumber(
(ev.target as HTMLInputElement).value
ev.currentTarget.value ?? ""
);
}
@@ -139,8 +141,8 @@ class ZWaveJSCustomParam extends LitElement {
this._valueSize = this._tryParseNumber(ev.detail.value) ?? 1;
}
private _customValueChanged(ev: Event) {
this._value = this._tryParseNumber((ev.target as HTMLInputElement).value);
private _customValueChanged(ev: HASSDomCurrentTargetEvent<HaInput>) {
this._value = this._tryParseNumber(ev.currentTarget.value ?? "");
}
private _customValueFormatChanged(ev: HaSelectSelectEvent) {
+4 -1
View File
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-alert";
@@ -283,7 +284,9 @@ export class SystemLogCard extends LitElement {
fileDownload(signedUrl.path, logFileName);
}
private _openLog(ev: Event): void {
private _openLog(
ev: HASSDomCurrentTargetEvent<HTMLElement & { logItem: LoggedError }>
): void {
const item = (ev.currentTarget as any).logItem;
showSystemLogDetailDialog(this, { item });
}
@@ -2,6 +2,7 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
import { isIPAddress } from "../../../common/string/is_ip_address";
import "../../../components/ha-alert";
import "../../../components/ha-button";
@@ -363,12 +364,12 @@ class ConfigUrlForm extends SubscribeMixin(LitElement) {
);
}
private _toggleCloud(ev: Event) {
private _toggleCloud(ev: HASSDomCurrentTargetEvent<HaSwitch>) {
this._cloudChecked = (ev.currentTarget as HaSwitch).checked;
this._showCustomExternalUrl = !this._cloudChecked;
}
private _toggleInternalAutomatic(ev: Event) {
private _toggleInternalAutomatic(ev: HASSDomCurrentTargetEvent<HaSwitch>) {
this._showCustomInternalUrl = !(ev.currentTarget as HaSwitch).checked;
}
@@ -2,6 +2,10 @@ import { mdiDeleteOutline, mdiMenuDown, mdiPlus, mdiWifi } from "@mdi/js";
import { css, type CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache";
import type {
HASSDomCurrentTargetEvent,
HASSDomTargetEvent,
} from "../../../common/dom/fire_event";
import "../../../components/ha-alert";
import "../../../components/ha-button";
import "../../../components/ha-card";
@@ -630,7 +634,9 @@ export class HassioNetwork extends LitElement {
this._interface = { ...this._interfaces[this._curTabIndex] };
}
private _handleRadioValueChanged(ev: Event): void {
private _handleRadioValueChanged(
ev: HASSDomCurrentTargetEvent<HaRadioGroup & { version: "ipv4" | "ipv6" }>
): void {
const source = ev.currentTarget as HaRadioGroup;
const value = source.value as "disabled" | "auto" | "static";
const version = (source as any).version as "ipv4" | "ipv6";
@@ -645,7 +651,9 @@ export class HassioNetwork extends LitElement {
this.requestUpdate("_interface");
}
private _handleRadioValueChangedAp(ev: Event): void {
private _handleRadioValueChangedAp(
ev: HASSDomCurrentTargetEvent<HaRadioGroup>
): void {
const source = ev.currentTarget as HaRadioGroup;
const value = source.value as "open" | "wep" | "wpa-psk";
this._wifiConfiguration!.auth = value;
@@ -653,7 +661,11 @@ export class HassioNetwork extends LitElement {
this.requestUpdate("_wifiConfiguration");
}
private _handleInputValueChanged(ev: Event): void {
private _handleInputValueChanged(
ev: HASSDomTargetEvent<
HaInput & { version: "ipv4" | "ipv6"; index: number }
>
): void {
const source = ev.target as HaInput;
const value = source.value;
const version = (ev.target as any).version as "ipv4" | "ipv6";
@@ -691,7 +703,7 @@ export class HassioNetwork extends LitElement {
}
}
private _handleInputValueChangedWifi(ev: Event): void {
private _handleInputValueChangedWifi(ev: HASSDomTargetEvent<HaInput>): void {
const source = ev.target as HaInput;
const value = source.value;
const id = source.id;
@@ -708,7 +720,9 @@ export class HassioNetwork extends LitElement {
this._wifiConfiguration![id] = value;
}
private _addAddress(ev: Event): void {
private _addAddress(
ev: HASSDomTargetEvent<HTMLElement & { version: "ipv4" | "ipv6" }>
): void {
const version = (ev.target as any).version as "ipv4" | "ipv6";
this._interface![version]!.address!.push(
version === "ipv4" ? "0.0.0.0/24" : "::/64"
@@ -717,7 +731,11 @@ export class HassioNetwork extends LitElement {
this.requestUpdate("_interface");
}
private _removeAddress(ev: Event): void {
private _removeAddress(
ev: HASSDomTargetEvent<
HTMLElement & { index: number; version: "ipv4" | "ipv6" }
>
): void {
const source = ev.target as any;
const index = source.index as number;
const version = source.version as "ipv4" | "ipv6";
@@ -752,7 +770,11 @@ export class HassioNetwork extends LitElement {
this.requestUpdate("_interface");
}
private _removeNameserver(ev: Event): void {
private _removeNameserver(
ev: HASSDomTargetEvent<
HTMLElement & { index: number; version: "ipv4" | "ipv6" }
>
): void {
const source = ev.target as any;
const index = source.index as number;
const version = source.version as "ipv4" | "ipv6";
@@ -11,6 +11,7 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { HASSDomCurrentTargetEvent } from "../../../../common/dom/fire_event";
import { computeAreaName } from "../../../../common/entity/compute_area_name";
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
import { computeEntityEntryName } from "../../../../common/entity/compute_entity_name";
@@ -260,7 +261,9 @@ class HaPanelDevStateRenderer extends LitElement {
return output;
}
private _copyEntity = async (ev: Event) => {
private _copyEntity = async (
ev: HASSDomCurrentTargetEvent<HTMLElement & { entity: HassEntity }>
) => {
ev.preventDefault();
const entity = (ev.currentTarget as HTMLElement & { entity: HassEntity })
.entity;
@@ -270,14 +273,18 @@ class HaPanelDevStateRenderer extends LitElement {
});
};
private _entityMoreInfo(ev: Event) {
private _entityMoreInfo(
ev: HASSDomCurrentTargetEvent<HTMLElement & { entity: HassEntity }>
) {
ev.preventDefault();
const entity = (ev.currentTarget as HTMLElement & { entity: HassEntity })
.entity;
fireEvent(this, "hass-more-info", { entityId: entity.entity_id });
}
private _entitySelected(ev: Event) {
private _entitySelected(
ev: HASSDomCurrentTargetEvent<HTMLElement & { entity: HassEntity }>
) {
ev.preventDefault();
const entity = (ev.currentTarget as HTMLElement & { entity: HassEntity })
.entity;
@@ -16,7 +16,11 @@ import { consume, type ContextType } from "@lit/context";
import { css, type CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import type {
HASSDomCurrentTargetEvent,
HASSDomEvent,
HASSDomTargetEvent,
} from "../../../../common/dom/fire_event";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeAreaName } from "../../../../common/entity/compute_area_name";
import {
@@ -596,7 +600,9 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
`;
}
private _handleSearchChange(ev: InputEvent) {
private _handleSearchChange(
ev: InputEvent & HASSDomTargetEvent<HaInputSearch>
) {
if (this.filter === (ev.target as HaInputSearch).value) {
return;
}
@@ -706,7 +712,9 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
);
}
private _showStatisticsAdjustSumDialog(ev: Event) {
private _showStatisticsAdjustSumDialog(
ev: HASSDomCurrentTargetEvent<HTMLElement & { statistic: StatisticData }>
) {
ev.stopPropagation();
showStatisticsAdjustSumDialog(this, {
statistic: (
@@ -782,7 +790,11 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
});
};
private _fixIssue = async (ev: Event) => {
private _fixIssue = async (
ev: HASSDomCurrentTargetEvent<
HTMLElement & { data: StatisticsValidationResult[] }
>
) => {
const issues = (
ev.currentTarget as HTMLElement & { data: StatisticsValidationResult[] }
).data.sort(
@@ -9,6 +9,7 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { HASSDomTargetEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import { debounce } from "../../../../common/util/debounce";
@@ -419,7 +420,7 @@ ${
`;
}
private _splitRepositioned(ev: Event) {
private _splitRepositioned(ev: HASSDomTargetEvent<HaSplitPanel>) {
this._splitPosition = (ev.target as HaSplitPanel).position;
this._storeSplitPosition();
}