mirror of
https://github.com/home-assistant/frontend.git
synced 2026-08-04 13:38:43 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f5b8099a6 | |||
| a9cc652214 | |||
| c0d963747a | |||
| 1bcd43fb75 | |||
| ce5925388c | |||
| 088f72fcc4 | |||
| b93f09ffb2 | |||
| fb499093e5 | |||
| c97716d4ad | |||
| b36361ca34 | |||
| 007a7916c0 | |||
| 637f6955a8 | |||
| b574aaa084 | |||
| c1dca9b377 | |||
| c9963b7f92 | |||
| 42954152dc | |||
| f5a28977c2 | |||
| 9b1f407ff4 | |||
| 277822e51c | |||
| 4a2bca6d76 | |||
| 8e47b5369e | |||
| 759ab155c7 | |||
| 8de939a696 | |||
| 5c3d74502b | |||
| 0bf67b603c | |||
| 01915f2e5a |
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: ha-frontend-lit
|
||||
description: Home Assistant frontend Lit conventions. Use when working with reactive properties, internal state, DOM queries, lifecycle methods, or render-derived state.
|
||||
---
|
||||
|
||||
# HA Frontend Lit
|
||||
|
||||
Use this skill when implementing or reviewing Lit component state, DOM access, lifecycle methods, or rendering behavior. Cross-load `ha-frontend-types` for Home Assistant data contracts, assertions, and lifecycle parameter types.
|
||||
|
||||
## Reactive Fields
|
||||
|
||||
This project currently uses Lit's TypeScript experimental decorators with `useDefineForClassFields: false`. Match existing declarations and do not introduce standard-decorator `accessor` syntax unless the project changes decorator mode.
|
||||
|
||||
- Use `@property()` for public reactive API and `@state()` for private reactive state.
|
||||
- Prefer inferred types for initialized reactive fields when inference preserves the intended type; annotate when widening or an external contract requires it.
|
||||
|
||||
## DOM Queries
|
||||
|
||||
Prefer Lit's `@query()` or `@queryAll()` decorators for fixed selectors in the component's render root.
|
||||
|
||||
- Type the decorated field with the narrowest useful DOM or component interface.
|
||||
- Keep the field optional when it may be absent at the point of access, including conditional rendering or pre-render lifecycle access.
|
||||
- Use a definite assignment assertion only when every call site runs after the node is guaranteed to exist.
|
||||
- The optional second argument to `@query()`, as in `@query("#target", true)`, caches the first query result. Use it only when later renders cannot replace the queried node.
|
||||
- Use a direct query when the selector is dynamic or the target is outside the component's render root. Before querying a child, consider whether the required value belongs in parent state or data flow.
|
||||
|
||||
## Render-Derived State
|
||||
|
||||
- Prefer render-local values for inexpensive structures used only by that render.
|
||||
- Assign a render-local value once when repeated evaluation is non-trivial or a local name improves clarity.
|
||||
- Keep purely presentational derivations in `render()`. Use stored state or `willUpdate()` when the value must participate in lifecycle work, reflection, CSS, or non-render consumers.
|
||||
- Use `memoizeOne` for pure, argument-derived transforms when stable input identity avoids meaningful repeated work. Keep inputs explicit and limited, and do not add caching without a credible benefit over computing the value directly.
|
||||
|
||||
## References
|
||||
|
||||
- [Reactive properties](https://lit.dev/docs/components/properties/)
|
||||
- [Decorators](https://lit.dev/docs/components/decorators/)
|
||||
- [Shadow DOM queries](https://lit.dev/docs/components/shadow-dom/#query)
|
||||
- [Reactive update cycle](https://lit.dev/docs/components/lifecycle/)
|
||||
@@ -31,6 +31,33 @@ When creating a pull request, use `.github/PULL_REQUEST_TEMPLATE.md` as the body
|
||||
|
||||
## Recurring Review Issues
|
||||
|
||||
Scope and public surface:
|
||||
|
||||
- Keep changes independently reviewable and limited to the requested area.
|
||||
- Prefer existing Home Assistant helpers, Lit primitives, and component seams over parallel implementations.
|
||||
- Challenge new public properties and optional feature surface when transient options or existing seams meet the requirement with less lifecycle and consistency cost.
|
||||
|
||||
Stateful and asynchronous UI:
|
||||
|
||||
- Review transitions in both directions, not only individual rendered states.
|
||||
- When controls reappear, restore valid defaults instead of retaining state that was only valid while they were hidden.
|
||||
- Establish immutable dirty-state baselines before asynchronous work, guard against stale responses, and preserve unsaved state in mounted editors.
|
||||
- Determine an action's current meaning before applying dirty-state checks, especially when an action can change between Save and Close.
|
||||
|
||||
Readiness and invalidation:
|
||||
|
||||
- Treat readiness as the first displayable terminal result, including stable empty and error states.
|
||||
- Register child readiness before resolving the parent, do not treat fallback work as terminal, and replay readiness correctly for cached or reused panels.
|
||||
- Ensure every value read by memoized output participates in its invalidation.
|
||||
|
||||
Repository-owned contracts:
|
||||
|
||||
- Consult the public [frontend developer documentation](https://developers.home-assistant.io/docs/frontend/) for documented architecture, data flow, design, and development workflows.
|
||||
- For new leaf components, load `ha-frontend-contexts` and verify they consume narrow contexts instead of introducing a broad `hass` property; containers and external APIs may still require `hass`.
|
||||
- Verify backend assumptions against the owning Core, Supervisor, or WebSocket implementation, and component assumptions against the exported component contract.
|
||||
- Prefer canonical repository helpers and test setup over duplicate local implementations.
|
||||
- Promote AI-review concerns into durable guidance only when supported by code evidence, reproduced behavior, an accepted corrective commit, or human-maintainer validation.
|
||||
|
||||
User experience and accessibility:
|
||||
|
||||
- Forms need proper labels, helper text, and validation feedback.
|
||||
@@ -70,4 +97,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-events`, `ha-frontend-styling`, `ha-frontend-testing`, or `ha-frontend-user-facing-text` when a finding falls in that area.
|
||||
- Load the matching `ha-frontend-*` skill when a finding falls within its area.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: ha-frontend-types
|
||||
description: Home Assistant frontend TypeScript conventions. Use when defining or reviewing backend data contracts, optional schemas, shared types, assertions, or Lit lifecycle types.
|
||||
---
|
||||
|
||||
# HA Frontend Types
|
||||
|
||||
Use this skill for Home Assistant-specific TypeScript contracts and type choices.
|
||||
|
||||
## Home Assistant Data Contracts
|
||||
|
||||
Verify data contracts against the source that owns them, such as Home Assistant Core, Supervisor, a WebSocket handler, or an exported component type. Do not shape a type around assumptions made by its current frontend consumers.
|
||||
|
||||
- Match required, optional, nullable, and defaulted fields to the producer and runtime contract. Preserve optional configuration fields when omission is supported and has defined behavior.
|
||||
- Use distinct request and response types when their wire shapes differ.
|
||||
- When changing a shared contract, check affected consumers, tests, fixtures, and mocks.
|
||||
|
||||
## Reuse Home Assistant Contracts
|
||||
|
||||
- Reuse the canonical Home Assistant type when one exists. Define shared contract types in the data or API module that owns them.
|
||||
- Reuse types exported by components and helpers rather than reconstructing their payloads. For event types, follow `ha-frontend-events`.
|
||||
- When one domain contract is used across modules, define and export it from the module that owns that contract.
|
||||
|
||||
Prefer an existing owning contract. Introduce a frontend-specific type when the frontend shape or boundary genuinely differs.
|
||||
|
||||
## Assertions
|
||||
|
||||
Prefer accurate types and runtime narrowing. Use assertions or TypeScript suppressions at boundaries where the runtime invariant is understood but cannot be expressed cleanly; keep them narrow and explain non-obvious invariants.
|
||||
|
||||
## Lit Lifecycle Types
|
||||
|
||||
For Lit lifecycle methods that receive changed properties, use `PropertyValues<this>` when the method only needs public reactive properties:
|
||||
|
||||
```ts
|
||||
protected willUpdate(changedProperties: PropertyValues<this>) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Use unparameterized `PropertyValues` when the method inspects private or protected reactive properties, which are not keys of `this`. Do not add assertions solely to retain `PropertyValues<this>`.
|
||||
|
||||
## Enforced Baseline
|
||||
|
||||
Use `import type` for type-only imports. This is enforced by the repository ESLint configuration.
|
||||
@@ -41,6 +41,8 @@ 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-types`: backend data contracts, optional schemas, shared types, assertions, and lifecycle types.
|
||||
- `ha-frontend-lit`: reactive fields, DOM queries, lifecycle behavior, and render-derived state.
|
||||
- `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.
|
||||
|
||||
@@ -23,7 +23,7 @@ export class DemoHaProgressButton extends LitElement {
|
||||
<ha-progress-button @click=${this._clickedFail}>
|
||||
Fail
|
||||
</ha-progress-button>
|
||||
<ha-progress-button size="small" @click=${this._clickedSuccess}>
|
||||
<ha-progress-button size="s" @click=${this._clickedSuccess}>
|
||||
small
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
|
||||
+5
-5
@@ -108,7 +108,7 @@
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"idb-keyval": "6.3.0",
|
||||
"intl-messageformat": "11.2.13",
|
||||
"js-yaml": "5.2.2",
|
||||
"js-yaml": "5.2.3",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||
"leaflet.markercluster": "1.5.3",
|
||||
@@ -160,11 +160,11 @@
|
||||
"@types/color-name": "2.0.0",
|
||||
"@types/culori": "4.0.1",
|
||||
"@types/html-minifier-terser": "7.0.2",
|
||||
"@types/leaflet": "1.9.21",
|
||||
"@types/leaflet": "1.9.22",
|
||||
"@types/leaflet-draw": "1.0.13",
|
||||
"@types/leaflet.markercluster": "1.5.6",
|
||||
"@types/lodash.merge": "4.6.9",
|
||||
"@types/luxon": "3.7.2",
|
||||
"@types/luxon": "3.7.3",
|
||||
"@types/qrcode": "1.5.6",
|
||||
"@types/sortablejs": "1.15.9",
|
||||
"@types/tar": "7.0.87",
|
||||
@@ -196,7 +196,7 @@
|
||||
"jsdom": "30.0.1",
|
||||
"jszip": "3.10.1",
|
||||
"license-checker-rseidelsohn": "5.0.1",
|
||||
"lint-staged": "17.2.0",
|
||||
"lint-staged": "17.3.0",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.18.1",
|
||||
@@ -230,6 +230,6 @@
|
||||
},
|
||||
"packageManager": "yarn@4.18.0",
|
||||
"volta": {
|
||||
"node": "24.18.1"
|
||||
"node": "24.19.0"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -8,10 +8,10 @@
|
||||
":prConcurrentLimit10",
|
||||
":semanticCommitsDisabled",
|
||||
"group:monorepos",
|
||||
"group:recommended",
|
||||
"security:minimumReleaseAgeNpm"
|
||||
"group:recommended"
|
||||
],
|
||||
"enabledManagers": ["npm", "nvm", "custom.regex"],
|
||||
"minimumReleaseAge": "3 days",
|
||||
"postUpdateOptions": ["yarnDedupeHighest"],
|
||||
"lockFileMaintenance": {
|
||||
"description": ["Run after patch releases but before next beta"],
|
||||
|
||||
@@ -18,6 +18,8 @@ export class HaProgressButton extends LitElement {
|
||||
|
||||
@property() appearance: Appearance = "accent";
|
||||
|
||||
@property() size: "xs" | "s" | "m" | "l" | "xl" = "m";
|
||||
|
||||
@property({ attribute: false }) public iconPath?: string;
|
||||
|
||||
@property() variant: "brand" | "danger" | "neutral" | "warning" | "success" =
|
||||
@@ -32,6 +34,7 @@ export class HaProgressButton extends LitElement {
|
||||
return html`
|
||||
<ha-button
|
||||
.appearance=${appearance}
|
||||
.size=${this.size}
|
||||
.disabled=${this.disabled}
|
||||
.loading=${this.progress}
|
||||
.variant=${
|
||||
@@ -118,6 +121,12 @@ export class HaProgressButton extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* The icon lives in this shadow root, so callers cannot size it themselves. */
|
||||
ha-button[size="xs"] ha-svg-icon[slot="start"],
|
||||
ha-button[size="s"] ha-svg-icon[slot="start"] {
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
|
||||
/* Fade the content out rather than hiding it, so the button keeps its
|
||||
accessible name while the result icon covers it. */
|
||||
ha-button.result::part(start),
|
||||
|
||||
@@ -26,7 +26,10 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { getAllGraphColors } from "../../common/color/colors";
|
||||
import { transform } from "../../common/decorators/transform";
|
||||
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 { listenMediaQuery } from "../../common/dom/media_query";
|
||||
import { afterNextRender } from "../../common/util/render-status";
|
||||
@@ -1216,7 +1219,9 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
|
||||
// Long-press to solo on touch/pen devices (500ms, consistent with action-handler-directive)
|
||||
private _legendPointerDown(ev: PointerEvent) {
|
||||
private _legendPointerDown(
|
||||
ev: PointerEvent & HASSDomCurrentTargetEvent<HTMLElement>
|
||||
) {
|
||||
// Mouse uses Ctrl/Cmd+click instead
|
||||
if (ev.pointerType === "mouse") {
|
||||
return;
|
||||
@@ -1246,7 +1251,9 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleDataset(ev: MouseEvent) {
|
||||
private _toggleDataset(
|
||||
ev: MouseEvent & HASSDomCurrentTargetEvent<HTMLElement>
|
||||
) {
|
||||
ev.stopPropagation();
|
||||
if (!this.chart) {
|
||||
return;
|
||||
@@ -1268,7 +1275,7 @@ export class HaChartBase extends LitElement {
|
||||
this._handleDatasetToggle(id);
|
||||
}
|
||||
|
||||
private _labelClick(ev: MouseEvent) {
|
||||
private _labelClick(ev: MouseEvent & HASSDomCurrentTargetEvent<HTMLElement>) {
|
||||
ev.stopPropagation();
|
||||
if (!this.chart) {
|
||||
return;
|
||||
|
||||
@@ -2,13 +2,10 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { LitElement, html, css } from "lit";
|
||||
import type { EChartsType } from "echarts/core";
|
||||
import type { SankeySeriesOption } from "echarts/types/dist/echarts";
|
||||
import type {
|
||||
CallbackDataParams,
|
||||
ECElementEvent,
|
||||
} from "echarts/types/src/util/types";
|
||||
import type { CallbackDataParams } from "echarts/types/src/util/types";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent, type HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import SankeyChart from "../../resources/echarts/components/sankey/install";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HaECOption } from "../../resources/echarts/echarts";
|
||||
@@ -121,11 +118,15 @@ export class HaSankeyChart extends LitElement {
|
||||
return null;
|
||||
};
|
||||
|
||||
private _handleChartSankeyRoam = (ev: CustomEvent) => {
|
||||
private _handleChartSankeyRoam = (
|
||||
ev: HASSDomEvent<HASSDomEvents["chart-sankeyroam"]>
|
||||
) => {
|
||||
this._currentZoom = ev.detail.zoom;
|
||||
};
|
||||
|
||||
private _handleChartClick = (ev: CustomEvent<ECElementEvent>) => {
|
||||
private _handleChartClick = (
|
||||
ev: HASSDomEvent<HASSDomEvents["chart-click"]>
|
||||
) => {
|
||||
const detail = ev.detail;
|
||||
// Only handle node clicks (not links)
|
||||
if (detail.dataType !== "node") {
|
||||
|
||||
@@ -216,11 +216,13 @@ export class StateHistoryChartLine extends LitElement {
|
||||
})}`;
|
||||
};
|
||||
|
||||
private _datasetHidden(ev: CustomEvent) {
|
||||
private _datasetHidden(ev: HASSDomEvent<HASSDomEvents["dataset-hidden"]>) {
|
||||
this._hiddenStats.add(ev.detail.id);
|
||||
}
|
||||
|
||||
private _datasetUnhidden(ev: CustomEvent) {
|
||||
private _datasetUnhidden(
|
||||
ev: HASSDomEvent<HASSDomEvents["dataset-unhidden"]>
|
||||
) {
|
||||
this._hiddenStats.delete(ev.detail.id);
|
||||
}
|
||||
|
||||
@@ -229,7 +231,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
chartBase.zoom(start, end, true);
|
||||
}
|
||||
|
||||
private _handleDataZoom(ev: CustomEvent) {
|
||||
private _handleDataZoom(ev: HASSDomEvent<HASSDomEvents["chart-zoom"]>) {
|
||||
fireEvent(this, "chart-zoom-with-index", {
|
||||
start: ev.detail.start ?? 0,
|
||||
end: ev.detail.end ?? 100,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import type {
|
||||
CustomSeriesOption,
|
||||
CustomSeriesRenderItem,
|
||||
ECElementEvent,
|
||||
TooltipPositionCallbackParams,
|
||||
} from "echarts/types/dist/shared";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
@@ -21,7 +20,7 @@ import echarts from "../../resources/echarts/echarts";
|
||||
import { luminosity } from "../../common/color/rgb";
|
||||
import { hex2rgb } from "../../common/color/convert-color";
|
||||
import { measureTextWidth } from "../../util/text";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent, type HASSDomEvent } from "../../common/dom/fire_event";
|
||||
|
||||
@customElement("state-history-chart-timeline")
|
||||
export class StateHistoryChartTimeline extends LitElement {
|
||||
@@ -271,7 +270,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
chartBase.zoom(start, end, true);
|
||||
}
|
||||
|
||||
private _handleDataZoom(ev: CustomEvent) {
|
||||
private _handleDataZoom(ev: HASSDomEvent<HASSDomEvents["chart-zoom"]>) {
|
||||
fireEvent(this, "chart-zoom-with-index", {
|
||||
start: ev.detail.start ?? 0,
|
||||
end: ev.detail.end ?? 100,
|
||||
@@ -385,7 +384,9 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
this._chartData = datasets;
|
||||
}
|
||||
|
||||
private _handleChartClick(e: CustomEvent<ECElementEvent>): void {
|
||||
private _handleChartClick(
|
||||
e: HASSDomEvent<HASSDomEvents["chart-click"]>
|
||||
): void {
|
||||
if (e.detail.targetType === "axisLabel") {
|
||||
const dataset = this._chartData[e.detail.dataIndex];
|
||||
if (dataset) {
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
} from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||
import type {
|
||||
HASSDomEvent,
|
||||
HASSDomTargetEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import type {
|
||||
HistoryResult,
|
||||
LineChartUnit,
|
||||
@@ -316,13 +320,13 @@ export class StateHistoryCharts extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yWidthChanged(e: CustomEvent<HASSDomEvents["y-width-changed"]>) {
|
||||
private _yWidthChanged(e: HASSDomEvent<HASSDomEvents["y-width-changed"]>) {
|
||||
this._childYWidths[e.detail.chartIndex] = e.detail.value;
|
||||
this._maxYWidth = Math.max(...Object.values(this._childYWidths), 0);
|
||||
}
|
||||
|
||||
private _handleTimelineSync(
|
||||
e: CustomEvent<HASSDomEvents["chart-zoom-with-index"]>
|
||||
e: HASSDomEvent<HASSDomEvents["chart-zoom-with-index"]>
|
||||
) {
|
||||
if (!this.syncCharts || this._isSyncing) {
|
||||
return;
|
||||
@@ -384,7 +388,7 @@ export class StateHistoryCharts extends LitElement {
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _saveScrollPos(e: Event) {
|
||||
private _saveScrollPos(e: HASSDomTargetEvent<HTMLDivElement>) {
|
||||
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
|
||||
}
|
||||
|
||||
|
||||
@@ -204,12 +204,14 @@ export class StatisticsChart extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _datasetHidden(ev: CustomEvent) {
|
||||
private _datasetHidden(ev: HASSDomEvent<HASSDomEvents["dataset-hidden"]>) {
|
||||
this._hiddenStats.add(ev.detail.id);
|
||||
this.requestUpdate("_hiddenStats");
|
||||
}
|
||||
|
||||
private _datasetUnhidden(ev: CustomEvent) {
|
||||
private _datasetUnhidden(
|
||||
ev: HASSDomEvent<HASSDomEvents["dataset-unhidden"]>
|
||||
) {
|
||||
this._hiddenStats.delete(ev.detail.id);
|
||||
this.requestUpdate("_hiddenStats");
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ import {
|
||||
} from "../../common/datetime/format_date";
|
||||
import { transform } from "../../common/decorators/transform";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
HASSDomEvent,
|
||||
HASSDomTargetEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import { configContext, internationalizationContext } from "../../data/context";
|
||||
import { TimeZone } from "../../data/translation";
|
||||
import { MobileAwareMixin } from "../../mixins/mobile-aware-mixin";
|
||||
@@ -307,7 +311,7 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _focusChanged(ev: CustomEvent<Date>) {
|
||||
private _focusChanged(ev: HASSDomEvent<Date>) {
|
||||
const date = ev.detail;
|
||||
this._pickerMonth = formatDateMonth(
|
||||
date,
|
||||
@@ -322,13 +326,19 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
|
||||
this._focusDate = undefined;
|
||||
}
|
||||
|
||||
private _handleChange(ev: CustomEvent) {
|
||||
private _handleChange(
|
||||
ev: HASSDomTargetEvent<HTMLElementTagNameMap["calendar-range"]>
|
||||
) {
|
||||
const dateElement = ev.target as HTMLElementTagNameMap["calendar-range"];
|
||||
this._dateValue = dateElement.value;
|
||||
this._focusDate = undefined;
|
||||
}
|
||||
|
||||
private _clickDateRangeChip(ev: Event) {
|
||||
private _clickDateRangeChip(
|
||||
ev: HASSDomTargetEvent<
|
||||
HaFilterChip & { index: number; range: [Date, Date] }
|
||||
>
|
||||
) {
|
||||
const chip = ev.target as HaFilterChip & {
|
||||
index: number;
|
||||
range: [Date, Date];
|
||||
@@ -336,7 +346,7 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
|
||||
this._saveDateRangePreset(chip.range, chip.index);
|
||||
}
|
||||
|
||||
private _setDateRange(ev: CustomEvent<ActionDetail>) {
|
||||
private _setDateRange(ev: HASSDomEvent<ActionDetail>) {
|
||||
const dateRange: [Date, Date] = Object.values(this.ranges!)[
|
||||
ev.detail.index
|
||||
];
|
||||
@@ -355,7 +365,9 @@ export class DateRangePicker extends MobileAwareMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleChangeTime(ev: ValueChangedEvent<string>) {
|
||||
private _handleChangeTime(
|
||||
ev: ValueChangedEvent<string> & HASSDomTargetEvent<HaBaseTimeInput>
|
||||
) {
|
||||
ev.stopPropagation();
|
||||
const time = ev.detail.value;
|
||||
const target = ev.target as HaBaseTimeInput;
|
||||
|
||||
@@ -13,6 +13,10 @@ import {
|
||||
} from "../../common/datetime/format_date";
|
||||
import { valueToDate } from "../../common/datetime/value_to_date";
|
||||
import { transform } from "../../common/decorators/transform";
|
||||
import type {
|
||||
HASSDomEvent,
|
||||
HASSDomTargetEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import { configContext, internationalizationContext } from "../../data/context";
|
||||
import { DialogMixin } from "../../dialogs/dialog-mixin";
|
||||
import type { HomeAssistantConfig } from "../../types";
|
||||
@@ -167,7 +171,7 @@ export class HaDialogDatePicker extends DialogMixin<DatePickerDialogParams>(
|
||||
</ha-adaptive-popover>`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
private _valueChanged(ev: HASSDomTargetEvent<CalendarDate>) {
|
||||
const dateElement = ev.target as CalendarDate;
|
||||
if (dateElement.value) {
|
||||
this._updateValue(dateElement.value);
|
||||
@@ -198,7 +202,7 @@ export class HaDialogDatePicker extends DialogMixin<DatePickerDialogParams>(
|
||||
}
|
||||
}
|
||||
|
||||
private _focusChanged(ev: CustomEvent<Date>) {
|
||||
private _focusChanged(ev: HASSDomEvent<Date>) {
|
||||
const date = ev.detail;
|
||||
this._pickerMonth = formatDateMonth(
|
||||
date,
|
||||
|
||||
@@ -7,6 +7,10 @@ import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consumeLocalize } from "../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type {
|
||||
HASSDomCurrentTargetEvent,
|
||||
HASSDomEvent,
|
||||
} from "../common/dom/fire_event";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
@@ -191,7 +195,7 @@ export class HaAreasFloorsDisplayEditor extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
private _floorMoved(ev: CustomEvent<HASSDomEvents["item-moved"]>) {
|
||||
private _floorMoved(ev: HASSDomEvent<HASSDomEvents["item-moved"]>) {
|
||||
ev.stopPropagation();
|
||||
const newIndex = ev.detail.newIndex;
|
||||
const oldIndex = ev.detail.oldIndex;
|
||||
@@ -215,7 +219,12 @@ export class HaAreasFloorsDisplayEditor extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
private async _areaDisplayChanged(ev: ValueChangedEvent<DisplayValue>) {
|
||||
private async _areaDisplayChanged(
|
||||
ev: ValueChangedEvent<DisplayValue> &
|
||||
HASSDomCurrentTargetEvent<
|
||||
HTMLElementTagNameMap["ha-items-display-editor"] & { floorId?: string }
|
||||
>
|
||||
) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
const currentFloorId = (ev.currentTarget as any).floorId;
|
||||
|
||||
@@ -53,8 +53,10 @@ export class HaControlNumberButton extends LitElement {
|
||||
});
|
||||
|
||||
private _boundedValue(value: number) {
|
||||
const clamped = conditionalClamp(value, this.min, this.max);
|
||||
return Math.round(clamped / this._step) * this._step;
|
||||
// Clamp after snapping: when the step does not divide the range evenly,
|
||||
// snapping alone rounds past the bounds (min 1, max 99, step 10 → 0 / 100).
|
||||
const stepped = Math.round(value / this._step) * this._step;
|
||||
return conditionalClamp(stepped, this.min, this.max);
|
||||
}
|
||||
|
||||
private get _step() {
|
||||
@@ -67,10 +69,7 @@ export class HaControlNumberButton extends LitElement {
|
||||
|
||||
private get _tenPercentStep() {
|
||||
if (this.max == null || this.min == null) return this._step;
|
||||
const range = this.max - this.min / 10;
|
||||
|
||||
if (range <= this._step) return this._step;
|
||||
return Math.max(range / 10);
|
||||
return Math.max((this.max - this.min) / 10, this._step);
|
||||
}
|
||||
|
||||
private _handlePlusButton() {
|
||||
|
||||
@@ -115,7 +115,9 @@ export class HaControlSlider extends LitElement {
|
||||
}
|
||||
|
||||
steppedValue(value: number) {
|
||||
return Math.round(value / this.step) * this.step;
|
||||
// Clamp after snapping: when the step does not divide the range evenly,
|
||||
// snapping alone rounds past the bounds (min 1, max 99, step 10 → 0 / 100).
|
||||
return this.boundedValue(Math.round(value / this.step) * this.step);
|
||||
}
|
||||
|
||||
private _displayedValue(value: number) {
|
||||
@@ -254,13 +256,9 @@ export class HaControlSlider extends LitElement {
|
||||
} else if (e.code === "End") {
|
||||
this.value = this.max;
|
||||
} else if (e.code === "PageUp") {
|
||||
this.value = this.steppedValue(
|
||||
this.boundedValue((this.value ?? 0) + this._tenPercentStep)
|
||||
);
|
||||
this.value = this.steppedValue((this.value ?? 0) + this._tenPercentStep);
|
||||
} else if (e.code === "PageDown") {
|
||||
this.value = this.steppedValue(
|
||||
this.boundedValue((this.value ?? 0) - this._tenPercentStep)
|
||||
);
|
||||
this.value = this.steppedValue((this.value ?? 0) - this._tenPercentStep);
|
||||
} else {
|
||||
const isRtl = mainWindow.document.dir === "rtl";
|
||||
let multiplier = 1;
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import "../ha-checkbox";
|
||||
import type { HaCheckbox } from "../ha-checkbox";
|
||||
import type {
|
||||
@@ -43,7 +44,7 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
private _valueChanged(ev: HASSDomTargetEvent<HaCheckbox>) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: (ev.target as HaCheckbox).checked,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { HaFormDividerSchema, HaFormElement } from "./types";
|
||||
|
||||
@customElement("ha-form-divider")
|
||||
export class HaFormDivider extends LitElement implements HaFormElement {
|
||||
@property({ attribute: false }) public schema!: HaFormDividerSchema;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<hr />`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--ha-color-border-neutral-quiet);
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-form-divider": HaFormDivider;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import "../input/ha-input";
|
||||
import type { HaInput } from "../input/ha-input";
|
||||
@@ -70,7 +71,7 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleInput(ev: InputEvent) {
|
||||
private _handleInput(ev: InputEvent & HASSDomTargetEvent<HaInput>) {
|
||||
const source = ev.target as HaInput;
|
||||
const rawValue = (source.value ?? "").replace(",", ".");
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import "../ha-checkbox";
|
||||
import type { HaCheckbox } from "../ha-checkbox";
|
||||
@@ -153,7 +154,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _handleCheckboxChange(ev: Event) {
|
||||
private _handleCheckboxChange(ev: HASSDomTargetEvent<HaCheckbox>) {
|
||||
const checked = (ev.target as HaCheckbox).checked;
|
||||
let value: HaFormIntegerData | undefined;
|
||||
if (checked) {
|
||||
@@ -178,7 +179,9 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: InputEvent) {
|
||||
private _valueChanged(
|
||||
ev: InputEvent & HASSDomTargetEvent<HaInput | HTMLInputElement>
|
||||
) {
|
||||
const source = ev.target as HaInput | HTMLInputElement;
|
||||
const rawValue = source.value;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import "../ha-check-list-item";
|
||||
import "../ha-checkbox";
|
||||
import type { HaCheckbox } from "../ha-checkbox";
|
||||
@@ -140,7 +141,7 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
private _valueChanged(ev: HASSDomTargetEvent<HaCheckbox>): void {
|
||||
const { value, checked } = ev.target as HaCheckbox;
|
||||
this._handleValueChanged(value, checked);
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@ import type { HaDropdownSelectEvent } from "../ha-dropdown";
|
||||
import "../ha-dropdown-item";
|
||||
import "../ha-svg-icon";
|
||||
import "./ha-form";
|
||||
import "./ha-form-divider";
|
||||
import type { HaForm } from "./ha-form";
|
||||
import type {
|
||||
HaFormDataContainer,
|
||||
HaFormDividerSchema,
|
||||
HaFormElement,
|
||||
HaFormOptionalActionsSchema,
|
||||
HaFormSchema,
|
||||
@@ -22,6 +24,11 @@ import type {
|
||||
|
||||
const NO_ACTIONS = [];
|
||||
|
||||
const DIVIDER = {
|
||||
name: "",
|
||||
type: "divider",
|
||||
} as const satisfies HaFormDividerSchema;
|
||||
|
||||
@customElement("ha-form-optional_actions")
|
||||
export class HaFormOptionalActions extends LitElement implements HaFormElement {
|
||||
@property({ attribute: false }) public localize?: LocalizeFunc;
|
||||
@@ -87,7 +94,9 @@ export class HaFormOptionalActions extends LitElement implements HaFormElement {
|
||||
schema: readonly HaFormSchema[],
|
||||
displayActions: string[]
|
||||
): HaFormSchema[] =>
|
||||
schema.filter((item) => displayActions.includes(item.name))
|
||||
schema
|
||||
.filter((item) => displayActions.includes(item.name))
|
||||
.flatMap((item) => [DIVIDER, item])
|
||||
);
|
||||
|
||||
public render(): TemplateResult {
|
||||
@@ -128,6 +137,7 @@ export class HaFormOptionalActions extends LitElement implements HaFormElement {
|
||||
${
|
||||
hiddenActions.length > 0
|
||||
? html`
|
||||
<ha-form-divider></ha-form-divider>
|
||||
<ha-dropdown
|
||||
@wa-select=${this._handleAddAction}
|
||||
@closed=${stopPropagation}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { SelectSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-selector/ha-selector-select";
|
||||
import type {
|
||||
HaFormElement,
|
||||
@@ -78,7 +78,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
private _valueChanged(ev: ValueChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
let value: HaFormSelectData | undefined = ev.detail.value;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import "../ha-icon-button";
|
||||
import "../input/ha-input";
|
||||
@@ -79,7 +80,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected _valueChanged(ev: Event): void {
|
||||
protected _valueChanged(ev: HASSDomTargetEvent<HaInput>): void {
|
||||
let value: string | undefined = (ev.target as HaInput).value;
|
||||
if (this.data === value) {
|
||||
return;
|
||||
|
||||
@@ -3,15 +3,24 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-alert";
|
||||
import "../ha-selector/ha-selector";
|
||||
import { getHiddenFields } from "./conditions";
|
||||
import type { HaFormDataContainer, HaFormElement, HaFormSchema } from "./types";
|
||||
import type {
|
||||
HaFormData,
|
||||
HaFormDataContainer,
|
||||
HaFormElement,
|
||||
HaFormSchema,
|
||||
} from "./types";
|
||||
|
||||
type HaFormDataChangedEvent = ValueChangedEvent<HaFormData>;
|
||||
type HaFormDataContainerChangedEvent = ValueChangedEvent<HaFormDataContainer>;
|
||||
|
||||
const LOAD_ELEMENTS = {
|
||||
boolean: () => import("./ha-form-boolean"),
|
||||
constant: () => import("./ha-form-constant"),
|
||||
divider: () => import("./ha-form-divider"),
|
||||
float: () => import("./ha-form-float"),
|
||||
grid: () => import("./ha-form-grid"),
|
||||
expandable: () => import("./ha-form-expandable"),
|
||||
@@ -261,16 +270,18 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
}
|
||||
|
||||
protected addValueChangedListener(element: Element | ShadowRoot) {
|
||||
element.addEventListener("value-changed", (ev) => {
|
||||
element.addEventListener("value-changed", (ev: Event) => {
|
||||
ev.stopPropagation();
|
||||
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
||||
|
||||
if (ev.target === this) return;
|
||||
|
||||
const changeEv = ev as
|
||||
HaFormDataChangedEvent | HaFormDataContainerChangedEvent;
|
||||
const newValue =
|
||||
!schema.name || ("flatten" in schema && schema.flatten)
|
||||
? ev.detail.value
|
||||
: { [schema.name]: ev.detail.value };
|
||||
? (changeEv.detail.value as HaFormDataContainer)
|
||||
: { [schema.name]: changeEv.detail.value as HaFormData };
|
||||
|
||||
this.data = {
|
||||
...this.data,
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { HaDurationData } from "../ha-duration-input";
|
||||
|
||||
export type HaFormSchema =
|
||||
| HaFormConstantSchema
|
||||
| HaFormDividerSchema
|
||||
| HaFormStringSchema
|
||||
| HaFormIntegerSchema
|
||||
| HaFormFloatSchema
|
||||
@@ -97,6 +98,10 @@ export interface HaFormConstantSchema extends HaFormBaseSchema {
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface HaFormDividerSchema extends HaFormBaseSchema {
|
||||
type: "divider";
|
||||
}
|
||||
|
||||
export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
||||
type: "integer";
|
||||
default?: HaFormIntegerData;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { CSSResultGroup, 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 type {
|
||||
Adapter,
|
||||
IPv4ConfiguredAddress,
|
||||
@@ -101,7 +102,9 @@ export class HaNetwork extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleAutoConfigureCheckboxClick(ev: Event) {
|
||||
private _handleAutoConfigureCheckboxClick(
|
||||
ev: HASSDomCurrentTargetEvent<HaCheckbox>
|
||||
) {
|
||||
const checkbox = ev.currentTarget as HaCheckbox;
|
||||
if (this.networkConfig === undefined) {
|
||||
return;
|
||||
@@ -127,7 +130,9 @@ export class HaNetwork extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleAdapterCheckboxClick(ev: Event) {
|
||||
private _handleAdapterCheckboxClick(
|
||||
ev: HASSDomCurrentTargetEvent<HaCheckbox>
|
||||
) {
|
||||
const checkbox = ev.currentTarget as HaCheckbox;
|
||||
const adapter_name = checkbox.id;
|
||||
if (this.networkConfig === undefined) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getAppKey } from "../data/notify_html5";
|
||||
import { showPromptDialog } from "../dialogs/generic/show-dialog-box";
|
||||
import type { HaSwitch } from "./ha-switch";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { fireEvent, type HASSDomTargetEvent } from "../common/dom/fire_event";
|
||||
import "./ha-switch";
|
||||
|
||||
export const pushSupported =
|
||||
@@ -55,7 +55,7 @@ class HaPushNotificationsToggle extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _handlePushChange(ev: Event) {
|
||||
private _handlePushChange(ev: HASSDomTargetEvent<HaSwitch>) {
|
||||
// Somehow this is triggered on Safari on page load causing
|
||||
// it to get into a loop and crash the page.
|
||||
if (!pushSupported) return;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HASSDomCurrentTargetEvent } from "../common/dom/fire_event";
|
||||
import "./ha-tooltip";
|
||||
|
||||
export interface Segment {
|
||||
@@ -120,7 +121,9 @@ class HaSegmentedBar extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSegmentClick(ev: Event): void {
|
||||
private _handleSegmentClick(
|
||||
ev: HASSDomCurrentTargetEvent<HTMLElement>
|
||||
): void {
|
||||
const target = ev.currentTarget as HTMLElement;
|
||||
const index = Number(target.dataset.index);
|
||||
const segment = this.segments[index];
|
||||
@@ -129,7 +132,7 @@ class HaSegmentedBar extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleLegendClick(ev: Event): void {
|
||||
private _handleLegendClick(ev: HASSDomCurrentTargetEvent<HTMLElement>): void {
|
||||
const target = ev.currentTarget as HTMLElement;
|
||||
const index = Number(target.dataset.index);
|
||||
const segment = this.segments[index];
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import "./ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "./ha-dropdown";
|
||||
import "./ha-dropdown-item";
|
||||
import "./ha-input-helper-text";
|
||||
import "./ha-picker-field";
|
||||
@@ -169,7 +170,7 @@ export class HaSelect extends LitElement {
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _handleSelect(ev: CustomEvent<{ item: { value: string | number } }>) {
|
||||
private _handleSelect(ev: HaDropdownSelectEvent<string | number>) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.item.value;
|
||||
if (value === this.value) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { stringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { throttle } from "../common/util/throttle";
|
||||
import { subscribeFrontendUserData } from "../data/frontend";
|
||||
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
|
||||
import type { ActionHandlerEvent } from "../data/lovelace/action_handler";
|
||||
import {
|
||||
FIXED_PANELS,
|
||||
getDefaultPanelUrlPath,
|
||||
@@ -634,7 +634,7 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionHandlerDetail>) {
|
||||
private _handleAction(ev: ActionHandlerEvent) {
|
||||
if (ev.detail.action !== "hold") {
|
||||
return;
|
||||
}
|
||||
@@ -646,7 +646,7 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
|
||||
fireEvent(this, "hass-show-notifications");
|
||||
}
|
||||
|
||||
private _toggleSidebar(ev: CustomEvent) {
|
||||
private _toggleSidebar(ev: ActionHandlerEvent) {
|
||||
if (ev.detail.action !== "tap") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import type { HASSDomCurrentTargetEvent } from "../common/dom/fire_event";
|
||||
import { goBack } from "../common/navigate";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import "./ha-icon-button-arrow-prev";
|
||||
@@ -324,7 +325,9 @@ export class HaTopAppBarFixed extends LitElement {
|
||||
this._subRowResizeObserver = undefined;
|
||||
}
|
||||
|
||||
private _subRowSlotChanged = (ev: Event) => {
|
||||
private _subRowSlotChanged = (
|
||||
ev: HASSDomCurrentTargetEvent<HTMLSlotElement>
|
||||
) => {
|
||||
const slot = ev.currentTarget as HTMLSlotElement;
|
||||
this._hasSubRow = slot
|
||||
.assignedNodes({ flatten: true })
|
||||
|
||||
@@ -2,6 +2,7 @@ import { consume, type ContextType } from "@lit/context";
|
||||
import { mdiContentCopy, mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../common/util/copy-clipboard";
|
||||
import { internationalizationContext } from "../../data/context";
|
||||
import { showToast } from "../../util/toast";
|
||||
@@ -111,7 +112,7 @@ export class HaInputCopy extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _focusInput(ev: Event) {
|
||||
private _focusInput(ev: HASSDomCurrentTargetEvent<HaInput>) {
|
||||
const inputElement = ev.currentTarget as HaInput;
|
||||
inputElement.select();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { HasSlotController } from "@home-assistant/webawesome/dist/internal/slot
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
|
||||
/**
|
||||
* @element ha-row-item
|
||||
@@ -56,7 +57,7 @@ export class HaRowItem extends LitElement {
|
||||
@state() private _hasEnd = false;
|
||||
|
||||
private _onSlotChange(name: "start" | "end") {
|
||||
return (ev: Event) => {
|
||||
return (ev: HASSDomTargetEvent<HTMLSlotElement>) => {
|
||||
const slot = ev.target as HTMLSlotElement;
|
||||
const hasContent = slot
|
||||
.assignedNodes({ flatten: true })
|
||||
|
||||
@@ -175,6 +175,8 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
private _pauseAutoFit = false;
|
||||
|
||||
private _pendingFit?: () => void;
|
||||
|
||||
public connectedCallback(): void {
|
||||
this._pauseAutoFit = false;
|
||||
document.addEventListener("visibilitychange", this._handleVisibilityChange);
|
||||
@@ -204,6 +206,7 @@ export class HaMap extends ReactiveElement {
|
||||
this.Leaflet = undefined;
|
||||
}
|
||||
|
||||
this._pendingFit = undefined;
|
||||
this._loaded = false;
|
||||
|
||||
if (this._resizeObserver) {
|
||||
@@ -347,6 +350,10 @@ export class HaMap extends ReactiveElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._deferIfUnsized(() => this.fitMap(options))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!this._mapFocusItems.length &&
|
||||
!this._mapFocusZones.length &&
|
||||
@@ -387,6 +394,40 @@ export class HaMap extends ReactiveElement {
|
||||
}, PROGRAMMITIC_FIT_DELAY);
|
||||
}
|
||||
|
||||
// Leaflet derives the zoom level that fits given bounds from the current
|
||||
// size of the map container. When the container has not been laid out yet,
|
||||
// that size is 0x0 and the computed zoom collapses to the minimum, leaving
|
||||
// the map zoomed out to the world even after the container gets its size.
|
||||
// Defer fitting until the resize observer reports a usable size.
|
||||
private _deferIfUnsized(fit: () => void): boolean {
|
||||
const size = this.leafletMap!.getSize();
|
||||
if (size.x > 0 && size.y > 0) {
|
||||
this._pendingFit = undefined;
|
||||
return false;
|
||||
}
|
||||
const container = this.leafletMap!.getContainer();
|
||||
if (container.clientWidth > 0 && container.clientHeight > 0) {
|
||||
// The container was laid out since Leaflet last measured it.
|
||||
this.leafletMap!.invalidateSize(false);
|
||||
this._pendingFit = undefined;
|
||||
return false;
|
||||
}
|
||||
this._pendingFit = fit;
|
||||
return true;
|
||||
}
|
||||
|
||||
private _runPendingFit(): void {
|
||||
if (!this._pendingFit || !this.leafletMap) {
|
||||
return;
|
||||
}
|
||||
const size = this.leafletMap.getSize();
|
||||
if (size.x > 0 && size.y > 0) {
|
||||
const pendingFit = this._pendingFit;
|
||||
this._pendingFit = undefined;
|
||||
pendingFit();
|
||||
}
|
||||
}
|
||||
|
||||
public fitBounds(
|
||||
boundingbox: LatLngExpression[],
|
||||
options?: { zoom?: number; pad?: number }
|
||||
@@ -394,6 +435,9 @@ export class HaMap extends ReactiveElement {
|
||||
if (!this.leafletMap || !this.Leaflet) {
|
||||
return;
|
||||
}
|
||||
if (this._deferIfUnsized(() => this.fitBounds(boundingbox, options))) {
|
||||
return;
|
||||
}
|
||||
const bounds = this.Leaflet.latLngBounds(boundingbox).pad(
|
||||
options?.pad ?? 0.5
|
||||
);
|
||||
@@ -773,6 +817,7 @@ export class HaMap extends ReactiveElement {
|
||||
if (!this._resizeObserver) {
|
||||
this._resizeObserver = new ResizeObserver(() => {
|
||||
this.leafletMap?.invalidateSize({ debounceMoveend: true });
|
||||
this._runPendingFit();
|
||||
});
|
||||
}
|
||||
this._resizeObserver.observe(this);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { css, html, LitElement, nothing, svg } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import { BRANCH_HEIGHT, SPACING } from "./hat-graph-const";
|
||||
|
||||
interface BranchConfig {
|
||||
@@ -31,7 +32,7 @@ export class HatGraphBranch extends LitElement {
|
||||
|
||||
private _maxHeight = 0;
|
||||
|
||||
private _updateBranches(ev: Event) {
|
||||
private _updateBranches(ev: HASSDomTargetEvent<HTMLSlotElement>) {
|
||||
let total_width = 0;
|
||||
const heights: number[] = [];
|
||||
const branches: BranchConfig[] = [];
|
||||
|
||||
@@ -14,7 +14,7 @@ declare global {
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"connection-status": HASSDomEvent<ConnectionStatus>;
|
||||
"connection-status": HASSDomEvent<HASSDomEvents["connection-status"]>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ declare global {
|
||||
"hass-related-context": RelatedContextItem | undefined;
|
||||
}
|
||||
interface HTMLElementEventMap {
|
||||
"hass-related-context": HASSDomEvent<RelatedContextItem | undefined>;
|
||||
"hass-related-context": HASSDomEvent<HASSDomEvents["hass-related-context"]>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -793,6 +793,17 @@ const findEnergyDataCollection = (
|
||||
return (hass.connection as any)[key];
|
||||
};
|
||||
|
||||
// The last-picked preset is remembered per energy collection, so each dashboard
|
||||
// reopens on its own default period. Derived from the connection key so the read
|
||||
// and write sides cannot drift apart.
|
||||
export const getEnergyDefaultPeriodStorageKey = (
|
||||
hass: HomeAssistant,
|
||||
collectionKey?: string
|
||||
): string => {
|
||||
const [key] = convertCollectionKeyToConnection(hass, collectionKey);
|
||||
return `energy-default-period-${key}`;
|
||||
};
|
||||
|
||||
// When does the collection's day period need to roll over to the next day?
|
||||
// With `midnightRollover` (the real-time "Now" view) it rolls over right at
|
||||
// midnight. Otherwise it waits an hour, until the new day's first hourly
|
||||
@@ -892,8 +903,9 @@ export const getEnergyDataCollection = (
|
||||
// The real-time "Now" view always tracks today; it shows live data even
|
||||
// before today's first statistic exists, so it never falls back to yesterday.
|
||||
const preferredPeriod =
|
||||
(localStorage.getItem(`energy-default-period-${key}`) as DateRange) ||
|
||||
"today";
|
||||
(localStorage.getItem(
|
||||
getEnergyDefaultPeriodStorageKey(hass, options.key)
|
||||
) as DateRange) || "today";
|
||||
const period =
|
||||
preferredPeriod === "today" && hour === "0" && !midnightRollover
|
||||
? "yesterday"
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ declare global {
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
haptic: HASSDomEvent<HapticType>;
|
||||
haptic: HASSDomEvent<HASSDomEvents["haptic"]>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-dialog-footer";
|
||||
import "../../components/ha-formfield";
|
||||
@@ -161,18 +162,18 @@ class DialogConfigEntrySystemOptions extends DirtyStateProviderMixin<SystemOptio
|
||||
`;
|
||||
}
|
||||
|
||||
private _disableNewEntitiesChanged(ev: Event): void {
|
||||
private _disableNewEntitiesChanged(ev: HASSDomTargetEvent<HaSwitch>): void {
|
||||
this._error = undefined;
|
||||
this._disableNewEntities = !(ev.target as HaSwitch).checked;
|
||||
this._disableNewEntities = !ev.target.checked;
|
||||
this._updateDirtyState({
|
||||
disableNewEntities: this._disableNewEntities,
|
||||
disablePolling: this._disablePolling,
|
||||
});
|
||||
}
|
||||
|
||||
private _disablePollingChanged(ev: Event): void {
|
||||
private _disablePollingChanged(ev: HASSDomTargetEvent<HaSwitch>): void {
|
||||
this._error = undefined;
|
||||
this._disablePolling = !(ev.target as HaSwitch).checked;
|
||||
this._disablePolling = !ev.target.checked;
|
||||
this._updateDirtyState({
|
||||
disableNewEntities: this._disableNewEntities,
|
||||
disablePolling: this._disablePolling,
|
||||
|
||||
@@ -70,8 +70,10 @@ declare global {
|
||||
}
|
||||
// for add event listener
|
||||
interface HTMLElementEventMap {
|
||||
"flow-update": HASSDomEvent<FlowUpdateEvent>;
|
||||
"flow-step-footer-state-changed": HASSDomEvent<FlowStepFooterStateChangedEvent>;
|
||||
"flow-update": HASSDomEvent<HASSDomEvents["flow-update"]>;
|
||||
"flow-step-footer-state-changed": HASSDomEvent<
|
||||
HASSDomEvents["flow-step-footer-state-changed"]
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,7 +743,7 @@ class DataEntryFlowDialog extends DirtyStateProviderMixin<
|
||||
};
|
||||
|
||||
private _handleFooterStateChanged = (
|
||||
ev: HASSDomEvent<FlowStepFooterStateChangedEvent>
|
||||
ev: HASSDomEvent<HASSDomEvents["flow-step-footer-state-changed"]>
|
||||
) => {
|
||||
if (ev.detail.loading !== undefined) {
|
||||
this._formStepLoading = ev.detail.loading;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { createRef, ref } from "lit/directives/ref";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent, type HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { isNavigationClick } from "../../common/dom/is-navigation-click";
|
||||
import "../../components/ha-alert";
|
||||
import { computeInitialHaFormData } from "../../components/ha-form/compute-initial-ha-form-data";
|
||||
@@ -153,7 +153,7 @@ class StepFlowForm extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _setError(ev: CustomEvent) {
|
||||
private _setError(ev: HASSDomEvent<DataEntryFlowStepForm["errors"]>) {
|
||||
this._previewErrors = ev.detail;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ import { consume } from "@lit/context";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import type { HaProgressButton } from "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/ha-camera-stream";
|
||||
import type { CameraEntity } from "../../../data/camera";
|
||||
import { apiContext } from "../../../data/context";
|
||||
@@ -66,8 +68,10 @@ class MoreInfoCamera extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _downloadSnapshot(ev: CustomEvent) {
|
||||
const button = ev.currentTarget as any;
|
||||
private async _downloadSnapshot(
|
||||
ev: HASSDomCurrentTargetEvent<HaProgressButton>
|
||||
) {
|
||||
const button = ev.currentTarget;
|
||||
this._waiting = true;
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,8 +3,10 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-button";
|
||||
import type { HaButton } from "../../../components/ha-button";
|
||||
import { apiContext } from "../../../data/context";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import type { HomeAssistantApi } from "../../../types";
|
||||
@@ -67,8 +69,10 @@ class MoreInfoCounter extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleActionClick(e: MouseEvent): void {
|
||||
const action = (e.currentTarget as any).action;
|
||||
private _handleActionClick(
|
||||
e: MouseEvent & HASSDomCurrentTargetEvent<HaButton & { action: string }>
|
||||
): void {
|
||||
const action = e.currentTarget.action;
|
||||
this._api.callService("counter", action, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../../common/dom/fire_event";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
@@ -872,12 +873,14 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _handleMediaSeekChanged(e: Event): Promise<void> {
|
||||
private async _handleMediaSeekChanged(
|
||||
e: HASSDomTargetEvent<HaSlider>
|
||||
): Promise<void> {
|
||||
if (!this.stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newValue = (e.target as any).value;
|
||||
const newValue = e.target.value;
|
||||
this._api.callService("media_player", "media_seek", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
seek_position: newValue,
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
hasRequiredScriptFields,
|
||||
requiredScriptFieldsFilled,
|
||||
} from "../../../data/script";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
import "../components/ha-more-info-state-header";
|
||||
|
||||
@customElement("more-info-script")
|
||||
@@ -209,7 +209,9 @@ class MoreInfoScript extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _scriptDataChanged(ev: CustomEvent): void {
|
||||
private _scriptDataChanged(
|
||||
ev: ValueChangedEvent<Record<string, unknown>>
|
||||
): void {
|
||||
this._scriptData = { ...this._scriptData, ...ev.detail.value };
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-button";
|
||||
import type { HaButton } from "../../../components/ha-button";
|
||||
import "../../../components/ha-duration-input";
|
||||
import type { HaDurationData } from "../../../components/ha-duration-input";
|
||||
import { apiContext } from "../../../data/context";
|
||||
@@ -137,8 +139,10 @@ class MoreInfoTimer extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleActionClick(e: MouseEvent): void {
|
||||
const action = (e.currentTarget as any).action;
|
||||
private _handleActionClick(
|
||||
e: MouseEvent & HASSDomCurrentTargetEvent<HaButton & { action: string }>
|
||||
): void {
|
||||
const action = e.currentTarget.action;
|
||||
this._api.callService("timer", action, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/animation/ha-fade-in";
|
||||
import "../../components/ha-adaptive-dialog";
|
||||
import "../../components/ha-alert";
|
||||
@@ -334,13 +335,18 @@ class DialogRestart extends LitElement {
|
||||
}
|
||||
};
|
||||
|
||||
private async _handleAction(ev: Event) {
|
||||
private async _handleAction(
|
||||
ev: HASSDomCurrentTargetEvent<
|
||||
HaListItemButton & {
|
||||
action: "restart" | "reboot" | "shutdown" | "restart-safe-mode";
|
||||
}
|
||||
>
|
||||
) {
|
||||
if (this._loadingBackupInfo) {
|
||||
return;
|
||||
}
|
||||
this._loadingBackupInfo = true;
|
||||
const action = (ev.currentTarget as HaListItemButton & { action: string })
|
||||
.action as "restart" | "reboot" | "shutdown" | "restart-safe-mode";
|
||||
const action = ev.currentTarget.action;
|
||||
|
||||
const backupState = await this._loadBackupState();
|
||||
|
||||
@@ -369,7 +375,7 @@ class DialogRestart extends LitElement {
|
||||
|
||||
this._dialogOpen = false;
|
||||
|
||||
let actionFunc;
|
||||
let actionFunc: () => Promise<void>;
|
||||
|
||||
if (["restart", "restart-safe-mode"].includes(action)) {
|
||||
const serviceData =
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
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 { fireEvent, type HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { formatLanguageCode } from "../../common/language/format_language";
|
||||
import "../../components/chips/ha-assist-chip";
|
||||
@@ -18,7 +18,7 @@ import { getLanguageScores } from "../../data/conversation";
|
||||
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import type { VoiceAssistantSetupDialogParams } from "./show-voice-assistant-setup-dialog";
|
||||
import "./voice-assistant-setup-step-area";
|
||||
import "./voice-assistant-setup-step-change-wake-word";
|
||||
@@ -337,7 +337,7 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
|
||||
this._language = ev.detail.item.value;
|
||||
}
|
||||
|
||||
private _languageChanged(ev: CustomEvent) {
|
||||
private _languageChanged(ev: ValueChangedEvent<string | undefined>) {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
}
|
||||
@@ -351,7 +351,7 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
|
||||
this._step = this._previousSteps.pop()!;
|
||||
}
|
||||
|
||||
private async _goToNextStep(ev?: CustomEvent) {
|
||||
private async _goToNextStep(ev?: HASSDomEvent<HASSDomEvents["next-step"]>) {
|
||||
if (ev?.detail?.updateConfig) {
|
||||
await this._fetchAssistConfiguration();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/item/ha-list-item-button";
|
||||
import type { HaListItemButton } from "../../components/item/ha-list-item-button";
|
||||
import "../../components/list/ha-list-base";
|
||||
@@ -50,14 +51,14 @@ export class HaVoiceAssistantSetupStepChangeWakeWord extends LitElement {
|
||||
</ha-list-base>`;
|
||||
}
|
||||
|
||||
private async _wakeWordPicked(ev: Event) {
|
||||
private async _wakeWordPicked(
|
||||
ev: HASSDomCurrentTargetEvent<HaListItemButton & { value: string }>
|
||||
) {
|
||||
if (!this.assistEntityId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wakeWordId = (
|
||||
ev.currentTarget as HaListItemButton & { value: string }
|
||||
).value;
|
||||
const wakeWordId = ev.currentTarget.value;
|
||||
|
||||
await setWakeWords(this.hass, this.assistEntityId, [wakeWordId]);
|
||||
this._nextStep();
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { LanguageScore, LanguageScores } from "../../data/conversation";
|
||||
import { getLanguageScores, listAgents } from "../../data/conversation";
|
||||
import { listSTTEngines } from "../../data/stt";
|
||||
import { listTTSEngines, listTTSVoices } from "../../data/tts";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { AssistantSetupStyles } from "./styles";
|
||||
import { STEP } from "./voice-assistant-setup-dialog";
|
||||
@@ -265,8 +265,8 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
|
||||
}
|
||||
|
||||
private async _createCloudPipeline(useLanguage: boolean): Promise<boolean> {
|
||||
let cloudTtsEntityId;
|
||||
let cloudSttEntityId;
|
||||
let cloudTtsEntityId: string | undefined;
|
||||
let cloudSttEntityId: string | undefined;
|
||||
for (const entity of Object.values(this.hass.entities)) {
|
||||
if (entity.platform === "cloud") {
|
||||
const domain = computeDomain(entity.entity_id);
|
||||
@@ -327,7 +327,7 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
|
||||
|
||||
const ttsVoices = await listTTSVoices(
|
||||
this.hass,
|
||||
cloudTtsEntityId,
|
||||
cloudTtsEntityId!,
|
||||
ttsEngine.supported_languages[0]
|
||||
);
|
||||
|
||||
@@ -357,9 +357,9 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
|
||||
language: (this.language || this.hass.config.language).split("-")[0],
|
||||
conversation_engine: "conversation.home_assistant",
|
||||
conversation_language: agent.supported_languages[0],
|
||||
stt_engine: cloudSttEntityId,
|
||||
stt_engine: cloudSttEntityId!,
|
||||
stt_language: sttEngine.supported_languages[0],
|
||||
tts_engine: cloudTtsEntityId,
|
||||
tts_engine: cloudTtsEntityId!,
|
||||
tts_language: ttsEngine.supported_languages[0],
|
||||
tts_voice: ttsVoices.voices![0].voice_id,
|
||||
wake_word_entity: null,
|
||||
@@ -380,7 +380,9 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
private _valueChanged(
|
||||
ev: ValueChangedEvent<(typeof OPTIONS)[number] | undefined>
|
||||
) {
|
||||
this._value = ev.detail.value;
|
||||
}
|
||||
|
||||
@@ -410,7 +412,7 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
|
||||
fireEvent(this, "next-step", { step: STEP.LOCAL, option: this._value });
|
||||
}
|
||||
|
||||
private _languageChanged(ev: CustomEvent) {
|
||||
private _languageChanged(ev: ValueChangedEvent<string | undefined>) {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Connection } from "home-assistant-js-websocket";
|
||||
import type { CSSResult } from "lit";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { isNavigationClick } from "../common/dom/is-navigation-click";
|
||||
@@ -7,6 +8,7 @@ import { navigate } from "../common/navigate";
|
||||
import type { CustomPanelInfo } from "../data/panel_custom";
|
||||
import { baseEntrypointStyles } from "../resources/styles";
|
||||
import { createCustomPanelElement } from "../util/custom-panel/create-custom-panel-element";
|
||||
import { dropRealmCollections } from "../util/custom-panel/drop-realm-collections";
|
||||
import { loadCustomPanel } from "../util/custom-panel/load-custom-panel";
|
||||
import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties";
|
||||
|
||||
@@ -29,8 +31,14 @@ window.loadES5Adapter = () => {
|
||||
|
||||
let panelEl: HTMLElement | undefined;
|
||||
let initialized = false;
|
||||
// Kept so we can clean up after ourselves on pagehide without depending on
|
||||
// `window.parent.customPanel`, which `_cleanupPanel()` may already have deleted.
|
||||
let connection: Connection | undefined;
|
||||
|
||||
function setProperties(properties) {
|
||||
if (properties.hass?.connection) {
|
||||
connection = properties.hass.connection;
|
||||
}
|
||||
if (!panelEl) {
|
||||
return;
|
||||
}
|
||||
@@ -150,4 +158,9 @@ window.addEventListener("pagehide", () => {
|
||||
while (document.body.lastChild) {
|
||||
document.body.removeChild(document.body.lastChild);
|
||||
}
|
||||
// The connection is owned by the main window and outlives this realm, so any
|
||||
// collection we created on it has to go with us.
|
||||
if (connection) {
|
||||
dropRealmCollections(connection);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -121,6 +121,14 @@ declare global {
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"matter-commission-finish": HASSDomEvent<MatterCommissionFinish>;
|
||||
"improv-discovered-device": HASSDomEvent<
|
||||
HASSDomEvents["improv-discovered-device"]
|
||||
>;
|
||||
"improv-device-setup-done": HASSDomEvent<
|
||||
HASSDomEvents["improv-device-setup-done"]
|
||||
>;
|
||||
"matter-commission-finish": HASSDomEvent<
|
||||
HASSDomEvents["matter-commission-finish"]
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, eventOptions, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import type { HASSDomTargetEvent } from "../common/dom/fire_event";
|
||||
import { goBack } from "../common/navigate";
|
||||
import { sanitizeNavigationPath } from "../common/url/sanitize-navigation-path";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
@@ -74,7 +75,7 @@ class HassSubpage extends LitElement {
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _saveScrollPos(e: Event) {
|
||||
private _saveScrollPos(e: HASSDomTargetEvent<HTMLDivElement>) {
|
||||
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { canShowPage } from "../common/config/can_show_page";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { fireEvent, type HASSDomTargetEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/chips/ha-assist-chip";
|
||||
import "../components/data-table/ha-data-table";
|
||||
@@ -740,7 +740,9 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
this._dataTable.clearSelection();
|
||||
};
|
||||
|
||||
private _handleSearchChange(ev: InputEvent) {
|
||||
private _handleSearchChange(
|
||||
ev: InputEvent & HASSDomTargetEvent<HaInputSearch>
|
||||
) {
|
||||
const target = ev.target as HaInputSearch;
|
||||
if (this.filter === target.value) {
|
||||
return;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { canShowPage } from "../common/config/can_show_page";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import type { HASSDomTargetEvent } from "../common/dom/fire_event";
|
||||
import { isNavigationClick } from "../common/dom/is-navigation-click";
|
||||
import { goBack, navigate } from "../common/navigate";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
@@ -232,7 +233,7 @@ export class HassTabsSubpage extends LitElement {
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _saveScrollPos(e: Event) {
|
||||
private _saveScrollPos(e: HASSDomTargetEvent<HTMLDivElement>) {
|
||||
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export const PreventUnsavedMixin = <T extends Constructor<LitElement>>(
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (this.isDirtyState) {
|
||||
if (this.isDirtyState && this.isConnected) {
|
||||
window.addEventListener("click", this._handleClick, true);
|
||||
window.addEventListener("beforeunload", this._handleUnload);
|
||||
} else {
|
||||
|
||||
@@ -37,7 +37,7 @@ import { subscribeUser } from "../data/ws-user";
|
||||
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { HassElement } from "../state/hass-element";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import { storeState } from "../util/ha-pref-storage";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "../components/progress/ha-progress-bar";
|
||||
@@ -80,8 +80,8 @@ declare global {
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"onboarding-step": HASSDomEvent<OnboardingEvent>;
|
||||
"onboarding-progress": HASSDomEvent<OnboardingProgressEvent>;
|
||||
"onboarding-step": HASSDomEvent<HASSDomEvents["onboarding-step"]>;
|
||||
"onboarding-progress": HASSDomEvent<HASSDomEvents["onboarding-progress"]>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +332,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleProgress(ev: HASSDomEvent<OnboardingProgressEvent>) {
|
||||
private _handleProgress(
|
||||
ev: HASSDomEvent<HASSDomEvents["onboarding-progress"]>
|
||||
) {
|
||||
const stepSize = 100 / this._steps!.length;
|
||||
if (ev.detail.increase) {
|
||||
this._progress += ev.detail.increase * stepSize;
|
||||
@@ -345,7 +347,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleStepDone(ev: HASSDomEvent<OnboardingEvent>) {
|
||||
private async _handleStepDone(
|
||||
ev: HASSDomEvent<HASSDomEvents["onboarding-step"]>
|
||||
) {
|
||||
const stepResult = ev.detail;
|
||||
this._steps = this._steps!.map((step) =>
|
||||
step.step === stepResult.type ? { ...step, done: true } : step
|
||||
@@ -479,7 +483,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _languageChanged(ev: CustomEvent) {
|
||||
private _languageChanged(ev: ValueChangedEvent<string>) {
|
||||
const language = ev.detail.value;
|
||||
this.language = language;
|
||||
if (this.hass) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mdiOpenInNew } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult, PropertyValues } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { fireEvent, type HASSDomEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-analytics";
|
||||
import "../components/ha-button";
|
||||
@@ -65,7 +65,9 @@ class OnboardingAnalytics extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _preferencesChanged(event: CustomEvent): void {
|
||||
private _preferencesChanged(
|
||||
event: HASSDomEvent<HASSDomEvents["analytics-preferences-changed"]>
|
||||
): void {
|
||||
this._analyticsDetails = {
|
||||
...this._analyticsDetails!,
|
||||
preferences: event.detail.preferences,
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { ConfigUpdateValues } from "../data/core";
|
||||
import { saveCoreConfig } from "../data/core";
|
||||
import { countryCurrency } from "../data/currency";
|
||||
import { onboardCoreConfigStep } from "../data/onboarding";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import { getLocalLanguage } from "../util/common-translation";
|
||||
import "./onboarding-location";
|
||||
|
||||
@@ -132,7 +132,9 @@ class OnboardingCoreConfig extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleFormChanged(ev: CustomEvent) {
|
||||
private _handleFormChanged(
|
||||
ev: ValueChangedEvent<{ country?: string; time_zone?: string }>
|
||||
) {
|
||||
const value = ev.detail.value as { country?: string; time_zone?: string };
|
||||
this._country = value.country || undefined;
|
||||
if (value.time_zone) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { TemplateResult, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { storage } from "../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { navigate } from "../common/navigate";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import { removeSearchParam } from "../common/url/search-params";
|
||||
@@ -280,7 +281,9 @@ class OnboardingRestoreBackup extends LitElement {
|
||||
setTimeout(() => this._loadBackupInfo(), delay);
|
||||
}
|
||||
|
||||
private async _backupUploaded(ev: CustomEvent) {
|
||||
private async _backupUploaded(
|
||||
ev: HASSDomEvent<HASSDomEvents["backup-uploaded"]>
|
||||
) {
|
||||
this._backupId = ev.detail.backupId;
|
||||
await this._loadBackupInfo();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { formatDateTimeWithBrowserDefaults } from "../../common/datetime/format_date_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
HASSDomCurrentTargetEvent,
|
||||
HASSDomTargetEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import "../../components/buttons/ha-progress-button";
|
||||
import type { HaProgressButton } from "../../components/buttons/ha-progress-button";
|
||||
@@ -9,6 +13,7 @@ import "../../components/ha-alert";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import "../../components/input/ha-input";
|
||||
import type { HaInput } from "../../components/input/ha-input";
|
||||
import "../../components/item/ha-row-item";
|
||||
import {
|
||||
getPreferredAgentForDownload,
|
||||
@@ -19,6 +24,7 @@ import { restoreOnboardingBackup } from "../../data/backup_onboarding";
|
||||
import "../../panels/config/backup/components/ha-backup-data-picker";
|
||||
import "../../panels/config/backup/components/ha-backup-formfield-label";
|
||||
import { onBoardingStyles } from "../styles";
|
||||
import type { ValueChangedEvent } from "../../types";
|
||||
|
||||
@customElement("onboarding-restore-backup-restore")
|
||||
class OnboardingRestoreBackupRestore extends LitElement {
|
||||
@@ -247,16 +253,20 @@ class OnboardingRestoreBackupRestore extends LitElement {
|
||||
fireEvent(this, "sign-out");
|
||||
}
|
||||
|
||||
private _selectedBackupChanged(ev: CustomEvent) {
|
||||
private _selectedBackupChanged(ev: ValueChangedEvent<BackupData>) {
|
||||
ev.stopPropagation();
|
||||
this._selectedData = ev.detail.value;
|
||||
}
|
||||
|
||||
private _encryptionKeyChanged(ev): void {
|
||||
private _encryptionKeyChanged(
|
||||
ev: HASSDomTargetEvent<Omit<HaInput, "value"> & { value: string }>
|
||||
): void {
|
||||
this._encryptionKey = ev.target.value;
|
||||
}
|
||||
|
||||
private async _startRestore(ev: CustomEvent): Promise<void> {
|
||||
private async _startRestore(
|
||||
ev: HASSDomCurrentTargetEvent<HaProgressButton>
|
||||
): Promise<void> {
|
||||
const agentId = Object.keys(this.backup.agents)[0];
|
||||
const backupProtected = this.backup.agents[agentId].protected;
|
||||
|
||||
@@ -270,7 +280,7 @@ class OnboardingRestoreBackupRestore extends LitElement {
|
||||
}
|
||||
|
||||
this._loading = true;
|
||||
const button = ev.currentTarget as HaProgressButton;
|
||||
const button = ev.currentTarget;
|
||||
this._error = undefined;
|
||||
this._encryptionKeyWrong = false;
|
||||
|
||||
|
||||
@@ -6,14 +6,20 @@ import type { ByWeekday, Options, WeekdayStr } from "rrule";
|
||||
import { RRule, Weekday } from "rrule";
|
||||
import { formatDate, formatTime } from "../../common/datetime/calc_date";
|
||||
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
|
||||
import type {
|
||||
HASSDomCurrentTargetEvent,
|
||||
HASSDomTargetEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../../common/translations/localize";
|
||||
import "../../components/chips/ha-chip-set";
|
||||
import "../../components/chips/ha-filter-chip";
|
||||
import type { HaFilterChip } from "../../components/chips/ha-filter-chip";
|
||||
import "../../components/ha-date-input";
|
||||
import "../../components/ha-select";
|
||||
import type { HaSelectSelectEvent } from "../../components/ha-select";
|
||||
import "../../components/input/ha-input";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HaInput } from "../../components/input/ha-input";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import type {
|
||||
MonthlyRepeatItem,
|
||||
RepeatEnd,
|
||||
@@ -321,8 +327,8 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _onIntervalChange(e: Event) {
|
||||
this._interval = (e.target! as any).value;
|
||||
private _onIntervalChange(e: HASSDomTargetEvent<HaInput>) {
|
||||
this._interval = Number(e.target.value);
|
||||
}
|
||||
|
||||
private _onRepeatSelected(e: HaSelectSelectEvent<RepeatFrequency>) {
|
||||
@@ -351,9 +357,12 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
this._monthday = selectedItem.bymonthday;
|
||||
}
|
||||
|
||||
private _onWeekdayToggle(e: MouseEvent) {
|
||||
const target = e.currentTarget as any;
|
||||
const value = target.value as WeekdayStr;
|
||||
private _onWeekdayToggle(
|
||||
e: MouseEvent &
|
||||
HASSDomCurrentTargetEvent<HaFilterChip & { value: WeekdayStr }>
|
||||
) {
|
||||
const target = e.currentTarget;
|
||||
const value = target.value;
|
||||
if (this._weekday.has(value)) {
|
||||
this._weekday.delete(value);
|
||||
} else {
|
||||
@@ -385,11 +394,11 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
private _onCountChange(e: Event) {
|
||||
this._count = (e.target! as any).value;
|
||||
private _onCountChange(e: HASSDomTargetEvent<HaInput>) {
|
||||
this._count = Number(e.target.value);
|
||||
}
|
||||
|
||||
private _onUntilChange(e: CustomEvent) {
|
||||
private _onUntilChange(e: ValueChangedEvent<string>) {
|
||||
e.stopPropagation();
|
||||
this._untilDay = new Date(
|
||||
new TZDate(e.detail.value + "T00:00:00", this.timezone).getTime()
|
||||
@@ -436,7 +445,7 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
);
|
||||
// rrule.js can't compute some UNTIL variations so we compute that ourself. Must be
|
||||
// in the same format as dtstart.
|
||||
let newUntilValue;
|
||||
let newUntilValue: string;
|
||||
if (this.allDay) {
|
||||
// For all-day events, only use the date part
|
||||
newUntilValue = until.toISOString().split("T")[0].replace(/-/g, "");
|
||||
|
||||
@@ -51,6 +51,8 @@ const baseConfigStruct = object({
|
||||
mode: optional(string()),
|
||||
max_exceeded: optional(string()),
|
||||
id: optional(string()),
|
||||
variables: optional(object()),
|
||||
trigger_variables: optional(object()),
|
||||
});
|
||||
|
||||
const automationConfigStruct = union([
|
||||
@@ -257,6 +259,27 @@ export class HaManualAutomationEditor extends ManualEditorMixin<ManualAutomation
|
||||
}
|
||||
}
|
||||
|
||||
// If an object can be ambiguously an action or an automation, check for
|
||||
// toplevel automation keywords to disqualify it
|
||||
function isQualifiedAction(cfg): boolean {
|
||||
const type = getActionType(cfg);
|
||||
if (type === "variables") {
|
||||
if (
|
||||
[
|
||||
"trigger",
|
||||
"triggers",
|
||||
"condition",
|
||||
"conditions",
|
||||
"action",
|
||||
"actions",
|
||||
].some((key) => key in cfg)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return type !== "unknown";
|
||||
}
|
||||
|
||||
if (Array.isArray(config)) {
|
||||
if (config.length === 1) {
|
||||
config = config[0];
|
||||
@@ -276,7 +299,7 @@ export class HaManualAutomationEditor extends ManualEditorMixin<ManualAutomation
|
||||
found = true;
|
||||
(newConfig.conditions as Condition[]).push(cfg);
|
||||
}
|
||||
if (getActionType(cfg) !== "unknown") {
|
||||
if (isQualifiedAction(cfg)) {
|
||||
found = true;
|
||||
(newConfig.actions as Action[]).push(cfg);
|
||||
}
|
||||
@@ -293,7 +316,7 @@ export class HaManualAutomationEditor extends ManualEditorMixin<ManualAutomation
|
||||
if (isCondition(config)) {
|
||||
config = { conditions: [config] };
|
||||
}
|
||||
if (getActionType(config) !== "unknown") {
|
||||
if (isQualifiedAction(config)) {
|
||||
config = { actions: [config] };
|
||||
}
|
||||
|
||||
@@ -375,6 +398,11 @@ export class HaManualAutomationEditor extends ManualEditorMixin<ManualAutomation
|
||||
if (!workingCopy) {
|
||||
return;
|
||||
}
|
||||
["variables", "trigger_variables"].forEach((key) => {
|
||||
if (key in config) {
|
||||
workingCopy[key] = { ...workingCopy[key], ...config[key] };
|
||||
}
|
||||
});
|
||||
|
||||
if ("triggers" in config) {
|
||||
workingCopy.triggers = ensureArray(workingCopy.triggers || []).concat(
|
||||
|
||||
@@ -14,11 +14,14 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import { animationStyles } from "../../../../../resources/theme/animations.globals";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/buttons/ha-progress-button";
|
||||
import type { HaProgressButton } from "../../../../../components/buttons/ha-progress-button";
|
||||
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
@@ -67,6 +70,8 @@ class ZHAConfigDashboard extends LitElement {
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _generatingBackup = false;
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (!this.hass) {
|
||||
@@ -355,17 +360,18 @@ class ZHAConfigDashboard extends LitElement {
|
||||
"ui.panel.config.zha.configuration_page.download_backup_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
<ha-progress-button
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
size="s"
|
||||
.iconPath=${mdiDownload}
|
||||
.progress=${this._generatingBackup}
|
||||
@click=${this._createAndDownloadBackup}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.download_backup_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-progress-button>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
@@ -408,30 +414,32 @@ class ZHAConfigDashboard extends LitElement {
|
||||
this._configuration = await fetchZHAConfiguration(this.hass!);
|
||||
}
|
||||
|
||||
private async _createAndDownloadBackup(): Promise<void> {
|
||||
private async _createAndDownloadBackup(
|
||||
ev: HASSDomCurrentTargetEvent<HaProgressButton>
|
||||
): Promise<void> {
|
||||
// Captured up front: currentTarget is null once the first await resolves.
|
||||
const button = ev.currentTarget;
|
||||
let backup_and_metadata: ZHANetworkBackupAndMetadata;
|
||||
|
||||
// Reading the backup from the coordinator can take 5-30 seconds.
|
||||
this._generatingBackup = true;
|
||||
|
||||
try {
|
||||
backup_and_metadata = await createZHANetworkBackup(this.hass!);
|
||||
} catch (err: any) {
|
||||
button.actionError();
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to create backup",
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.backup_failed"
|
||||
),
|
||||
text: err.message,
|
||||
warning: true,
|
||||
});
|
||||
return;
|
||||
} finally {
|
||||
this._generatingBackup = false;
|
||||
}
|
||||
|
||||
if (!backup_and_metadata.is_complete) {
|
||||
await showAlertDialog(this, {
|
||||
title: "Backup is incomplete",
|
||||
text: "A backup has been created but it is incomplete and cannot be restored. This is a coordinator firmware limitation.",
|
||||
});
|
||||
}
|
||||
|
||||
const backupJSON: string =
|
||||
"data:text/plain;charset=utf-8," +
|
||||
encodeURIComponent(JSON.stringify(backup_and_metadata.backup, null, 4));
|
||||
const backupTime: Date = new Date(
|
||||
Date.parse(backup_and_metadata.backup.backup_time)
|
||||
);
|
||||
@@ -441,7 +449,23 @@ class ZHAConfigDashboard extends LitElement {
|
||||
basename = `Incomplete ${basename}`;
|
||||
}
|
||||
|
||||
fileDownload(backupJSON, `${basename}.json`);
|
||||
const blob = new Blob(
|
||||
[JSON.stringify(backup_and_metadata.backup, null, 4)],
|
||||
{ type: "application/json" }
|
||||
);
|
||||
fileDownload(URL.createObjectURL(blob), `${basename}.json`);
|
||||
button.actionSuccess();
|
||||
|
||||
if (!backup_and_metadata.is_complete) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.backup_incomplete_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.zha.configuration_page.backup_incomplete_text"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _openOptionFlow() {
|
||||
@@ -517,10 +541,6 @@ class ZHAConfigDashboard extends LitElement {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
|
||||
ha-button[size="s"] ha-svg-icon {
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
|
||||
.network-status div.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -47,6 +47,7 @@ const scriptConfigStruct = object({
|
||||
mode: optional(enums([typeof MODES])),
|
||||
max: optional(number()),
|
||||
fields: optional(object()),
|
||||
variables: optional(object()),
|
||||
});
|
||||
|
||||
@customElement("manual-script-editor")
|
||||
@@ -232,8 +233,9 @@ export class HaManualScriptEditor extends ManualEditorMixin<ScriptConfig>(
|
||||
|
||||
const actionType = getActionType(config);
|
||||
if (
|
||||
!["sequence", "unknown"].includes(actionType) ||
|
||||
(actionType === "sequence" && "metadata" in config)
|
||||
!["sequence", "variables", "unknown"].includes(actionType) ||
|
||||
(actionType === "sequence" && "metadata" in config) ||
|
||||
(actionType === "variables" && !("sequence" in config))
|
||||
) {
|
||||
config = { sequence: [config] };
|
||||
}
|
||||
@@ -312,12 +314,14 @@ export class HaManualScriptEditor extends ManualEditorMixin<ScriptConfig>(
|
||||
return;
|
||||
}
|
||||
|
||||
if ("fields" in config) {
|
||||
workingCopy.fields = {
|
||||
...workingCopy.fields,
|
||||
...config.fields,
|
||||
};
|
||||
}
|
||||
["fields", "variables"].forEach((key) => {
|
||||
if (key in config) {
|
||||
workingCopy[key] = {
|
||||
...workingCopy[key],
|
||||
...config[key],
|
||||
};
|
||||
}
|
||||
});
|
||||
if ("sequence" in config) {
|
||||
workingCopy.sequence = ensureArray(workingCopy.sequence || []).concat(
|
||||
ensureArray(config.sequence)
|
||||
|
||||
@@ -296,6 +296,10 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
||||
await this._getStatisticsMetaData(this._entityIds);
|
||||
}
|
||||
await this._getStatistics();
|
||||
if (!this.isConnected) {
|
||||
this._interval = undefined;
|
||||
return;
|
||||
}
|
||||
// statistics are created every hour
|
||||
if (!this._config?.energy_date_selection) {
|
||||
this._interval = window.setInterval(
|
||||
|
||||
@@ -147,7 +147,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
handleAction(this, this.hass!, this._config!, ev.detail.action!);
|
||||
}
|
||||
|
||||
private _handleIconAction(ev: CustomEvent) {
|
||||
private _handleIconAction(ev: ActionHandlerEvent) {
|
||||
ev.stopPropagation();
|
||||
const config = {
|
||||
entity: this._config!.entity,
|
||||
|
||||
@@ -64,6 +64,7 @@ import {
|
||||
CompareMode,
|
||||
downloadEnergyData,
|
||||
getEnergyDataCollection,
|
||||
getEnergyDefaultPeriodStorageKey,
|
||||
} from "../../../data/energy";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
@@ -544,7 +545,7 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _presetSelected(ev) {
|
||||
localStorage.setItem(
|
||||
`energy-default-period-_${this.collectionKey || "energy"}`,
|
||||
getEnergyDefaultPeriodStorageKey(this.hass, this.collectionKey),
|
||||
RANGE_KEYS[ev.detail.index]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { STATES_OFF } from "../../../common/const";
|
||||
import type {
|
||||
HASSDomCurrentTargetEvent,
|
||||
HASSDomTargetEvent,
|
||||
} from "../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
|
||||
import "../../../components/ha-camera-stream";
|
||||
@@ -378,9 +382,11 @@ export class HuiImage extends LitElement {
|
||||
this._loadState = LoadState.Error;
|
||||
}
|
||||
|
||||
private async _onImageLoad(ev: Event): Promise<void> {
|
||||
private async _onImageLoad(
|
||||
ev: HASSDomTargetEvent<HTMLImageElement>
|
||||
): Promise<void> {
|
||||
this._loadState = LoadState.Loaded;
|
||||
const imgEl = ev.target as HTMLImageElement;
|
||||
const imgEl = ev.target;
|
||||
if (this._ratio && this._ratio.w > 0 && this._ratio.h > 0) {
|
||||
this._loadedImageSrc = imgEl.src;
|
||||
}
|
||||
@@ -388,9 +394,11 @@ export class HuiImage extends LitElement {
|
||||
this._lastImageHeight = imgEl.offsetHeight;
|
||||
}
|
||||
|
||||
private async _onVideoLoad(ev: Event): Promise<void> {
|
||||
private async _onVideoLoad(
|
||||
ev: HASSDomCurrentTargetEvent<HaCameraStream>
|
||||
): Promise<void> {
|
||||
this._loadState = LoadState.Loaded;
|
||||
const videoEl = ev.currentTarget as HaCameraStream;
|
||||
const videoEl = ev.currentTarget;
|
||||
await this.updateComplete;
|
||||
this._lastImageHeight = videoEl.offsetHeight;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../../types";
|
||||
import type { ImageElementConfig } from "../../../elements/types";
|
||||
import type { LovelacePictureElementEditor } from "../../../types";
|
||||
import { ACTION_RELATED_CONTEXT } from "../../../components/hui-action-editor";
|
||||
@@ -159,7 +159,7 @@ export class HuiImageElementEditor
|
||||
: {}),
|
||||
}));
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
private _valueChanged(ev: ValueChangedEvent<ImageElementConfig>): void {
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-switch";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
|
||||
import {
|
||||
PREVIEW_CLICK_CALLBACK,
|
||||
type PictureElementsCardConfig,
|
||||
@@ -29,6 +29,7 @@ import type { LovelaceCardEditor } from "../../types";
|
||||
import "../hui-sub-element-editor";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import type { EditDetailElementEvent, SubElementEditorConfig } from "../types";
|
||||
import type { UIConfigChangedEvent } from "../hui-element-editor";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import "../hui-picture-elements-card-row-editor";
|
||||
import type { LovelaceElementConfig } from "../../elements/types";
|
||||
@@ -228,7 +229,7 @@ export class HuiPictureElementsCardEditor
|
||||
: {}),
|
||||
}));
|
||||
|
||||
private _formChanged(ev: CustomEvent): void {
|
||||
private _formChanged(ev: ValueChangedEvent<PictureElementsCardConfig>): void {
|
||||
ev.stopPropagation();
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
@@ -261,7 +262,9 @@ export class HuiPictureElementsCardEditor
|
||||
}
|
||||
}
|
||||
|
||||
private _handleSubElementChanged(ev: CustomEvent): void {
|
||||
private _handleSubElementChanged(
|
||||
ev: UIConfigChangedEvent<LovelaceElementConfig>
|
||||
): void {
|
||||
ev.stopPropagation();
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
|
||||
@@ -135,6 +135,10 @@ export class HuiShortcutBadgeEditor
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
name: "double_tap_action",
|
||||
selector: {
|
||||
|
||||
@@ -168,6 +168,10 @@ export class HuiShortcutCardEditor
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
name: "double_tap_action",
|
||||
selector: {
|
||||
|
||||
@@ -500,13 +500,20 @@ export class HuiStatisticsGraphCardEditor
|
||||
|
||||
// update card config with updated entity config
|
||||
const index = this._subElementEditorConfig!.index!;
|
||||
const oldEntityConfig = this._config!.entities[index];
|
||||
const oldEntityId =
|
||||
typeof oldEntityConfig === "string"
|
||||
? oldEntityConfig
|
||||
: oldEntityConfig?.entity;
|
||||
const newEntities = [...this._config!.entities];
|
||||
newEntities[index] = newEntityConfig;
|
||||
let config = this._config!;
|
||||
config = { ...config, entities: newEntities };
|
||||
|
||||
// remove inappropriate stat options dependently on entities
|
||||
config = await this._cleanConfig(config);
|
||||
// only a different statistic can invalidate the stat options
|
||||
if (newEntityConfig.entity !== oldEntityId) {
|
||||
config = await this._cleanConfig(config);
|
||||
}
|
||||
// normalize a generated yaml code
|
||||
config = this._orderProperties(config);
|
||||
this._config = config;
|
||||
|
||||
@@ -199,6 +199,10 @@ export class HuiTileCardEditor
|
||||
},
|
||||
context: ACTION_RELATED_CONTEXT,
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
name: "icon_tap_action",
|
||||
selector: {
|
||||
|
||||
@@ -12,7 +12,10 @@ class EntityRowDirective extends Directive {
|
||||
|
||||
private _name?: string;
|
||||
|
||||
private _lastHass?: HomeAssistant;
|
||||
|
||||
render(entityId: string, name: string, hass: HomeAssistant) {
|
||||
this._lastHass = hass;
|
||||
if (
|
||||
!this._element ||
|
||||
(this._entityId !== entityId &&
|
||||
@@ -22,6 +25,15 @@ class EntityRowDirective extends Directive {
|
||||
entity: entityId,
|
||||
name,
|
||||
} as LovelaceRowConfig);
|
||||
this._element.addEventListener(
|
||||
"ll-upgrade",
|
||||
() => {
|
||||
if ("hass" in this._element!) {
|
||||
this._element!.hass = this._lastHass;
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
} else if (this._entityId !== entityId || this._name !== name) {
|
||||
(this._element as LovelaceRow).setConfig?.({
|
||||
entity: entityId,
|
||||
|
||||
@@ -12,7 +12,12 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { until } from "lit/directives/until";
|
||||
import { fireEvent, type HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
HASSDomCurrentTargetEvent,
|
||||
HASSDomEvent,
|
||||
HASSDomTargetEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeEntityPickerDisplay } from "../../common/entity/compute_entity_name_display";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
@@ -582,14 +587,15 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
||||
this._volumeSlider.value = this._volumeValue;
|
||||
}
|
||||
|
||||
private _handleControlClick(e: MouseEvent): void {
|
||||
const action = (e.currentTarget! as HTMLElement).getAttribute("action")!;
|
||||
|
||||
private _handleControlClick(
|
||||
e: MouseEvent & HASSDomCurrentTargetEvent<HTMLElement>
|
||||
): void {
|
||||
const action = e.currentTarget.getAttribute("action")!;
|
||||
if (!this._browserPlayer) {
|
||||
handleMediaControlClick(
|
||||
this.hass!,
|
||||
this._stateObj!,
|
||||
(e.currentTarget as HTMLElement).getAttribute("action")!
|
||||
e.currentTarget.getAttribute("action")!
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -600,12 +606,12 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleMediaSeekChanged(e: Event): void {
|
||||
private _handleMediaSeekChanged(e: HASSDomTargetEvent<HaSlider>): void {
|
||||
if (this.entityId === BROWSER_PLAYER || !this._stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newValue = (e.target as HaSlider).value;
|
||||
const newValue = e.target.value;
|
||||
this.hass.callService("media_player", "media_seek", {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
seek_position: newValue,
|
||||
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
} from "../../data/data_entry_flow";
|
||||
import { DirtyStateProviderMixin } from "../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
|
||||
let instance = 0;
|
||||
|
||||
@@ -229,7 +229,7 @@ class HaMfaModuleSetupFlow extends DirtyStateProviderMixin<
|
||||
});
|
||||
}
|
||||
|
||||
private _stepDataChanged(ev: CustomEvent) {
|
||||
private _stepDataChanged(ev: ValueChangedEvent<Record<string, unknown>>) {
|
||||
this._stepData = ev.detail.value;
|
||||
this._updateDirtyState(this._stepData);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../components/ha-switch";
|
||||
import "../../components/item/ha-row-item";
|
||||
@@ -33,8 +34,8 @@ class HaEnableShortcutsRow extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _checkedChanged(ev: Event) {
|
||||
const enabled = (ev.target as HaSwitch).checked;
|
||||
private async _checkedChanged(ev: HASSDomTargetEvent<HaSwitch>) {
|
||||
const enabled = ev.target.checked;
|
||||
if (enabled === this.hass.enableShortcuts) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../components/ha-switch";
|
||||
import "../../components/item/ha-row-item";
|
||||
@@ -31,8 +32,8 @@ class HaForcedNarrowRow extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _checkedChanged(ev: Event) {
|
||||
const newValue = (ev.target as HaSwitch).checked;
|
||||
private async _checkedChanged(ev: HASSDomTargetEvent<HaSwitch>) {
|
||||
const newValue = ev.target.checked;
|
||||
if (newValue === (this.hass.dockedSidebar === "always_hidden")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { CSSResultGroup, 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 { copyToClipboard } from "../../common/util/copy-clipboard";
|
||||
import { withViewTransition } from "../../common/util/view-transition";
|
||||
import "../../components/ha-alert";
|
||||
@@ -11,6 +12,7 @@ import "../../components/ha-dialog";
|
||||
import "../../components/ha-dialog-footer";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/input/ha-input";
|
||||
import type { HaInput } from "../../components/input/ha-input";
|
||||
import { DirtyStateProviderMixin } from "../../mixins/dirty-state-provider-mixin";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { showToast } from "../../util/toast";
|
||||
@@ -190,8 +192,10 @@ export class HaLongLivedAccessTokenDialog extends DirtyStateProviderMixin<string
|
||||
`;
|
||||
}
|
||||
|
||||
private _nameChanged(ev: Event) {
|
||||
this._name = (ev.currentTarget as HTMLInputElement).value;
|
||||
private _nameChanged(
|
||||
ev: HASSDomCurrentTargetEvent<Omit<HaInput, "value"> & { value: string }>
|
||||
) {
|
||||
this._name = ev.currentTarget.value;
|
||||
this._errorMessage = undefined;
|
||||
this._updateDirtyState(this._name);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { relativeTime } from "../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-icon-button";
|
||||
import type { HaIconButton } from "../../components/ha-icon-button";
|
||||
import "../../components/ha-settings-row";
|
||||
import type { RefreshToken } from "../../data/refresh_token";
|
||||
import {
|
||||
@@ -110,8 +112,10 @@ class HaLongLivedTokens extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _deleteToken(ev: Event): Promise<void> {
|
||||
const token = (ev.currentTarget as any).token;
|
||||
private async _deleteToken(
|
||||
ev: HASSDomCurrentTargetEvent<HaIconButton & { token: RefreshToken }>
|
||||
): Promise<void> {
|
||||
const token = ev.currentTarget.token;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
|
||||
@@ -16,6 +16,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "../../components/ha-dropdown";
|
||||
import "../../components/ha-dropdown-item";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/item/ha-list-item-base";
|
||||
@@ -221,7 +222,9 @@ class HaRefreshTokens extends LitElement {
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(
|
||||
ev: CustomEvent<{ item: { action: string; token: RefreshToken } }>
|
||||
ev: HaDropdownSelectEvent<string> & {
|
||||
detail: { item: { action: string; token: RefreshToken } };
|
||||
}
|
||||
) {
|
||||
if (ev.detail.item.action === "toggle_expiration") {
|
||||
this._toggleTokenExpiration(ev.detail.item.token);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
HASSDomEvent,
|
||||
HASSDomTargetEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../components/ha-switch";
|
||||
@@ -15,9 +18,9 @@ declare global {
|
||||
}
|
||||
// for add event listener
|
||||
interface HTMLElementEventMap {
|
||||
"hass-suspend-when-hidden": HASSDomEvent<{
|
||||
suspend: HomeAssistant["suspendWhenHidden"];
|
||||
}>;
|
||||
"hass-suspend-when-hidden": HASSDomEvent<
|
||||
HASSDomEvents["hass-suspend-when-hidden"]
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +46,8 @@ class HaSetSuspendRow extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _checkedChanged(ev: Event) {
|
||||
const suspend = (ev.target as HaSwitch).checked;
|
||||
private async _checkedChanged(ev: HASSDomTargetEvent<HaSwitch>) {
|
||||
const suspend = ev.target.checked;
|
||||
if (suspend === this.hass.suspendWhenHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../components/ha-switch";
|
||||
import "../../components/item/ha-row-item";
|
||||
@@ -30,8 +31,8 @@ class HaSetVibrateRow extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _checkedChanged(ev: Event) {
|
||||
const vibrate = (ev.target as HaSwitch).checked;
|
||||
private async _checkedChanged(ev: HASSDomTargetEvent<HaSwitch>) {
|
||||
const vibrate = ev.target.checked;
|
||||
if (vibrate === this.hass.vibrate) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,16 +6,19 @@ import memoizeOne from "memoize-one";
|
||||
import { formatShortDateTimeWithConditionalYear } from "../../common/datetime/format_date_time";
|
||||
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomTargetEvent } from "../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { supportsMarkdownHelper } from "../../common/translations/markdown_support";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-checkbox";
|
||||
import type { HaCheckbox } from "../../components/ha-checkbox";
|
||||
import "../../components/ha-date-input";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-dialog-footer";
|
||||
import "../../components/ha-textarea";
|
||||
import type { HaTextArea } from "../../components/ha-textarea";
|
||||
import "../../components/ha-time-input";
|
||||
import "../../components/input/ha-input";
|
||||
import type { HaInput } from "../../components/input/ha-input";
|
||||
@@ -30,7 +33,7 @@ import {
|
||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { DirtyStateProviderMixin } from "../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import type { TodoItemEditDialogParams } from "./show-dialog-todo-item-editor";
|
||||
|
||||
interface TodoItemFormState {
|
||||
@@ -166,7 +169,7 @@ class DialogTodoItemEditor extends DirtyStateProviderMixin<TodoItemFormState>()(
|
||||
<div class="flex">
|
||||
<ha-checkbox
|
||||
.checked=${this._checked}
|
||||
@change=${this._checkedCanged}
|
||||
@change=${this._checkedChanged}
|
||||
.disabled=${isCreate || !canUpdate}
|
||||
></ha-checkbox>
|
||||
<ha-input
|
||||
@@ -338,22 +341,22 @@ class DialogTodoItemEditor extends DirtyStateProviderMixin<TodoItemFormState>()(
|
||||
return new Date(tzDate.getTime());
|
||||
}
|
||||
|
||||
private _checkedCanged(ev) {
|
||||
private _checkedChanged(ev: HASSDomTargetEvent<HaCheckbox>) {
|
||||
this._checked = ev.target.checked;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _handleSummaryChanged(ev: InputEvent) {
|
||||
this._summary = (ev.target as HaInput).value ?? "";
|
||||
private _handleSummaryChanged(ev: InputEvent & HASSDomTargetEvent<HaInput>) {
|
||||
this._summary = ev.target.value ?? "";
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _handleDescriptionChanged(ev) {
|
||||
private _handleDescriptionChanged(ev: HASSDomTargetEvent<HaTextArea>) {
|
||||
this._description = ev.target.value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _dueDateChanged(ev: CustomEvent) {
|
||||
private _dueDateChanged(ev: ValueChangedEvent<string | undefined>) {
|
||||
if (!ev.detail.value) {
|
||||
this._due = undefined;
|
||||
this._updateDirtyState(this._currentState());
|
||||
@@ -364,7 +367,7 @@ class DialogTodoItemEditor extends DirtyStateProviderMixin<TodoItemFormState>()(
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _dueTimeChanged(ev: CustomEvent) {
|
||||
private _dueTimeChanged(ev: ValueChangedEvent<string>) {
|
||||
this._hasTime = true;
|
||||
this._due = this._parseDate(
|
||||
`${this._formatDate(this._due || new Date())}T${ev.detail.value}`
|
||||
|
||||
@@ -15,6 +15,7 @@ import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { storage } from "../../common/decorators/storage";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { navigate } from "../../common/navigate";
|
||||
@@ -396,8 +397,8 @@ class PanelTodo extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _setEntityId(ev: Event) {
|
||||
const item = ev.currentTarget as HaDropdownItem;
|
||||
private _setEntityId(ev: HASSDomCurrentTargetEvent<HaDropdownItem>) {
|
||||
const item = ev.currentTarget;
|
||||
|
||||
this._entityId = item.value;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@ import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { HASSDomTargetEvent } from "../common/dom/fire_event";
|
||||
import "../components/entity/state-info";
|
||||
import type { HaSlider } from "../components/ha-slider";
|
||||
import "../components/ha-slider";
|
||||
import type { HaInput } from "../components/input/ha-input";
|
||||
import "../components/input/ha-input";
|
||||
import { UNAVAILABLE } from "../data/entity/entity";
|
||||
import { setValue } from "../data/input_text";
|
||||
@@ -152,13 +155,12 @@ class StateCardInputNumber extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _selectedValueChanged(ev: Event): void {
|
||||
if ((ev.target as HTMLInputElement).value !== this.stateObj.state) {
|
||||
setValue(
|
||||
this.hass!,
|
||||
this.stateObj.entity_id,
|
||||
(ev.target as HTMLInputElement).value
|
||||
);
|
||||
private _selectedValueChanged(
|
||||
ev: HASSDomTargetEvent<HaSlider | HaInput>
|
||||
): void {
|
||||
const value = String(ev.target.value);
|
||||
if (value !== this.stateObj.state) {
|
||||
setValue(this.hass!, this.stateObj.entity_id, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import type { HASSDomTargetEvent } from "../common/dom/fire_event";
|
||||
import "../components/entity/state-info";
|
||||
import "../components/input/ha-input";
|
||||
import type { HaInput } from "../components/input/ha-input";
|
||||
@@ -50,7 +51,7 @@ class StateCardInputText extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _onInput(ev: InputEvent) {
|
||||
private _onInput(ev: InputEvent & HASSDomTargetEvent<HaInput>) {
|
||||
this.value = (ev.target as HaInput).value ?? "";
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,11 @@ import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { HASSDomTargetEvent } from "../common/dom/fire_event";
|
||||
import "../components/entity/state-info";
|
||||
import type { HaSlider } from "../components/ha-slider";
|
||||
import "../components/ha-slider";
|
||||
import type { HaInput } from "../components/input/ha-input";
|
||||
import "../components/input/ha-input";
|
||||
import { UNAVAILABLE } from "../data/entity/entity";
|
||||
import { showNumberSlider } from "../data/number";
|
||||
@@ -124,8 +127,10 @@ class StateCardNumber extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _selectedValueChanged(ev: Event) {
|
||||
const value = (ev.target as HTMLInputElement).value;
|
||||
private async _selectedValueChanged(
|
||||
ev: HASSDomTargetEvent<HaSlider | HaInput>
|
||||
) {
|
||||
const value = String(ev.target.value);
|
||||
if (value === this.stateObj.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import type { HASSDomTargetEvent } from "../common/dom/fire_event";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import "../components/entity/state-badge";
|
||||
import "../components/input/ha-input";
|
||||
@@ -36,7 +37,7 @@ class StateCardText extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: InputEvent): void {
|
||||
private _valueChanged(ev: InputEvent & HASSDomTargetEvent<HaInput>): void {
|
||||
const value = (ev.target as HaInput).value ?? "";
|
||||
|
||||
// Filter out invalid text states
|
||||
|
||||
@@ -20,7 +20,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
}
|
||||
|
||||
private async _handleAction(
|
||||
ev: HASSDomEvent<{ config: ActionConfigParams; action: string }>
|
||||
ev: HASSDomEvent<HASSDomEvents["hass-action"]>
|
||||
) {
|
||||
if (!this.hass) return;
|
||||
handleAction(this, this.hass, ev.detail.config, ev.detail.action);
|
||||
|
||||
@@ -23,13 +23,13 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
super.firstUpdated(changedProps);
|
||||
this.addEventListener("hass-automation-editor", (ev) =>
|
||||
this._handleShowAutomationEditor(
|
||||
ev as HASSDomEvent<ShowAutomationEditorParams>
|
||||
ev as HASSDomEvent<HASSDomEvents["hass-automation-editor"]>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _handleShowAutomationEditor(
|
||||
ev: HASSDomEvent<ShowAutomationEditorParams>
|
||||
ev: HASSDomEvent<HASSDomEvents["hass-automation-editor"]>
|
||||
) {
|
||||
showAutomationEditor(ev.detail.data, ev.detail.expanded);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ declare global {
|
||||
}
|
||||
// for add event listener
|
||||
interface HTMLElementEventMap {
|
||||
"register-dialog": HASSDomEvent<RegisterDialogParams>;
|
||||
"register-dialog": HASSDomEvent<HASSDomEvents["register-dialog"]>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ export const dialogManagerMixin = <T extends Constructor<HassBaseEl>>(
|
||||
showDialog(
|
||||
this,
|
||||
dialogTag,
|
||||
(showEv as HASSDomEvent<unknown>).detail,
|
||||
(showEv as HASSDomEvent<HASSDomEvents[typeof dialogShowEvent]>)
|
||||
.detail,
|
||||
dialogImport,
|
||||
undefined,
|
||||
addHistory
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user