mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-15 13:47:13 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0dfb801ff6 | |||
| d94dcf50fb | |||
| fb1f5ef722 | |||
| e5d5797d91 | |||
| adee24f745 | |||
| 1b695e24d0 | |||
| 7f9259edf9 | |||
| 6954dc1a54 | |||
| 032d0fb332 | |||
| 43ed97da43 | |||
| 9f4d35bc05 | |||
| 11afde6b5f | |||
| 1b0dcb33b1 | |||
| 67eecbc51d | |||
| 969ccf85d2 |
@@ -1,4 +1,3 @@
|
||||
import "@material/mwc-drawer";
|
||||
import "@material/mwc-top-app-bar-fixed";
|
||||
import { html, css, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
+25
-10
@@ -1,4 +1,3 @@
|
||||
import "@material/mwc-drawer";
|
||||
import "@material/mwc-top-app-bar-fixed";
|
||||
import { mdiMenu, mdiSwapHorizontal } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
@@ -7,6 +6,8 @@ import { customElement, query, state } from "lit/decorators";
|
||||
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
|
||||
import { setDirectionStyles } from "../../src/common/util/compute_rtl";
|
||||
import "../../src/components/ha-button";
|
||||
import "../../src/components/ha-drawer";
|
||||
import type { HaDrawer } from "../../src/components/ha-drawer";
|
||||
import { HaExpansionPanel } from "../../src/components/ha-expansion-panel";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import "../../src/components/ha-svg-icon";
|
||||
@@ -39,8 +40,8 @@ class HaGallery extends LitElement {
|
||||
@query("notification-manager")
|
||||
private _notifications!: HTMLElementTagNameMap["notification-manager"];
|
||||
|
||||
@query("mwc-drawer")
|
||||
private _drawer!: HTMLElementTagNameMap["mwc-drawer"];
|
||||
@query("ha-drawer")
|
||||
private _drawer!: HaDrawer;
|
||||
|
||||
private _narrow = window.matchMedia("(max-width: 600px)").matches;
|
||||
|
||||
@@ -75,15 +76,14 @@ class HaGallery extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<mwc-drawer
|
||||
hasHeader
|
||||
<ha-drawer
|
||||
.direction=${this._rtl ? "rtl" : "ltr"}
|
||||
.open=${!this._narrow}
|
||||
.type=${this._narrow ? "modal" : "dismissible"}
|
||||
>
|
||||
<span slot="title">Home Assistant Design</span>
|
||||
<!-- <span slot="subtitle">subtitle</span> -->
|
||||
<div class="drawer-title">Home Assistant Design</div>
|
||||
<div class="sidebar">${sidebar}</div>
|
||||
<div slot="appContent">
|
||||
<div slot="appContent" class="app-content">
|
||||
<mwc-top-app-bar-fixed>
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
@@ -144,7 +144,7 @@ class HaGallery extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mwc-drawer>
|
||||
</ha-drawer>
|
||||
<notification-manager
|
||||
.hass=${FAKE_HASS}
|
||||
id="notifications"
|
||||
@@ -226,12 +226,27 @@ class HaGallery extends LitElement {
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
--ha-sidebar-width: 256px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
box-sizing: border-box;
|
||||
max-height: calc(100vh - 64px);
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.drawer-title {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: var(--primary-text-color);
|
||||
display: flex;
|
||||
font-size: var(--ha-font-size-l);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
min-height: 64px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
@@ -255,7 +270,7 @@ class HaGallery extends LitElement {
|
||||
opacity: 0.12;
|
||||
}
|
||||
|
||||
div[slot="appContent"] {
|
||||
.app-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
|
||||
+5
-6
@@ -64,7 +64,6 @@
|
||||
"@lit/reactive-element": "2.1.2",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/mwc-base": "0.27.0",
|
||||
"@material/mwc-drawer": "0.27.0",
|
||||
"@material/mwc-formfield": "patch:@material/mwc-formfield@npm%3A0.27.0#~/.yarn/patches/@material-mwc-formfield-npm-0.27.0-9528cb60f6.patch",
|
||||
"@material/mwc-list": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
||||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
@@ -142,7 +141,7 @@
|
||||
"@octokit/auth-oauth-device": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.1.0",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.5.10",
|
||||
"@rsdoctor/rspack-plugin": "1.5.11",
|
||||
"@rspack/core": "2.0.2",
|
||||
"@rspack/dev-server": "2.0.1",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
@@ -162,7 +161,7 @@
|
||||
"@types/sortablejs": "1.15.9",
|
||||
"@types/tar": "7.0.87",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@vitest/coverage-v8": "4.1.5",
|
||||
"@vitest/coverage-v8": "4.1.6",
|
||||
"babel-loader": "10.1.1",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.4",
|
||||
@@ -171,7 +170,7 @@
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-import-resolver-webpack": "0.13.11",
|
||||
"eslint-plugin-import-x": "4.16.2",
|
||||
"eslint-plugin-lit": "2.2.1",
|
||||
"eslint-plugin-lit": "2.3.1",
|
||||
"eslint-plugin-lit-a11y": "5.1.1",
|
||||
"eslint-plugin-unused-imports": "4.4.1",
|
||||
"eslint-plugin-wc": "3.1.0",
|
||||
@@ -203,9 +202,9 @@
|
||||
"terser-webpack-plugin": "5.6.0",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.59.2",
|
||||
"typescript-eslint": "8.59.3",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.1.5",
|
||||
"vitest": "4.1.6",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
"workbox-build": "patch:workbox-build@npm%3A7.4.1#~/.yarn/patches/workbox-build-npm-7.4.1-c84561662c.patch"
|
||||
|
||||
@@ -54,6 +54,8 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
@query("ha-auth-form") private _form?: HaAuthForm;
|
||||
|
||||
@query("ha-form") private _haForm?: HTMLElement;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
@@ -160,9 +162,8 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
// 100ms to give all the form elements time to initialize.
|
||||
setTimeout(() => {
|
||||
const form = this.renderRoot.querySelector("ha-form");
|
||||
if (form) {
|
||||
(form as any).focus();
|
||||
if (this._haForm) {
|
||||
(this._haForm as any).focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { isComponentLoaded } from "./is_component_loaded";
|
||||
|
||||
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) =>
|
||||
(isCore(page) || isLoadedIntegration(hass, page)) &&
|
||||
!hideAdvancedPage(hass, page) &&
|
||||
isNotLoadedIntegration(hass, page);
|
||||
|
||||
export const isLoadedIntegration = (
|
||||
@@ -27,8 +26,3 @@ export const isNotLoadedIntegration = (
|
||||
);
|
||||
|
||||
export const isCore = (page: PageNavigation) => page.core;
|
||||
export const isAdvancedPage = (page: PageNavigation) => page.advancedOnly;
|
||||
export const userWantsAdvanced = (hass: HomeAssistant) =>
|
||||
hass.userData?.showAdvanced;
|
||||
export const hideAdvancedPage = (hass: HomeAssistant, page: PageNavigation) =>
|
||||
isAdvancedPage(page) && !userWantsAdvanced(hass);
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
} from "echarts/types/dist/shared";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
@@ -102,6 +102,8 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
@state() private _hiddenDatasets = new Set<string>();
|
||||
|
||||
@query(".chart") private _chartContainer?: HTMLDivElement;
|
||||
|
||||
private _modifierPressed = false;
|
||||
|
||||
private _isTouchDevice = "ontouchstart" in window;
|
||||
@@ -469,7 +471,6 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
private async _setupChart() {
|
||||
if (this._loading) return;
|
||||
const container = this.renderRoot.querySelector(".chart") as HTMLDivElement;
|
||||
this._loading = true;
|
||||
try {
|
||||
if (this.chart) {
|
||||
@@ -484,7 +485,7 @@ export class HaChartBase extends LitElement {
|
||||
const style = getComputedStyle(this);
|
||||
echarts.registerTheme("custom", this._createTheme(style));
|
||||
|
||||
this.chart = echarts.init(container, "custom");
|
||||
this.chart = echarts.init(this._chartContainer!, "custom");
|
||||
this.chart.on("datazoom", (e: any) => {
|
||||
this._handleDataZoomEvent(e);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ import type { LineChartEntity, LineChartState } from "../../data/history";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
|
||||
import { sideTooltipPosition } from "./chart-tooltip-position";
|
||||
import { computeYAxisFractionDigits } from "./y-axis-fraction-digits";
|
||||
import type { ECOption } from "../../resources/echarts/echarts";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import {
|
||||
@@ -118,7 +117,9 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
private _chartTime: Date = new Date();
|
||||
|
||||
private _yAxisFractionDigits = 1;
|
||||
private _previousYAxisLabelValue = 0;
|
||||
|
||||
private _yAxisMaximumFractionDigits = 0;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
@@ -435,14 +436,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
const datasets: LineSeriesOption[] = [];
|
||||
const entityIds: string[] = [];
|
||||
const datasetToDataIndex: number[] = [];
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number | null | undefined) => {
|
||||
if (typeof v === "number" && Number.isFinite(v)) {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
}
|
||||
};
|
||||
if (entityStates.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -478,7 +471,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
d.data!.push([timestamp, prevValues[i]]);
|
||||
}
|
||||
d.data!.push([timestamp, datavalues[i]]);
|
||||
trackY(datavalues[i]);
|
||||
});
|
||||
prevValues = datavalues;
|
||||
};
|
||||
@@ -829,7 +821,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
const currentValue = stateObj ? safeParseFloat(stateObj.state) : null;
|
||||
if (currentValue !== null) {
|
||||
data[0].data!.push([now, currentValue]);
|
||||
trackY(currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,7 +828,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
Array.prototype.push.apply(datasets, data);
|
||||
});
|
||||
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
this._chartData = datasets;
|
||||
this._entityIds = entityIds;
|
||||
this._datasetToDataIndex = datasetToDataIndex;
|
||||
@@ -871,8 +861,20 @@ export class StateHistoryChartLine extends LitElement {
|
||||
}
|
||||
|
||||
private _formatYAxisLabel = (value: number) => {
|
||||
// show the first significant digit for tiny values
|
||||
const maximumFractionDigits = Math.max(
|
||||
1,
|
||||
// use the difference to the previous value to determine the number of significant digits #25526
|
||||
-Math.floor(
|
||||
Math.log10(Math.abs(value - this._previousYAxisLabelValue || 1))
|
||||
)
|
||||
);
|
||||
this._yAxisMaximumFractionDigits = Math.max(
|
||||
this._yAxisMaximumFractionDigits,
|
||||
maximumFractionDigits
|
||||
);
|
||||
const label = formatNumber(value, this.hass.locale, {
|
||||
maximumFractionDigits: this._yAxisFractionDigits,
|
||||
maximumFractionDigits: this._yAxisMaximumFractionDigits,
|
||||
});
|
||||
const width = measureTextWidth(label, 12) + 5;
|
||||
if (width > this._yWidth) {
|
||||
@@ -882,6 +884,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
chartIndex: this.chartIndex,
|
||||
});
|
||||
}
|
||||
this._previousYAxisLabelValue = value;
|
||||
return label;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,13 @@ import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { mdiRestart } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, eventOptions, property, state } from "lit/decorators";
|
||||
import {
|
||||
customElement,
|
||||
eventOptions,
|
||||
property,
|
||||
queryAll,
|
||||
state,
|
||||
} from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||
import type {
|
||||
@@ -104,6 +110,11 @@ export class StateHistoryCharts extends LitElement {
|
||||
|
||||
@state() private _hasZoomedCharts = false;
|
||||
|
||||
@queryAll("state-history-chart-line, state-history-chart-timeline")
|
||||
private _chartComponents!: NodeListOf<
|
||||
StateHistoryChartLine | StateHistoryChartTimeline
|
||||
>;
|
||||
|
||||
private _isSyncing = false;
|
||||
|
||||
// @ts-ignore
|
||||
@@ -327,11 +338,7 @@ export class StateHistoryCharts extends LitElement {
|
||||
this._isSyncing = true;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const chartComponents = this.renderRoot.querySelectorAll(
|
||||
"state-history-chart-line, state-history-chart-timeline"
|
||||
) as unknown as (StateHistoryChartLine | StateHistoryChartTimeline)[];
|
||||
|
||||
chartComponents.forEach((chartComponent, index) => {
|
||||
this._chartComponents.forEach((chartComponent, index) => {
|
||||
if (index === sourceChartIndex) {
|
||||
return;
|
||||
}
|
||||
@@ -350,11 +357,7 @@ export class StateHistoryCharts extends LitElement {
|
||||
this._isSyncing = true;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const chartComponents = this.renderRoot.querySelectorAll(
|
||||
"state-history-chart-line, state-history-chart-timeline"
|
||||
);
|
||||
|
||||
chartComponents.forEach((chartComponent: any) => {
|
||||
this._chartComponents.forEach((chartComponent: any) => {
|
||||
const chartBase =
|
||||
chartComponent.renderRoot?.querySelector("ha-chart-base");
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ import type { CustomLegendOption } from "./ha-chart-base";
|
||||
import "./ha-chart-base";
|
||||
import { sideTooltipPosition } from "./chart-tooltip-position";
|
||||
import { fillDataGapsAndRoundCaps } from "./round-caps";
|
||||
import { computeYAxisFractionDigits } from "./y-axis-fraction-digits";
|
||||
|
||||
export const supportedStatTypeMap: Record<StatisticType, StatisticType> = {
|
||||
mean: "mean",
|
||||
@@ -132,7 +131,7 @@ export class StatisticsChart extends LitElement {
|
||||
|
||||
private _computedStyle?: CSSStyleDeclaration;
|
||||
|
||||
private _yAxisFractionDigits = 1;
|
||||
private _previousYAxisLabelValue = 0;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues<this>): boolean {
|
||||
return changedProps.size > 1 || !changedProps.has("hass");
|
||||
@@ -144,7 +143,8 @@ export class StatisticsChart extends LitElement {
|
||||
changedProps.has("statTypes") ||
|
||||
changedProps.has("chartType") ||
|
||||
changedProps.has("hideLegend") ||
|
||||
changedProps.has("_hiddenStats")
|
||||
changedProps.has("_hiddenStats") ||
|
||||
changedProps.has("names")
|
||||
) {
|
||||
this._generateData();
|
||||
}
|
||||
@@ -496,14 +496,6 @@ export class StatisticsChart extends LitElement {
|
||||
const chartStacked = this.chartType.endsWith("stack");
|
||||
const statisticsData = Object.entries(this.statisticsData);
|
||||
const totalDataSets: typeof this._chartData = [];
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number | null | undefined) => {
|
||||
if (typeof v === "number" && Number.isFinite(v)) {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
}
|
||||
};
|
||||
const legendData: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -609,9 +601,6 @@ export class StatisticsChart extends LitElement {
|
||||
d.data!.push([prevEndTime, null]);
|
||||
}
|
||||
d.data!.push([start, ...dataValues[i]!]);
|
||||
// For band-top rows dataValues[i] is [diff, top]; the actual Y is
|
||||
// the last element. For regular rows it's [value]. Same call works.
|
||||
trackY(dataValues[i][dataValues[i].length - 1]);
|
||||
} else {
|
||||
let time = start;
|
||||
if (centerBars) {
|
||||
@@ -622,7 +611,6 @@ export class StatisticsChart extends LitElement {
|
||||
// Data value should always be a scalar for bar charts. Pass in
|
||||
// real start time as extra value to allow formatting tooltip.
|
||||
d.data!.push([time, dataValues[i][0]!, start, end]);
|
||||
trackY(dataValues[i][0]);
|
||||
}
|
||||
});
|
||||
prevValues = dataValues;
|
||||
@@ -835,7 +823,6 @@ export class StatisticsChart extends LitElement {
|
||||
val.push(currentValue);
|
||||
}
|
||||
statDataSets[i].data!.push([now, ...val]);
|
||||
trackY(val[val.length - 1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -869,7 +856,6 @@ export class StatisticsChart extends LitElement {
|
||||
});
|
||||
});
|
||||
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
this._chartData = totalDataSets;
|
||||
if (legendData.length !== this._legendData?.length) {
|
||||
// only update the legend if it has changed or it will trigger options update
|
||||
@@ -903,10 +889,21 @@ export class StatisticsChart extends LitElement {
|
||||
return Math.abs(value) < 1 ? value : roundingFn(value);
|
||||
}
|
||||
|
||||
private _formatYAxisLabel = (value: number) =>
|
||||
formatNumber(value, this.hass.locale, {
|
||||
maximumFractionDigits: this._yAxisFractionDigits,
|
||||
private _formatYAxisLabel = (value: number) => {
|
||||
// show the first significant digit for tiny values
|
||||
const maximumFractionDigits = Math.max(
|
||||
1,
|
||||
// use the difference to the previous value to determine the number of significant digits #25526
|
||||
-Math.floor(
|
||||
Math.log10(Math.abs(value - this._previousYAxisLabelValue || 1))
|
||||
)
|
||||
);
|
||||
const label = formatNumber(value, this.hass.locale, {
|
||||
maximumFractionDigits,
|
||||
});
|
||||
this._previousYAxisLabelValue = value;
|
||||
return label;
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// Derive the number of decimal digits to use for Y-axis labels from the
|
||||
// observed data range. We estimate the tick interval as `range / 10` (twice
|
||||
// ECharts' default splitNumber of 5, as a safety margin against finer "nice"
|
||||
// intervals), then derive `ceil(-log10(interval))`.
|
||||
export function computeYAxisFractionDigits(min: number, max: number): number {
|
||||
const range = max - min;
|
||||
if (!Number.isFinite(range) || range <= 0) return 1;
|
||||
return Math.max(0, Math.ceil(-Math.log10(range / 10)));
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import "../ha-icon-button";
|
||||
import "../ha-icon-button-next";
|
||||
import "../ha-icon-button-prev";
|
||||
import "../ha-textarea";
|
||||
import type { HaTextArea } from "../ha-textarea";
|
||||
import "./date-range-picker";
|
||||
|
||||
export type DateRangePickerRanges = Record<string, [Date, Date]>;
|
||||
@@ -98,6 +99,8 @@ export class HaDateRangePicker extends LitElement {
|
||||
|
||||
@query(".container") private _containerElement?: HTMLDivElement;
|
||||
|
||||
@query("ha-textarea") private _textareaElement?: HaTextArea;
|
||||
|
||||
private _narrow = false;
|
||||
|
||||
private _unsubscribeTinyKeys?: () => void;
|
||||
@@ -335,9 +338,8 @@ export class HaDateRangePicker extends LitElement {
|
||||
};
|
||||
|
||||
private _setTextareaFocusStyle(focused: boolean) {
|
||||
const textarea = this.renderRoot.querySelector("ha-textarea");
|
||||
if (textarea) {
|
||||
textarea.setFocused(focused);
|
||||
if (this._textareaElement) {
|
||||
this._textareaElement.setFocused(focused);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@home-assistant/webawesome/dist/components/popover/popover";
|
||||
import { css, html, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { ScrollLockMixin } from "../mixins/scroll-lock-mixin";
|
||||
@@ -25,6 +25,8 @@ export class HaAdaptivePopover extends ScrollLockMixin(HaAdaptiveDialog) {
|
||||
|
||||
@state() private _shouldRenderPopover = false;
|
||||
|
||||
@query("wa-popover") private _popoverElement?: HTMLElement;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>) {
|
||||
if (
|
||||
changedProperties.has("dialogAnchor") ||
|
||||
@@ -188,7 +190,7 @@ export class HaAdaptivePopover extends ScrollLockMixin(HaAdaptiveDialog) {
|
||||
}
|
||||
|
||||
private _handlePopoverPointerDown(ev: PointerEvent) {
|
||||
const popover = this.renderRoot.querySelector("wa-popover");
|
||||
const popover = this._popoverElement;
|
||||
const dialog = popover?.shadowRoot?.querySelector(
|
||||
"dialog"
|
||||
) as HTMLDialogElement | null;
|
||||
@@ -215,7 +217,7 @@ export class HaAdaptivePopover extends ScrollLockMixin(HaAdaptiveDialog) {
|
||||
}
|
||||
|
||||
private _pulsePopover() {
|
||||
const popover = this.renderRoot.querySelector("wa-popover");
|
||||
const popover = this._popoverElement;
|
||||
const popup = popover?.shadowRoot?.querySelector("wa-popup") as {
|
||||
popup?: HTMLElement;
|
||||
} | null;
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
customElement,
|
||||
property,
|
||||
query,
|
||||
queryAll,
|
||||
state as litState,
|
||||
} from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -31,6 +32,8 @@ export class HaAnsiToHtml extends LitElement {
|
||||
|
||||
@query("pre") private _pre?: HTMLPreElement;
|
||||
|
||||
@queryAll("div") private _divs!: NodeListOf<HTMLDivElement>;
|
||||
|
||||
@litState() private _filter = "";
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -320,7 +323,7 @@ export class HaAnsiToHtml extends LitElement {
|
||||
*/
|
||||
filterLines(filter: string): boolean {
|
||||
this._filter = filter;
|
||||
const lines = this.shadowRoot?.querySelectorAll("div") || [];
|
||||
const lines = this._divs;
|
||||
let numberOfFoundLines = 0;
|
||||
if (!filter) {
|
||||
lines.forEach((line) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
@@ -24,11 +24,12 @@ class HaBluePrintPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@query("ha-select") private _select?: HTMLElement;
|
||||
|
||||
public open() {
|
||||
const select = this.shadowRoot?.querySelector("ha-select");
|
||||
if (select) {
|
||||
if (this._select) {
|
||||
// @ts-expect-error
|
||||
select.menuOpen = true;
|
||||
this._select.menuOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@query("#body") private _bodyElement!: HTMLDivElement;
|
||||
|
||||
@query("[autofocus]") private _autofocusElement?: HTMLElement;
|
||||
|
||||
protected get scrollableElement(): HTMLElement | null {
|
||||
return this._bodyElement;
|
||||
}
|
||||
@@ -93,12 +95,12 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
await this.updateComplete;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const element = this._autofocusElement;
|
||||
if (
|
||||
this._hassConfig?.auth.external &&
|
||||
isIosApp(this._hassConfig.auth.external)
|
||||
) {
|
||||
const element = this.renderRoot.querySelector("[autofocus]");
|
||||
if (element !== null) {
|
||||
if (element) {
|
||||
if (!element.id) {
|
||||
element.id = "ha-bottom-sheet-autofocus";
|
||||
}
|
||||
@@ -111,9 +113,7 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
(
|
||||
this.renderRoot.querySelector("[autofocus]") as HTMLElement | null
|
||||
)?.focus();
|
||||
element?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -188,6 +188,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
this.codemirror.state,
|
||||
[this._loadedCodeMirror.tags.comment]
|
||||
);
|
||||
// eslint-disable-next-line lit/prefer-query-decorators
|
||||
return !!this.renderRoot.querySelector(`span.${className}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ export class HaControlSelect extends LitElement {
|
||||
this._activeIndex = index;
|
||||
this.requestUpdate();
|
||||
this.updateComplete.then(() => {
|
||||
// eslint-disable-next-line lit/prefer-query-decorators
|
||||
const option = this.shadowRoot?.querySelector(
|
||||
`#option-${this.options![index].value}`
|
||||
) as HTMLElement;
|
||||
|
||||
@@ -25,7 +25,7 @@ export class HaDrawer extends LitElement {
|
||||
|
||||
@property({ reflect: true }) public direction: "ltr" | "rtl" = "ltr";
|
||||
|
||||
@property() public type = "";
|
||||
@property({ reflect: true }) public type: "" | "dismissible" | "modal" = "";
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public open = false;
|
||||
|
||||
@@ -105,7 +105,11 @@ export class HaDrawer extends LitElement {
|
||||
}
|
||||
|
||||
private _handleDrawerTransitionStart = (ev: TransitionEvent) => {
|
||||
if (ev.propertyName !== "width" || this._sidebarTransitionActive) {
|
||||
if (
|
||||
ev.propertyName !==
|
||||
(this.type === "dismissible" ? "transform" : "width") ||
|
||||
this._sidebarTransitionActive
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._sidebarTransitionActive = true;
|
||||
@@ -116,7 +120,11 @@ export class HaDrawer extends LitElement {
|
||||
};
|
||||
|
||||
private _handleDrawerTransitionEnd = (ev: TransitionEvent) => {
|
||||
if (ev.propertyName !== "width" || !this._sidebarTransitionActive) {
|
||||
if (
|
||||
ev.propertyName !==
|
||||
(this.type === "dismissible" ? "transform" : "width") ||
|
||||
!this._sidebarTransitionActive
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._sidebarTransitionActive = false;
|
||||
@@ -300,6 +308,22 @@ export class HaDrawer extends LitElement {
|
||||
width var(--ha-animation-duration-normal) ease;
|
||||
}
|
||||
|
||||
:host([type="dismissible"]) .sidebar-shell {
|
||||
transition: transform var(--ha-animation-duration-normal) ease;
|
||||
}
|
||||
|
||||
:host([type="dismissible"]:not([open])) .sidebar-shell {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
:host([type="dismissible"][direction="rtl"]:not([open])) .sidebar-shell {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
:host([type="dismissible"]:not([open])) .app-content {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
wa-drawer {
|
||||
--size: var(--ha-sidebar-width, 256px);
|
||||
--show-duration: var(--ha-animation-duration-normal);
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { SelectedDetail } from "@material/mwc-list";
|
||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { deepEqual } from "../common/util/deep-equal";
|
||||
import type { Blueprints } from "../data/blueprint";
|
||||
@@ -32,6 +32,8 @@ export class HaFilterBlueprints extends LitElement {
|
||||
|
||||
@state() private _blueprints?: Blueprints;
|
||||
|
||||
@query("ha-list") private _list?: HTMLElement;
|
||||
|
||||
public willUpdate(properties: PropertyValues<this>) {
|
||||
super.willUpdate(properties);
|
||||
|
||||
@@ -96,8 +98,7 @@ export class HaFilterBlueprints extends LitElement {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (this.narrow || !this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list")!.style.height =
|
||||
`${this.clientHeight - 49}px`;
|
||||
this._list!.style.height = `${this.clientHeight - 49}px`;
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import type { CategoryRegistryEntry } from "../data/category_registry";
|
||||
@@ -49,6 +49,8 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _shouldRender = false;
|
||||
|
||||
@query("ha-list") private _list?: HTMLElement;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["scope"];
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
@@ -169,8 +171,7 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (!this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list")!.style.height =
|
||||
`${this.clientHeight - (49 + 48)}px`;
|
||||
this._list!.style.height = `${this.clientHeight - (49 + 48)}px`;
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../common/entity/compute_device_name";
|
||||
@@ -34,6 +34,8 @@ export class HaFilterDevices extends LitElement {
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
@query("ha-list") private _list?: HTMLElement;
|
||||
|
||||
public willUpdate(properties: PropertyValues<this>) {
|
||||
super.willUpdate(properties);
|
||||
|
||||
@@ -135,8 +137,7 @@ export class HaFilterDevices extends LitElement {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (!this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list")!.style.height =
|
||||
`${this.clientHeight - 49 - 4 - 32}px`;
|
||||
this._list!.style.height = `${this.clientHeight - 49 - 4 - 32}px`;
|
||||
// 49px - height of a header + 1px
|
||||
// 4px - padding-top of the search-input
|
||||
// 32px - height of the search input
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { SelectedDetail } from "@material/mwc-list";
|
||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
@@ -31,6 +32,8 @@ export class HaFilterDomains extends LitElement {
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
@query("ha-list") private _list?: HTMLElement;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
@@ -62,7 +65,7 @@ export class HaFilterDomains extends LitElement {
|
||||
multi
|
||||
>
|
||||
${repeat(
|
||||
this._domains(this.hass.states, this._filter),
|
||||
this._domains(this.hass.states, this._filter, this.value),
|
||||
(i) => i,
|
||||
(domain) =>
|
||||
html`<ha-check-list-item
|
||||
@@ -84,7 +87,7 @@ export class HaFilterDomains extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _domains = memoizeOne((states, filter) => {
|
||||
private _domains = memoizeOne((states, filter, _value) => {
|
||||
const domains = new Set<string>();
|
||||
Object.keys(states).forEach((entityId) => {
|
||||
domains.add(computeDomain(entityId));
|
||||
@@ -109,8 +112,7 @@ export class HaFilterDomains extends LitElement {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (!this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list")!.style.height =
|
||||
`${this.clientHeight - 49 - 4 - 32}px`;
|
||||
this._list!.style.height = `${this.clientHeight - 49 - 4 - 32}px`;
|
||||
// 49px - height of a header + 1px
|
||||
// 4px - padding-top of the search-input
|
||||
// 32px - height of the search input
|
||||
@@ -126,19 +128,19 @@ export class HaFilterDomains extends LitElement {
|
||||
this.expanded = ev.detail.expanded;
|
||||
}
|
||||
|
||||
private _handleItemSelected(
|
||||
ev: CustomEvent<{ diff: { added: number[]; removed: number[] } }>
|
||||
) {
|
||||
const domains = this._domains(this.hass.states, this._filter);
|
||||
if (ev.detail.diff.added.length) {
|
||||
this.value = [...(this.value || []), domains[ev.detail.diff.added[0]]];
|
||||
} else if (ev.detail.diff.removed.length) {
|
||||
const removedDomain = domains[ev.detail.diff.removed[0]];
|
||||
this.value = this.value?.filter((value) => value !== removedDomain);
|
||||
}
|
||||
private _handleItemSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) {
|
||||
const domains = this._domains(this.hass.states, this._filter, this.value);
|
||||
|
||||
const visibleDomains = new Set(domains);
|
||||
const preserved = (this.value || []).filter((d) => !visibleDomains.has(d));
|
||||
const selected = [...ev.detail.index]
|
||||
.map((i) => domains[i])
|
||||
.filter((d): d is string => !!d);
|
||||
|
||||
this.value = [...preserved, ...selected];
|
||||
|
||||
fireEvent(this, "data-table-filter-changed", {
|
||||
value: this.value,
|
||||
value: this.value.length ? this.value : undefined,
|
||||
items: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
@@ -36,6 +36,8 @@ export class HaFilterEntities extends LitElement {
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
@query("ha-list") private _list?: HTMLElement;
|
||||
|
||||
public willUpdate(properties: PropertyValues<this>) {
|
||||
super.willUpdate(properties);
|
||||
|
||||
@@ -102,8 +104,7 @@ export class HaFilterEntities extends LitElement {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (!this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list")!.style.height =
|
||||
`${this.clientHeight - 49 - 4 - 32}px`;
|
||||
this._list!.style.height = `${this.clientHeight - 49 - 4 - 32}px`;
|
||||
// 49px - height of a header + 1px
|
||||
// 4px - padding-top of the search-input
|
||||
// 32px - height of the search input
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiFilterVariantRemove, mdiTextureBox } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -42,6 +42,8 @@ export class HaFilterFloorAreas extends LitElement {
|
||||
|
||||
@state() private _shouldRender = false;
|
||||
|
||||
@query("ha-list-selectable") private _list?: HTMLElement;
|
||||
|
||||
public willUpdate(properties: PropertyValues<this>) {
|
||||
super.willUpdate(properties);
|
||||
|
||||
@@ -207,8 +209,7 @@ export class HaFilterFloorAreas extends LitElement {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (!this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list-selectable")!.style.height =
|
||||
`${this.clientHeight - 49}px`;
|
||||
this._list!.style.height = `${this.clientHeight - 49}px`;
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { SelectedDetail } from "@material/mwc-list";
|
||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
@@ -34,6 +35,8 @@ export class HaFilterIntegrations extends LitElement {
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
@query("ha-list") private _list?: HTMLElement;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
@@ -98,8 +101,7 @@ export class HaFilterIntegrations extends LitElement {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (!this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list")!.style.height =
|
||||
`${this.clientHeight - 49 - 4 - 32}px`;
|
||||
this._list!.style.height = `${this.clientHeight - 49 - 4 - 32}px`;
|
||||
// 49px - height of a header + 1px
|
||||
// 4px - padding-top of the search-input
|
||||
// 32px - height of the search input
|
||||
@@ -147,9 +149,7 @@ export class HaFilterIntegrations extends LitElement {
|
||||
)
|
||||
);
|
||||
|
||||
private _itemSelected(
|
||||
ev: CustomEvent<{ diff: { added: number[]; removed: number[] } }>
|
||||
) {
|
||||
private _itemSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) {
|
||||
const integrations = this._integrations(
|
||||
this.hass.localize,
|
||||
this._manifests!,
|
||||
@@ -157,18 +157,16 @@ export class HaFilterIntegrations extends LitElement {
|
||||
this.value
|
||||
);
|
||||
|
||||
if (ev.detail.diff.added.length) {
|
||||
this.value = [
|
||||
...(this.value || []),
|
||||
integrations[ev.detail.diff.added[0]].domain,
|
||||
];
|
||||
} else if (ev.detail.diff.removed.length) {
|
||||
const removedDomain = integrations[ev.detail.diff.removed[0]].domain;
|
||||
this.value = this.value?.filter((val) => val !== removedDomain);
|
||||
}
|
||||
const visibleDomains = new Set(integrations.map((i) => i.domain));
|
||||
const preserved = (this.value || []).filter((d) => !visibleDomains.has(d));
|
||||
const selected = [...ev.detail.index]
|
||||
.map((i) => integrations[i]?.domain)
|
||||
.filter((d): d is string => !!d);
|
||||
|
||||
this.value = [...preserved, ...selected];
|
||||
|
||||
fireEvent(this, "data-table-filter-changed", {
|
||||
value: this.value,
|
||||
value: this.value.length ? this.value : undefined,
|
||||
items: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { SelectedDetail } from "@material/mwc-list";
|
||||
import { mdiCog, mdiFilterVariantRemove } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
@@ -41,6 +41,8 @@ export class HaFilterLabels extends LitElement {
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
@query("ha-list") private _list?: HTMLElement;
|
||||
|
||||
private _filteredLabels = memoizeOne(
|
||||
// `_value` used to recalculate the memoization when the selection changes
|
||||
(labels: LabelRegistryEntry[], filter: string | undefined, _value) =>
|
||||
@@ -137,8 +139,7 @@ export class HaFilterLabels extends LitElement {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (!this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list")!.style.height =
|
||||
`${this.clientHeight - (49 + 48 + 32 + 4)}px`;
|
||||
this._list!.style.height = `${this.clientHeight - (49 + 48 + 32 + 4)}px`;
|
||||
// 49px - height of a header + 1px
|
||||
// 4px - padding-top of the search-input
|
||||
// 32px - height of the search input
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { SelectedDetail } from "@material/mwc-list";
|
||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
@@ -33,6 +33,8 @@ export class HaFilterVoiceAssistants extends LitElement {
|
||||
|
||||
@state() private _shouldRender = false;
|
||||
|
||||
@query("ha-list") private _list?: HTMLElement;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
@@ -93,8 +95,7 @@ export class HaFilterVoiceAssistants extends LitElement {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (!this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list")!.style.height =
|
||||
`${this.clientHeight - 49}px`;
|
||||
this._list!.style.height = `${this.clientHeight - 49}px`;
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
@@ -49,14 +49,15 @@ export class HaFormOptionalActions extends LitElement implements HaFormElement {
|
||||
|
||||
@state() private _displayActions?: string[];
|
||||
|
||||
@query("ha-form") private _form?: HaForm;
|
||||
|
||||
public async focus() {
|
||||
await this.updateComplete;
|
||||
this.renderRoot.querySelector("ha-form")?.focus();
|
||||
this._form?.focus();
|
||||
}
|
||||
|
||||
public reportValidity(): boolean {
|
||||
const form = this.renderRoot.querySelector<HaForm>("ha-form");
|
||||
return form ? form.reportValidity() : true;
|
||||
return this._form ? this._form.reportValidity() : true;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues<this>): void {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
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";
|
||||
@@ -83,8 +83,10 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
delegatesFocus: true,
|
||||
};
|
||||
|
||||
@query(".root") private _root?: HTMLElement;
|
||||
|
||||
public reportValidity(): boolean {
|
||||
const root = this.renderRoot.querySelector(".root");
|
||||
const root = this._root;
|
||||
if (!root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -314,6 +314,7 @@ export class HaItemDisplayEditor extends LitElement {
|
||||
// refocus the item after the sort
|
||||
setTimeout(async () => {
|
||||
await this.updateComplete;
|
||||
// eslint-disable-next-line lit/prefer-query-decorators
|
||||
const selectedElement = this.shadowRoot?.querySelector(
|
||||
`ha-md-list-item:nth-child(${this._dragIndex! + 1})`
|
||||
) as HTMLElement | null;
|
||||
|
||||
@@ -86,9 +86,6 @@ export class HaServiceControl extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "show-advanced", type: Boolean })
|
||||
public showAdvanced = false;
|
||||
|
||||
@property({ attribute: "show-service-id", type: Boolean })
|
||||
public showServiceId = false;
|
||||
|
||||
@@ -666,10 +663,7 @@ export class HaServiceControl extends LitElement {
|
||||
? this.hass.services[domain][serviceName].description_placeholders
|
||||
: undefined;
|
||||
|
||||
return dataField.selector &&
|
||||
(!dataField.advanced ||
|
||||
this.showAdvanced ||
|
||||
(this._value?.data && this._value.data[dataField.key] !== undefined))
|
||||
return dataField.selector
|
||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||
${!showOptional
|
||||
? hasOptional
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mdiStarFourPoints } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type {
|
||||
@@ -52,6 +52,10 @@ export class HaSuggestWithAIButton extends LitElement {
|
||||
@state()
|
||||
private _minWidth?: string;
|
||||
|
||||
@query("ha-assist-chip") private _chip?: HTMLElement & {
|
||||
offsetWidth: number;
|
||||
};
|
||||
|
||||
private _intervalId?: number;
|
||||
|
||||
protected firstUpdated(_changedProperties: PropertyValues<this>): void {
|
||||
@@ -109,9 +113,8 @@ export class HaSuggestWithAIButton extends LitElement {
|
||||
}
|
||||
|
||||
// Capture current width before changing state
|
||||
const chip = this.shadowRoot?.querySelector("ha-assist-chip");
|
||||
if (chip) {
|
||||
this._minWidth = `${chip.offsetWidth}px`;
|
||||
if (this._chip) {
|
||||
this._minWidth = `${this._chip.offsetWidth}px`;
|
||||
}
|
||||
|
||||
// Reset to suggesting state
|
||||
|
||||
@@ -486,6 +486,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
|
||||
// eslint-disable-next-line lit/prefer-query-decorators
|
||||
this.shadowRoot
|
||||
?.querySelector(
|
||||
`ha-target-picker-item-group[type='${this._newTarget?.type}']`
|
||||
|
||||
@@ -2,7 +2,7 @@ import { consume, type ContextType } from "@lit/context";
|
||||
import { mdiDeleteOutline, mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { internationalizationContext } from "../../data/context";
|
||||
@@ -67,6 +67,8 @@ class HaInputMulti extends LitElement {
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n?: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@query("ha-input[data-last]") private _lastInput?: HaInput;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-sortable
|
||||
@@ -163,10 +165,7 @@ class HaInputMulti extends LitElement {
|
||||
const items = [...this._items, ""];
|
||||
this._fireChanged(items);
|
||||
await this.updateComplete;
|
||||
const field = this.shadowRoot?.querySelector(`ha-input[data-last]`) as
|
||||
| HaInput
|
||||
| undefined;
|
||||
field?.focus();
|
||||
this._lastInput?.focus();
|
||||
}
|
||||
|
||||
private async _editItem(ev: Event) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import "../ha-ripple";
|
||||
import { HaListItemBase } from "./ha-list-item-base";
|
||||
@@ -34,8 +34,10 @@ export class HaListItemButton extends HaListItemBase {
|
||||
|
||||
@property({ type: String }) public download?: string;
|
||||
|
||||
@query("#item") private _item?: HTMLElement;
|
||||
|
||||
public override activate(): void {
|
||||
this.renderRoot.querySelector<HTMLElement>("#item")?.click();
|
||||
this._item?.click();
|
||||
}
|
||||
|
||||
protected _renderBase(inner: TemplateResult): TemplateResult {
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
} from "leaflet";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, ReactiveElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||
import {
|
||||
formatTimeWeekday,
|
||||
@@ -105,6 +105,8 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
@state() private _loaded = false;
|
||||
|
||||
@query("#map") private _mapElement?: HTMLElement;
|
||||
|
||||
public leafletMap?: Map;
|
||||
|
||||
private Leaflet?: LeafletModuleType;
|
||||
@@ -235,11 +237,11 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
|
||||
private _updateMapStyle(): void {
|
||||
const map = this.renderRoot.querySelector("#map");
|
||||
map!.classList.toggle("clickable", this.clickable);
|
||||
map!.classList.toggle("dark", this._darkMode);
|
||||
map!.classList.toggle("forced-dark", this.themeMode === "dark");
|
||||
map!.classList.toggle("forced-light", this.themeMode === "light");
|
||||
const map = this._mapElement!;
|
||||
map.classList.toggle("clickable", this.clickable);
|
||||
map.classList.toggle("dark", this._darkMode);
|
||||
map.classList.toggle("forced-dark", this.themeMode === "dark");
|
||||
map.classList.toggle("forced-light", this.themeMode === "light");
|
||||
}
|
||||
|
||||
private _loading = false;
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { Condition, Trigger } from "../../data/automation";
|
||||
@@ -73,6 +73,9 @@ export class HatScriptGraph extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public selected?: string;
|
||||
|
||||
@query("hat-graph-node[active], hat-graph-branch[active]")
|
||||
private _activeNode?: HTMLElement;
|
||||
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
public renderedNodes: Record<string, NodeInfo> = {};
|
||||
@@ -667,12 +670,10 @@ export class HatScriptGraph extends LitElement {
|
||||
|
||||
// Scroll to active node when selection changes
|
||||
if (changedProps.has("selected")) {
|
||||
const activeNode = this.renderRoot.querySelector(
|
||||
"hat-graph-node[active], hat-graph-branch[active]"
|
||||
) as HTMLElement;
|
||||
if (activeNode) {
|
||||
activeNode.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
||||
}
|
||||
this._activeNode?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
});
|
||||
}
|
||||
|
||||
if (!changedProps.has("trace")) {
|
||||
|
||||
@@ -120,7 +120,6 @@ class MoreInfoScript extends LitElement {
|
||||
...(this.data ? { data: this.data } : {}),
|
||||
...this._scriptData,
|
||||
}}
|
||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||
.narrow=${this.narrow}
|
||||
@value-changed=${this._scriptDataChanged}
|
||||
></ha-service-control>
|
||||
|
||||
@@ -214,10 +214,8 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
if (
|
||||
changedProperties.has("tabs") ||
|
||||
(changedProperties.has("hass") &&
|
||||
(this.hass?.config.components !==
|
||||
changedProperties.get("hass")?.config.components ||
|
||||
this.hass?.userData?.showAdvanced !==
|
||||
changedProperties.get("hass")?.userData?.showAdvanced))
|
||||
this.hass?.config.components !==
|
||||
changedProperties.get("hass")?.config.components)
|
||||
) {
|
||||
this.showTabs =
|
||||
this.tabs.filter((page) => canShowPage(this.hass, page)).length > 1;
|
||||
|
||||
@@ -33,7 +33,6 @@ export interface PageNavigation {
|
||||
name?: string;
|
||||
not_component?: string | string[];
|
||||
core?: boolean;
|
||||
advancedOnly?: boolean;
|
||||
/** Hide from non-admin users in filtered navigation and quick bar. */
|
||||
adminOnly?: boolean;
|
||||
iconPath?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { LOCAL_TIME_ZONE } from "../common/datetime/resolve-time-zone";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
@@ -42,6 +42,8 @@ class OnboardingCoreConfig extends LitElement {
|
||||
|
||||
@state() private _skipCore = false;
|
||||
|
||||
@query("ha-country-picker") private _countryPicker?: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._location) {
|
||||
return html`<onboarding-location
|
||||
@@ -143,10 +145,7 @@ class OnboardingCoreConfig extends LitElement {
|
||||
|
||||
fireEvent(this, "onboarding-progress", { increase: 0.5 });
|
||||
await this.updateComplete;
|
||||
setTimeout(
|
||||
() => this.renderRoot.querySelector("ha-country-picker")!.focus(),
|
||||
100
|
||||
);
|
||||
setTimeout(() => this._countryPicker!.focus(), 100);
|
||||
}
|
||||
|
||||
private async _save(ev) {
|
||||
|
||||
@@ -64,6 +64,8 @@ class OnboardingLocation extends LitElement {
|
||||
|
||||
@query("ha-locations-editor", true) private map!: HaLocationsEditor;
|
||||
|
||||
@query("ha-input") private _input?: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const addressAttribution = this.onboardingLocalize(
|
||||
"ui.panel.page-onboarding.core-config.location_address",
|
||||
@@ -201,7 +203,7 @@ class OnboardingLocation extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
setTimeout(() => this.renderRoot.querySelector("ha-input")!.focus(), 100);
|
||||
setTimeout(() => this._input!.focus(), 100);
|
||||
this.addEventListener("keyup", (ev) => {
|
||||
if (ev.key === "Enter") {
|
||||
this._save(ev);
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoize from "memoize-one";
|
||||
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
|
||||
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
|
||||
@@ -103,6 +103,8 @@ export class HAFullCalendar extends LitElement {
|
||||
|
||||
@state() private _activeView = this.initialView;
|
||||
|
||||
@query("style[data-fullcalendar]") private _fullCalendarStyle?: HTMLElement;
|
||||
|
||||
// @ts-ignore
|
||||
private _resizeController = new ResizeController(this, {
|
||||
callback: () => this.calendar?.updateSize(),
|
||||
@@ -113,7 +115,7 @@ export class HAFullCalendar extends LitElement {
|
||||
super.disconnectedCallback();
|
||||
this.calendar?.destroy();
|
||||
this.calendar = undefined;
|
||||
this.renderRoot.querySelector("style[data-fullcalendar]")?.remove();
|
||||
this._fullCalendarStyle?.remove();
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
|
||||
@@ -85,7 +85,6 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this._action}
|
||||
.disabled=${this.disabled}
|
||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||
.hidePicker=${!!this._action.metadata}
|
||||
@value-changed=${this._actionChanged}
|
||||
></ha-service-control>
|
||||
|
||||
@@ -36,6 +36,7 @@ import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import type {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
@@ -121,6 +122,8 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
|
||||
@query("manual-automation-editor")
|
||||
private _manualEditor?: HaManualAutomationEditor;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
private _configSubscriptions: Record<
|
||||
string,
|
||||
(config?: AutomationConfig) => void
|
||||
@@ -827,7 +830,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
|
||||
this.blueprintConfig = config;
|
||||
this.config = newConfig;
|
||||
if (this.mode === "yaml") {
|
||||
this.renderRoot.querySelector("ha-yaml-editor")?.setValue(this.config);
|
||||
this._yamlEditor?.setValue(this.config);
|
||||
}
|
||||
this.readOnly = true;
|
||||
this.errors = undefined;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
@@ -92,6 +92,8 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _config?: BackupConfig;
|
||||
|
||||
@query("div") private _copyContainer?: HTMLElement;
|
||||
|
||||
public showDialog(params: BackupOnboardingDialogParams): void {
|
||||
this._params = params;
|
||||
|
||||
@@ -478,7 +480,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
private async _copyKeyToClipboard() {
|
||||
await copyToClipboard(
|
||||
this._config!.create_backup.password!,
|
||||
this.renderRoot.querySelector("div")!
|
||||
this._copyContainer!
|
||||
);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-button";
|
||||
@@ -37,6 +37,8 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _newEncryptionKey?: string;
|
||||
|
||||
@query("div") private _copyContainer?: HTMLElement;
|
||||
|
||||
public showDialog(params: ChangeBackupEncryptionKeyDialogParams): void {
|
||||
this._params = params;
|
||||
this._step = STEPS[0];
|
||||
@@ -233,10 +235,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
private async _copyKeyToClipboard() {
|
||||
await copyToClipboard(
|
||||
this._newEncryptionKey,
|
||||
this.renderRoot.querySelector("div")!
|
||||
);
|
||||
await copyToClipboard(this._newEncryptionKey, this._copyContainer!);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
@@ -246,10 +245,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
if (!this._params?.currentKey) {
|
||||
return;
|
||||
}
|
||||
await copyToClipboard(
|
||||
this._params.currentKey,
|
||||
this.renderRoot.querySelector("div")!
|
||||
);
|
||||
await copyToClipboard(this._params.currentKey, this._copyContainer!);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiContentCopy, mdiDownload } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-button";
|
||||
@@ -36,6 +36,8 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _newEncryptionKey?: string;
|
||||
|
||||
@query("div") private _copyContainer?: HTMLElement;
|
||||
|
||||
public showDialog(params: SetBackupEncryptionKeyDialogParams): void {
|
||||
this._params = params;
|
||||
this._step = STEPS[0];
|
||||
@@ -178,10 +180,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
private async _copyKeyToClipboard() {
|
||||
await copyToClipboard(
|
||||
this._newEncryptionKey,
|
||||
this.renderRoot.querySelector("div")!
|
||||
);
|
||||
await copyToClipboard(this._newEncryptionKey, this._copyContainer!);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-button";
|
||||
@@ -26,6 +26,8 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _params?: ShowBackupEncryptionKeyDialogParams;
|
||||
|
||||
@query("div") private _copyContainer?: HTMLElement;
|
||||
|
||||
public showDialog(params: ShowBackupEncryptionKeyDialogParams): void {
|
||||
this._params = params;
|
||||
this._open = true;
|
||||
@@ -105,10 +107,7 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
if (!this._params?.currentKey) {
|
||||
return;
|
||||
}
|
||||
await copyToClipboard(
|
||||
this._params?.currentKey,
|
||||
this.renderRoot.querySelector("div")!
|
||||
);
|
||||
await copyToClipboard(this._params?.currentKey, this._copyContainer!);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
|
||||
@@ -250,6 +250,7 @@ class ConfigAnalytics extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _scrollToSection(section: string): void {
|
||||
// eslint-disable-next-line lit/prefer-query-decorators
|
||||
const card = this.shadowRoot?.querySelector(
|
||||
`[data-section="${section}"]`
|
||||
) as HTMLElement;
|
||||
|
||||
@@ -203,7 +203,6 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
.hass=${this.hass}
|
||||
.value=${this._serviceData}
|
||||
.narrow=${this.narrow}
|
||||
show-advanced
|
||||
show-service-id
|
||||
@value-changed=${this._serviceDataChanged}
|
||||
class="card-content ui-mode-content"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { debounce } from "../../../../common/util/debounce";
|
||||
@@ -58,10 +58,14 @@ class HaPanelDevTemplate extends LitElement {
|
||||
|
||||
@state() private _descriptionExpanded = false;
|
||||
|
||||
@query("ha-tip") private _editorTip?: HTMLElement;
|
||||
|
||||
private _template = "";
|
||||
|
||||
private _inited = false;
|
||||
|
||||
private _tipResizeObserver?: ResizeObserver;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this._template && !this._unsubRenderTemplate) {
|
||||
@@ -72,6 +76,8 @@ class HaPanelDevTemplate extends LitElement {
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubscribeTemplate();
|
||||
this._tipResizeObserver?.disconnect();
|
||||
this._tipResizeObserver = undefined;
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
@@ -81,6 +87,7 @@ class HaPanelDevTemplate extends LitElement {
|
||||
this._template = DEMO_TEMPLATE;
|
||||
}
|
||||
this._subscribeTemplate();
|
||||
this._observeTipHeight();
|
||||
this._inited = true;
|
||||
}
|
||||
|
||||
@@ -288,6 +295,21 @@ ${type === "object"
|
||||
`;
|
||||
}
|
||||
|
||||
private _observeTipHeight() {
|
||||
if (!this._editorTip || this._tipResizeObserver) {
|
||||
return;
|
||||
}
|
||||
this._tipResizeObserver = new ResizeObserver((entries) => {
|
||||
const height =
|
||||
entries[0]?.borderBoxSize?.[0]?.blockSize ??
|
||||
entries[0]?.contentRect.height;
|
||||
if (height) {
|
||||
this.style.setProperty("--tip-height", `${height}px`);
|
||||
}
|
||||
});
|
||||
this._tipResizeObserver.observe(this._editorTip);
|
||||
}
|
||||
|
||||
private _expandedChanged(
|
||||
ev: HASSDomEvent<HASSDomEvents["expanded-changed"]>
|
||||
) {
|
||||
@@ -331,6 +353,9 @@ ${type === "object"
|
||||
var(--ha-card-header-font-size, var(--ha-font-size-2xl))
|
||||
);
|
||||
--card-actions-height: calc(1px + var(--ha-space-2) * 2 + 40px);
|
||||
--tip-height-minimal: calc(
|
||||
var(--mdc-icon-size, 24px) + var(--ha-space-4)
|
||||
);
|
||||
--edit-pane-height: calc(
|
||||
100vh - var(--panel-header-height) - var(
|
||||
--description-pane-height
|
||||
@@ -340,8 +365,9 @@ ${type === "object"
|
||||
--code-mirror-max-height: calc(
|
||||
var(--edit-pane-height) - var(--card-header-height) +
|
||||
var(--ha-space-2) - var(--card-actions-height) - var(
|
||||
--ha-space-4
|
||||
) - var(--ha-card-border-width, 1px) *
|
||||
--tip-height,
|
||||
var(--tip-height-minimal)
|
||||
) - var(--ha-space-4) - var(--ha-card-border-width, 1px) *
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,15 +143,17 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
private _filter: string = history.state?.filter || "";
|
||||
|
||||
@state()
|
||||
private _filters: DataTableFilters = {};
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "devices-table-filters-full",
|
||||
state: true,
|
||||
state: false,
|
||||
subscribe: false,
|
||||
serializer: serializeFilters,
|
||||
deserializer: deserializeFilters,
|
||||
})
|
||||
private _filters: DataTableFilters = {};
|
||||
private _storageFilters: DataTableFilters = {};
|
||||
|
||||
@state() private _expandedFilter?: string;
|
||||
|
||||
@@ -188,6 +190,8 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
|
||||
private _ignoreLocationChange = false;
|
||||
|
||||
private _fromUrl = false;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("location-changed", this._locationChanged);
|
||||
@@ -228,6 +232,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
this._filters = this._storageFilters;
|
||||
this._setFiltersFromUrl();
|
||||
}
|
||||
if (changedProps.has("_selected")) {
|
||||
@@ -253,6 +258,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
this._fromUrl = true;
|
||||
this._filter = history.state?.filter || "";
|
||||
|
||||
this._filters = {
|
||||
@@ -295,6 +301,9 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
|
||||
private _clearFilter() {
|
||||
this._filters = {};
|
||||
if (!this._fromUrl) {
|
||||
this._storageFilters = {};
|
||||
}
|
||||
}
|
||||
|
||||
private _devicesAndFilterDomains = memoizeOne(
|
||||
@@ -948,6 +957,9 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
private _filterChanged(ev) {
|
||||
const type = ev.target.localName;
|
||||
this._filters = { ...this._filters, [type]: ev.detail };
|
||||
if (!this._fromUrl) {
|
||||
this._storageFilters = this._filters;
|
||||
}
|
||||
}
|
||||
|
||||
private _batteryEntity(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/entity/ha-statistic-picker";
|
||||
import "../../../../components/ha-button";
|
||||
@@ -53,6 +53,8 @@ export class DialogEnergyBatterySettings
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@query("ha-energy-power-config") private _powerConfigEl?: HaEnergyPowerConfig;
|
||||
|
||||
private _excludeList?: string[];
|
||||
|
||||
private _excludeListPower?: string[];
|
||||
@@ -223,10 +225,7 @@ export class DialogEnergyBatterySettings
|
||||
}
|
||||
|
||||
// Check power config validity
|
||||
const powerConfigEl = this.shadowRoot?.querySelector(
|
||||
"ha-energy-power-config"
|
||||
) as HaEnergyPowerConfig | null;
|
||||
if (powerConfigEl && !powerConfigEl.isValid()) {
|
||||
if (this._powerConfigEl && !this._powerConfigEl.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../components/entity/ha-statistic-picker";
|
||||
@@ -63,6 +63,8 @@ export class DialogEnergyGridSettings
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@query("ha-energy-power-config") private _powerConfigEl?: HaEnergyPowerConfig;
|
||||
|
||||
private _excludeList?: string[];
|
||||
|
||||
private _excludeListPower?: string[];
|
||||
@@ -434,10 +436,7 @@ export class DialogEnergyGridSettings
|
||||
|
||||
// Check power config validity (if power is configured)
|
||||
if (hasPower) {
|
||||
const powerConfigEl = this.shadowRoot?.querySelector(
|
||||
"ha-energy-power-config"
|
||||
) as HaEnergyPowerConfig | null;
|
||||
if (powerConfigEl && !powerConfigEl.isValid()) {
|
||||
if (this._powerConfigEl && !this._powerConfigEl.isValid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeAreaName } from "../../../../common/entity/compute_area_name";
|
||||
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
|
||||
@@ -46,6 +46,9 @@ export class DialogVacuumSegmentMapping
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@query("ha-vacuum-segment-area-mapper")
|
||||
private _mapper?: HaVacuumSegmentAreaMapper;
|
||||
|
||||
private _entry?: ExtEntityRegistryEntry;
|
||||
|
||||
public async showDialog(
|
||||
@@ -91,9 +94,7 @@ export class DialogVacuumSegmentMapping
|
||||
this._submitting = true;
|
||||
|
||||
try {
|
||||
const mapper = this.renderRoot.querySelector(
|
||||
"ha-vacuum-segment-area-mapper"
|
||||
) as HaVacuumSegmentAreaMapper;
|
||||
const mapper = this._mapper!;
|
||||
|
||||
const options: VacuumEntityOptions = {
|
||||
...(this._entry?.options?.vacuum ?? {}),
|
||||
|
||||
@@ -414,7 +414,6 @@ export const configSections: Record<string, PageNavigation[]> = {
|
||||
iconPath: mdiBadgeAccountHorizontal,
|
||||
iconColor: "#5A87FA",
|
||||
core: true,
|
||||
advancedOnly: true,
|
||||
adminOnly: true,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiAlertOutline } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
@@ -120,6 +120,9 @@ export class DialogHelperDetail extends LitElement {
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
@query("ha-input-search")
|
||||
private _searchInput?: HTMLElement & { updateComplete?: Promise<unknown> };
|
||||
|
||||
private _pendingConfigFlow?: {
|
||||
startFlowHandler: string;
|
||||
manifest: IntegrationManifest;
|
||||
@@ -429,9 +432,7 @@ export class DialogHelperDetail extends LitElement {
|
||||
}
|
||||
|
||||
private async _focusSearchInput() {
|
||||
const searchInput = this.shadowRoot?.querySelector("ha-input-search") as
|
||||
| (HTMLElement & { updateComplete?: Promise<unknown> })
|
||||
| null;
|
||||
const searchInput = this._searchInput;
|
||||
|
||||
if (!searchInput) {
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
@@ -35,6 +35,8 @@ class HaCounterForm extends LitElement {
|
||||
|
||||
@state() private _step?: number;
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
set item(item: Counter) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
@@ -57,11 +59,7 @@ class HaCounterForm extends LitElement {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
this.updateComplete.then(() => this._focusElement?.focus());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/input/ha-input";
|
||||
@@ -22,6 +22,8 @@ class HaInputBooleanForm extends LitElement {
|
||||
|
||||
@state() private _icon!: string;
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
set item(item: InputBoolean) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
@@ -34,11 +36,7 @@ class HaInputBooleanForm extends LitElement {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
this.updateComplete.then(() => this._focusElement?.focus());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/input/ha-input";
|
||||
@@ -20,6 +20,8 @@ class HaInputButtonForm extends LitElement {
|
||||
|
||||
@state() private _icon!: string;
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
private _item?: InputButton;
|
||||
|
||||
set item(item: InputButton) {
|
||||
@@ -34,11 +36,7 @@ class HaInputButtonForm extends LitElement {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
this.updateComplete.then(() => this._focusElement?.focus());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/input/ha-input";
|
||||
@@ -27,6 +27,8 @@ class HaInputDateTimeForm extends LitElement {
|
||||
|
||||
@state() private _mode!: "date" | "time" | "datetime";
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
set item(item: InputDateTime) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
@@ -48,11 +50,7 @@ class HaInputDateTimeForm extends LitElement {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
this.updateComplete.then(() => this._focusElement?.focus());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/radio/ha-radio-group";
|
||||
@@ -49,6 +49,8 @@ class HaInputNumberForm extends LitElement {
|
||||
// eslint-disable-next-line: variable-name
|
||||
@state() private _unit_of_measurement?: string;
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
/* Configuring initial value is intentionally not supported because the behavior
|
||||
compared to restoring the value after restart is hard to explain */
|
||||
set item(item: InputNumber) {
|
||||
@@ -76,11 +78,7 @@ class HaInputNumberForm extends LitElement {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
this.updateComplete.then(() => this._focusElement?.focus());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -36,6 +36,8 @@ class HaInputSelectForm extends LitElement {
|
||||
|
||||
@query("#option_input", true) private _optionInput?: HaInput;
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
private _optionMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
@@ -62,11 +64,7 @@ class HaInputSelectForm extends LitElement {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
this.updateComplete.then(() => this._focusElement?.focus());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
@@ -35,6 +35,8 @@ class HaInputTextForm extends LitElement {
|
||||
|
||||
@state() private _pattern?: string;
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
set item(item: InputText) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
@@ -54,11 +56,7 @@ class HaInputTextForm extends LitElement {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
this.updateComplete.then(() => this._focusElement?.focus());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Day } from "date-fns";
|
||||
import { addDays, isSameDay, isSameWeek, nextDay } from "date-fns";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { firstWeekdayIndex } from "../../../../common/datetime/first_weekday";
|
||||
import { formatTime24h } from "../../../../common/datetime/format_time";
|
||||
import { useAmPm } from "../../../../common/datetime/use_am_pm";
|
||||
@@ -65,6 +65,10 @@ class HaScheduleForm extends LitElement {
|
||||
|
||||
@state() private calendar?: Calendar;
|
||||
|
||||
@query("style[data-fullcalendar]") private _fullCalendarStyle?: HTMLElement;
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
private _item?: Schedule;
|
||||
|
||||
set item(item: Schedule) {
|
||||
@@ -96,7 +100,7 @@ class HaScheduleForm extends LitElement {
|
||||
super.disconnectedCallback();
|
||||
this.calendar?.destroy();
|
||||
this.calendar = undefined;
|
||||
this.renderRoot.querySelector("style[data-fullcalendar]")?.remove();
|
||||
this._fullCalendarStyle?.remove();
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
@@ -107,11 +111,7 @@ class HaScheduleForm extends LitElement {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
this.updateComplete.then(() => this._focusElement?.focus());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { createDurationData } from "../../../../common/datetime/create_duration_data";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-checkbox";
|
||||
@@ -33,6 +33,8 @@ class HaTimerForm extends LitElement {
|
||||
|
||||
@state() private _restore!: boolean;
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
set item(item: Timer) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
@@ -51,11 +53,7 @@ class HaTimerForm extends LitElement {
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
this.updateComplete.then(() => this._focusElement?.focus());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
+4
-5
@@ -17,7 +17,7 @@ import {
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { goBack } from "../../../../../common/navigate";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
@@ -84,6 +84,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _multipleNetworks = false;
|
||||
|
||||
@query("#nvm-restore-file") private _restoreFileInput?: HTMLInputElement;
|
||||
|
||||
private _dialogOpen = false;
|
||||
|
||||
private _s2InclusionUnsubscribe?: Promise<UnsubscribeFunc>;
|
||||
@@ -702,10 +704,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _restoreButtonClick() {
|
||||
const fileInput = this.shadowRoot?.querySelector(
|
||||
"#nvm-restore-file"
|
||||
) as HTMLInputElement;
|
||||
fileInput?.click();
|
||||
this._restoreFileInput?.click();
|
||||
}
|
||||
|
||||
private async _handleRestoreFileSelected(ev: Event) {
|
||||
|
||||
@@ -277,6 +277,7 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _scrollToPreviewFeature(previewFeatureId: string): void {
|
||||
// eslint-disable-next-line lit/prefer-query-decorators
|
||||
const card = this.shadowRoot?.querySelector(
|
||||
`[data-feature-id="${previewFeatureId}"]`
|
||||
) as HTMLElement;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiContentCopy } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import "../../../components/ha-alert";
|
||||
@@ -35,6 +35,8 @@ class DialogSystemLogDetail extends LitElement {
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@query(".contents") private _contents?: HTMLElement;
|
||||
|
||||
public async showDialog(params: SystemLogDetailDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._manifest = undefined;
|
||||
@@ -205,9 +207,7 @@ class DialogSystemLogDetail extends LitElement {
|
||||
}
|
||||
|
||||
private async _copyLog(): Promise<void> {
|
||||
const copyElement = this.shadowRoot?.querySelector(
|
||||
".contents"
|
||||
) as HTMLElement;
|
||||
const copyElement = this._contents!;
|
||||
|
||||
let text = copyElement.innerText;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import { substituteBlueprint } from "../../../data/blueprint";
|
||||
import { validateConfig } from "../../../data/config";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
@@ -90,6 +91,8 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
@query("manual-script-editor")
|
||||
private _manualEditor?: HaManualScriptEditor;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
private _newScriptId?: string;
|
||||
|
||||
protected domainHooks: EditorDomainHooks<ScriptConfig> = {
|
||||
@@ -750,7 +753,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
this.blueprintConfig = config;
|
||||
this.config = newConfig;
|
||||
if (this.mode === "yaml") {
|
||||
this.renderRoot.querySelector("ha-yaml-editor")?.setValue(this.config);
|
||||
this._yamlEditor?.setValue(this.config);
|
||||
}
|
||||
this.readOnly = true;
|
||||
this.errors = undefined;
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
@@ -15,10 +15,11 @@ export class AssistPipelineDetailConfig extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public supportedLanguages?: string[];
|
||||
|
||||
@query("ha-form") private _form?: HTMLElement;
|
||||
|
||||
public async focus() {
|
||||
await this.updateComplete;
|
||||
const input = this.renderRoot?.querySelector("ha-form");
|
||||
input?.focus();
|
||||
this._form?.focus();
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
|
||||
@@ -433,6 +433,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
|
||||
private async _editZone(id: string) {
|
||||
await this.updateComplete;
|
||||
// eslint-disable-next-line lit/prefer-query-decorators
|
||||
(this.shadowRoot?.querySelector(`[id="${id}"]`) as HTMLElement)?.click();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import { mdiPencil } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { navigate } from "../../common/navigate";
|
||||
@@ -48,6 +48,8 @@ class PanelHome extends LitElement {
|
||||
|
||||
@state() private _extraActionItems?: ExtraActionItem[];
|
||||
|
||||
@query(".banner") private _banner?: HTMLElement;
|
||||
|
||||
private _loadConfigPromise?: Promise<void>;
|
||||
|
||||
private get _showBanner(): boolean {
|
||||
@@ -299,9 +301,8 @@ class PanelHome extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("_showBanner") || changedProps.has("_lovelace")) {
|
||||
const banner = this.shadowRoot?.querySelector(".banner");
|
||||
if (banner) {
|
||||
this._bannerHeight.observe(banner);
|
||||
if (this._banner) {
|
||||
this._bannerHeight.observe(this._banner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { mdiVolumeHigh, mdiVolumeOff } from "@mdi/js";
|
||||
import { html, nothing } from "lit";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { supportsFeature } from "../../../../common/entity/supports-feature";
|
||||
import "../../../../components/ha-control-button";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { forwardHaptic } from "../../../../data/haptics";
|
||||
import {
|
||||
MediaPlayerEntityFeature,
|
||||
type MediaPlayerEntity,
|
||||
} from "../../../../data/media-player";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
export const renderMuteButton = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: MediaPlayerEntity,
|
||||
showMuteButton: boolean | undefined,
|
||||
disabled: boolean,
|
||||
onToggleMute: (ev: Event) => void
|
||||
): TemplateResult | typeof nothing => {
|
||||
if (
|
||||
!(showMuteButton ?? true) ||
|
||||
!supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_MUTE)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
const isMuted = stateObj.attributes.is_volume_muted;
|
||||
return html`
|
||||
<ha-control-button
|
||||
class="mute"
|
||||
.label=${hass.localize(
|
||||
`ui.card.media_player.${isMuted ? "media_volume_unmute" : "media_volume_mute"}`
|
||||
)}
|
||||
.disabled=${disabled}
|
||||
@click=${onToggleMute}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${isMuted ? mdiVolumeOff : mdiVolumeHigh}
|
||||
></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`;
|
||||
};
|
||||
|
||||
export const toggleMediaPlayerMute = (
|
||||
ev: Event,
|
||||
hass: HomeAssistant,
|
||||
stateObj: MediaPlayerEntity,
|
||||
el: HTMLElement
|
||||
): void => {
|
||||
ev.stopPropagation();
|
||||
forwardHaptic(el, "light");
|
||||
hass.callService("media_player", "volume_mute", {
|
||||
entity_id: stateObj.entity_id,
|
||||
is_volume_muted: !stateObj.attributes.is_volume_muted,
|
||||
});
|
||||
};
|
||||
@@ -5,6 +5,11 @@ import {
|
||||
mdiPower,
|
||||
mdiPowerOff,
|
||||
mdiPowerOn,
|
||||
mdiRepeat,
|
||||
mdiRepeatOff,
|
||||
mdiRepeatOnce,
|
||||
mdiShuffle,
|
||||
mdiShuffleDisabled,
|
||||
mdiSkipNext,
|
||||
mdiSkipPrevious,
|
||||
mdiStop,
|
||||
@@ -59,6 +64,8 @@ const MEDIA_PLAYER_PLAYBACK_CONTROLS_FEATURES: Record<
|
||||
volume_down: [MediaPlayerEntityFeature.VOLUME_STEP],
|
||||
volume_up: [MediaPlayerEntityFeature.VOLUME_STEP],
|
||||
volume_mute: [MediaPlayerEntityFeature.VOLUME_MUTE],
|
||||
shuffle: [MediaPlayerEntityFeature.SHUFFLE_SET],
|
||||
repeat: [MediaPlayerEntityFeature.REPEAT_SET],
|
||||
};
|
||||
|
||||
export const supportsMediaPlayerPlaybackControl = (
|
||||
@@ -293,6 +300,30 @@ class HuiMediaPlayerPlaybackCardFeature
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "shuffle":
|
||||
if (supportsFeature(stateObj, MediaPlayerEntityFeature.SHUFFLE_SET)) {
|
||||
buttons.push({
|
||||
icon:
|
||||
stateObj.attributes.shuffle === true
|
||||
? mdiShuffle
|
||||
: mdiShuffleDisabled,
|
||||
action: "shuffle",
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "repeat":
|
||||
if (supportsFeature(stateObj, MediaPlayerEntityFeature.REPEAT_SET)) {
|
||||
buttons.push({
|
||||
icon:
|
||||
stateObj.attributes.repeat === "all"
|
||||
? mdiRepeat
|
||||
: stateObj.attributes.repeat === "one"
|
||||
? mdiRepeatOnce
|
||||
: mdiRepeatOff,
|
||||
action: "repeat",
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +367,23 @@ class HuiMediaPlayerPlaybackCardFeature
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "shuffle") {
|
||||
this.hass!.callService("media_player", "shuffle_set", {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
shuffle: !this._stateObj.attributes.shuffle,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "repeat") {
|
||||
const repeat = this._stateObj.attributes.repeat ?? "off";
|
||||
this.hass!.callService("media_player", "repeat_set", {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
repeat: repeat === "off" ? "one" : repeat === "one" ? "all" : "off",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass!.callService("media_player", action, {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "../../../data/media-player";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { hasConfigChanged } from "../common/has-changed";
|
||||
import type { LovelaceCardFeature } from "../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
@@ -43,6 +43,11 @@ class HuiMediaPlayerSourceCardFeature
|
||||
|
||||
protected readonly _modesAttribute = "source_list";
|
||||
|
||||
protected get _configuredModes() {
|
||||
const sources = this._config?.sources;
|
||||
return sources?.length ? sources : undefined;
|
||||
}
|
||||
|
||||
protected readonly _serviceDomain = "media_player";
|
||||
|
||||
protected readonly _serviceAction = "select_source";
|
||||
@@ -63,6 +68,13 @@ class HuiMediaPlayerSourceCardFeature
|
||||
};
|
||||
}
|
||||
|
||||
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
|
||||
await import("../editor/config-elements/hui-media-player-source-card-feature-editor");
|
||||
return document.createElement(
|
||||
"hui-media-player-source-card-feature-editor"
|
||||
);
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
const entityId = this.context?.entity_id;
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { mdiVolumeHigh, mdiVolumeOff } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { clamp } from "../../../common/number/clamp";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-number-buttons";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import {
|
||||
MediaPlayerEntityFeature,
|
||||
@@ -16,6 +12,10 @@ import {
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
import {
|
||||
renderMuteButton,
|
||||
toggleMediaPlayerMute,
|
||||
} from "./common/media-player-mute-button";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
MediaPlayerVolumeButtonsCardFeatureConfig,
|
||||
@@ -90,10 +90,6 @@ class HuiMediaPlayerVolumeButtonsCardFeature
|
||||
|
||||
const stateObj = this._stateObj;
|
||||
const disabled = isUnavailableState(stateObj.state);
|
||||
const showMute =
|
||||
(this._config.show_mute_button ?? true) &&
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_MUTE);
|
||||
const isMuted = stateObj.attributes.is_volume_muted;
|
||||
|
||||
const position =
|
||||
stateObj.attributes.volume_level != null
|
||||
@@ -111,22 +107,13 @@ class HuiMediaPlayerVolumeButtonsCardFeature
|
||||
unit="%"
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-control-number-buttons>
|
||||
${showMute
|
||||
? html`
|
||||
<ha-control-button
|
||||
class="mute"
|
||||
.label=${this.hass.localize(
|
||||
`ui.card.media_player.${isMuted ? "media_volume_unmute" : "media_volume_mute"}`
|
||||
)}
|
||||
.disabled=${disabled}
|
||||
@click=${this._toggleMute}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${isMuted ? mdiVolumeOff : mdiVolumeHigh}
|
||||
></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
: nothing}
|
||||
${renderMuteButton(
|
||||
this.hass,
|
||||
stateObj,
|
||||
this._config.show_mute_button,
|
||||
disabled,
|
||||
this._toggleMute
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -139,14 +126,9 @@ class HuiMediaPlayerVolumeButtonsCardFeature
|
||||
});
|
||||
}
|
||||
|
||||
private _toggleMute(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
forwardHaptic(this, "light");
|
||||
this.hass!.callService("media_player", "volume_mute", {
|
||||
entity_id: this._stateObj!.entity_id,
|
||||
is_volume_muted: !this._stateObj!.attributes.is_volume_muted,
|
||||
});
|
||||
}
|
||||
private _toggleMute = (ev: Event) => {
|
||||
toggleMediaPlayerMute(ev, this.hass!, this._stateObj!, this);
|
||||
};
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
@@ -10,8 +10,12 @@ import {
|
||||
type MediaPlayerEntity,
|
||||
} from "../../../data/media-player";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature } from "../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
import {
|
||||
renderMuteButton,
|
||||
toggleMediaPlayerMute,
|
||||
} from "./common/media-player-mute-button";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
MediaPlayerVolumeSliderCardFeatureConfig,
|
||||
@@ -58,6 +62,13 @@ class HuiMediaPlayerVolumeSliderCardFeature
|
||||
};
|
||||
}
|
||||
|
||||
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
|
||||
await import("../editor/config-elements/hui-media-player-volume-slider-card-feature-editor");
|
||||
return document.createElement(
|
||||
"hui-media-player-volume-slider-card-feature-editor"
|
||||
);
|
||||
}
|
||||
|
||||
public setConfig(config: MediaPlayerVolumeSliderCardFeatureConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
@@ -76,9 +87,12 @@ class HuiMediaPlayerVolumeSliderCardFeature
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateObj = this._stateObj;
|
||||
const disabled = isUnavailableState(stateObj.state);
|
||||
|
||||
const position =
|
||||
this._stateObj.attributes.volume_level != null
|
||||
? Math.round(this._stateObj.attributes.volume_level * 100)
|
||||
stateObj.attributes.volume_level != null
|
||||
? Math.round(stateObj.attributes.volume_level * 100)
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
@@ -86,12 +100,19 @@ class HuiMediaPlayerVolumeSliderCardFeature
|
||||
.value=${position}
|
||||
min="0"
|
||||
max="100"
|
||||
.showHandle=${stateActive(this._stateObj)}
|
||||
.disabled=${!this._stateObj || isUnavailableState(this._stateObj.state)}
|
||||
.showHandle=${stateActive(stateObj)}
|
||||
.disabled=${disabled}
|
||||
@value-changed=${this._valueChanged}
|
||||
unit="%"
|
||||
.locale=${this.hass.locale}
|
||||
></ha-control-slider>
|
||||
${renderMuteButton(
|
||||
this.hass,
|
||||
stateObj,
|
||||
this._config.show_mute_button,
|
||||
disabled,
|
||||
this._toggleMute
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -105,8 +126,29 @@ class HuiMediaPlayerVolumeSliderCardFeature
|
||||
});
|
||||
}
|
||||
|
||||
private _toggleMute = (ev: Event) => {
|
||||
toggleMediaPlayerMute(ev, this.hass!, this._stateObj!, this);
|
||||
};
|
||||
|
||||
static get styles() {
|
||||
return cardFeatureStyles;
|
||||
return [
|
||||
cardFeatureStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--feature-button-spacing);
|
||||
}
|
||||
ha-control-slider {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.mute {
|
||||
width: var(--feature-height);
|
||||
height: var(--feature-height);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ export const MEDIA_PLAYER_PLAYBACK_CONTROLS = [
|
||||
"volume_down",
|
||||
"volume_up",
|
||||
"volume_mute",
|
||||
"shuffle",
|
||||
"repeat",
|
||||
] as const;
|
||||
|
||||
export type MediaPlayerPlaybackControl =
|
||||
@@ -78,10 +80,12 @@ export interface MediaPlayerPlaybackCardFeatureConfig {
|
||||
|
||||
export interface MediaPlayerSourceCardFeatureConfig {
|
||||
type: "media-player-source";
|
||||
sources?: string[];
|
||||
}
|
||||
|
||||
export interface MediaPlayerVolumeSliderCardFeatureConfig {
|
||||
type: "media-player-volume-slider";
|
||||
show_mute_button?: boolean;
|
||||
}
|
||||
|
||||
export interface MediaPlayerVolumeButtonsCardFeatureConfig {
|
||||
|
||||
@@ -91,12 +91,17 @@ export function getSuggestedMax(
|
||||
return suggestedMax;
|
||||
}
|
||||
|
||||
function createYAxisLabelFormatter(
|
||||
locale: FrontendLocaleData,
|
||||
fractionDigits: number
|
||||
) {
|
||||
return (value: number): string =>
|
||||
formatNumber(value, locale, { maximumFractionDigits: fractionDigits });
|
||||
function createYAxisLabelFormatter(locale: FrontendLocaleData) {
|
||||
let previousValue: number | undefined;
|
||||
|
||||
return (value: number): string => {
|
||||
const maximumFractionDigits = Math.max(
|
||||
1,
|
||||
-Math.floor(Math.log10(Math.abs(value - (previousValue ?? value) || 1)))
|
||||
);
|
||||
previousValue = value;
|
||||
return formatNumber(value, locale, { maximumFractionDigits });
|
||||
};
|
||||
}
|
||||
|
||||
export function getCommonOptions(
|
||||
@@ -108,8 +113,7 @@ export function getCommonOptions(
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date,
|
||||
formatTotal?: (total: number) => string,
|
||||
detailedDailyData = false,
|
||||
yAxisFractionDigits = 1
|
||||
detailedDailyData = false
|
||||
): ECOption {
|
||||
const suggestedPeriod = getSuggestedPeriod(start, end, detailedDailyData);
|
||||
const suggestedMax = getSuggestedMax(suggestedPeriod, end, detailedDailyData);
|
||||
@@ -148,7 +152,7 @@ export function getCommonOptions(
|
||||
align: "left",
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: createYAxisLabelFormatter(locale, yAxisFractionDigits),
|
||||
formatter: createYAxisLabelFormatter(locale),
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
|
||||
@@ -10,7 +10,6 @@ import { getGraphColorByIndex } from "../../../../common/color/colors";
|
||||
import { getEnergyColor } from "./common/color";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import { computeYAxisFractionDigits } from "../../../../components/chart/y-axis-fraction-digits";
|
||||
import type {
|
||||
DeviceConsumptionEnergyPreference,
|
||||
EnergyData,
|
||||
@@ -76,8 +75,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
|
||||
@state() private _chartData: BarSeriesOption[] = [];
|
||||
|
||||
@state() private _yAxisFractionDigits = 1;
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
@state() private _legendData?: CustomLegendOption["data"];
|
||||
@@ -160,8 +157,7 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
this.hass.config,
|
||||
UNIT,
|
||||
this._compareStart,
|
||||
this._compareEnd,
|
||||
this._yAxisFractionDigits
|
||||
this._compareEnd
|
||||
)}
|
||||
click-label-for-more-info
|
||||
@dataset-hidden=${this._datasetHidden}
|
||||
@@ -212,10 +208,9 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
end: Date,
|
||||
locale: FrontendLocaleData,
|
||||
config: HassConfig,
|
||||
unit: string | undefined,
|
||||
compareStart: Date | undefined,
|
||||
compareEnd: Date | undefined,
|
||||
yAxisFractionDigits: number
|
||||
unit?: string,
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date
|
||||
): ECOption => {
|
||||
const commonOptions = getCommonOptions(
|
||||
start,
|
||||
@@ -225,9 +220,7 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
unit,
|
||||
compareStart,
|
||||
compareEnd,
|
||||
this._formatTotal,
|
||||
false,
|
||||
yAxisFractionDigits
|
||||
this._formatTotal
|
||||
);
|
||||
|
||||
const selected = this._legendData
|
||||
@@ -316,13 +309,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
|
||||
const datasets: BarSeriesOption[] = [];
|
||||
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number) => {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
};
|
||||
|
||||
const { summedData, compareSummedData } = getSummedData(energyData);
|
||||
|
||||
const showUntracked =
|
||||
@@ -345,7 +331,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
energyData.prefs.device_consumption,
|
||||
sorted_devices,
|
||||
childMap,
|
||||
trackY,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -356,7 +341,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
computedStyle,
|
||||
processedCompareData,
|
||||
consumptionCompareData,
|
||||
trackY,
|
||||
true
|
||||
);
|
||||
datasets.push(untrackedCompareData);
|
||||
@@ -378,8 +362,7 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
energyData.statsMetadata,
|
||||
energyData.prefs.device_consumption,
|
||||
sorted_devices,
|
||||
childMap,
|
||||
trackY
|
||||
childMap
|
||||
);
|
||||
|
||||
datasets.push(...processedData);
|
||||
@@ -402,7 +385,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
computedStyle,
|
||||
processedData,
|
||||
consumptionData,
|
||||
trackY,
|
||||
false
|
||||
);
|
||||
datasets.push(untrackedData);
|
||||
@@ -419,7 +401,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
}
|
||||
|
||||
fillDataGapsAndRoundCaps(datasets);
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
this._chartData = datasets;
|
||||
}
|
||||
|
||||
@@ -427,7 +408,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
computedStyle: CSSStyleDeclaration,
|
||||
processedData,
|
||||
consumptionData,
|
||||
trackY: (v: number) => void,
|
||||
compare: boolean
|
||||
): BarSeriesOption {
|
||||
const totalDeviceConsumption: Record<number, number> = {};
|
||||
@@ -463,7 +443,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
dataPoint[0] = compareTransform(new Date(ts)).getTime() + periodOffset;
|
||||
}
|
||||
untrackedConsumption.push(dataPoint);
|
||||
trackY(value);
|
||||
});
|
||||
// random id to always add untracked at the end
|
||||
const order = Date.now();
|
||||
@@ -504,7 +483,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
devices: DeviceConsumptionEnergyPreference[],
|
||||
sorted_devices: string[],
|
||||
childMap: Record<string, string[]>,
|
||||
trackY: (v: number) => void,
|
||||
compare = false
|
||||
) {
|
||||
const data: BarSeriesOption[] = [];
|
||||
@@ -552,7 +530,6 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
cStats?.find((cStat) => cStat.start === point.start)?.change || 0;
|
||||
});
|
||||
|
||||
const y = point.change - sumChildren;
|
||||
const dataPoint: EnergyDataPoint = [
|
||||
computeStatMidpoint(
|
||||
point.start,
|
||||
@@ -560,11 +537,10 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
period,
|
||||
compare ? compareTransform : undefined
|
||||
),
|
||||
y,
|
||||
point.change - sumChildren,
|
||||
point.start,
|
||||
];
|
||||
consumptionData.push(dataPoint);
|
||||
trackY(y);
|
||||
prevStart = point.start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import type { BarSeriesOption } from "echarts/charts";
|
||||
import { getEnergyColor } from "./common/color";
|
||||
import { formatNumber } from "../../../../common/number/format_number";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import { computeYAxisFractionDigits } from "../../../../components/chart/y-axis-fraction-digits";
|
||||
import "../../../../components/ha-card";
|
||||
import type {
|
||||
EnergyData,
|
||||
@@ -65,8 +64,6 @@ export class HuiEnergyGasGraphCard
|
||||
|
||||
@state() private _chartData: BarSeriesOption[] = [];
|
||||
|
||||
@state() private _yAxisFractionDigits = 1;
|
||||
|
||||
@state() private _start = startOfToday();
|
||||
|
||||
@state() private _end = endOfToday();
|
||||
@@ -142,8 +139,7 @@ export class HuiEnergyGasGraphCard
|
||||
this.hass.config,
|
||||
this._unit,
|
||||
this._compareStart,
|
||||
this._compareEnd,
|
||||
this._yAxisFractionDigits
|
||||
this._compareEnd
|
||||
)}
|
||||
chart-type="bar"
|
||||
></ha-chart-base>
|
||||
@@ -173,10 +169,9 @@ export class HuiEnergyGasGraphCard
|
||||
end: Date,
|
||||
locale: FrontendLocaleData,
|
||||
config: HassConfig,
|
||||
unit: string | undefined,
|
||||
compareStart: Date | undefined,
|
||||
compareEnd: Date | undefined,
|
||||
yAxisFractionDigits: number
|
||||
unit?: string,
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date
|
||||
): ECOption =>
|
||||
getCommonOptions(
|
||||
start,
|
||||
@@ -186,9 +181,7 @@ export class HuiEnergyGasGraphCard
|
||||
unit,
|
||||
compareStart,
|
||||
compareEnd,
|
||||
this._formatTotal,
|
||||
false,
|
||||
yAxisFractionDigits
|
||||
this._formatTotal
|
||||
)
|
||||
);
|
||||
|
||||
@@ -210,13 +203,6 @@ export class HuiEnergyGasGraphCard
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number) => {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
};
|
||||
|
||||
if (energyData.statsCompare) {
|
||||
datasets.push(
|
||||
...this._processDataSet(
|
||||
@@ -224,7 +210,6 @@ export class HuiEnergyGasGraphCard
|
||||
energyData.statsMetadata,
|
||||
gasSources,
|
||||
computedStyles,
|
||||
trackY,
|
||||
true
|
||||
)
|
||||
);
|
||||
@@ -245,13 +230,11 @@ export class HuiEnergyGasGraphCard
|
||||
energyData.stats,
|
||||
energyData.statsMetadata,
|
||||
gasSources,
|
||||
computedStyles,
|
||||
trackY
|
||||
computedStyles
|
||||
)
|
||||
);
|
||||
|
||||
fillDataGapsAndRoundCaps(datasets);
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
this._chartData = datasets;
|
||||
this._total = this._processTotal(energyData.stats, gasSources);
|
||||
}
|
||||
@@ -278,7 +261,6 @@ export class HuiEnergyGasGraphCard
|
||||
statisticsMetaData: Record<string, StatisticsMetaData>,
|
||||
gasSources: GasSourceTypeEnergyPreference[],
|
||||
computedStyles: CSSStyleDeclaration,
|
||||
trackY: (v: number) => void,
|
||||
compare = false
|
||||
) {
|
||||
const data: BarSeriesOption[] = [];
|
||||
@@ -318,7 +300,6 @@ export class HuiEnergyGasGraphCard
|
||||
point.start,
|
||||
];
|
||||
gasConsumptionData.push(dataPoint);
|
||||
trackY(point.change);
|
||||
prevStart = point.start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import type { BarSeriesOption, LineSeriesOption } from "echarts/charts";
|
||||
import { getEnergyColor } from "./common/color";
|
||||
import { formatNumber } from "../../../../common/number/format_number";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import { computeYAxisFractionDigits } from "../../../../components/chart/y-axis-fraction-digits";
|
||||
import "../../../../components/ha-card";
|
||||
import type {
|
||||
EnergyData,
|
||||
@@ -67,8 +66,6 @@ export class HuiEnergySolarGraphCard
|
||||
|
||||
@state() private _chartData: ECOption["series"][] = [];
|
||||
|
||||
@state() private _yAxisFractionDigits = 1;
|
||||
|
||||
@state() private _start = startOfToday();
|
||||
|
||||
@state() private _end = endOfToday();
|
||||
@@ -141,8 +138,7 @@ export class HuiEnergySolarGraphCard
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this._compareStart,
|
||||
this._compareEnd,
|
||||
this._yAxisFractionDigits
|
||||
this._compareEnd
|
||||
)}
|
||||
chart-type="bar"
|
||||
></ha-chart-base>
|
||||
@@ -172,9 +168,8 @@ export class HuiEnergySolarGraphCard
|
||||
end: Date,
|
||||
locale: FrontendLocaleData,
|
||||
config: HassConfig,
|
||||
compareStart: Date | undefined,
|
||||
compareEnd: Date | undefined,
|
||||
yAxisFractionDigits: number
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date
|
||||
): ECOption =>
|
||||
getCommonOptions(
|
||||
start,
|
||||
@@ -184,9 +179,7 @@ export class HuiEnergySolarGraphCard
|
||||
"kWh",
|
||||
compareStart,
|
||||
compareEnd,
|
||||
this._formatTotal,
|
||||
false,
|
||||
yAxisFractionDigits
|
||||
this._formatTotal
|
||||
)
|
||||
);
|
||||
|
||||
@@ -217,13 +210,6 @@ export class HuiEnergySolarGraphCard
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number) => {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
};
|
||||
|
||||
if (energyData.statsCompare) {
|
||||
datasets.push(
|
||||
...this._processDataSet(
|
||||
@@ -231,7 +217,6 @@ export class HuiEnergySolarGraphCard
|
||||
energyData.statsMetadata,
|
||||
solarSources,
|
||||
computedStyles,
|
||||
trackY,
|
||||
true
|
||||
)
|
||||
);
|
||||
@@ -252,8 +237,7 @@ export class HuiEnergySolarGraphCard
|
||||
energyData.stats,
|
||||
energyData.statsMetadata,
|
||||
solarSources,
|
||||
computedStyles,
|
||||
trackY
|
||||
computedStyles
|
||||
)
|
||||
);
|
||||
|
||||
@@ -267,13 +251,11 @@ export class HuiEnergySolarGraphCard
|
||||
solarSources,
|
||||
computedStyles.getPropertyValue("--primary-text-color"),
|
||||
energyData.start,
|
||||
energyData.end,
|
||||
trackY
|
||||
energyData.end
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
this._chartData = datasets;
|
||||
this._total = this._processTotal(energyData.stats, solarSources);
|
||||
}
|
||||
@@ -300,7 +282,6 @@ export class HuiEnergySolarGraphCard
|
||||
statisticsMetaData: Record<string, StatisticsMetaData>,
|
||||
solarSources: SolarSourceTypeEnergyPreference[],
|
||||
computedStyles: CSSStyleDeclaration,
|
||||
trackY: (v: number) => void,
|
||||
compare = false
|
||||
) {
|
||||
const data: BarSeriesOption[] = [];
|
||||
@@ -341,7 +322,6 @@ export class HuiEnergySolarGraphCard
|
||||
point.start,
|
||||
];
|
||||
solarProductionData.push(dataPoint);
|
||||
trackY(point.change);
|
||||
prevStart = point.start;
|
||||
}
|
||||
}
|
||||
@@ -395,8 +375,7 @@ export class HuiEnergySolarGraphCard
|
||||
solarSources: SolarSourceTypeEnergyPreference[],
|
||||
borderColor: string,
|
||||
start: Date,
|
||||
end: Date | undefined,
|
||||
trackY: (v: number) => void
|
||||
end?: Date
|
||||
) {
|
||||
const data: LineSeriesOption[] = [];
|
||||
|
||||
@@ -450,9 +429,10 @@ export class HuiEnergySolarGraphCard
|
||||
: 0;
|
||||
}
|
||||
for (const [time, value] of Object.entries(forecastsData)) {
|
||||
const kWh = value / 1000;
|
||||
solarForecastData.push([Number(time) + forecastOffset, kWh]);
|
||||
trackY(kWh);
|
||||
solarForecastData.push([
|
||||
Number(time) + forecastOffset,
|
||||
value / 1000,
|
||||
]);
|
||||
}
|
||||
|
||||
if (solarForecastData.length) {
|
||||
|
||||
@@ -13,7 +13,6 @@ import type {
|
||||
import { getEnergyColor } from "./common/color";
|
||||
import { formatNumber } from "../../../../common/number/format_number";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import { computeYAxisFractionDigits } from "../../../../components/chart/y-axis-fraction-digits";
|
||||
import "../../../../components/ha-card";
|
||||
import "./common/hui-energy-graph-chip";
|
||||
import type {
|
||||
@@ -80,8 +79,6 @@ export class HuiEnergyUsageGraphCard
|
||||
|
||||
@state() private _chartData: BarSeriesOption[] = [];
|
||||
|
||||
@state() private _yAxisFractionDigits = 1;
|
||||
|
||||
@state() private _start = startOfToday();
|
||||
|
||||
@state() private _end = endOfToday();
|
||||
@@ -157,8 +154,7 @@ export class HuiEnergyUsageGraphCard
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this._compareStart,
|
||||
this._compareEnd,
|
||||
this._yAxisFractionDigits
|
||||
this._compareEnd
|
||||
)}
|
||||
chart-type="bar"
|
||||
></ha-chart-base>
|
||||
@@ -193,9 +189,8 @@ export class HuiEnergyUsageGraphCard
|
||||
end: Date,
|
||||
locale: FrontendLocaleData,
|
||||
config: HassConfig,
|
||||
compareStart: Date | undefined,
|
||||
compareEnd: Date | undefined,
|
||||
yAxisFractionDigits: number
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date
|
||||
): ECOption => {
|
||||
const commonOptions = getCommonOptions(
|
||||
start,
|
||||
@@ -205,9 +200,7 @@ export class HuiEnergyUsageGraphCard
|
||||
"kWh",
|
||||
compareStart,
|
||||
compareEnd,
|
||||
this._formatTotal,
|
||||
false,
|
||||
yAxisFractionDigits
|
||||
this._formatTotal
|
||||
);
|
||||
const options: ECOption = {
|
||||
...commonOptions,
|
||||
@@ -244,13 +237,6 @@ export class HuiEnergyUsageGraphCard
|
||||
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||
const datasets: BarSeriesOption[] = [];
|
||||
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number) => {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
};
|
||||
|
||||
const statIds: {
|
||||
to_grid?: string[];
|
||||
from_grid?: string[];
|
||||
@@ -355,7 +341,6 @@ export class HuiEnergyUsageGraphCard
|
||||
colorIndices,
|
||||
computedStyles,
|
||||
labels,
|
||||
trackY,
|
||||
true
|
||||
)
|
||||
);
|
||||
@@ -382,7 +367,6 @@ export class HuiEnergyUsageGraphCard
|
||||
colorIndices,
|
||||
computedStyles,
|
||||
labels,
|
||||
trackY,
|
||||
false
|
||||
)
|
||||
);
|
||||
@@ -390,7 +374,6 @@ export class HuiEnergyUsageGraphCard
|
||||
// @ts-expect-error
|
||||
datasets.sort((a, b) => a.order - b.order);
|
||||
fillDataGapsAndRoundCaps(datasets);
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
this._chartData = datasets;
|
||||
this._total = this._processTotal(consumption);
|
||||
}
|
||||
@@ -420,7 +403,6 @@ export class HuiEnergyUsageGraphCard
|
||||
used_solar: string;
|
||||
used_battery: string;
|
||||
},
|
||||
trackY: (v: number) => void,
|
||||
compare = false
|
||||
) {
|
||||
const data: BarSeriesOption[] = [];
|
||||
@@ -522,17 +504,18 @@ export class HuiEnergyUsageGraphCard
|
||||
// Process chart data.
|
||||
for (const key of uniqueKeys) {
|
||||
const value = source[key] || 0;
|
||||
const y =
|
||||
const dataPoint: EnergyDataPoint = [
|
||||
key + periodOffset,
|
||||
value && ["to_grid", "to_battery"].includes(type)
|
||||
? -1 * value
|
||||
: value;
|
||||
const dataPoint: EnergyDataPoint = [key + periodOffset, y, key];
|
||||
: value,
|
||||
key,
|
||||
];
|
||||
if (compare) {
|
||||
dataPoint[0] =
|
||||
compareTransform(new Date(key)).getTime() + periodOffset;
|
||||
}
|
||||
points.push(dataPoint);
|
||||
trackY(y);
|
||||
}
|
||||
|
||||
data.push({
|
||||
|
||||
@@ -8,7 +8,6 @@ import memoizeOne from "memoize-one";
|
||||
import type { BarSeriesOption } from "echarts/charts";
|
||||
import { getEnergyColor } from "./common/color";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import { computeYAxisFractionDigits } from "../../../../components/chart/y-axis-fraction-digits";
|
||||
import "../../../../components/ha-card";
|
||||
import type {
|
||||
EnergyData,
|
||||
@@ -65,8 +64,6 @@ export class HuiEnergyWaterGraphCard
|
||||
|
||||
@state() private _chartData: BarSeriesOption[] = [];
|
||||
|
||||
@state() private _yAxisFractionDigits = 1;
|
||||
|
||||
@state() private _start = startOfToday();
|
||||
|
||||
@state() private _end = endOfToday();
|
||||
@@ -142,8 +139,7 @@ export class HuiEnergyWaterGraphCard
|
||||
this.hass.config,
|
||||
this._unit,
|
||||
this._compareStart,
|
||||
this._compareEnd,
|
||||
this._yAxisFractionDigits
|
||||
this._compareEnd
|
||||
)}
|
||||
chart-type="bar"
|
||||
></ha-chart-base>
|
||||
@@ -173,10 +169,9 @@ export class HuiEnergyWaterGraphCard
|
||||
end: Date,
|
||||
locale: FrontendLocaleData,
|
||||
config: HassConfig,
|
||||
unit: string | undefined,
|
||||
compareStart: Date | undefined,
|
||||
compareEnd: Date | undefined,
|
||||
yAxisFractionDigits: number
|
||||
unit?: string,
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date
|
||||
): ECOption =>
|
||||
getCommonOptions(
|
||||
start,
|
||||
@@ -186,9 +181,7 @@ export class HuiEnergyWaterGraphCard
|
||||
unit,
|
||||
compareStart,
|
||||
compareEnd,
|
||||
this._formatTotal,
|
||||
false,
|
||||
yAxisFractionDigits
|
||||
this._formatTotal
|
||||
)
|
||||
);
|
||||
|
||||
@@ -210,13 +203,6 @@ export class HuiEnergyWaterGraphCard
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number) => {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
};
|
||||
|
||||
if (energyData.statsCompare) {
|
||||
datasets.push(
|
||||
...this._processDataSet(
|
||||
@@ -224,7 +210,6 @@ export class HuiEnergyWaterGraphCard
|
||||
energyData.statsMetadata,
|
||||
waterSources,
|
||||
computedStyles,
|
||||
trackY,
|
||||
true
|
||||
)
|
||||
);
|
||||
@@ -245,13 +230,11 @@ export class HuiEnergyWaterGraphCard
|
||||
energyData.stats,
|
||||
energyData.statsMetadata,
|
||||
waterSources,
|
||||
computedStyles,
|
||||
trackY
|
||||
computedStyles
|
||||
)
|
||||
);
|
||||
|
||||
fillDataGapsAndRoundCaps(datasets);
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
this._chartData = datasets;
|
||||
this._total = this._processTotal(energyData.stats, waterSources);
|
||||
}
|
||||
@@ -278,7 +261,6 @@ export class HuiEnergyWaterGraphCard
|
||||
statisticsMetaData: Record<string, StatisticsMetaData>,
|
||||
waterSources: WaterSourceTypeEnergyPreference[],
|
||||
computedStyles: CSSStyleDeclaration,
|
||||
trackY: (v: number) => void,
|
||||
compare = false
|
||||
) {
|
||||
const data: BarSeriesOption[] = [];
|
||||
@@ -318,7 +300,6 @@ export class HuiEnergyWaterGraphCard
|
||||
point.start,
|
||||
];
|
||||
waterConsumptionData.push(dataPoint);
|
||||
trackY(point.change);
|
||||
prevStart = point.start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import memoizeOne from "memoize-one";
|
||||
import type { LineSeriesOption } from "echarts/charts";
|
||||
import { LinearGradient } from "../../../../resources/echarts/echarts";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import { computeYAxisFractionDigits } from "../../../../components/chart/y-axis-fraction-digits";
|
||||
import "../../../../components/ha-card";
|
||||
import type { EnergyData } from "../../../../data/energy";
|
||||
import {
|
||||
@@ -54,8 +53,6 @@ export class HuiPowerSourcesGraphCard
|
||||
|
||||
@state() private _chartData: LineSeriesOption[] = [];
|
||||
|
||||
@state() private _yAxisFractionDigits = 1;
|
||||
|
||||
@state() private _legendData?: CustomLegendOption["data"];
|
||||
|
||||
@state() private _start = startOfToday();
|
||||
@@ -120,8 +117,7 @@ export class HuiPowerSourcesGraphCard
|
||||
this.hass.config,
|
||||
this._compareStart,
|
||||
this._compareEnd,
|
||||
this._legendData,
|
||||
this._yAxisFractionDigits
|
||||
this._legendData
|
||||
)}
|
||||
></ha-chart-base>
|
||||
${!this._chartData.some((dataset) => dataset.data!.length)
|
||||
@@ -144,10 +140,9 @@ export class HuiPowerSourcesGraphCard
|
||||
end: Date,
|
||||
locale: FrontendLocaleData,
|
||||
config: HassConfig,
|
||||
compareStart: Date | undefined,
|
||||
compareEnd: Date | undefined,
|
||||
legendData: CustomLegendOption["data"] | undefined,
|
||||
yAxisFractionDigits: number
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date,
|
||||
legendData?: CustomLegendOption["data"]
|
||||
): ECOption => ({
|
||||
...getCommonOptions(
|
||||
start,
|
||||
@@ -158,8 +153,7 @@ export class HuiPowerSourcesGraphCard
|
||||
compareStart,
|
||||
compareEnd,
|
||||
undefined,
|
||||
true,
|
||||
yAxisFractionDigits
|
||||
true
|
||||
),
|
||||
legend: {
|
||||
show: this._config?.show_legend !== false,
|
||||
@@ -199,13 +193,6 @@ export class HuiPowerSourcesGraphCard
|
||||
|
||||
const computedStyles = getComputedStyle(this);
|
||||
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number) => {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
};
|
||||
|
||||
for (const source of energyData.prefs.energy_sources) {
|
||||
if (source.type === "solar") {
|
||||
if (source.stat_rate) {
|
||||
@@ -258,8 +245,7 @@ export class HuiPowerSourcesGraphCard
|
||||
}
|
||||
}
|
||||
return stats;
|
||||
}),
|
||||
trackY
|
||||
})
|
||||
);
|
||||
datasets.push({
|
||||
...commonSeriesOptions,
|
||||
@@ -321,7 +307,6 @@ export class HuiPowerSourcesGraphCard
|
||||
this._end = energyData.end || endOfToday();
|
||||
|
||||
this._chartData = fillLineGaps(datasets);
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
|
||||
const usageData: NonNullable<LineSeriesOption["data"]> = [];
|
||||
this._chartData[0]?.data!.forEach((item, i) => {
|
||||
@@ -368,7 +353,7 @@ export class HuiPowerSourcesGraphCard
|
||||
});
|
||||
}
|
||||
|
||||
private _processData(stats: StatisticValue[][], trackY: (v: number) => void) {
|
||||
private _processData(stats: StatisticValue[][]) {
|
||||
const data: Record<number, number[]> = {};
|
||||
stats.forEach((statSet) => {
|
||||
statSet.forEach((point) => {
|
||||
@@ -384,12 +369,8 @@ export class HuiPowerSourcesGraphCard
|
||||
Object.entries(data).forEach(([x, y]) => {
|
||||
const ts = Number(x);
|
||||
const sumY = y.reduce((a, b) => a + b, 0);
|
||||
const pos = Math.max(0, sumY);
|
||||
const neg = Math.min(0, sumY);
|
||||
positive.push([ts, pos]);
|
||||
negative.push([ts, neg]);
|
||||
trackY(pos);
|
||||
trackY(neg);
|
||||
positive.push([ts, Math.max(0, sumY)]);
|
||||
negative.push([ts, Math.min(0, sumY)]);
|
||||
});
|
||||
return { positive, negative };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { customElement, query, queryAll, state } from "lit/decorators";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
@@ -75,6 +75,10 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@state() private _config?: EntitiesCardConfig;
|
||||
|
||||
@queryAll("#states > div > *") private _rowElements!: NodeListOf<HTMLElement>;
|
||||
|
||||
@query("hui-entities-toggle") private _entitiesToggle?: HTMLElement;
|
||||
|
||||
private _hass?: HomeAssistant;
|
||||
|
||||
private _configEntities?: LovelaceRowConfig[];
|
||||
@@ -102,22 +106,17 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
|
||||
set hass(hass: HomeAssistant) {
|
||||
this._hass = hass;
|
||||
this.shadowRoot
|
||||
?.querySelectorAll("#states > div > *")
|
||||
.forEach((element: unknown) => {
|
||||
(element as LovelaceRow).hass = hass;
|
||||
});
|
||||
this._rowElements.forEach((element: unknown) => {
|
||||
(element as LovelaceRow).hass = hass;
|
||||
});
|
||||
if (this._headerElement) {
|
||||
this._headerElement.hass = hass;
|
||||
}
|
||||
if (this._footerElement) {
|
||||
this._footerElement.hass = hass;
|
||||
}
|
||||
const entitiesToggle = this.shadowRoot?.querySelector(
|
||||
"hui-entities-toggle"
|
||||
);
|
||||
if (entitiesToggle) {
|
||||
(entitiesToggle as any).hass = hass;
|
||||
if (this._entitiesToggle) {
|
||||
(this._entitiesToggle as any).hass = hass;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,7 +225,11 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("hass") || changedProps.has("_config")) {
|
||||
if (
|
||||
changedProps.has("hass") ||
|
||||
changedProps.has("_config") ||
|
||||
changedProps.has("_metadata")
|
||||
) {
|
||||
this._computeNames();
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@query("ha-input", true) private _input!: HaInput;
|
||||
|
||||
@query("ha-list") private _list?: List;
|
||||
|
||||
private _unsubItems?: Promise<UnsubscribeFunc>;
|
||||
|
||||
private _refreshTimer?: number;
|
||||
@@ -764,7 +766,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
let focusedIndex: number | undefined;
|
||||
let list: List | undefined;
|
||||
if (ev.type === "keydown") {
|
||||
list = this.renderRoot.querySelector("ha-list")!;
|
||||
list = this._list!;
|
||||
focusedIndex = list.getFocusedItemIndex();
|
||||
}
|
||||
const item = this._getItem(ev.currentTarget.itemId);
|
||||
@@ -894,7 +896,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
|
||||
private async _moveItem(oldIndex: number, newIndex: number) {
|
||||
await this.updateComplete;
|
||||
|
||||
const list = this.renderRoot.querySelector("ha-list")!;
|
||||
const list = this._list!;
|
||||
|
||||
const items = list.children;
|
||||
|
||||
|
||||
@@ -217,7 +217,6 @@ export class HuiActionEditor extends LitElement {
|
||||
<ha-service-control
|
||||
.hass=${this.hass}
|
||||
.value=${this._serviceAction(this.config)}
|
||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||
narrow
|
||||
@value-changed=${this._serviceValueChanged}
|
||||
></ha-service-control>
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { until } from "lit/directives/until";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -62,14 +62,15 @@ export class HuiCardPicker extends LitElement {
|
||||
|
||||
@state() private _filter = "";
|
||||
|
||||
@query("ha-input-search") private _searchInput?: HTMLElement;
|
||||
|
||||
private _unusedEntities?: string[];
|
||||
|
||||
private _usedEntities?: string[];
|
||||
|
||||
public async focus(): Promise<void> {
|
||||
const searchInput = this.renderRoot.querySelector("ha-input-search");
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
if (this._searchInput) {
|
||||
this._searchInput.focus();
|
||||
} else {
|
||||
await this.updateComplete;
|
||||
this.focus();
|
||||
|
||||
-1
@@ -65,7 +65,6 @@ export class HuiServiceButtonElementEditor
|
||||
<ha-service-control
|
||||
.hass=${this.hass}
|
||||
.value=${this._serviceData(this._config)}
|
||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||
narrow
|
||||
@value-changed=${this._serviceDataChanged}
|
||||
></ha-service-control>
|
||||
|
||||
@@ -96,7 +96,6 @@ export class HuiButtonCardFeatureEditor
|
||||
hide-description
|
||||
.hass=${this.hass}
|
||||
.value=${scriptData}
|
||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||
.narrow=${false}
|
||||
@value-changed=${this._scriptFieldVariablesChanged}
|
||||
></ha-service-control
|
||||
|
||||
@@ -152,7 +152,9 @@ const EDITABLES_FEATURE_TYPES = new Set<UiFeatureTypes>([
|
||||
"lawn-mower-commands",
|
||||
"media-player-playback",
|
||||
"light-color-favorites",
|
||||
"media-player-source",
|
||||
"media-player-volume-buttons",
|
||||
"media-player-volume-slider",
|
||||
"numeric-input",
|
||||
"select-options",
|
||||
"trend-graph",
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import { 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 "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { MediaPlayerEntity } from "../../../../data/media-player";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
MediaPlayerSourceCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
|
||||
@customElement("hui-media-player-source-card-feature-editor")
|
||||
export class HuiMediaPlayerSourceCardFeatureEditor
|
||||
extends LitElement
|
||||
implements LovelaceCardFeatureEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@state() private _config?: MediaPlayerSourceCardFeatureConfig;
|
||||
|
||||
public setConfig(config: MediaPlayerSourceCardFeatureConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(stateObj?: MediaPlayerEntity) =>
|
||||
[
|
||||
{
|
||||
name: "sources",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
mode: "list" as const,
|
||||
reorder: true,
|
||||
options:
|
||||
stateObj?.attributes.source_list?.map((source) => ({
|
||||
value: source,
|
||||
label: source,
|
||||
})) ?? [],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateObj = this.context?.entity_id
|
||||
? (this.hass.states[this.context.entity_id] as
|
||||
| MediaPlayerEntity
|
||||
| undefined)
|
||||
: undefined;
|
||||
|
||||
const schema = this._schema(stateObj);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._config}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(
|
||||
ev: ValueChangedEvent<MediaPlayerSourceCardFeatureConfig>
|
||||
): void {
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "sources":
|
||||
return this.hass?.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-source.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-media-player-source-card-feature-editor": HuiMediaPlayerSourceCardFeatureEditor;
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import { 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 "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import { supportsFeature } from "../../../../common/entity/supports-feature";
|
||||
import { MediaPlayerEntityFeature } from "../../../../data/media-player";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
MediaPlayerVolumeSliderCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
|
||||
@customElement("hui-media-player-volume-slider-card-feature-editor")
|
||||
export class HuiMediaPlayerVolumeSliderCardFeatureEditor
|
||||
extends LitElement
|
||||
implements LovelaceCardFeatureEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@state() private _config?: MediaPlayerVolumeSliderCardFeatureConfig;
|
||||
|
||||
public setConfig(config: MediaPlayerVolumeSliderCardFeatureConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(supportsMute: boolean) =>
|
||||
[
|
||||
{
|
||||
name: "show_mute_button",
|
||||
disabled: !supportsMute,
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateObj = this.context?.entity_id
|
||||
? this.hass.states[this.context.entity_id]
|
||||
: undefined;
|
||||
const supportsMute =
|
||||
!!stateObj &&
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_MUTE);
|
||||
|
||||
const data: MediaPlayerVolumeSliderCardFeatureConfig = {
|
||||
type: "media-player-volume-slider",
|
||||
show_mute_button: this._config.show_mute_button ?? true,
|
||||
};
|
||||
|
||||
const schema = this._schema(supportsMute);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(
|
||||
ev: ValueChangedEvent<MediaPlayerVolumeSliderCardFeatureConfig>
|
||||
): void {
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-volume-slider.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-media-player-volume-slider-card-feature-editor": HuiMediaPlayerVolumeSliderCardFeatureEditor;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiArrowLeft, mdiArrowRight, mdiPlus } from "@mdi/js";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button";
|
||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
@@ -30,6 +30,12 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
@state() private _config?: LovelaceViewConfig;
|
||||
|
||||
@query("#main") private _oldMain?: HTMLElement;
|
||||
|
||||
@query("#sidebar") private _oldSidebar?: HTMLElement;
|
||||
|
||||
@query(".container") private _container?: HTMLElement;
|
||||
|
||||
private _mqlListenerRef?: () => void;
|
||||
|
||||
private _mql?: MediaQueryList;
|
||||
@@ -123,20 +129,18 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
||||
}
|
||||
|
||||
if (this.hasUpdated) {
|
||||
const oldMain = this.renderRoot.querySelector("#main");
|
||||
const oldSidebar = this.renderRoot.querySelector("#sidebar");
|
||||
const container = this.renderRoot.querySelector(".container")!;
|
||||
if (oldMain) {
|
||||
container.removeChild(oldMain);
|
||||
const container = this._container!;
|
||||
if (this._oldMain) {
|
||||
container.removeChild(this._oldMain);
|
||||
}
|
||||
if (oldSidebar) {
|
||||
container.removeChild(oldSidebar);
|
||||
if (this._oldSidebar) {
|
||||
container.removeChild(this._oldSidebar);
|
||||
}
|
||||
container.appendChild(mainDiv);
|
||||
container.appendChild(sidebarDiv);
|
||||
} else {
|
||||
this.updateComplete.then(() => {
|
||||
const container = this.renderRoot.querySelector(".container")!;
|
||||
const container = this._container!;
|
||||
container.appendChild(mainDiv);
|
||||
container.appendChild(sidebarDiv);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-hls-player";
|
||||
@@ -16,6 +16,8 @@ export class HuiDialogWebBrowserPlayMedia extends LitElement {
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@query("img") private _img?: HTMLImageElement;
|
||||
|
||||
public showDialog(params: WebBrowserPlayMediaDialogParams): void {
|
||||
this._params = params;
|
||||
this._open = true;
|
||||
@@ -26,10 +28,9 @@ export class HuiDialogWebBrowserPlayMedia extends LitElement {
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
const img = this.renderRoot.querySelector("img");
|
||||
if (img) {
|
||||
if (this._img) {
|
||||
// Unload streaming images so the connection can be closed
|
||||
img.src = "";
|
||||
this._img.src = "";
|
||||
}
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
|
||||
@@ -259,6 +259,8 @@
|
||||
"volume_up": "Volume up",
|
||||
"volume_down": "Volume down",
|
||||
"volume_mute": "Mute",
|
||||
"shuffle": "Shuffle",
|
||||
"repeat": "Repeat",
|
||||
"media_volume_up": "Volume up",
|
||||
"media_volume_down": "Volume down",
|
||||
"media_volume_mute": "Volume mute",
|
||||
@@ -9992,7 +9994,8 @@
|
||||
"label": "Media player sound mode"
|
||||
},
|
||||
"media-player-source": {
|
||||
"label": "Media player source"
|
||||
"label": "Media player source",
|
||||
"sources": "Sources"
|
||||
},
|
||||
"media-player-volume-buttons": {
|
||||
"label": "Media player volume buttons",
|
||||
@@ -10000,7 +10003,8 @@
|
||||
"show_mute_button": "Show mute button"
|
||||
},
|
||||
"media-player-volume-slider": {
|
||||
"label": "Media player volume slider"
|
||||
"label": "Media player volume slider",
|
||||
"show_mute_button": "Show mute button"
|
||||
},
|
||||
"vacuum-commands": {
|
||||
"label": "Vacuum commands",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user