Compare commits

..

1 Commits

Author SHA1 Message Date
Petar Petrov b391d6d36d Fix Y-axis label precision in statistics and history charts 2026-05-14 10:01:21 +03:00
35 changed files with 590 additions and 524 deletions
+1
View File
@@ -1,3 +1,4 @@
import "@material/mwc-drawer";
import "@material/mwc-top-app-bar-fixed";
import { html, css, LitElement } from "lit";
import { customElement } from "lit/decorators";
+10 -25
View File
@@ -1,3 +1,4 @@
import "@material/mwc-drawer";
import "@material/mwc-top-app-bar-fixed";
import { mdiMenu, mdiSwapHorizontal } from "@mdi/js";
import type { PropertyValues } from "lit";
@@ -6,8 +7,6 @@ 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";
@@ -40,8 +39,8 @@ class HaGallery extends LitElement {
@query("notification-manager")
private _notifications!: HTMLElementTagNameMap["notification-manager"];
@query("ha-drawer")
private _drawer!: HaDrawer;
@query("mwc-drawer")
private _drawer!: HTMLElementTagNameMap["mwc-drawer"];
private _narrow = window.matchMedia("(max-width: 600px)").matches;
@@ -76,14 +75,15 @@ class HaGallery extends LitElement {
}
return html`
<ha-drawer
.direction=${this._rtl ? "rtl" : "ltr"}
<mwc-drawer
hasHeader
.open=${!this._narrow}
.type=${this._narrow ? "modal" : "dismissible"}
>
<div class="drawer-title">Home Assistant Design</div>
<span slot="title">Home Assistant Design</span>
<!-- <span slot="subtitle">subtitle</span> -->
<div class="sidebar">${sidebar}</div>
<div slot="appContent" class="app-content">
<div slot="appContent">
<mwc-top-app-bar-fixed>
<ha-icon-button
slot="navigationIcon"
@@ -144,7 +144,7 @@ class HaGallery extends LitElement {
</div>
</div>
</div>
</ha-drawer>
</mwc-drawer>
<notification-manager
.hass=${FAKE_HASS}
id="notifications"
@@ -226,27 +226,12 @@ 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;
@@ -270,7 +255,7 @@ class HaGallery extends LitElement {
opacity: 0.12;
}
.app-content {
div[slot="appContent"] {
display: flex;
flex-direction: column;
min-height: 100vh;
+5 -4
View File
@@ -64,6 +64,7 @@
"@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",
@@ -141,7 +142,7 @@
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.1.0",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.5.11",
"@rsdoctor/rspack-plugin": "1.5.10",
"@rspack/core": "2.0.2",
"@rspack/dev-server": "2.0.1",
"@types/babel__plugin-transform-runtime": "7.9.5",
@@ -161,7 +162,7 @@
"@types/sortablejs": "1.15.9",
"@types/tar": "7.0.87",
"@types/webspeechapi": "0.0.29",
"@vitest/coverage-v8": "4.1.6",
"@vitest/coverage-v8": "4.1.5",
"babel-loader": "10.1.1",
"babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.4",
@@ -202,9 +203,9 @@
"terser-webpack-plugin": "5.6.0",
"ts-lit-plugin": "2.0.2",
"typescript": "6.0.3",
"typescript-eslint": "8.59.3",
"typescript-eslint": "8.59.2",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.1.6",
"vitest": "4.1.5",
"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"
+6
View File
@@ -5,6 +5,7 @@ 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 = (
@@ -26,3 +27,8 @@ 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);
@@ -12,6 +12,7 @@ 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 {
@@ -117,9 +118,7 @@ export class StateHistoryChartLine extends LitElement {
private _chartTime: Date = new Date();
private _previousYAxisLabelValue = 0;
private _yAxisMaximumFractionDigits = 0;
private _yAxisFractionDigits = 1;
protected render() {
return html`
@@ -436,6 +435,14 @@ 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;
}
@@ -471,6 +478,7 @@ export class StateHistoryChartLine extends LitElement {
d.data!.push([timestamp, prevValues[i]]);
}
d.data!.push([timestamp, datavalues[i]]);
trackY(datavalues[i]);
});
prevValues = datavalues;
};
@@ -821,6 +829,7 @@ export class StateHistoryChartLine extends LitElement {
const currentValue = stateObj ? safeParseFloat(stateObj.state) : null;
if (currentValue !== null) {
data[0].data!.push([now, currentValue]);
trackY(currentValue);
}
}
@@ -828,6 +837,7 @@ 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;
@@ -861,20 +871,8 @@ 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._yAxisMaximumFractionDigits,
maximumFractionDigits: this._yAxisFractionDigits,
});
const width = measureTextWidth(label, 12) + 5;
if (width > this._yWidth) {
@@ -884,7 +882,6 @@ export class StateHistoryChartLine extends LitElement {
chartIndex: this.chartIndex,
});
}
this._previousYAxisLabelValue = value;
return label;
};
+19 -15
View File
@@ -41,6 +41,7 @@ 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",
@@ -131,7 +132,7 @@ export class StatisticsChart extends LitElement {
private _computedStyle?: CSSStyleDeclaration;
private _previousYAxisLabelValue = 0;
private _yAxisFractionDigits = 1;
protected shouldUpdate(changedProps: PropertyValues<this>): boolean {
return changedProps.size > 1 || !changedProps.has("hass");
@@ -495,6 +496,14 @@ 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;
@@ -600,6 +609,9 @@ 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) {
@@ -610,6 +622,7 @@ 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;
@@ -822,6 +835,7 @@ export class StatisticsChart extends LitElement {
val.push(currentValue);
}
statDataSets[i].data!.push([now, ...val]);
trackY(val[val.length - 1]);
});
}
}
@@ -855,6 +869,7 @@ 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
@@ -888,21 +903,10 @@ export class StatisticsChart extends LitElement {
return Math.abs(value) < 1 ? value : roundingFn(value);
}
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,
private _formatYAxisLabel = (value: number) =>
formatNumber(value, this.hass.locale, {
maximumFractionDigits: this._yAxisFractionDigits,
});
this._previousYAxisLabelValue = value;
return label;
};
static styles = css`
:host {
@@ -0,0 +1,9 @@
// 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)));
}
+3 -27
View File
@@ -25,7 +25,7 @@ export class HaDrawer extends LitElement {
@property({ reflect: true }) public direction: "ltr" | "rtl" = "ltr";
@property({ reflect: true }) public type: "" | "dismissible" | "modal" = "";
@property() public type = "";
@property({ type: Boolean, reflect: true }) public open = false;
@@ -105,11 +105,7 @@ export class HaDrawer extends LitElement {
}
private _handleDrawerTransitionStart = (ev: TransitionEvent) => {
if (
ev.propertyName !==
(this.type === "dismissible" ? "transform" : "width") ||
this._sidebarTransitionActive
) {
if (ev.propertyName !== "width" || this._sidebarTransitionActive) {
return;
}
this._sidebarTransitionActive = true;
@@ -120,11 +116,7 @@ export class HaDrawer extends LitElement {
};
private _handleDrawerTransitionEnd = (ev: TransitionEvent) => {
if (
ev.propertyName !==
(this.type === "dismissible" ? "transform" : "width") ||
!this._sidebarTransitionActive
) {
if (ev.propertyName !== "width" || !this._sidebarTransitionActive) {
return;
}
this._sidebarTransitionActive = false;
@@ -308,22 +300,6 @@ 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);
+7 -1
View File
@@ -86,6 +86,9 @@ 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;
@@ -663,7 +666,10 @@ export class HaServiceControl extends LitElement {
? this.hass.services[domain][serviceName].description_placeholders
: undefined;
return dataField.selector
return dataField.selector &&
(!dataField.advanced ||
this.showAdvanced ||
(this._value?.data && this._value.data[dataField.key] !== undefined))
? html`<ha-settings-row .narrow=${this.narrow}>
${!showOptional
? hasOptional
@@ -120,6 +120,7 @@ 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>
+4 -2
View File
@@ -214,8 +214,10 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
if (
changedProperties.has("tabs") ||
(changedProperties.has("hass") &&
this.hass?.config.components !==
changedProperties.get("hass")?.config.components)
(this.hass?.config.components !==
changedProperties.get("hass")?.config.components ||
this.hass?.userData?.showAdvanced !==
changedProperties.get("hass")?.userData?.showAdvanced))
) {
this.showTabs =
this.tabs.filter((page) => canShowPage(this.hass, page)).length > 1;
+1
View File
@@ -33,6 +33,7 @@ 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;
@@ -85,6 +85,7 @@ 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>
@@ -203,6 +203,7 @@ 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"
@@ -3,7 +3,6 @@ import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { debounce } from "../../../../common/util/debounce";
import "../../../../components/ha-alert";
@@ -93,7 +92,6 @@ class HaPanelDevTemplate extends LitElement {
? "list"
: "dict"
: type;
const editorTipHeight = this._getTipHeight();
return html`
<div class="content">
@@ -145,10 +143,7 @@ class HaPanelDevTemplate extends LitElement {
layout: !this.narrow,
horizontal: !this.narrow,
})}"
style=${styleMap({
"--description-expanded": `${this._descriptionExpanded ? 1 : 0}`,
"--tip-height": `${editorTipHeight}`,
})}
style="--description-expanded: ${this._descriptionExpanded ? 1 : 0}"
>
<ha-card
class="edit-pane"
@@ -178,7 +173,7 @@ class HaPanelDevTemplate extends LitElement {
${this.hass.localize("ui.common.clear")}
</ha-button>
</div>
<ha-tip id="editor-tip" .hass=${this.hass}>
<ha-tip .hass=${this.hass}>
${this.hass.localize(
"ui.panel.config.developer-tools.tabs.templates.keyboard_tip",
{
@@ -293,18 +288,6 @@ ${type === "object"
`;
}
private _getTipHeight(): string | undefined {
const tip = this.shadowRoot?.getElementById("editor-tip");
if (tip) {
const height = tip.scrollHeight;
if (!isNaN(height)) {
return `${height}px`;
}
return undefined;
}
return undefined;
}
private _expandedChanged(
ev: HASSDomEvent<HASSDomEvents["expanded-changed"]>
) {
@@ -348,9 +331,6 @@ ${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
@@ -360,9 +340,8 @@ ${type === "object"
--code-mirror-max-height: calc(
var(--edit-pane-height) - var(--card-header-height) +
var(--ha-space-2) - var(--card-actions-height) - var(
--tip-height,
var(--tip-height-minimal)
) - var(--ha-space-4) - var(--ha-card-border-width, 1px) *
--ha-space-4
) - var(--ha-card-border-width, 1px) *
2
);
}
+1
View File
@@ -414,6 +414,7 @@ export const configSections: Record<string, PageNavigation[]> = {
iconPath: mdiBadgeAccountHorizontal,
iconColor: "#5A87FA",
core: true,
advancedOnly: true,
adminOnly: true,
},
],
@@ -5,11 +5,6 @@ import {
mdiPower,
mdiPowerOff,
mdiPowerOn,
mdiRepeat,
mdiRepeatOff,
mdiRepeatOnce,
mdiShuffle,
mdiShuffleDisabled,
mdiSkipNext,
mdiSkipPrevious,
mdiStop,
@@ -64,8 +59,6 @@ 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 = (
@@ -300,30 +293,6 @@ 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;
}
}
@@ -367,23 +336,6 @@ 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, LovelaceCardFeatureEditor } from "../types";
import type { LovelaceCardFeature } from "../types";
import { HuiModeSelectCardFeatureBase } from "./hui-mode-select-card-feature-base";
import type {
LovelaceCardFeatureContext,
@@ -43,11 +43,6 @@ 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";
@@ -68,13 +63,6 @@ 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;
@@ -66,8 +66,6 @@ export const MEDIA_PLAYER_PLAYBACK_CONTROLS = [
"volume_down",
"volume_up",
"volume_mute",
"shuffle",
"repeat",
] as const;
export type MediaPlayerPlaybackControl =
@@ -80,7 +78,6 @@ export interface MediaPlayerPlaybackCardFeatureConfig {
export interface MediaPlayerSourceCardFeatureConfig {
type: "media-player-source";
sources?: string[];
}
export interface MediaPlayerVolumeSliderCardFeatureConfig {
@@ -91,17 +91,12 @@ export function getSuggestedMax(
return suggestedMax;
}
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 });
};
function createYAxisLabelFormatter(
locale: FrontendLocaleData,
fractionDigits: number
) {
return (value: number): string =>
formatNumber(value, locale, { maximumFractionDigits: fractionDigits });
}
export function getCommonOptions(
@@ -113,7 +108,8 @@ export function getCommonOptions(
compareStart?: Date,
compareEnd?: Date,
formatTotal?: (total: number) => string,
detailedDailyData = false
detailedDailyData = false,
yAxisFractionDigits = 1
): ECOption {
const suggestedPeriod = getSuggestedPeriod(start, end, detailedDailyData);
const suggestedMax = getSuggestedMax(suggestedPeriod, end, detailedDailyData);
@@ -152,7 +148,7 @@ export function getCommonOptions(
align: "left",
},
axisLabel: {
formatter: createYAxisLabelFormatter(locale),
formatter: createYAxisLabelFormatter(locale, yAxisFractionDigits),
},
splitLine: {
show: true,
@@ -10,6 +10,7 @@ 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,
@@ -75,6 +76,8 @@ export class HuiEnergyDevicesDetailGraphCard
@state() private _chartData: BarSeriesOption[] = [];
@state() private _yAxisFractionDigits = 1;
@state() private _data?: EnergyData;
@state() private _legendData?: CustomLegendOption["data"];
@@ -157,7 +160,8 @@ export class HuiEnergyDevicesDetailGraphCard
this.hass.config,
UNIT,
this._compareStart,
this._compareEnd
this._compareEnd,
this._yAxisFractionDigits
)}
click-label-for-more-info
@dataset-hidden=${this._datasetHidden}
@@ -208,9 +212,10 @@ export class HuiEnergyDevicesDetailGraphCard
end: Date,
locale: FrontendLocaleData,
config: HassConfig,
unit?: string,
compareStart?: Date,
compareEnd?: Date
unit: string | undefined,
compareStart: Date | undefined,
compareEnd: Date | undefined,
yAxisFractionDigits: number
): ECOption => {
const commonOptions = getCommonOptions(
start,
@@ -220,7 +225,9 @@ export class HuiEnergyDevicesDetailGraphCard
unit,
compareStart,
compareEnd,
this._formatTotal
this._formatTotal,
false,
yAxisFractionDigits
);
const selected = this._legendData
@@ -309,6 +316,13 @@ 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 =
@@ -331,6 +345,7 @@ export class HuiEnergyDevicesDetailGraphCard
energyData.prefs.device_consumption,
sorted_devices,
childMap,
trackY,
true
);
@@ -341,6 +356,7 @@ export class HuiEnergyDevicesDetailGraphCard
computedStyle,
processedCompareData,
consumptionCompareData,
trackY,
true
);
datasets.push(untrackedCompareData);
@@ -362,7 +378,8 @@ export class HuiEnergyDevicesDetailGraphCard
energyData.statsMetadata,
energyData.prefs.device_consumption,
sorted_devices,
childMap
childMap,
trackY
);
datasets.push(...processedData);
@@ -385,6 +402,7 @@ export class HuiEnergyDevicesDetailGraphCard
computedStyle,
processedData,
consumptionData,
trackY,
false
);
datasets.push(untrackedData);
@@ -401,6 +419,7 @@ export class HuiEnergyDevicesDetailGraphCard
}
fillDataGapsAndRoundCaps(datasets);
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
this._chartData = datasets;
}
@@ -408,6 +427,7 @@ export class HuiEnergyDevicesDetailGraphCard
computedStyle: CSSStyleDeclaration,
processedData,
consumptionData,
trackY: (v: number) => void,
compare: boolean
): BarSeriesOption {
const totalDeviceConsumption: Record<number, number> = {};
@@ -443,6 +463,7 @@ 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();
@@ -483,6 +504,7 @@ export class HuiEnergyDevicesDetailGraphCard
devices: DeviceConsumptionEnergyPreference[],
sorted_devices: string[],
childMap: Record<string, string[]>,
trackY: (v: number) => void,
compare = false
) {
const data: BarSeriesOption[] = [];
@@ -530,6 +552,7 @@ export class HuiEnergyDevicesDetailGraphCard
cStats?.find((cStat) => cStat.start === point.start)?.change || 0;
});
const y = point.change - sumChildren;
const dataPoint: EnergyDataPoint = [
computeStatMidpoint(
point.start,
@@ -537,10 +560,11 @@ export class HuiEnergyDevicesDetailGraphCard
period,
compare ? compareTransform : undefined
),
point.change - sumChildren,
y,
point.start,
];
consumptionData.push(dataPoint);
trackY(y);
prevStart = point.start;
}
}
@@ -9,6 +9,7 @@ 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,
@@ -64,6 +65,8 @@ export class HuiEnergyGasGraphCard
@state() private _chartData: BarSeriesOption[] = [];
@state() private _yAxisFractionDigits = 1;
@state() private _start = startOfToday();
@state() private _end = endOfToday();
@@ -139,7 +142,8 @@ export class HuiEnergyGasGraphCard
this.hass.config,
this._unit,
this._compareStart,
this._compareEnd
this._compareEnd,
this._yAxisFractionDigits
)}
chart-type="bar"
></ha-chart-base>
@@ -169,9 +173,10 @@ export class HuiEnergyGasGraphCard
end: Date,
locale: FrontendLocaleData,
config: HassConfig,
unit?: string,
compareStart?: Date,
compareEnd?: Date
unit: string | undefined,
compareStart: Date | undefined,
compareEnd: Date | undefined,
yAxisFractionDigits: number
): ECOption =>
getCommonOptions(
start,
@@ -181,7 +186,9 @@ export class HuiEnergyGasGraphCard
unit,
compareStart,
compareEnd,
this._formatTotal
this._formatTotal,
false,
yAxisFractionDigits
)
);
@@ -203,6 +210,13 @@ 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(
@@ -210,6 +224,7 @@ export class HuiEnergyGasGraphCard
energyData.statsMetadata,
gasSources,
computedStyles,
trackY,
true
)
);
@@ -230,11 +245,13 @@ export class HuiEnergyGasGraphCard
energyData.stats,
energyData.statsMetadata,
gasSources,
computedStyles
computedStyles,
trackY
)
);
fillDataGapsAndRoundCaps(datasets);
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
this._chartData = datasets;
this._total = this._processTotal(energyData.stats, gasSources);
}
@@ -261,6 +278,7 @@ export class HuiEnergyGasGraphCard
statisticsMetaData: Record<string, StatisticsMetaData>,
gasSources: GasSourceTypeEnergyPreference[],
computedStyles: CSSStyleDeclaration,
trackY: (v: number) => void,
compare = false
) {
const data: BarSeriesOption[] = [];
@@ -300,6 +318,7 @@ export class HuiEnergyGasGraphCard
point.start,
];
gasConsumptionData.push(dataPoint);
trackY(point.change);
prevStart = point.start;
}
}
@@ -9,6 +9,7 @@ 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,
@@ -66,6 +67,8 @@ export class HuiEnergySolarGraphCard
@state() private _chartData: ECOption["series"][] = [];
@state() private _yAxisFractionDigits = 1;
@state() private _start = startOfToday();
@state() private _end = endOfToday();
@@ -138,7 +141,8 @@ export class HuiEnergySolarGraphCard
this.hass.locale,
this.hass.config,
this._compareStart,
this._compareEnd
this._compareEnd,
this._yAxisFractionDigits
)}
chart-type="bar"
></ha-chart-base>
@@ -168,8 +172,9 @@ export class HuiEnergySolarGraphCard
end: Date,
locale: FrontendLocaleData,
config: HassConfig,
compareStart?: Date,
compareEnd?: Date
compareStart: Date | undefined,
compareEnd: Date | undefined,
yAxisFractionDigits: number
): ECOption =>
getCommonOptions(
start,
@@ -179,7 +184,9 @@ export class HuiEnergySolarGraphCard
"kWh",
compareStart,
compareEnd,
this._formatTotal
this._formatTotal,
false,
yAxisFractionDigits
)
);
@@ -210,6 +217,13 @@ 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(
@@ -217,6 +231,7 @@ export class HuiEnergySolarGraphCard
energyData.statsMetadata,
solarSources,
computedStyles,
trackY,
true
)
);
@@ -237,7 +252,8 @@ export class HuiEnergySolarGraphCard
energyData.stats,
energyData.statsMetadata,
solarSources,
computedStyles
computedStyles,
trackY
)
);
@@ -251,11 +267,13 @@ export class HuiEnergySolarGraphCard
solarSources,
computedStyles.getPropertyValue("--primary-text-color"),
energyData.start,
energyData.end
energyData.end,
trackY
)
);
}
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
this._chartData = datasets;
this._total = this._processTotal(energyData.stats, solarSources);
}
@@ -282,6 +300,7 @@ export class HuiEnergySolarGraphCard
statisticsMetaData: Record<string, StatisticsMetaData>,
solarSources: SolarSourceTypeEnergyPreference[],
computedStyles: CSSStyleDeclaration,
trackY: (v: number) => void,
compare = false
) {
const data: BarSeriesOption[] = [];
@@ -322,6 +341,7 @@ export class HuiEnergySolarGraphCard
point.start,
];
solarProductionData.push(dataPoint);
trackY(point.change);
prevStart = point.start;
}
}
@@ -375,7 +395,8 @@ export class HuiEnergySolarGraphCard
solarSources: SolarSourceTypeEnergyPreference[],
borderColor: string,
start: Date,
end?: Date
end: Date | undefined,
trackY: (v: number) => void
) {
const data: LineSeriesOption[] = [];
@@ -429,10 +450,9 @@ export class HuiEnergySolarGraphCard
: 0;
}
for (const [time, value] of Object.entries(forecastsData)) {
solarForecastData.push([
Number(time) + forecastOffset,
value / 1000,
]);
const kWh = value / 1000;
solarForecastData.push([Number(time) + forecastOffset, kWh]);
trackY(kWh);
}
if (solarForecastData.length) {
@@ -13,6 +13,7 @@ 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 {
@@ -79,6 +80,8 @@ export class HuiEnergyUsageGraphCard
@state() private _chartData: BarSeriesOption[] = [];
@state() private _yAxisFractionDigits = 1;
@state() private _start = startOfToday();
@state() private _end = endOfToday();
@@ -154,7 +157,8 @@ export class HuiEnergyUsageGraphCard
this.hass.locale,
this.hass.config,
this._compareStart,
this._compareEnd
this._compareEnd,
this._yAxisFractionDigits
)}
chart-type="bar"
></ha-chart-base>
@@ -189,8 +193,9 @@ export class HuiEnergyUsageGraphCard
end: Date,
locale: FrontendLocaleData,
config: HassConfig,
compareStart?: Date,
compareEnd?: Date
compareStart: Date | undefined,
compareEnd: Date | undefined,
yAxisFractionDigits: number
): ECOption => {
const commonOptions = getCommonOptions(
start,
@@ -200,7 +205,9 @@ export class HuiEnergyUsageGraphCard
"kWh",
compareStart,
compareEnd,
this._formatTotal
this._formatTotal,
false,
yAxisFractionDigits
);
const options: ECOption = {
...commonOptions,
@@ -237,6 +244,13 @@ 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[];
@@ -341,6 +355,7 @@ export class HuiEnergyUsageGraphCard
colorIndices,
computedStyles,
labels,
trackY,
true
)
);
@@ -367,6 +382,7 @@ export class HuiEnergyUsageGraphCard
colorIndices,
computedStyles,
labels,
trackY,
false
)
);
@@ -374,6 +390,7 @@ 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);
}
@@ -403,6 +420,7 @@ export class HuiEnergyUsageGraphCard
used_solar: string;
used_battery: string;
},
trackY: (v: number) => void,
compare = false
) {
const data: BarSeriesOption[] = [];
@@ -504,18 +522,17 @@ export class HuiEnergyUsageGraphCard
// Process chart data.
for (const key of uniqueKeys) {
const value = source[key] || 0;
const dataPoint: EnergyDataPoint = [
key + periodOffset,
const y =
value && ["to_grid", "to_battery"].includes(type)
? -1 * value
: value,
key,
];
: value;
const dataPoint: EnergyDataPoint = [key + periodOffset, y, key];
if (compare) {
dataPoint[0] =
compareTransform(new Date(key)).getTime() + periodOffset;
}
points.push(dataPoint);
trackY(y);
}
data.push({
@@ -8,6 +8,7 @@ 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,
@@ -64,6 +65,8 @@ export class HuiEnergyWaterGraphCard
@state() private _chartData: BarSeriesOption[] = [];
@state() private _yAxisFractionDigits = 1;
@state() private _start = startOfToday();
@state() private _end = endOfToday();
@@ -139,7 +142,8 @@ export class HuiEnergyWaterGraphCard
this.hass.config,
this._unit,
this._compareStart,
this._compareEnd
this._compareEnd,
this._yAxisFractionDigits
)}
chart-type="bar"
></ha-chart-base>
@@ -169,9 +173,10 @@ export class HuiEnergyWaterGraphCard
end: Date,
locale: FrontendLocaleData,
config: HassConfig,
unit?: string,
compareStart?: Date,
compareEnd?: Date
unit: string | undefined,
compareStart: Date | undefined,
compareEnd: Date | undefined,
yAxisFractionDigits: number
): ECOption =>
getCommonOptions(
start,
@@ -181,7 +186,9 @@ export class HuiEnergyWaterGraphCard
unit,
compareStart,
compareEnd,
this._formatTotal
this._formatTotal,
false,
yAxisFractionDigits
)
);
@@ -203,6 +210,13 @@ 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(
@@ -210,6 +224,7 @@ export class HuiEnergyWaterGraphCard
energyData.statsMetadata,
waterSources,
computedStyles,
trackY,
true
)
);
@@ -230,11 +245,13 @@ export class HuiEnergyWaterGraphCard
energyData.stats,
energyData.statsMetadata,
waterSources,
computedStyles
computedStyles,
trackY
)
);
fillDataGapsAndRoundCaps(datasets);
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
this._chartData = datasets;
this._total = this._processTotal(energyData.stats, waterSources);
}
@@ -261,6 +278,7 @@ export class HuiEnergyWaterGraphCard
statisticsMetaData: Record<string, StatisticsMetaData>,
waterSources: WaterSourceTypeEnergyPreference[],
computedStyles: CSSStyleDeclaration,
trackY: (v: number) => void,
compare = false
) {
const data: BarSeriesOption[] = [];
@@ -300,6 +318,7 @@ export class HuiEnergyWaterGraphCard
point.start,
];
waterConsumptionData.push(dataPoint);
trackY(point.change);
prevStart = point.start;
}
}
@@ -8,6 +8,7 @@ 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 {
@@ -53,6 +54,8 @@ export class HuiPowerSourcesGraphCard
@state() private _chartData: LineSeriesOption[] = [];
@state() private _yAxisFractionDigits = 1;
@state() private _legendData?: CustomLegendOption["data"];
@state() private _start = startOfToday();
@@ -117,7 +120,8 @@ export class HuiPowerSourcesGraphCard
this.hass.config,
this._compareStart,
this._compareEnd,
this._legendData
this._legendData,
this._yAxisFractionDigits
)}
></ha-chart-base>
${!this._chartData.some((dataset) => dataset.data!.length)
@@ -140,9 +144,10 @@ export class HuiPowerSourcesGraphCard
end: Date,
locale: FrontendLocaleData,
config: HassConfig,
compareStart?: Date,
compareEnd?: Date,
legendData?: CustomLegendOption["data"]
compareStart: Date | undefined,
compareEnd: Date | undefined,
legendData: CustomLegendOption["data"] | undefined,
yAxisFractionDigits: number
): ECOption => ({
...getCommonOptions(
start,
@@ -153,7 +158,8 @@ export class HuiPowerSourcesGraphCard
compareStart,
compareEnd,
undefined,
true
true,
yAxisFractionDigits
),
legend: {
show: this._config?.show_legend !== false,
@@ -193,6 +199,13 @@ 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) {
@@ -245,7 +258,8 @@ export class HuiPowerSourcesGraphCard
}
}
return stats;
})
}),
trackY
);
datasets.push({
...commonSeriesOptions,
@@ -307,6 +321,7 @@ 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) => {
@@ -353,7 +368,7 @@ export class HuiPowerSourcesGraphCard
});
}
private _processData(stats: StatisticValue[][]) {
private _processData(stats: StatisticValue[][], trackY: (v: number) => void) {
const data: Record<number, number[]> = {};
stats.forEach((statSet) => {
statSet.forEach((point) => {
@@ -369,8 +384,12 @@ export class HuiPowerSourcesGraphCard
Object.entries(data).forEach(([x, y]) => {
const ts = Number(x);
const sumY = y.reduce((a, b) => a + b, 0);
positive.push([ts, Math.max(0, sumY)]);
negative.push([ts, Math.min(0, sumY)]);
const pos = Math.max(0, sumY);
const neg = Math.min(0, sumY);
positive.push([ts, pos]);
negative.push([ts, neg]);
trackY(pos);
trackY(neg);
});
return { positive, negative };
}
@@ -217,6 +217,7 @@ 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>
@@ -65,6 +65,7 @@ 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,6 +96,7 @@ 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,6 @@ const EDITABLES_FEATURE_TYPES = new Set<UiFeatureTypes>([
"lawn-mower-commands",
"media-player-playback",
"light-color-favorites",
"media-player-source",
"media-player-volume-buttons",
"numeric-input",
"select-options",
@@ -1,99 +0,0 @@
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;
}
}
+1 -4
View File
@@ -259,8 +259,6 @@
"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",
@@ -9994,8 +9992,7 @@
"label": "Media player sound mode"
},
"media-player-source": {
"label": "Media player source",
"sources": "Sources"
"label": "Media player source"
},
"media-player-volume-buttons": {
"label": "Media player volume buttons",
+53
View File
@@ -4,6 +4,9 @@ import {
isLoadedIntegration,
isNotLoadedIntegration,
isCore,
isAdvancedPage,
userWantsAdvanced,
hideAdvancedPage,
} from "../../../src/common/config/can_show_page";
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
import type { HomeAssistant } from "../../../src/types";
@@ -12,10 +15,12 @@ describe("canShowPage", () => {
it("should return true if the page can be shown", () => {
const hass = {
config: { components: ["test_component"] },
userData: { showAdvanced: true },
} as unknown as HomeAssistant;
const page = {
component: "test_component",
core: true,
advancedOnly: false,
} as unknown as PageNavigation;
expect(canShowPage(hass, page)).toBe(true);
});
@@ -23,10 +28,12 @@ describe("canShowPage", () => {
it("should return false if the page cannot be shown", () => {
const hass = {
config: { components: ["test_component"] },
userData: { showAdvanced: false },
} as unknown as HomeAssistant;
const page = {
component: "other_component",
core: false,
advancedOnly: true,
} as unknown as PageNavigation;
expect(canShowPage(hass, page)).toBe(false);
});
@@ -83,3 +90,49 @@ describe("isCore", () => {
expect(isCore(page)).toBe(false);
});
});
describe("isAdvancedPage", () => {
it("should return true if the page is advanced", () => {
const page = { advancedOnly: true } as unknown as PageNavigation;
expect(isAdvancedPage(page)).toBe(true);
});
it("should return false if the page is not advanced", () => {
const page = { advancedOnly: false } as unknown as PageNavigation;
expect(isAdvancedPage(page)).toBe(false);
});
});
describe("userWantsAdvanced", () => {
it("should return true if the user wants advanced pages", () => {
const hass = {
userData: { showAdvanced: true },
} as unknown as HomeAssistant;
expect(userWantsAdvanced(hass)).toBe(true);
});
it("should return false if the user does not want advanced pages", () => {
const hass = {
userData: { showAdvanced: false },
} as unknown as HomeAssistant;
expect(userWantsAdvanced(hass)).toBe(false);
});
});
describe("hideAdvancedPage", () => {
it("should return true if the advanced page should be hidden", () => {
const hass = {
userData: { showAdvanced: false },
} as unknown as HomeAssistant;
const page = { advancedOnly: true } as unknown as PageNavigation;
expect(hideAdvancedPage(hass, page)).toBe(true);
});
it("should return false if the advanced page should not be hidden", () => {
const hass = {
userData: { showAdvanced: true },
} as unknown as HomeAssistant;
const page = { advancedOnly: true } as unknown as PageNavigation;
expect(hideAdvancedPage(hass, page)).toBe(false);
});
});
@@ -0,0 +1,42 @@
import { describe, expect, it } from "vitest";
import { computeYAxisFractionDigits } from "../../../src/components/chart/y-axis-fraction-digits";
describe("computeYAxisFractionDigits", () => {
it("uses two decimals for a sub-unit range (e.g. gas prices around 1.85-2.00)", () => {
expect(computeYAxisFractionDigits(1.85, 2.0)).toBe(2);
});
it("uses no decimals for integer-scale ranges", () => {
expect(computeYAxisFractionDigits(0, 100)).toBe(0);
expect(computeYAxisFractionDigits(0, 1000)).toBe(0);
});
it("uses no decimals when the range covers an order of magnitude or more", () => {
expect(computeYAxisFractionDigits(0, 10)).toBe(0);
expect(computeYAxisFractionDigits(0, 50)).toBe(0);
});
it("uses one decimal for ranges around one", () => {
expect(computeYAxisFractionDigits(0, 1)).toBe(1);
expect(computeYAxisFractionDigits(0, 2)).toBe(1);
});
it("uses more decimals as the range shrinks", () => {
expect(computeYAxisFractionDigits(0, 0.05)).toBe(3);
expect(computeYAxisFractionDigits(0, 0.005)).toBe(4);
});
it("falls back to one decimal when min equals max", () => {
expect(computeYAxisFractionDigits(1.5, 1.5)).toBe(1);
});
it("falls back to one decimal when range is non-finite", () => {
expect(computeYAxisFractionDigits(Infinity, -Infinity)).toBe(1);
expect(computeYAxisFractionDigits(NaN, 1)).toBe(1);
});
it("handles negative-to-positive ranges by the magnitude of the range", () => {
expect(computeYAxisFractionDigits(-2, 2)).toBe(1);
expect(computeYAxisFractionDigits(-0.1, 0.1)).toBe(2);
});
});
+228 -179
View File
@@ -2444,6 +2444,26 @@ __metadata:
languageName: node
linkType: hard
"@material/drawer@npm:=14.0.0-canary.53b3cad2f.0":
version: 14.0.0-canary.53b3cad2f.0
resolution: "@material/drawer@npm:14.0.0-canary.53b3cad2f.0"
dependencies:
"@material/animation": "npm:14.0.0-canary.53b3cad2f.0"
"@material/base": "npm:14.0.0-canary.53b3cad2f.0"
"@material/dom": "npm:14.0.0-canary.53b3cad2f.0"
"@material/elevation": "npm:14.0.0-canary.53b3cad2f.0"
"@material/feature-targeting": "npm:14.0.0-canary.53b3cad2f.0"
"@material/list": "npm:14.0.0-canary.53b3cad2f.0"
"@material/ripple": "npm:14.0.0-canary.53b3cad2f.0"
"@material/rtl": "npm:14.0.0-canary.53b3cad2f.0"
"@material/shape": "npm:14.0.0-canary.53b3cad2f.0"
"@material/theme": "npm:14.0.0-canary.53b3cad2f.0"
"@material/typography": "npm:14.0.0-canary.53b3cad2f.0"
tslib: "npm:^2.1.0"
checksum: 10/4401b23f77e24cbf7d25dbd98fb30cc58872438b702000a9b809c728ecb2008ac51389884235b8510660007fcaeb2134c237c47f0adfffa297a94b5f3b41cae0
languageName: node
linkType: hard
"@material/elevation@npm:14.0.0-canary.53b3cad2f.0":
version: 14.0.0-canary.53b3cad2f.0
resolution: "@material/elevation@npm:14.0.0-canary.53b3cad2f.0"
@@ -2633,6 +2653,20 @@ __metadata:
languageName: node
linkType: hard
"@material/mwc-drawer@npm:0.27.0":
version: 0.27.0
resolution: "@material/mwc-drawer@npm:0.27.0"
dependencies:
"@material/drawer": "npm:=14.0.0-canary.53b3cad2f.0"
"@material/mwc-base": "npm:^0.27.0"
blocking-elements: "npm:^0.1.0"
lit: "npm:^2.0.0"
tslib: "npm:^2.0.1"
wicg-inert: "npm:^3.0.0"
checksum: 10/5f329a216cb72e1431ffcab83855aa694bcfb58fc65abc62e4d5258d10240fe03815089c532ea604a8758c4719a6c284ccbb2e8cd117f8d7f4f58b79046fb650
languageName: node
linkType: hard
"@material/mwc-formfield@npm:0.27.0":
version: 0.27.0
resolution: "@material/mwc-formfield@npm:0.27.0"
@@ -3768,22 +3802,22 @@ __metadata:
languageName: node
linkType: hard
"@rsdoctor/client@npm:1.5.11":
version: 1.5.11
resolution: "@rsdoctor/client@npm:1.5.11"
checksum: 10/2c8f1774a29fb2fad0f91dc26a97508be4c6c97990d4ee1346310bd35a4956d2c8b2b03c87e4919c221e9566f02b8b8ef9fb40f5e840df033a0372653883dff2
"@rsdoctor/client@npm:1.5.10":
version: 1.5.10
resolution: "@rsdoctor/client@npm:1.5.10"
checksum: 10/f6d03aa576f04233616afee5c4ed3278f7fed13b96c7e4e62a204fc191ecda89866ad7dc06947133dde8d0e4613ae7621fed2ff1dc081756573fa7109321e976
languageName: node
linkType: hard
"@rsdoctor/core@npm:1.5.11":
version: 1.5.11
resolution: "@rsdoctor/core@npm:1.5.11"
"@rsdoctor/core@npm:1.5.10":
version: 1.5.10
resolution: "@rsdoctor/core@npm:1.5.10"
dependencies:
"@rsbuild/plugin-check-syntax": "npm:1.6.1"
"@rsdoctor/graph": "npm:1.5.11"
"@rsdoctor/sdk": "npm:1.5.11"
"@rsdoctor/types": "npm:1.5.11"
"@rsdoctor/utils": "npm:1.5.11"
"@rsdoctor/graph": "npm:1.5.10"
"@rsdoctor/sdk": "npm:1.5.10"
"@rsdoctor/types": "npm:1.5.10"
"@rsdoctor/utils": "npm:1.5.10"
"@rspack/resolver": "npm:0.2.8"
browserslist-load-config: "npm:^1.0.1"
es-toolkit: "npm:^1.45.1"
@@ -3791,60 +3825,60 @@ __metadata:
fs-extra: "npm:^11.1.1"
semver: "npm:^7.7.4"
source-map: "npm:^0.7.6"
checksum: 10/2545e40bdb394ae20d3743fc7263458737aaad5bc88b090088c83d231daf2c6c6c82895100cc86be2788f77dcac7b849a4523148e05cfbb1bbfb73abc90cc64f
checksum: 10/1af72612cbfb224f977eac20aae4abd8af3dae4f95392c6885364c9152a9a1e4d7f50d7de2a6c486c8fd147ab045345c6f40c95df56b0567886f85288441be8b
languageName: node
linkType: hard
"@rsdoctor/graph@npm:1.5.11":
version: 1.5.11
resolution: "@rsdoctor/graph@npm:1.5.11"
"@rsdoctor/graph@npm:1.5.10":
version: 1.5.10
resolution: "@rsdoctor/graph@npm:1.5.10"
dependencies:
"@rsdoctor/types": "npm:1.5.11"
"@rsdoctor/utils": "npm:1.5.11"
"@rsdoctor/types": "npm:1.5.10"
"@rsdoctor/utils": "npm:1.5.10"
es-toolkit: "npm:^1.45.1"
path-browserify: "npm:1.0.1"
source-map: "npm:^0.7.6"
checksum: 10/ad8edf65aad15a920f82c78cf88e0d0f27198bef9d535695f86a8dd0bad464520efd42aa3e48d8c0d87d6fe190b609e5c89e665a09e4796c31ba5262c5688386
checksum: 10/136c68a5a7c77fd5719d2d00ae7eacf957b71c7a83178fb50ac211376b476348b02a30f41b022d4e15f61bc8452de31757433b7a55ff4f25689425ce78f0d529
languageName: node
linkType: hard
"@rsdoctor/rspack-plugin@npm:1.5.11":
version: 1.5.11
resolution: "@rsdoctor/rspack-plugin@npm:1.5.11"
"@rsdoctor/rspack-plugin@npm:1.5.10":
version: 1.5.10
resolution: "@rsdoctor/rspack-plugin@npm:1.5.10"
dependencies:
"@rsdoctor/core": "npm:1.5.11"
"@rsdoctor/graph": "npm:1.5.11"
"@rsdoctor/sdk": "npm:1.5.11"
"@rsdoctor/types": "npm:1.5.11"
"@rsdoctor/utils": "npm:1.5.11"
"@rsdoctor/core": "npm:1.5.10"
"@rsdoctor/graph": "npm:1.5.10"
"@rsdoctor/sdk": "npm:1.5.10"
"@rsdoctor/types": "npm:1.5.10"
"@rsdoctor/utils": "npm:1.5.10"
peerDependencies:
"@rspack/core": "*"
peerDependenciesMeta:
"@rspack/core":
optional: true
checksum: 10/e2fc2ff4c317b98fae6bbbe4db80d9d46c68f654bc9a9493d575dad288571d4eafceb41697cbfdf479b1d35b6b9c7b94ac29c876e5f4a241db10f2cff5d7a703
checksum: 10/0caaced3edb5ffe164e39a55e64b543ed0dcc32a13f6244a121e80e2bea3198c4a1bb6cbe5a3cee7eca0fb6c55fb040c19392c393e958cd9e71080eae53c785b
languageName: node
linkType: hard
"@rsdoctor/sdk@npm:1.5.11":
version: 1.5.11
resolution: "@rsdoctor/sdk@npm:1.5.11"
"@rsdoctor/sdk@npm:1.5.10":
version: 1.5.10
resolution: "@rsdoctor/sdk@npm:1.5.10"
dependencies:
"@rsdoctor/client": "npm:1.5.11"
"@rsdoctor/graph": "npm:1.5.11"
"@rsdoctor/types": "npm:1.5.11"
"@rsdoctor/utils": "npm:1.5.11"
"@rsdoctor/client": "npm:1.5.10"
"@rsdoctor/graph": "npm:1.5.10"
"@rsdoctor/types": "npm:1.5.10"
"@rsdoctor/utils": "npm:1.5.10"
launch-editor: "npm:^2.13.2"
safer-buffer: "npm:2.1.2"
socket.io: "npm:4.8.1"
tapable: "npm:2.3.3"
checksum: 10/ea90e2f58288101a5ca99efe0383437163f1808260e1f5b2c82c3807a4f19e51b4020c8a57b2847aca9485395e62a089099c58526fb72067aa384f29b1242872
checksum: 10/4448f28f43d105834fb51a462d84c60fe2e760cbafe4164cd388f02e46e75f5adb466c58496c075c0911e25070a4328441ca5f7ea4bd302cd83fae003f190d9c
languageName: node
linkType: hard
"@rsdoctor/types@npm:1.5.11":
version: 1.5.11
resolution: "@rsdoctor/types@npm:1.5.11"
"@rsdoctor/types@npm:1.5.10":
version: 1.5.10
resolution: "@rsdoctor/types@npm:1.5.10"
dependencies:
"@types/connect": "npm:3.4.38"
"@types/estree": "npm:1.0.5"
@@ -3858,16 +3892,16 @@ __metadata:
optional: true
webpack:
optional: true
checksum: 10/12d15c94778681ce186985494fb28c38bce6c2504dd4a7aeb9424223343b0f3a104b80efaa200b721ed3054606ebaa4cd803fd3808208e79b08c9a6ec869238d
checksum: 10/40dbcc864510ebec0f3568a2c2c0903eefec793ff89a096b773c0cf2e261ee345dfebc01bc14f46e8fbceaf3345779f02e93abc876723695263936ef35a903f9
languageName: node
linkType: hard
"@rsdoctor/utils@npm:1.5.11":
version: 1.5.11
resolution: "@rsdoctor/utils@npm:1.5.11"
"@rsdoctor/utils@npm:1.5.10":
version: 1.5.10
resolution: "@rsdoctor/utils@npm:1.5.10"
dependencies:
"@babel/code-frame": "npm:7.26.2"
"@rsdoctor/types": "npm:1.5.11"
"@rsdoctor/types": "npm:1.5.10"
"@types/estree": "npm:1.0.5"
acorn: "npm:^8.10.0"
acorn-import-attributes: "npm:^1.9.5"
@@ -3881,7 +3915,7 @@ __metadata:
picocolors: "npm:^1.1.1"
rslog: "npm:^1.3.2"
strip-ansi: "npm:^6.0.1"
checksum: 10/1411c09e79de2b5c8042e98a0e88a1acbdc417796e6dadc86c506000f218b34ca3022a6d110697860ef83c46fa3881e0ae5357344eaf526e370cc2bc72399749
checksum: 10/fcdf15564a1f0c6792ffbe850e17e1239e2db3c131fcf38e59fdc936b488c9d92c3e449f957db1d7e777e47d27148c73a303127c7e6df86339afd34ce6d56479
languageName: node
linkType: hard
@@ -4815,105 +4849,105 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/eslint-plugin@npm:8.59.3"
"@typescript-eslint/eslint-plugin@npm:8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/eslint-plugin@npm:8.59.2"
dependencies:
"@eslint-community/regexpp": "npm:^4.12.2"
"@typescript-eslint/scope-manager": "npm:8.59.3"
"@typescript-eslint/type-utils": "npm:8.59.3"
"@typescript-eslint/utils": "npm:8.59.3"
"@typescript-eslint/visitor-keys": "npm:8.59.3"
"@typescript-eslint/scope-manager": "npm:8.59.2"
"@typescript-eslint/type-utils": "npm:8.59.2"
"@typescript-eslint/utils": "npm:8.59.2"
"@typescript-eslint/visitor-keys": "npm:8.59.2"
ignore: "npm:^7.0.5"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.5.0"
peerDependencies:
"@typescript-eslint/parser": ^8.59.3
"@typescript-eslint/parser": ^8.59.2
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/85a0dcd6c1b5ece73bbea9171e1ad25a2480b89a8f039057beaa82a000bc49b3909489dd0d68097011192fa878c30d91198ab49641dac165248b071ca220e2d1
checksum: 10/c0101b37dac651fbd33e225e84b96cd96e9916257db23ca0e16a611c3fb3f5faaa708f261e3a0440bd5994b6a19a1790f3e39f03ccc25c942137ee64113acf48
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/parser@npm:8.59.3"
"@typescript-eslint/parser@npm:8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/parser@npm:8.59.2"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.59.3"
"@typescript-eslint/types": "npm:8.59.3"
"@typescript-eslint/typescript-estree": "npm:8.59.3"
"@typescript-eslint/visitor-keys": "npm:8.59.3"
"@typescript-eslint/scope-manager": "npm:8.59.2"
"@typescript-eslint/types": "npm:8.59.2"
"@typescript-eslint/typescript-estree": "npm:8.59.2"
"@typescript-eslint/visitor-keys": "npm:8.59.2"
debug: "npm:^4.4.3"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/2bf8a3e868391c89178a975f6edf6ed3d07d87ce15c4c8c77bacd5851f9091598c5f4637bd6921febb190c59b337724674eccd9b04d4f213b1ae3a30990895d8
checksum: 10/601eab709a6c7f7273f41b034435cb0a7102ee9e888b154ed9454003fe7b40d8a38286181b1a8ef2b7e4bfa4be6e7a48ee302e715b11d0550550f4ce1a114984
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/project-service@npm:8.59.3"
"@typescript-eslint/project-service@npm:8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/project-service@npm:8.59.2"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.59.3"
"@typescript-eslint/types": "npm:^8.59.3"
"@typescript-eslint/tsconfig-utils": "npm:^8.59.2"
"@typescript-eslint/types": "npm:^8.59.2"
debug: "npm:^4.4.3"
peerDependencies:
typescript: ">=4.8.4 <6.1.0"
checksum: 10/19af9dbd61f5c307d15f8814a74170952aef06c59775e5f5abbce14c8f3752dd61b99e7bb0d25332c5aed141e43f3c7f00fae55ccc99a22610685418ffcfee31
checksum: 10/768d311bdf366519549a3806b16eb3be030328b7cda9882e60ea2a6c112111a531ef94289ec88225b70ca61d2071f1bddf2c5faa841a837d44992c918d198d7b
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/scope-manager@npm:8.59.3"
"@typescript-eslint/scope-manager@npm:8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/scope-manager@npm:8.59.2"
dependencies:
"@typescript-eslint/types": "npm:8.59.3"
"@typescript-eslint/visitor-keys": "npm:8.59.3"
checksum: 10/d7be76f9bbd33c1d762493d2c6e9c7cfd87aa6d6ae778e6d8cb3fcfdf669941791d8e5f6207011e8bdc64476ca46954d880863c4125957ee0521fe0bb861b7cd
"@typescript-eslint/types": "npm:8.59.2"
"@typescript-eslint/visitor-keys": "npm:8.59.2"
checksum: 10/9a63eb5d4ae26235ce2d3348eb45ff0e1a8cc7b198f622c48e921c7bfe0f1f7fa29a8cd3856547a4d42c08b7ed334315c682a714cb3b8b62841b5d59a1d08fd4
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.59.3, @typescript-eslint/tsconfig-utils@npm:^8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/tsconfig-utils@npm:8.59.3"
"@typescript-eslint/tsconfig-utils@npm:8.59.2, @typescript-eslint/tsconfig-utils@npm:^8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/tsconfig-utils@npm:8.59.2"
peerDependencies:
typescript: ">=4.8.4 <6.1.0"
checksum: 10/a6f230d66dc5cadfbc789a8468ff7bdf8f3100254eb68657007398feb4d46688c4ef3fb35784332ae9af65d52e6c4eabab0a47ed54a0ceac0cb025ae778dadf2
checksum: 10/42479906a01469322d22e8d45c6200998382f19c1c2dcb59d6adb2e796238a0476f1aa8fd1a1b2c3b36c0c7aa77ebb72ffc958bd11b6efadd36cd175646d13de
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/type-utils@npm:8.59.3"
"@typescript-eslint/type-utils@npm:8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/type-utils@npm:8.59.2"
dependencies:
"@typescript-eslint/types": "npm:8.59.3"
"@typescript-eslint/typescript-estree": "npm:8.59.3"
"@typescript-eslint/utils": "npm:8.59.3"
"@typescript-eslint/types": "npm:8.59.2"
"@typescript-eslint/typescript-estree": "npm:8.59.2"
"@typescript-eslint/utils": "npm:8.59.2"
debug: "npm:^4.4.3"
ts-api-utils: "npm:^2.5.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/864628416bad1c4bb4d46c796c86d799e1f57e23d4c81ccd2edc7446244156f2c8f08a8556878cb71e119ebb0c8089044c0f76fa4487f5ccea871a2c2d0e42a9
checksum: 10/931f4fc262722f7cb61bcaf8f3a1cc6aec93690692cb06d37847880f1216fd9183146030b4b313407ae0bb8cda83dbd9e59d0d77dac2cff015f45fb23a0f5911
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.59.3, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/types@npm:8.59.3"
checksum: 10/71c2128b5744ef99d084d1d42f85625f7b8c4de8eaeec393e4e64838aacac0da126b31670247629dca3432cd8994ca509ee1c7c59393e9f56518a933af50c1c2
"@typescript-eslint/types@npm:8.59.2, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/types@npm:8.59.2"
checksum: 10/dc828a5c50debac37047a30ec5bfdc21e2b410c7c8c517c1ab01164fa9a0197f4f6b829f502dd992d21044442277029bfacf0c0b70d7ac9446977cbc8d375e13
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/typescript-estree@npm:8.59.3"
"@typescript-eslint/typescript-estree@npm:8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/typescript-estree@npm:8.59.2"
dependencies:
"@typescript-eslint/project-service": "npm:8.59.3"
"@typescript-eslint/tsconfig-utils": "npm:8.59.3"
"@typescript-eslint/types": "npm:8.59.3"
"@typescript-eslint/visitor-keys": "npm:8.59.3"
"@typescript-eslint/project-service": "npm:8.59.2"
"@typescript-eslint/tsconfig-utils": "npm:8.59.2"
"@typescript-eslint/types": "npm:8.59.2"
"@typescript-eslint/visitor-keys": "npm:8.59.2"
debug: "npm:^4.4.3"
minimatch: "npm:^10.2.2"
semver: "npm:^7.7.3"
@@ -4921,32 +4955,32 @@ __metadata:
ts-api-utils: "npm:^2.5.0"
peerDependencies:
typescript: ">=4.8.4 <6.1.0"
checksum: 10/acd4e71c087f2f1a1b6a8670d390642e68f12d63512ef7d1eaf0472b846c49f9a5dddfc271cfa03d6e8917ea01f3b3a81119195daa97e083244f083a5a6ee5a6
checksum: 10/54a2689e5c08f35364214a542e328745401951e94526c9f95d68b14c57521e9aade1e946074a02ed2c9cc95e94fc1866c3f725f820263759a1ee2072e3ed146f
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/utils@npm:8.59.3"
"@typescript-eslint/utils@npm:8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/utils@npm:8.59.2"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.9.1"
"@typescript-eslint/scope-manager": "npm:8.59.3"
"@typescript-eslint/types": "npm:8.59.3"
"@typescript-eslint/typescript-estree": "npm:8.59.3"
"@typescript-eslint/scope-manager": "npm:8.59.2"
"@typescript-eslint/types": "npm:8.59.2"
"@typescript-eslint/typescript-estree": "npm:8.59.2"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/24b0e91051d0aff8ef30b1262b2d0b88954ba9f73ced0d17c0bcf305361fc27d11849b501532761cef76fd45873d4907254240e8ae1b4dcfa2e5252f77e1e7fa
checksum: 10/4e157a18b28d656b13ae07583765cc871d992abad0ae0aeb2cde819dd632d62b89da9f9e468dfefead18b9440aa2b9040ca36841525dff4ea97479583114afe0
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.59.3":
version: 8.59.3
resolution: "@typescript-eslint/visitor-keys@npm:8.59.3"
"@typescript-eslint/visitor-keys@npm:8.59.2":
version: 8.59.2
resolution: "@typescript-eslint/visitor-keys@npm:8.59.2"
dependencies:
"@typescript-eslint/types": "npm:8.59.3"
"@typescript-eslint/types": "npm:8.59.2"
eslint-visitor-keys: "npm:^5.0.0"
checksum: 10/66241bdcf6679ae784cf0ae5581ca009cdb7d589656da6df8ef1d8b3ffca6717f55718776d13af35d28a49b124ab5f5533f7631b51878f08f1070e0407e5a4a4
checksum: 10/ec8d797272c12b53b9eb2b508c326823b2cb17f6bcf57606238b812bb73854675919a5e772a42499ec1ac7787a16d018fffd94e6b168cc0b58a9872b16d6f1da
languageName: node
linkType: hard
@@ -5193,12 +5227,12 @@ __metadata:
languageName: node
linkType: hard
"@vitest/coverage-v8@npm:4.1.6":
version: 4.1.6
resolution: "@vitest/coverage-v8@npm:4.1.6"
"@vitest/coverage-v8@npm:4.1.5":
version: 4.1.5
resolution: "@vitest/coverage-v8@npm:4.1.5"
dependencies:
"@bcoe/v8-coverage": "npm:^1.0.2"
"@vitest/utils": "npm:4.1.6"
"@vitest/utils": "npm:4.1.5"
ast-v8-to-istanbul: "npm:^1.0.0"
istanbul-lib-coverage: "npm:^3.2.2"
istanbul-lib-report: "npm:^3.0.1"
@@ -5208,34 +5242,34 @@ __metadata:
std-env: "npm:^4.0.0-rc.1"
tinyrainbow: "npm:^3.1.0"
peerDependencies:
"@vitest/browser": 4.1.6
vitest: 4.1.6
"@vitest/browser": 4.1.5
vitest: 4.1.5
peerDependenciesMeta:
"@vitest/browser":
optional: true
checksum: 10/351ddb5ccebc57ba290b669676db1e24960e4becd9c776a49e2a1ddb02cc2c644870a88010ff044f557fd9082dbe291b8c5e868d562fac93bd02c40d4bedf6bd
checksum: 10/378e1d85a1c4670af15a18b544995a43d320460b418c188d7000f96518859e4537e00ea5e38a563c42b6183437252f0ecc92b471ede30c6d43ae87b7c8e09ed3
languageName: node
linkType: hard
"@vitest/expect@npm:4.1.6":
version: 4.1.6
resolution: "@vitest/expect@npm:4.1.6"
"@vitest/expect@npm:4.1.5":
version: 4.1.5
resolution: "@vitest/expect@npm:4.1.5"
dependencies:
"@standard-schema/spec": "npm:^1.1.0"
"@types/chai": "npm:^5.2.2"
"@vitest/spy": "npm:4.1.6"
"@vitest/utils": "npm:4.1.6"
"@vitest/spy": "npm:4.1.5"
"@vitest/utils": "npm:4.1.5"
chai: "npm:^6.2.2"
tinyrainbow: "npm:^3.1.0"
checksum: 10/20de26292c543f7f5076b59fd50a5fa89217755402de89b62e5d8c104c90441413b87b5c1d310a682a310418c76c0d4bd309dd1faf13b1b2dec79dc3bb90fef0
checksum: 10/3e94d2d0cf4f7018ed6a7a9394bff971353ea0cc85bcbcff39212279156840b8c533be99e2fd52112e4904c4a5190bdaaf441db7c6b17e356c18577072a3f057
languageName: node
linkType: hard
"@vitest/mocker@npm:4.1.6":
version: 4.1.6
resolution: "@vitest/mocker@npm:4.1.6"
"@vitest/mocker@npm:4.1.5":
version: 4.1.5
resolution: "@vitest/mocker@npm:4.1.5"
dependencies:
"@vitest/spy": "npm:4.1.6"
"@vitest/spy": "npm:4.1.5"
estree-walker: "npm:^3.0.3"
magic-string: "npm:^0.30.21"
peerDependencies:
@@ -5246,56 +5280,56 @@ __metadata:
optional: true
vite:
optional: true
checksum: 10/d0669d0b1a8822ec3bc83b5261ead6b05a7e5d8c2077d1f8b9eb0c8507967e54347f16027894be28ca26cf8993e544b8269230a3b78c4eb50c8feb780cb4c688
checksum: 10/949784ba08996543a313459a36a730d4b0847e42ee56cfda07a3e2add67c7adf8acbd59dcf9f75b1e4bc3fe7cc487f9f260905ff9a334866d389478112e5ae82
languageName: node
linkType: hard
"@vitest/pretty-format@npm:4.1.6":
version: 4.1.6
resolution: "@vitest/pretty-format@npm:4.1.6"
"@vitest/pretty-format@npm:4.1.5":
version: 4.1.5
resolution: "@vitest/pretty-format@npm:4.1.5"
dependencies:
tinyrainbow: "npm:^3.1.0"
checksum: 10/28dc121181fdf619e4a9ea4a3279a63974e54567fc59f82462d3b11d4b72d893cd7966f8a7c1a9365c62eae6dee4c6fb08353074486f708aee50b80462d0bd37
checksum: 10/783f8c4a0e419d1024446ae8593411c95443ea09b50c4a378986b48893998acda34429b2d1deebc065405a7ef40bb19e19c68fdeb93acd46ae98b156c42d5f39
languageName: node
linkType: hard
"@vitest/runner@npm:4.1.6":
version: 4.1.6
resolution: "@vitest/runner@npm:4.1.6"
"@vitest/runner@npm:4.1.5":
version: 4.1.5
resolution: "@vitest/runner@npm:4.1.5"
dependencies:
"@vitest/utils": "npm:4.1.6"
"@vitest/utils": "npm:4.1.5"
pathe: "npm:^2.0.3"
checksum: 10/0e175bb61b10ca6cb79a0734a45b3d8b1570806078d53b4f2aa7dbfabd10307c9566460ee8f263a34ac909e8481da614551eee28eaff834fbecd86b4902b845b
checksum: 10/ba19d84a9f7bcc3102ae5304c23e5dae789aaf8fd283f826e3fd4aca87ea2687ed606cf89869773d15799666553fd265524f7d9a0869e2869e00ebd8fd53af5b
languageName: node
linkType: hard
"@vitest/snapshot@npm:4.1.6":
version: 4.1.6
resolution: "@vitest/snapshot@npm:4.1.6"
"@vitest/snapshot@npm:4.1.5":
version: 4.1.5
resolution: "@vitest/snapshot@npm:4.1.5"
dependencies:
"@vitest/pretty-format": "npm:4.1.6"
"@vitest/utils": "npm:4.1.6"
"@vitest/pretty-format": "npm:4.1.5"
"@vitest/utils": "npm:4.1.5"
magic-string: "npm:^0.30.21"
pathe: "npm:^2.0.3"
checksum: 10/167b96971ae6e31a8a7c42063abf3d48590908bdea8ae24d9e5035cd08690e47e15a12ab96cc017e5ddd6324a994b8096c901c8e87ac6e5e617910a2814717fd
checksum: 10/cf70530d8a7320c012bdf7f6ca4f3ddbbb47c9aeb9ff5d28319e552ce64db93423d0c4facff3e112c6d711ed4228369c8fa73c88350fe6c16cf04f9ac2558caf
languageName: node
linkType: hard
"@vitest/spy@npm:4.1.6":
version: 4.1.6
resolution: "@vitest/spy@npm:4.1.6"
checksum: 10/6c1bddbf1eaae42af96d66e31f8c14837203707552f60e7a0f512dc2513d285e3de1fdcf057a79a5588fd20ee382ce5a53c1a69430b2a79eb623fd3517d54878
"@vitest/spy@npm:4.1.5":
version: 4.1.5
resolution: "@vitest/spy@npm:4.1.5"
checksum: 10/4db4bb3aea01cd737fdb06d8f498bcd2127b8c2afeaa78ff9df4147e1474aa26dd16f42dc0512c31385824e94dbb17b17fa0f4c60b7595b7b4ab946f098220ab
languageName: node
linkType: hard
"@vitest/utils@npm:4.1.6":
version: 4.1.6
resolution: "@vitest/utils@npm:4.1.6"
"@vitest/utils@npm:4.1.5":
version: 4.1.5
resolution: "@vitest/utils@npm:4.1.5"
dependencies:
"@vitest/pretty-format": "npm:4.1.6"
"@vitest/pretty-format": "npm:4.1.5"
convert-source-map: "npm:^2.0.0"
tinyrainbow: "npm:^3.1.0"
checksum: 10/a81506e9f167389e771503ba5bee91a61cd4f09ac386867815b65c12c9c236051fab6450d686c69b41e3fd028461d0195ee4c4ae47fd22ead649716ddb7777b3
checksum: 10/4f75a2df6f910578a361ae92eb92a2b6921f50cc748994f3b2e5900d0ae687b6683f33b090dedf9b96eaca23bac117817d9448a4a333c7a96b94ee767399f18c
languageName: node
linkType: hard
@@ -5998,6 +6032,13 @@ __metadata:
languageName: node
linkType: hard
"blocking-elements@npm:^0.1.0":
version: 0.1.1
resolution: "blocking-elements@npm:0.1.1"
checksum: 10/4022a3aa56d7ee83af690d6c3a39304bdfe23ab0ee60b067b0b821db9e462cbd038340b3be8917dd7cd73d9c6dfaacd11cd7dafa374231567eef85ba04c4762a
languageName: node
linkType: hard
"bmp-js@npm:^0.1.0":
version: 0.1.0
resolution: "bmp-js@npm:0.1.0"
@@ -8729,6 +8770,7 @@ __metadata:
"@lokalise/node-api": "npm:16.0.0"
"@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0"
"@material/mwc-base": "npm:0.27.0"
"@material/mwc-drawer": "npm: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": "npm:0.27.0"
@@ -8741,7 +8783,7 @@ __metadata:
"@octokit/plugin-retry": "npm:8.1.0"
"@octokit/rest": "npm:22.0.1"
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@rsdoctor/rspack-plugin": "npm:1.5.11"
"@rsdoctor/rspack-plugin": "npm:1.5.10"
"@rspack/core": "npm:2.0.2"
"@rspack/dev-server": "npm:2.0.1"
"@swc/helpers": "npm:0.5.21"
@@ -8766,7 +8808,7 @@ __metadata:
"@types/tar": "npm:7.0.87"
"@types/webspeechapi": "npm:0.0.29"
"@vibrant/color": "npm:4.0.4"
"@vitest/coverage-v8": "npm:4.1.6"
"@vitest/coverage-v8": "npm:4.1.5"
"@webcomponents/scoped-custom-element-registry": "npm:0.0.10"
"@webcomponents/webcomponentsjs": "npm:2.8.0"
babel-loader: "npm:10.1.1"
@@ -8849,9 +8891,9 @@ __metadata:
tinykeys: "npm:3.0.0"
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:6.0.3"
typescript-eslint: "npm:8.59.3"
typescript-eslint: "npm:8.59.2"
vite-tsconfig-paths: "npm:6.1.1"
vitest: "npm:4.1.6"
vitest: "npm:4.1.5"
webpack-stats-plugin: "npm:1.1.3"
webpackbar: "npm:7.0.0"
weekstart: "npm:2.0.0"
@@ -13822,18 +13864,18 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.59.3":
version: 8.59.3
resolution: "typescript-eslint@npm:8.59.3"
"typescript-eslint@npm:8.59.2":
version: 8.59.2
resolution: "typescript-eslint@npm:8.59.2"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.59.3"
"@typescript-eslint/parser": "npm:8.59.3"
"@typescript-eslint/typescript-estree": "npm:8.59.3"
"@typescript-eslint/utils": "npm:8.59.3"
"@typescript-eslint/eslint-plugin": "npm:8.59.2"
"@typescript-eslint/parser": "npm:8.59.2"
"@typescript-eslint/typescript-estree": "npm:8.59.2"
"@typescript-eslint/utils": "npm:8.59.2"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/01cfffb7340e0479558d327e680233febaf0eb1b0150d7c07a41edc98d6351e31e2ffe6e0a5eceef82a785644e242376e05913372c9e81f0f07c585609612921
checksum: 10/f729dffc5e2beef2685d7fdc5ecac973c02cad30d24929a5cc4df5d94b58d8841f34e06c87cf9df14caae7a0c264e4371d9c5b59b7210ce769519417c8494698
languageName: node
linkType: hard
@@ -14307,17 +14349,17 @@ __metadata:
languageName: node
linkType: hard
"vitest@npm:4.1.6":
version: 4.1.6
resolution: "vitest@npm:4.1.6"
"vitest@npm:4.1.5":
version: 4.1.5
resolution: "vitest@npm:4.1.5"
dependencies:
"@vitest/expect": "npm:4.1.6"
"@vitest/mocker": "npm:4.1.6"
"@vitest/pretty-format": "npm:4.1.6"
"@vitest/runner": "npm:4.1.6"
"@vitest/snapshot": "npm:4.1.6"
"@vitest/spy": "npm:4.1.6"
"@vitest/utils": "npm:4.1.6"
"@vitest/expect": "npm:4.1.5"
"@vitest/mocker": "npm:4.1.5"
"@vitest/pretty-format": "npm:4.1.5"
"@vitest/runner": "npm:4.1.5"
"@vitest/snapshot": "npm:4.1.5"
"@vitest/spy": "npm:4.1.5"
"@vitest/utils": "npm:4.1.5"
es-module-lexer: "npm:^2.0.0"
expect-type: "npm:^1.3.0"
magic-string: "npm:^0.30.21"
@@ -14335,12 +14377,12 @@ __metadata:
"@edge-runtime/vm": "*"
"@opentelemetry/api": ^1.9.0
"@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0
"@vitest/browser-playwright": 4.1.6
"@vitest/browser-preview": 4.1.6
"@vitest/browser-webdriverio": 4.1.6
"@vitest/coverage-istanbul": 4.1.6
"@vitest/coverage-v8": 4.1.6
"@vitest/ui": 4.1.6
"@vitest/browser-playwright": 4.1.5
"@vitest/browser-preview": 4.1.5
"@vitest/browser-webdriverio": 4.1.5
"@vitest/coverage-istanbul": 4.1.5
"@vitest/coverage-v8": 4.1.5
"@vitest/ui": 4.1.5
happy-dom: "*"
jsdom: "*"
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -14371,7 +14413,7 @@ __metadata:
optional: false
bin:
vitest: vitest.mjs
checksum: 10/3816121537930455e5338b5b3305179fa6c68d6cbba50e5d8ca8dcb2c0410887ed38aca61e0d2da9f673af1cc1f20278eef941fc4756644e6f8f96366822b8e6
checksum: 10/8b768514993d8908fc9b5f2d619943d23b81aaba9443132583bd58aeb441bf76d152961326de9ca328ff0efcddbf8a58f4568a7b66a4391202542ed772613d81
languageName: node
linkType: hard
@@ -14714,6 +14756,13 @@ __metadata:
languageName: node
linkType: hard
"wicg-inert@npm:^3.0.0":
version: 3.1.3
resolution: "wicg-inert@npm:3.1.3"
checksum: 10/b9ca89ba4d4ce17eceab37aa6a10124490584b0d76e5287a43126bff1042aded8f624792db52f9e80f59bd70ffd0e79f243312a94fe928883f6c8834009b12e1
languageName: node
linkType: hard
"widest-line@npm:^4.0.1":
version: 4.0.1
resolution: "widest-line@npm:4.0.1"