mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-23 01:27:07 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 854e56947f | |||
| 4728eb7231 | |||
| d02b92bd32 | |||
| 98525d23e6 | |||
| ec98b21276 | |||
| defad3beca | |||
| 635d61256b | |||
| 60c5bea6e0 | |||
| aed83ccc07 |
+13
-13
@@ -37,18 +37,18 @@
|
||||
"@codemirror/lint": "6.9.6",
|
||||
"@codemirror/search": "6.7.0",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.42.1",
|
||||
"@codemirror/view": "6.43.0",
|
||||
"@date-fns/tz": "1.4.1",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "7.4.4",
|
||||
"@formatjs/intl-displaynames": "7.3.6",
|
||||
"@formatjs/intl-durationformat": "0.10.10",
|
||||
"@formatjs/intl-getcanonicallocales": "3.2.7",
|
||||
"@formatjs/intl-listformat": "8.3.6",
|
||||
"@formatjs/intl-locale": "5.3.6",
|
||||
"@formatjs/intl-numberformat": "9.3.7",
|
||||
"@formatjs/intl-pluralrules": "6.3.6",
|
||||
"@formatjs/intl-relativetimeformat": "12.3.6",
|
||||
"@formatjs/intl-datetimeformat": "7.4.5",
|
||||
"@formatjs/intl-displaynames": "7.3.7",
|
||||
"@formatjs/intl-durationformat": "0.10.11",
|
||||
"@formatjs/intl-getcanonicallocales": "3.2.8",
|
||||
"@formatjs/intl-listformat": "8.3.7",
|
||||
"@formatjs/intl-locale": "5.3.7",
|
||||
"@formatjs/intl-numberformat": "9.3.8",
|
||||
"@formatjs/intl-pluralrules": "6.3.7",
|
||||
"@formatjs/intl-relativetimeformat": "12.3.7",
|
||||
"@fullcalendar/core": "6.1.20",
|
||||
"@fullcalendar/daygrid": "6.1.20",
|
||||
"@fullcalendar/interaction": "6.1.20",
|
||||
@@ -75,8 +75,8 @@
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@swc/helpers": "0.5.21",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "3.9.1",
|
||||
"@tsparticles/preset-links": "3.2.0",
|
||||
"@tsparticles/engine": "4.0.0",
|
||||
"@tsparticles/preset-links": "4.0.0",
|
||||
"@vibrant/color": "4.0.4",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
@@ -99,7 +99,7 @@
|
||||
"hls.js": "1.6.16",
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"idb-keyval": "6.2.2",
|
||||
"intl-messageformat": "11.2.5",
|
||||
"intl-messageformat": "11.2.6",
|
||||
"js-yaml": "4.1.1",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import type {
|
||||
ReactiveController,
|
||||
ReactiveControllerHost,
|
||||
} from "@lit/reactive-element/reactive-controller";
|
||||
import type {
|
||||
Condition,
|
||||
ConditionContext,
|
||||
} from "../../panels/lovelace/common/validate-condition";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { setupConditionListeners } from "../condition/listeners";
|
||||
|
||||
/**
|
||||
* Reactive controller that manages the media-query and time-based listeners
|
||||
* needed to keep a set of lovelace visibility conditions evaluated live.
|
||||
*
|
||||
* The host is responsible for the actual evaluation (e.g. computing visible /
|
||||
* hidden / invalid state); the controller only triggers it via the supplied
|
||||
* `onUpdate` callback when something the conditions depend on changes. Call
|
||||
* `setup()` whenever the conditions change; the controller clears previous
|
||||
* listeners and re-subscribes. Listeners are automatically released when the
|
||||
* host disconnects.
|
||||
*/
|
||||
export class ConditionListenersController implements ReactiveController {
|
||||
private _unsubs: (() => void)[] = [];
|
||||
|
||||
constructor(host: ReactiveControllerHost) {
|
||||
host.addController(this);
|
||||
}
|
||||
|
||||
public hostDisconnected(): void {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
public setup(
|
||||
conditions: Condition[],
|
||||
hass: HomeAssistant,
|
||||
onUpdate: () => void,
|
||||
getContext?: () => ConditionContext
|
||||
): void {
|
||||
this.clear();
|
||||
if (!conditions.length) {
|
||||
return;
|
||||
}
|
||||
setupConditionListeners(
|
||||
conditions,
|
||||
hass,
|
||||
(unsub) => this._unsubs.push(unsub),
|
||||
() => onUpdate(),
|
||||
getContext
|
||||
);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
for (const unsub of this._unsubs) {
|
||||
unsub();
|
||||
}
|
||||
this._unsubs = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../ha-tooltip";
|
||||
|
||||
export type LiveTestState = "pass" | "fail" | "invalid" | "unknown";
|
||||
|
||||
/**
|
||||
* @element ha-automation-row-live-test
|
||||
*
|
||||
* @summary
|
||||
* Small status indicator dot used in automation/condition rows to surface the
|
||||
* live evaluation result. Renders an optional tooltip with details on hover.
|
||||
*
|
||||
* @attr {"pass"|"fail"|"invalid"|"unknown"} state - The current live-test state. Defaults to `unknown`.
|
||||
* @attr {string} label - Accessible label announced by assistive technology.
|
||||
* @attr {string} message - Optional tooltip body shown on hover/focus.
|
||||
*/
|
||||
@customElement("ha-automation-row-live-test")
|
||||
export class HaAutomationRowLiveTest extends LitElement {
|
||||
@property({ reflect: true }) public state: LiveTestState = "unknown";
|
||||
|
||||
@property() public label = "";
|
||||
|
||||
@property() public message?: string;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
id="indicator"
|
||||
role="status"
|
||||
tabindex="0"
|
||||
aria-label=${this.label}
|
||||
></div>
|
||||
${this.message
|
||||
? html`<ha-tooltip for="indicator">${this.message}</ha-tooltip>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: absolute;
|
||||
inset-inline-end: -6px;
|
||||
display: inline-block;
|
||||
}
|
||||
#indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
border: 3px solid;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--card-background-color);
|
||||
transition: all var(--ha-animation-duration-normal) ease-in-out;
|
||||
}
|
||||
:host([state="pass"]) #indicator {
|
||||
background-color: var(--ha-color-fill-success-loud-resting);
|
||||
border-color: var(--ha-color-fill-success-loud-resting);
|
||||
}
|
||||
:host([state="pass"]) #indicator:hover {
|
||||
background-color: var(--ha-color-fill-success-loud-hover);
|
||||
border-color: var(--ha-color-fill-success-loud-hover);
|
||||
}
|
||||
:host([state="fail"]) #indicator {
|
||||
border-color: var(--ha-color-fill-warning-loud-resting);
|
||||
}
|
||||
:host([state="fail"]) #indicator:hover {
|
||||
background-color: var(--ha-color-fill-warning-loud-hover);
|
||||
border-color: var(--ha-color-fill-warning-loud-hover);
|
||||
}
|
||||
:host([state="invalid"]) #indicator {
|
||||
border-color: var(--ha-color-fill-danger-loud-resting);
|
||||
}
|
||||
:host([state="invalid"]) #indicator:hover {
|
||||
background-color: var(--ha-color-fill-danger-loud-hover);
|
||||
border-color: var(--ha-color-fill-danger-loud-hover);
|
||||
}
|
||||
:host([state="unknown"]) #indicator {
|
||||
border-color: var(--ha-color-fill-neutral-loud-resting);
|
||||
}
|
||||
:host([state="unknown"]) #indicator:hover {
|
||||
background-color: var(--ha-color-fill-neutral-loud-hover);
|
||||
border-color: var(--ha-color-fill-neutral-loud-hover);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-row-live-test": HaAutomationRowLiveTest;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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");
|
||||
@@ -496,6 +497,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;
|
||||
@@ -601,6 +610,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) {
|
||||
@@ -611,6 +623,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;
|
||||
@@ -823,6 +836,7 @@ export class StatisticsChart extends LitElement {
|
||||
val.push(currentValue);
|
||||
}
|
||||
statDataSets[i].data!.push([now, ...val]);
|
||||
trackY(val[val.length - 1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -856,6 +870,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
|
||||
@@ -889,21 +904,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)));
|
||||
}
|
||||
@@ -38,7 +38,7 @@ import { stateActive } from "../common/entity/state_active";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import type { MediaPlayerItemId } from "../components/media-player/ha-media-player-browse";
|
||||
import type { HomeAssistant, TranslationDict } from "../types";
|
||||
import { isUnavailableState } from "./entity/entity";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
import { isTTSMediaSource } from "./tts";
|
||||
|
||||
interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
|
||||
@@ -284,7 +284,8 @@ export const computeMediaControls = (
|
||||
|
||||
const state = stateObj.state;
|
||||
|
||||
if (isUnavailableState(state)) {
|
||||
// We only filter out `unavailable`, not `unknown`
|
||||
if (state === UNAVAILABLE) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-tooltip";
|
||||
import { showJoinMediaPlayersDialog } from "../../../components/media-player/show-join-media-players-dialog";
|
||||
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import type {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerEntity,
|
||||
@@ -275,7 +275,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
protected _renderGrouping() {
|
||||
if (
|
||||
!this.stateObj ||
|
||||
isUnavailableState(this.stateObj.state) ||
|
||||
// Compare against `unavailable` so we allow `unknown`
|
||||
this.stateObj.state === UNAVAILABLE ||
|
||||
!supportsFeature(this.stateObj, MediaPlayerEntityFeature.GROUPING)
|
||||
) {
|
||||
return nothing;
|
||||
@@ -315,7 +316,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
if (isUnavailableState(this.stateObj.state)) {
|
||||
if (this.stateObj.state === UNAVAILABLE) {
|
||||
return this._renderEmptyCover(this.hass.formatEntityState(this.stateObj));
|
||||
}
|
||||
|
||||
@@ -461,7 +462,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
: nothing}
|
||||
${this._renderVolumeControl()}
|
||||
<div class="controls-row">
|
||||
${!isUnavailableState(stateObj.state) &&
|
||||
${stateObj.state !== UNAVAILABLE &&
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.BROWSE_MEDIA)
|
||||
? this._renderControlButton(
|
||||
"browse_media",
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
mdiAlertCircleCheck,
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiArrowDown,
|
||||
mdiArrowRightThin,
|
||||
mdiArrowUp,
|
||||
mdiCheckboxBlankOutline,
|
||||
mdiCheckboxOutline,
|
||||
@@ -333,10 +332,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
${type !== "condition" &&
|
||||
(this.action as NonConditionAction).continue_on_error === true
|
||||
? html`<ha-svg-icon
|
||||
class="arrow-right"
|
||||
.path=${mdiArrowRightThin}
|
||||
></ha-svg-icon
|
||||
><ha-svg-icon
|
||||
id="svg-icon"
|
||||
.path=${mdiAlertCircleCheck}
|
||||
></ha-svg-icon>
|
||||
@@ -1163,9 +1158,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
rowStyles,
|
||||
overflowStyles,
|
||||
css`
|
||||
ha-svg-icon.arrow-right {
|
||||
--icon-primary-color: var(--ha-color-fill-neutral-loud-resting);
|
||||
}
|
||||
ha-svg-icon#svg-icon {
|
||||
--icon-primary-color: var(--ha-color-fill-neutral-loud-active);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import type {
|
||||
} from "home-assistant-js-websocket";
|
||||
import { dump } from "js-yaml";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -39,6 +39,7 @@ import { debounce } from "../../../../common/util/debounce";
|
||||
import "../../../../components/automation/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/automation/ha-automation-row";
|
||||
import "../../../../components/automation/ha-automation-row-event-chip";
|
||||
import "../../../../components/automation/ha-automation-row-live-test";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-condition-icon";
|
||||
import "../../../../components/ha-dropdown";
|
||||
@@ -46,7 +47,6 @@ import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
@@ -498,23 +498,15 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
@click=${this._toggleSidebar}
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
>${this._renderRow()}
|
||||
<div
|
||||
<ha-automation-row-live-test
|
||||
slot="icons"
|
||||
id="live-test"
|
||||
class=${this._liveTestResult.state}
|
||||
role="status"
|
||||
tabindex="0"
|
||||
aria-label=${this.hass.localize(
|
||||
.state=${this._liveTestResult.state}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.live_test_state.${this._liveTestResult.state}`
|
||||
)}
|
||||
>
|
||||
${this._liveTestResult.message
|
||||
? html`<ha-tooltip for="live-test">
|
||||
${this._liveTestResult.message}
|
||||
</ha-tooltip>`
|
||||
: nothing}
|
||||
</div></ha-automation-row
|
||||
>`
|
||||
.message=${this._liveTestResult.message}
|
||||
></ha-automation-row-live-test
|
||||
></ha-automation-row>`
|
||||
: html`
|
||||
<ha-expansion-panel
|
||||
left-chevron
|
||||
@@ -1064,52 +1056,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
overflowStyles,
|
||||
css`
|
||||
#live-test {
|
||||
position: absolute;
|
||||
inset-inline-end: -6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
border: 3px solid;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--card-background-color);
|
||||
transition: all var(--ha-animation-duration-normal) ease-in-out;
|
||||
}
|
||||
#live-test.pass {
|
||||
background-color: var(--ha-color-fill-success-loud-resting);
|
||||
border-color: var(--ha-color-fill-success-loud-resting);
|
||||
}
|
||||
#live-test.pass:hover {
|
||||
background-color: var(--ha-color-fill-success-loud-hover);
|
||||
border-color: var(--ha-color-fill-success-loud-hover);
|
||||
}
|
||||
#live-test.fail {
|
||||
border-color: var(--ha-color-fill-warning-loud-resting);
|
||||
}
|
||||
#live-test.fail:hover {
|
||||
background-color: var(--ha-color-fill-warning-loud-hover);
|
||||
border-color: var(--ha-color-fill-warning-loud-hover);
|
||||
}
|
||||
#live-test.invalid {
|
||||
border-color: var(--ha-color-fill-danger-loud-resting);
|
||||
}
|
||||
#live-test.invalid:hover {
|
||||
background-color: var(--ha-color-fill-danger-loud-hover);
|
||||
border-color: var(--ha-color-fill-danger-loud-hover);
|
||||
}
|
||||
#live-test.unknown {
|
||||
border-color: var(--ha-color-fill-neutral-loud-resting);
|
||||
}
|
||||
#live-test.unknown:hover {
|
||||
background-color: var(--ha-color-fill-neutral-loud-hover);
|
||||
border-color: var(--ha-color-fill-neutral-loud-hover);
|
||||
}
|
||||
`,
|
||||
];
|
||||
return [rowStyles, overflowStyles];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiDelete,
|
||||
mdiWater,
|
||||
mdiDragHorizontalVariant,
|
||||
@@ -6,7 +7,7 @@ import {
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
@@ -15,10 +16,12 @@ import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-sortable";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import type {
|
||||
DeviceConsumptionEnergyPreference,
|
||||
EnergyPreferences,
|
||||
EnergyPreferencesValidation,
|
||||
EnergyValidationIssue,
|
||||
} from "../../../../data/energy";
|
||||
import { saveEnergyPreferences } from "../../../../data/energy";
|
||||
import type { StatisticsMetaData } from "../../../../data/recorder";
|
||||
@@ -93,7 +96,7 @@ export class EnergyDeviceSettingsWater extends LitElement {
|
||||
${repeat(
|
||||
this.preferences.device_consumption_water,
|
||||
(device) => device.stat_consumption,
|
||||
(device) => html`
|
||||
(device, index) => html`
|
||||
<div class="row" .device=${device}>
|
||||
<div class="handle">
|
||||
<ha-svg-icon
|
||||
@@ -108,6 +111,12 @@ export class EnergyDeviceSettingsWater extends LitElement {
|
||||
this.statsMetadata?.[device.stat_consumption]
|
||||
)}</span
|
||||
>
|
||||
${this._renderIssueIndicator(
|
||||
this.validationResult?.device_consumption_water[
|
||||
index
|
||||
],
|
||||
index
|
||||
)}
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.edit")}
|
||||
@click=${this._editDevice}
|
||||
@@ -144,6 +153,31 @@ export class EnergyDeviceSettingsWater extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderIssueIndicator(
|
||||
issues: EnergyValidationIssue[] | undefined,
|
||||
index: number
|
||||
) {
|
||||
if (!issues?.length) {
|
||||
return nothing;
|
||||
}
|
||||
const titles = issues.map(
|
||||
(issue) =>
|
||||
this.hass.localize(`component.energy.issues.${issue.type}.title`) ||
|
||||
issue.type
|
||||
);
|
||||
const label = titles.join("\n");
|
||||
const id = `issue-icon-${index}`;
|
||||
return html`
|
||||
<ha-svg-icon
|
||||
id=${id}
|
||||
class="issue-icon"
|
||||
.path=${mdiAlertCircle}
|
||||
aria-label=${label}
|
||||
></ha-svg-icon>
|
||||
<ha-tooltip .for=${id} placement="top">${label}</ha-tooltip>
|
||||
`;
|
||||
}
|
||||
|
||||
private _itemMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
@@ -248,6 +282,9 @@ export class EnergyDeviceSettingsWater extends LitElement {
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
.issue-icon {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiDelete,
|
||||
mdiDevices,
|
||||
mdiDragHorizontalVariant,
|
||||
@@ -6,7 +7,7 @@ import {
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
@@ -15,10 +16,12 @@ import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-sortable";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import type {
|
||||
DeviceConsumptionEnergyPreference,
|
||||
EnergyPreferences,
|
||||
EnergyPreferencesValidation,
|
||||
EnergyValidationIssue,
|
||||
} from "../../../../data/energy";
|
||||
import { saveEnergyPreferences } from "../../../../data/energy";
|
||||
import type { StatisticsMetaData } from "../../../../data/recorder";
|
||||
@@ -93,7 +96,7 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
${repeat(
|
||||
this.preferences.device_consumption,
|
||||
(device) => device.stat_consumption,
|
||||
(device) => html`
|
||||
(device, index) => html`
|
||||
<div class="row" .device=${device}>
|
||||
<div class="handle">
|
||||
<ha-svg-icon
|
||||
@@ -108,6 +111,10 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
this.statsMetadata?.[device.stat_consumption]
|
||||
)}</span
|
||||
>
|
||||
${this._renderIssueIndicator(
|
||||
this.validationResult?.device_consumption[index],
|
||||
index
|
||||
)}
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.edit")}
|
||||
@click=${this._editDevice}
|
||||
@@ -144,6 +151,31 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderIssueIndicator(
|
||||
issues: EnergyValidationIssue[] | undefined,
|
||||
index: number
|
||||
) {
|
||||
if (!issues?.length) {
|
||||
return nothing;
|
||||
}
|
||||
const titles = issues.map(
|
||||
(issue) =>
|
||||
this.hass.localize(`component.energy.issues.${issue.type}.title`) ||
|
||||
issue.type
|
||||
);
|
||||
const label = titles.join("\n");
|
||||
const id = `issue-icon-${index}`;
|
||||
return html`
|
||||
<ha-svg-icon
|
||||
id=${id}
|
||||
class="issue-icon"
|
||||
.path=${mdiAlertCircle}
|
||||
aria-label=${label}
|
||||
></ha-svg-icon>
|
||||
<ha-tooltip .for=${id} placement="top">${label}</ha-tooltip>
|
||||
`;
|
||||
}
|
||||
|
||||
private _itemMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
@@ -244,6 +276,9 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
.issue-icon {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
import { conditionsEntityContext } from "../conditions/context";
|
||||
import "../conditions/ha-card-conditions-editor";
|
||||
import "../conditions/ha-visibility-status";
|
||||
|
||||
@customElement("hui-badge-visibility-editor")
|
||||
export class HuiBadgeVisibilityEditor extends LitElement {
|
||||
@@ -34,11 +34,10 @@ export class HuiBadgeVisibilityEditor extends LitElement {
|
||||
render() {
|
||||
const conditions = this.config.visibility ?? [];
|
||||
return html`
|
||||
<p class="intro">
|
||||
${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_badge.visibility.explanation`
|
||||
)}
|
||||
</p>
|
||||
<ha-visibility-status
|
||||
.hass=${this.hass}
|
||||
.conditions=${conditions}
|
||||
></ha-visibility-status>
|
||||
<ha-card-conditions-editor
|
||||
.hass=${this.hass}
|
||||
.conditions=${conditions}
|
||||
@@ -62,10 +61,8 @@ export class HuiBadgeVisibilityEditor extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.intro {
|
||||
margin: 0;
|
||||
color: var(--secondary-text-color);
|
||||
margin-bottom: 8px;
|
||||
ha-visibility-status {
|
||||
margin-bottom: var(--ha-space-3);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
import { conditionsEntityContext } from "../conditions/context";
|
||||
import "../conditions/ha-card-conditions-editor";
|
||||
import "../conditions/ha-visibility-status";
|
||||
|
||||
@customElement("hui-card-visibility-editor")
|
||||
export class HuiCardVisibilityEditor extends LitElement {
|
||||
@@ -34,11 +34,10 @@ export class HuiCardVisibilityEditor extends LitElement {
|
||||
render() {
|
||||
const conditions = this.config.visibility ?? [];
|
||||
return html`
|
||||
<p class="intro">
|
||||
${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_card.visibility.explanation`
|
||||
)}
|
||||
</p>
|
||||
<ha-visibility-status
|
||||
.hass=${this.hass}
|
||||
.conditions=${conditions}
|
||||
></ha-visibility-status>
|
||||
<ha-card-conditions-editor
|
||||
.hass=${this.hass}
|
||||
.conditions=${conditions}
|
||||
@@ -62,10 +61,8 @@ export class HuiCardVisibilityEditor extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.intro {
|
||||
margin: 0;
|
||||
color: var(--secondary-text-color);
|
||||
margin-bottom: 8px;
|
||||
ha-visibility-status {
|
||||
margin-bottom: var(--ha-space-3);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@ import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ConditionListenersController } from "../../../../common/controllers/condition-listeners-controller";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import "../../../../components/automation/ha-automation-row-event-chip";
|
||||
import "../../../../components/automation/ha-automation-row-live-test";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
@@ -33,11 +35,11 @@ import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { ICON_CONDITION } from "../../common/icon-condition";
|
||||
import type {
|
||||
AndCondition,
|
||||
Condition,
|
||||
LegacyCondition,
|
||||
OrCondition,
|
||||
AndCondition,
|
||||
NotCondition,
|
||||
OrCondition,
|
||||
} from "../../common/validate-condition";
|
||||
import {
|
||||
checkConditionsMet,
|
||||
@@ -103,6 +105,13 @@ export class HaCardConditionEditor extends LitElement {
|
||||
|
||||
@state() private _testingResult?: boolean;
|
||||
|
||||
@state() private _liveTestResult: {
|
||||
state: "pass" | "fail" | "invalid" | "unknown";
|
||||
message?: string;
|
||||
} = { state: "unknown" };
|
||||
|
||||
private _listeners = new ConditionListenersController(this);
|
||||
|
||||
private get _editor() {
|
||||
if (!this._condition) return undefined;
|
||||
return customElements.get(
|
||||
@@ -116,6 +125,14 @@ export class HaCardConditionEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _setupConditionListeners() {
|
||||
this._listeners.setup(
|
||||
this.condition ? [this.condition as Condition] : [],
|
||||
this.hass,
|
||||
() => this._evaluateLiveTest()
|
||||
);
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
if (changedProperties.has("condition")) {
|
||||
this._condition = {
|
||||
@@ -143,7 +160,61 @@ export class HaCardConditionEditor extends LitElement {
|
||||
if (!this._uiAvailable && !this._yamlMode) {
|
||||
this._yamlMode = true;
|
||||
}
|
||||
|
||||
this._setupConditionListeners();
|
||||
}
|
||||
|
||||
if (changedProperties.has("condition") || changedProperties.has("hass")) {
|
||||
this._evaluateLiveTest();
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>): void {
|
||||
if ((changedProperties as Map<string, unknown>).has("_entityContext")) {
|
||||
this._evaluateLiveTest();
|
||||
}
|
||||
}
|
||||
|
||||
private _evaluateLiveTest() {
|
||||
if (!this.condition || !this._condition) {
|
||||
this._liveTestResult = { state: "unknown" };
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isNoEntityCondition(this._condition.condition, this._noEntity) ||
|
||||
containsNoEntityCondition(this._condition, this._noEntity)
|
||||
) {
|
||||
this._liveTestResult = {
|
||||
state: "unknown",
|
||||
message: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.live_test_state.unknown"
|
||||
),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateConditionalConfig([this.condition])) {
|
||||
this._liveTestResult = {
|
||||
state: "invalid",
|
||||
message: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.live_test_state.invalid"
|
||||
),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const testContext =
|
||||
this._entityContext?.mode === "current"
|
||||
? { entity_id: this._entityContext.entityId }
|
||||
: {};
|
||||
const pass = checkConditionsMet([this.condition], this.hass, testContext);
|
||||
this._liveTestResult = {
|
||||
state: pass ? "pass" : "fail",
|
||||
message: this.hass.localize(
|
||||
`ui.panel.lovelace.editor.condition-editor.live_test_state.${pass ? "pass" : "fail"}`
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -151,6 +222,10 @@ export class HaCardConditionEditor extends LitElement {
|
||||
|
||||
if (!condition) return nothing;
|
||||
|
||||
const hideLiveTest =
|
||||
isNoEntityCondition(condition.condition, this._noEntity) ||
|
||||
containsNoEntityCondition(condition, this._noEntity);
|
||||
|
||||
return html`
|
||||
<div class="container">
|
||||
<ha-expansion-panel left-chevron>
|
||||
@@ -164,6 +239,33 @@ export class HaCardConditionEditor extends LitElement {
|
||||
`ui.panel.lovelace.editor.condition-editor.condition.${condition.condition}.label`
|
||||
) || condition.condition}
|
||||
</h3>
|
||||
<ha-automation-row-event-chip
|
||||
.show=${this._testingResult !== undefined}
|
||||
.variant=${this._testingResult ? "success" : "warning"}
|
||||
slot="event"
|
||||
class="event-chip"
|
||||
aria-live="polite"
|
||||
>
|
||||
${this._testingResult
|
||||
? this.hass.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.testing_pass"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.testing_error"
|
||||
)}
|
||||
</ha-automation-row-event-chip>
|
||||
${hideLiveTest
|
||||
? nothing
|
||||
: html`
|
||||
<ha-automation-row-live-test
|
||||
slot="icons"
|
||||
.state=${this._liveTestResult.state}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.condition-editor.live_test_state.${this._liveTestResult.state}`
|
||||
)}
|
||||
.message=${this._liveTestResult.message}
|
||||
></ha-automation-row-live-test>
|
||||
`}
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@wa-select=${this._handleAction}
|
||||
@@ -267,23 +369,6 @@ export class HaCardConditionEditor extends LitElement {
|
||||
`}
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
<div
|
||||
class="testing ${classMap({
|
||||
active: this._testingResult !== undefined,
|
||||
pass: this._testingResult === true,
|
||||
error: this._testingResult === false,
|
||||
})}"
|
||||
>
|
||||
${this._testingResult
|
||||
? this.hass.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.testing_pass"
|
||||
)
|
||||
: this._testingResult === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.testing_error"
|
||||
)
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -418,41 +503,9 @@ export class HaCardConditionEditor extends LitElement {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.testing {
|
||||
.event-chip {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
text-transform: uppercase;
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
background-color: var(--divider-color, #e0e0e0);
|
||||
color: var(--text-primary-color);
|
||||
max-height: 0px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s;
|
||||
text-align: center;
|
||||
border-top-right-radius: calc(
|
||||
var(--ha-card-border-radius, var(--ha-border-radius-lg)) - var(
|
||||
--ha-card-border-width,
|
||||
1px
|
||||
)
|
||||
);
|
||||
border-top-left-radius: calc(
|
||||
var(--ha-card-border-radius, var(--ha-border-radius-lg)) - var(
|
||||
--ha-card-border-width,
|
||||
1px
|
||||
)
|
||||
);
|
||||
}
|
||||
.testing.active {
|
||||
max-height: 100px;
|
||||
}
|
||||
.testing.error {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
.testing.pass {
|
||||
background-color: var(--success-color);
|
||||
inset-inline-end: 40px;
|
||||
}
|
||||
.container {
|
||||
position: relative;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { mdiAlertCircle, mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ConditionListenersController } from "../../../../common/controllers/condition-listeners-controller";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { HaRowItem } from "../../../../components/item/ha-row-item";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
Condition,
|
||||
LegacyCondition,
|
||||
} from "../../common/validate-condition";
|
||||
import {
|
||||
checkConditionsMet,
|
||||
validateConditionalConfig,
|
||||
} from "../../common/validate-condition";
|
||||
import type { ConditionsEntityContext } from "./context";
|
||||
import { conditionsEntityContext } from "./context";
|
||||
|
||||
type VisibilityState = "visible" | "hidden" | "invalid";
|
||||
|
||||
const STATE_ICONS: Record<VisibilityState, string> = {
|
||||
visible: mdiEye,
|
||||
hidden: mdiEyeOff,
|
||||
invalid: mdiAlertCircle,
|
||||
};
|
||||
|
||||
/**
|
||||
* @element ha-visibility-status
|
||||
* @extends {HaRowItem}
|
||||
*
|
||||
* @summary
|
||||
* Row-style banner that surfaces the live visibility result for a set of
|
||||
* lovelace conditions. Replaces the static explanation alert at the top of
|
||||
* card / section / badge / conditional-card visibility editors.
|
||||
*
|
||||
* @attr {"visible"|"hidden"|"invalid"} state - Computed visibility state (reflected for styling).
|
||||
*/
|
||||
@customElement("ha-visibility-status")
|
||||
export class HaVisibilityStatus extends HaRowItem {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
public conditions: (Condition | LegacyCondition)[] = [];
|
||||
|
||||
@state()
|
||||
@consume({ context: conditionsEntityContext, subscribe: true })
|
||||
private _entityContext?: ConditionsEntityContext;
|
||||
|
||||
@property({ reflect: true })
|
||||
public state: VisibilityState = "visible";
|
||||
|
||||
private _listeners = new ConditionListenersController(this);
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("conditions") || changedProperties.has("hass")) {
|
||||
this._listeners.setup(
|
||||
(this.conditions ?? []) as Condition[],
|
||||
this.hass,
|
||||
() => this._evaluate()
|
||||
);
|
||||
}
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("conditions") ||
|
||||
(changedProperties as Map<string, unknown>).has("_entityContext")
|
||||
) {
|
||||
this._evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
protected override _renderInner(): TemplateResult {
|
||||
return html`
|
||||
<div part="start" class="start">
|
||||
<ha-svg-icon .path=${STATE_ICONS[this.state]}></ha-svg-icon>
|
||||
</div>
|
||||
<div part="content" class="content">
|
||||
<div part="headline" class="headline">
|
||||
${this.hass?.localize(
|
||||
`ui.panel.lovelace.editor.condition-editor.visibility_status.${this.state}.headline`
|
||||
)}
|
||||
</div>
|
||||
<div part="supporting-text" class="supporting">
|
||||
${this.hass?.localize(
|
||||
`ui.panel.lovelace.editor.condition-editor.visibility_status.${this.state}.supporting${(this.conditions?.length ?? 0) === 0 ? "_empty" : ""}`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _evaluate() {
|
||||
const conditions = this.conditions ?? [];
|
||||
let newState: VisibilityState;
|
||||
if (conditions.length === 0) {
|
||||
newState = "visible";
|
||||
} else if (!validateConditionalConfig(conditions)) {
|
||||
newState = "invalid";
|
||||
} else {
|
||||
const context =
|
||||
this._entityContext?.mode === "current"
|
||||
? { entity_id: this._entityContext.entityId }
|
||||
: {};
|
||||
newState = checkConditionsMet(conditions, this.hass, context)
|
||||
? "visible"
|
||||
: "hidden";
|
||||
}
|
||||
if (newState === this.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = newState;
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
HaRowItem.styles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
border-radius: var(--ha-border-radius-xl);
|
||||
transition: background-color var(--ha-animation-duration-normal)
|
||||
ease-in-out;
|
||||
}
|
||||
.base {
|
||||
padding: var(--ha-space-4);
|
||||
}
|
||||
:host([state="visible"]) {
|
||||
background-color: var(--ha-color-fill-success-quiet-resting);
|
||||
--visibility-status-color: var(--ha-color-on-success-normal);
|
||||
}
|
||||
:host([state="hidden"]) {
|
||||
background-color: var(--ha-color-fill-warning-quiet-resting);
|
||||
--visibility-status-color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
:host([state="invalid"]) {
|
||||
background-color: var(--ha-color-fill-danger-quiet-resting);
|
||||
--visibility-status-color: var(--ha-color-on-danger-normal);
|
||||
}
|
||||
.start {
|
||||
align-self: start;
|
||||
}
|
||||
.start ha-svg-icon {
|
||||
color: var(--visibility-status-color);
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
.headline {
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
white-space: normal;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-visibility-status": HaVisibilityStatus;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import { any, array, assert, assign, object, optional } from "superstruct";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-tab-group";
|
||||
@@ -21,6 +20,7 @@ import "../card-editor/hui-card-element-editor";
|
||||
import type { HuiCardElementEditor } from "../card-editor/hui-card-element-editor";
|
||||
import "../card-editor/hui-card-picker";
|
||||
import "../conditions/ha-card-conditions-editor";
|
||||
import "../conditions/ha-visibility-status";
|
||||
import "../hui-element-editor";
|
||||
import type { ConfigChangedEvent } from "../hui-element-editor";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
@@ -147,11 +147,10 @@ export class HuiConditionalCardEditor
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.explanation"
|
||||
)}
|
||||
</ha-alert>
|
||||
<ha-visibility-status
|
||||
.hass=${this.hass}
|
||||
.conditions=${this._config.conditions ?? []}
|
||||
></ha-visibility-status>
|
||||
<ha-card-conditions-editor
|
||||
.hass=${this.hass}
|
||||
.conditions=${this._config.conditions}
|
||||
@@ -246,9 +245,9 @@ export class HuiConditionalCardEditor
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
ha-visibility-status {
|
||||
margin-top: var(--ha-space-3);
|
||||
margin-bottom: var(--ha-space-3);
|
||||
}
|
||||
.card {
|
||||
margin-top: 8px;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
import "../conditions/ha-card-conditions-editor";
|
||||
import "../conditions/ha-visibility-status";
|
||||
|
||||
@customElement("hui-section-visibility-editor")
|
||||
export class HuiDialogEditSection extends LitElement {
|
||||
@@ -16,11 +16,10 @@ export class HuiDialogEditSection extends LitElement {
|
||||
render() {
|
||||
const conditions = this.config.visibility ?? [];
|
||||
return html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.edit_section.visibility.explanation`
|
||||
)}
|
||||
</ha-alert>
|
||||
<ha-visibility-status
|
||||
.hass=${this.hass}
|
||||
.conditions=${conditions}
|
||||
></ha-visibility-status>
|
||||
<ha-card-conditions-editor
|
||||
.hass=${this.hass}
|
||||
.conditions=${conditions}
|
||||
@@ -30,6 +29,12 @@ export class HuiDialogEditSection extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-visibility-status {
|
||||
margin-bottom: var(--ha-space-3);
|
||||
}
|
||||
`;
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const conditions = ev.detail.value as Condition[];
|
||||
|
||||
@@ -22,7 +22,7 @@ import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-slider";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import type {
|
||||
ControlButton,
|
||||
MediaPlayerEntity,
|
||||
@@ -195,7 +195,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
||||
<div class="controls">
|
||||
${supportsFeature(stateObj, MediaPlayerEntityFeature.TURN_ON) &&
|
||||
(!stateActive(stateObj) || assumedState) &&
|
||||
!isUnavailableState(entityState)
|
||||
entityState !== UNAVAILABLE
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.path=${assumedState ? mdiPowerOn : mdiPower}
|
||||
@@ -209,7 +209,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
||||
(stateActive(stateObj) ||
|
||||
assumedState ||
|
||||
!supportsFeature(stateObj, MediaPlayerEntityFeature.TURN_ON) ||
|
||||
isUnavailableState(entityState))
|
||||
entityState === UNAVAILABLE)
|
||||
? buttons
|
||||
: ""}
|
||||
${supportsFeature(stateObj, MediaPlayerEntityFeature.TURN_OFF) &&
|
||||
|
||||
+22
-11
@@ -8929,9 +8929,6 @@
|
||||
"tab_visibility": "Visibility",
|
||||
"tab_layout": "Layout",
|
||||
"paste_condition": "Paste condition",
|
||||
"visibility": {
|
||||
"explanation": "The card will be shown when ALL conditions below are fulfilled. If no conditions are set, the card will always be shown."
|
||||
},
|
||||
"layout": {
|
||||
"full_width": "Full width",
|
||||
"full_width_helper": "Take up the full width of the section whatever its size",
|
||||
@@ -8962,10 +8959,7 @@
|
||||
"cut": "[%key:ui::panel::lovelace::editor::edit_card::cut%]",
|
||||
"duplicate": "[%key:ui::panel::lovelace::editor::edit_card::duplicate%]",
|
||||
"tab_config": "[%key:ui::panel::lovelace::editor::edit_card::tab_config%]",
|
||||
"tab_visibility": "[%key:ui::panel::lovelace::editor::edit_card::tab_visibility%]",
|
||||
"visibility": {
|
||||
"explanation": "The badge will be shown when ALL conditions below are fulfilled. If no conditions are set, the badge will always be shown."
|
||||
}
|
||||
"tab_visibility": "[%key:ui::panel::lovelace::editor::edit_card::tab_visibility%]"
|
||||
},
|
||||
"suggest_badge": {
|
||||
"header": "[%key:ui::panel::lovelace::editor::suggest_card::header%]",
|
||||
@@ -9037,9 +9031,6 @@
|
||||
"background_opacity": "Opacity",
|
||||
"theme": "Theme",
|
||||
"theme_helper": "Apply a specific theme to this section, overriding the view theme"
|
||||
},
|
||||
"visibility": {
|
||||
"explanation": "The section will be shown when ALL conditions below are fulfilled. If no conditions are set, the section will always be shown."
|
||||
}
|
||||
},
|
||||
"suggest_card": {
|
||||
@@ -9081,11 +9072,31 @@
|
||||
}
|
||||
},
|
||||
"condition-editor": {
|
||||
"explanation": "The card will be shown when ALL conditions below are fulfilled.",
|
||||
"add": "Add condition",
|
||||
"test": "[%key:ui::panel::config::automation::editor::conditions::test%]",
|
||||
"testing_pass": "[%key:ui::panel::config::automation::editor::conditions::testing_pass%]",
|
||||
"testing_error": "[%key:ui::panel::config::automation::editor::conditions::testing_error%]",
|
||||
"live_test_state": {
|
||||
"pass": "[%key:ui::panel::config::automation::editor::conditions::testing_pass%]",
|
||||
"fail": "[%key:ui::panel::config::automation::editor::conditions::testing_error%]",
|
||||
"invalid": "[%key:ui::panel::config::automation::editor::conditions::live_test_state::invalid%]",
|
||||
"unknown": "[%key:ui::panel::config::automation::editor::conditions::live_test_state::unknown%]"
|
||||
},
|
||||
"visibility_status": {
|
||||
"visible": {
|
||||
"headline": "Current visibility: Visible",
|
||||
"supporting": "All visibility conditions are met",
|
||||
"supporting_empty": "No visibility conditions are set"
|
||||
},
|
||||
"hidden": {
|
||||
"headline": "Current visibility: Hidden",
|
||||
"supporting": "Not all visibility conditions are met"
|
||||
},
|
||||
"invalid": {
|
||||
"headline": "Visibility status unknown",
|
||||
"supporting": "One or more conditions have an invalid configuration"
|
||||
}
|
||||
},
|
||||
"invalid_config_title": "Invalid configuration",
|
||||
"invalid_config_text": "The condition cannot be tested because the configuration is not valid.",
|
||||
"condition": {
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
export const IFRAME_SANDBOX =
|
||||
"allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts allow-modals allow-downloads";
|
||||
"allow-forms allow-popups allow-pointer-lock allow-scripts allow-modals allow-downloads";
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -1384,15 +1384,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/view@npm:6.42.1, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.37.0, @codemirror/view@npm:^6.42.0":
|
||||
version: 6.42.1
|
||||
resolution: "@codemirror/view@npm:6.42.1"
|
||||
"@codemirror/view@npm:6.43.0, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.37.0, @codemirror/view@npm:^6.42.0":
|
||||
version: 6.43.0
|
||||
resolution: "@codemirror/view@npm:6.43.0"
|
||||
dependencies:
|
||||
"@codemirror/state": "npm:^6.6.0"
|
||||
crelt: "npm:^1.0.6"
|
||||
style-mod: "npm:^4.1.0"
|
||||
w3c-keyname: "npm:^2.2.4"
|
||||
checksum: 10/5732718743c048d81d0f63b2c1804f7eb3ae61eac9442d0fee645214cdcca5bb2c5acceaa959c70602d7f1133b3fb150591c8e1c5e36ffbeaff6d6d252447fa2
|
||||
checksum: 10/7cfeebe1507f71a960dfb2d5152400507d28ed5827680bc73e0a093bfba9a796c2e559c960fd2b046379fac31ff0b59663dfc481baadf1d6ececd71eb5b48014
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1666,10 +1666,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/bigdecimal@npm:0.2.4":
|
||||
version: 0.2.4
|
||||
resolution: "@formatjs/bigdecimal@npm:0.2.4"
|
||||
checksum: 10/c945a25c8ef3dcdb31bbe7d829ef764e2006f9997eb275c2242d75b2094cd6bc53ecbf8bf9751b12f373c4aae0944cbeb7083be26f2386a197f1200918389bde
|
||||
"@formatjs/bigdecimal@npm:0.2.5":
|
||||
version: 0.2.5
|
||||
resolution: "@formatjs/bigdecimal@npm:0.2.5"
|
||||
checksum: 10/035a70be4175d47d82d81025ad4386d7c248ef7afb2676b4e0773595d01df1078f3d5224e2f7f17721c9169bbb77d4282898624bf0112d2e6350438b7032a1cb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1680,121 +1680,121 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-messageformat-parser@npm:3.5.8":
|
||||
version: 3.5.8
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.5.8"
|
||||
"@formatjs/icu-messageformat-parser@npm:3.5.9":
|
||||
version: 3.5.9
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.5.9"
|
||||
dependencies:
|
||||
"@formatjs/icu-skeleton-parser": "npm:2.1.8"
|
||||
checksum: 10/e082fe0c1abf0cbd5d65fb154710a894351ac7855e807ffb274e1dc7b3728bd6c8e0dc68cfdb88266e5554d31f0ca623514467d7d2c166698c17354415e7a073
|
||||
"@formatjs/icu-skeleton-parser": "npm:2.1.9"
|
||||
checksum: 10/b2543274b8359873ea279139c9da3ab0f42421651b28855c63d2ca7768a747e662f30ff3d296a1807425d08f1b3ae84376372289749da2fb17ba342e9686673a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-skeleton-parser@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@formatjs/icu-skeleton-parser@npm:2.1.8"
|
||||
checksum: 10/004ea08c6106c4eb3b073f4d7e231232508817b9d081499ec338479f89e41b874e1b02dee951d9ca32b16692a208f9e6a1c38ca3d8160837206a002d024d3b50
|
||||
"@formatjs/icu-skeleton-parser@npm:2.1.9":
|
||||
version: 2.1.9
|
||||
resolution: "@formatjs/icu-skeleton-parser@npm:2.1.9"
|
||||
checksum: 10/eacb8acd60d487092fc1a6b7fdbac87dfc32475db7001562034a8ca7b0a4be7a35f95c30928ae8314f5e680f63302180bece9462c872a042a0302a5f4cf6a842
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-datetimeformat@npm:7.4.4":
|
||||
version: 7.4.4
|
||||
resolution: "@formatjs/intl-datetimeformat@npm:7.4.4"
|
||||
"@formatjs/intl-datetimeformat@npm:7.4.5":
|
||||
version: 7.4.5
|
||||
resolution: "@formatjs/intl-datetimeformat@npm:7.4.5"
|
||||
dependencies:
|
||||
"@formatjs/bigdecimal": "npm:0.2.4"
|
||||
"@formatjs/intl-localematcher": "npm:0.8.7"
|
||||
checksum: 10/c731b19bdcdd5d407eaf51d5ea5ec6adc784e0ca0627c8bab93585577ade004b45c8158d69f11c7b2cfe5327f068c68e3df09148c955944288d5b25735b6fbdd
|
||||
"@formatjs/bigdecimal": "npm:0.2.5"
|
||||
"@formatjs/intl-localematcher": "npm:0.8.8"
|
||||
checksum: 10/e45b8dcb745016e705d2fd7078114ddaa04f9f5fbea7615c9155736633069d65039d41f7c41e492247b58d29d9828a3a9782b7de414d80492f221a1639944fab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-displaynames@npm:7.3.6":
|
||||
version: 7.3.6
|
||||
resolution: "@formatjs/intl-displaynames@npm:7.3.6"
|
||||
"@formatjs/intl-displaynames@npm:7.3.7":
|
||||
version: 7.3.7
|
||||
resolution: "@formatjs/intl-displaynames@npm:7.3.7"
|
||||
dependencies:
|
||||
"@formatjs/intl-localematcher": "npm:0.8.7"
|
||||
checksum: 10/78bf1f5300fae292eeefcd4009d9836deec4f8970fed88f7cce8c2db3afb764bb62a6145cedf0744e4af29ca4bb223f829452d0b01307e940123ec53862f9f3c
|
||||
"@formatjs/intl-localematcher": "npm:0.8.8"
|
||||
checksum: 10/1b7b38b3c45babd76b1b59656fa96a4f8cb27668a64e419231d04786b571546934d4e8706955ca6b16d0c8bb5bd2a5157a438c3081d43d491a7c65a141f84d2e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-durationformat@npm:0.10.10":
|
||||
version: 0.10.10
|
||||
resolution: "@formatjs/intl-durationformat@npm:0.10.10"
|
||||
"@formatjs/intl-durationformat@npm:0.10.11":
|
||||
version: 0.10.11
|
||||
resolution: "@formatjs/intl-durationformat@npm:0.10.11"
|
||||
dependencies:
|
||||
"@formatjs/bigdecimal": "npm:0.2.4"
|
||||
"@formatjs/intl-localematcher": "npm:0.8.7"
|
||||
checksum: 10/dbab9cb8410452d93287d9d7e26946533b82615aee973620ae828e0f55976421e9eb283781e6f20b28701e5fdaefaa6baf9e0dde02d9dd979c00c466d65eca5c
|
||||
"@formatjs/bigdecimal": "npm:0.2.5"
|
||||
"@formatjs/intl-localematcher": "npm:0.8.8"
|
||||
checksum: 10/df44f858d918ae25764aaee747d6e0679c9d5a3c5e09e3171ac991e72cd6f39f5863a5f92b66a2d19ce819bdad02ec4ad6e7434a84141af5eeb8e5fb82aa0595
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-getcanonicallocales@npm:3.2.7":
|
||||
version: 3.2.7
|
||||
resolution: "@formatjs/intl-getcanonicallocales@npm:3.2.7"
|
||||
checksum: 10/6e9ce95d71d07cd42b298ae1964e87ef205df8f698cf32e5945e6d31dffc970ca54d89334d13417388ae60b9c5178c314ee45f84c024dff19b7b7a9627945c64
|
||||
"@formatjs/intl-getcanonicallocales@npm:3.2.8":
|
||||
version: 3.2.8
|
||||
resolution: "@formatjs/intl-getcanonicallocales@npm:3.2.8"
|
||||
checksum: 10/d81f8752b118bf8b2de9049d482daba4c50c5b87c22ab6ed111499313d0007460d5d14d4909a5b3754d46b930b56e5fcf34b185098c04b826f1544b4acabe5e5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-listformat@npm:8.3.6":
|
||||
version: 8.3.6
|
||||
resolution: "@formatjs/intl-listformat@npm:8.3.6"
|
||||
"@formatjs/intl-listformat@npm:8.3.7":
|
||||
version: 8.3.7
|
||||
resolution: "@formatjs/intl-listformat@npm:8.3.7"
|
||||
dependencies:
|
||||
"@formatjs/intl-localematcher": "npm:0.8.7"
|
||||
checksum: 10/9fac6ad108fe9e085b7f7cdd70b142e74299d5290918aba4b09a55c405824271cf8dcb329a632bc4897484e8468445bfd89c9f19cf702b66863bcb616f3f4972
|
||||
"@formatjs/intl-localematcher": "npm:0.8.8"
|
||||
checksum: 10/01757f47172150d5eae7ce2cd47386dc6888b9c3d23305b9cc3f16b457e10cbdda7c5894629326532715d85fc04406a9844784d247994442dd2df9380d5cb53e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-locale@npm:5.3.6":
|
||||
version: 5.3.6
|
||||
resolution: "@formatjs/intl-locale@npm:5.3.6"
|
||||
"@formatjs/intl-locale@npm:5.3.7":
|
||||
version: 5.3.7
|
||||
resolution: "@formatjs/intl-locale@npm:5.3.7"
|
||||
dependencies:
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.2.7"
|
||||
"@formatjs/intl-supportedvaluesof": "npm:2.3.5"
|
||||
checksum: 10/7102d69ca243464d1dc4a396fa620492fbc7e2e51c11ebcd8a30b226428f586d45fdec2779fb91344b4ee5e34e317bfe97f1c09f9c523ce4d51276f133fcef1a
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.2.8"
|
||||
"@formatjs/intl-supportedvaluesof": "npm:2.3.6"
|
||||
checksum: 10/c601d1cabd96e96a08e94676b0d676f9f0bf348fca037e4390b9a55f22fa94fb9f2c72679a9be6ed74091e11f78a137ed4d2026889c43f2c45045967f18d6b3c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-localematcher@npm:0.8.7":
|
||||
version: 0.8.7
|
||||
resolution: "@formatjs/intl-localematcher@npm:0.8.7"
|
||||
"@formatjs/intl-localematcher@npm:0.8.8":
|
||||
version: 0.8.8
|
||||
resolution: "@formatjs/intl-localematcher@npm:0.8.8"
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize": "npm:3.1.5"
|
||||
checksum: 10/f7fc35a24af76e2010a0f272356915857739dfb5e41e77a200ae6ae6be37c6b3ea4e01632382f63063918074b00095f56ad5a03495505eb9d8913d122886f9be
|
||||
checksum: 10/21b02d3d5e40a9a3530f314e7ac2020c1caccc538baca120be6d37bf47ca3208ecf1a3d1fe1dde89d0d46382ec457c6c2714423dc7322ecbd1a3b60d553572a6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-numberformat@npm:9.3.7":
|
||||
version: 9.3.7
|
||||
resolution: "@formatjs/intl-numberformat@npm:9.3.7"
|
||||
"@formatjs/intl-numberformat@npm:9.3.8":
|
||||
version: 9.3.8
|
||||
resolution: "@formatjs/intl-numberformat@npm:9.3.8"
|
||||
dependencies:
|
||||
"@formatjs/bigdecimal": "npm:0.2.4"
|
||||
"@formatjs/intl-localematcher": "npm:0.8.7"
|
||||
checksum: 10/077fba3e9bcc15d3eac55407998026541a373350e7264f3f99f8d28743a3dd9e666e943b617ab7fadfeb8239cf79be7fe78e99f721539f89b4cb70912f1ac51c
|
||||
"@formatjs/bigdecimal": "npm:0.2.5"
|
||||
"@formatjs/intl-localematcher": "npm:0.8.8"
|
||||
checksum: 10/bb61e4489a75ad3e243e03fb6d2169549d90f088630b0300b3adc57e12882946f0280726b3bc8781b97ef1b22d95eac6b6346640e63fd77e89b1bf7303e02610
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-pluralrules@npm:6.3.6":
|
||||
version: 6.3.6
|
||||
resolution: "@formatjs/intl-pluralrules@npm:6.3.6"
|
||||
"@formatjs/intl-pluralrules@npm:6.3.7":
|
||||
version: 6.3.7
|
||||
resolution: "@formatjs/intl-pluralrules@npm:6.3.7"
|
||||
dependencies:
|
||||
"@formatjs/bigdecimal": "npm:0.2.4"
|
||||
"@formatjs/intl-localematcher": "npm:0.8.7"
|
||||
checksum: 10/bfea3c0643b7d3a4b662d01e518c5204dc995ca3ada26646c804348bf2c6b3c7ecc78e0e550de8a3cdd08fc9e9724904e36a1160b9b67bd8f113e1a8537674e5
|
||||
"@formatjs/bigdecimal": "npm:0.2.5"
|
||||
"@formatjs/intl-localematcher": "npm:0.8.8"
|
||||
checksum: 10/0f98dcec85d7365988e5c0ed5d21f13f3b514e9fc1227b0d7d9fbef9ea71dd09427a672c80b06cf065b76b48d48fbed6ee1a687c1365b63260aab68cb5f3b478
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-relativetimeformat@npm:12.3.6":
|
||||
version: 12.3.6
|
||||
resolution: "@formatjs/intl-relativetimeformat@npm:12.3.6"
|
||||
"@formatjs/intl-relativetimeformat@npm:12.3.7":
|
||||
version: 12.3.7
|
||||
resolution: "@formatjs/intl-relativetimeformat@npm:12.3.7"
|
||||
dependencies:
|
||||
"@formatjs/intl-localematcher": "npm:0.8.7"
|
||||
checksum: 10/0448dd9a94f768d764b15b4d348a04780fd0d85fbbb864f21fc5b4988d95980c8902fa00deb6aeea2a0f98001ec91ba7741379960b0fce0a585e6965009fa914
|
||||
"@formatjs/intl-localematcher": "npm:0.8.8"
|
||||
checksum: 10/d5dc0d3e6d87409deae64ca349010fba0d8c2b4d79fccf57f1cb05ed16732503d41b891997d2f6c7c46068ea7b3044b4e6adf9d632eab9a550272442d02490ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-supportedvaluesof@npm:2.3.5":
|
||||
version: 2.3.5
|
||||
resolution: "@formatjs/intl-supportedvaluesof@npm:2.3.5"
|
||||
"@formatjs/intl-supportedvaluesof@npm:2.3.6":
|
||||
version: 2.3.6
|
||||
resolution: "@formatjs/intl-supportedvaluesof@npm:2.3.6"
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize": "npm:3.1.5"
|
||||
checksum: 10/dd0423f4d69578b6bf81d91a94660a3eedabcd78563a3d46a41400b9fceb5919eb1a15169ac38e7f9d21380f3c1704a72258b57305e9481f5217a01609d7a3fc
|
||||
checksum: 10/04f7bc6e256533e8eebf365bc986c17f5c3dd81b58d3452e26b032cab2cda2de68593376119913caf28c44afeb08760367e44de7efb6850d977b5bb3482e011b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4335,129 +4335,141 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/basic@npm:^3.7.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/basic@npm:3.9.1"
|
||||
"@tsparticles/basic@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/basic@npm:4.0.0"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
"@tsparticles/move-base": "npm:3.9.1"
|
||||
"@tsparticles/plugin-hex-color": "npm:3.9.1"
|
||||
"@tsparticles/plugin-hsl-color": "npm:3.9.1"
|
||||
"@tsparticles/plugin-rgb-color": "npm:3.9.1"
|
||||
"@tsparticles/shape-circle": "npm:3.9.1"
|
||||
"@tsparticles/updater-color": "npm:3.9.1"
|
||||
"@tsparticles/updater-opacity": "npm:3.9.1"
|
||||
"@tsparticles/updater-out-modes": "npm:3.9.1"
|
||||
"@tsparticles/updater-size": "npm:3.9.1"
|
||||
checksum: 10/a3d0c926e5822931df9762b2038955093ecfb558715807482f691d54efb848286a5d78a55a184885b2d4f46a005bf52c3c54e0013e29e71158ae1ccb5dce08d3
|
||||
"@tsparticles/engine": "npm:4.0.0"
|
||||
"@tsparticles/plugin-hex-color": "npm:4.0.0"
|
||||
"@tsparticles/plugin-hsl-color": "npm:4.0.0"
|
||||
"@tsparticles/plugin-move": "npm:4.0.0"
|
||||
"@tsparticles/plugin-rgb-color": "npm:4.0.0"
|
||||
"@tsparticles/shape-circle": "npm:4.0.0"
|
||||
"@tsparticles/updater-opacity": "npm:4.0.0"
|
||||
"@tsparticles/updater-out-modes": "npm:4.0.0"
|
||||
"@tsparticles/updater-paint": "npm:4.0.0"
|
||||
"@tsparticles/updater-size": "npm:4.0.0"
|
||||
checksum: 10/5e455beb0663019d719bc111928a9e22a0f471450414391eed4be2f00455019c59ec85e1e8b7fadf8da2dc258c803d555ebb9b2e0a497a8f53b5922fffab314e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/engine@npm:3.9.1, @tsparticles/engine@npm:^3.7.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/engine@npm:3.9.1"
|
||||
checksum: 10/91e95f33d526558e0f7251a75dc2971873a7854bb903b61aab95d394c954d3d5f6c2429c151483ebe83445e14e2a7ed9ceadb0fd9c0b7e8c11ec316e4bfd04fa
|
||||
"@tsparticles/engine@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/engine@npm:4.0.0"
|
||||
checksum: 10/05fd84ad82f75c9a9d44280ed948d9340cbfb5b18a18f112ee08c0c7d70ba35687197c9ce7508f7c2600410f42075369f2708504485912eb57e5e6e1cf8394ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/interaction-particles-links@npm:^3.7.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/interaction-particles-links@npm:3.9.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/be3925f0892de0eb9a4bc35b1ad402a462874272174379625bccce4c162a528d4f2a4526398f1b4a8b53ff27dae95e2b42a799a9c81ab99e233a9d16c7123774
|
||||
"@tsparticles/interaction-particles-links@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/interaction-particles-links@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/canvas-utils": 4.0.0
|
||||
"@tsparticles/engine": 4.0.0
|
||||
"@tsparticles/plugin-interactivity": 4.0.0
|
||||
checksum: 10/98d7defc0af362775339f4d7016fa096d352065579036837c99c1a721d88f3207a074075d78130b95236e60a77f66dd29fe957b42e26b7421bdfb797e3271a28
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/move-base@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/move-base@npm:3.9.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/d03795bb4d789295ce4179e1b22d618658a15c31915cba5c8f137bf4a8f183186e3969145ef3951df07fddea0e9d1830a4e25a22baa70d904769c488041da40c
|
||||
"@tsparticles/plugin-hex-color@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/plugin-hex-color@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/356310741b0019bcdd352e2affc9090662d7191aff932a1e2743c8d1911f87bd8a49a623a3733cf032911884d52b4e65a291267f37e28513c597d5cd4cadba1e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hex-color@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/plugin-hex-color@npm:3.9.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/726a2ae6182bc6e40ed443e1d664bae7ddb4e606a108e92a0c5fc50f0623105144672295720c06e915ef0e36c2a2455ed80dc335a850f11e3a1de1ad44e4ed08
|
||||
"@tsparticles/plugin-hsl-color@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/plugin-hsl-color@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/cb04a297a40fdad04f25dfaa10d77a04c70dceded6c24e6f3ab766d5cc70514464011527d822fc883bb5da5003fdd5d8ab92b71046cb290a3c8ca60f7a40483f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hsl-color@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/plugin-hsl-color@npm:3.9.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/f81aaed365045e437c8f1627c03f3d255dd2bba2d5ff5231b5e90d576b24fbb5dc110f3b860e13d8696ef820feb465414c0e62962a59e55e6e4a86883cb0f003
|
||||
"@tsparticles/plugin-interactivity@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/plugin-interactivity@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/23b089e537f5fed67fe7e3cf6b67aa639d9586ebda6838d349d9a02095449ca7bcfe164040a9fa08bdfd779f44ae5917434a335e5c67db5df6d6a6bfd521819e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-rgb-color@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/plugin-rgb-color@npm:3.9.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/17352010973ad83e6c9292722896dd0eef0b9f4411684d3fbcf110363bd2aa41594a77b28709dbf1ee9945624567d945ef71900cb37630b0da68714375333c6f
|
||||
"@tsparticles/plugin-move@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/plugin-move@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/abc5d175105243171ce440d88ae322f2412fb1c390bd39859fde1dc201b528e5d6099f38fa4a87a47ff110eaf65a930306922c409b1404271ba61d222ca9e48d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/preset-links@npm:3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "@tsparticles/preset-links@npm:3.2.0"
|
||||
dependencies:
|
||||
"@tsparticles/basic": "npm:^3.7.1"
|
||||
"@tsparticles/engine": "npm:^3.7.1"
|
||||
"@tsparticles/interaction-particles-links": "npm:^3.7.1"
|
||||
checksum: 10/1cae6c097d3cac1ba210ed681a40626a79f8579a4e82b1827e0b5864b1cb1fb737471f699800447a7a2bd6e17c706b05db36f84741d9f0c9600bd638e7e29999
|
||||
"@tsparticles/plugin-rgb-color@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/plugin-rgb-color@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/7d2255d5f428c56cd56fcf00839707ca3fa554a25fa59b2353e30c1c275ca24f06b9f2eee28a869633a740c56b3609cb6540135fb7297ec54d5fcc140e2ce575
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/shape-circle@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/shape-circle@npm:3.9.1"
|
||||
"@tsparticles/preset-links@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/preset-links@npm:4.0.0"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/1f0e5add252ee6e59b32b018b585106189a8938798e879a5a09b42434091c82f748b7656206d316705d65821f45e87bb9ef4c7a240c33be4384dbef0def1e2f5
|
||||
"@tsparticles/basic": "npm:4.0.0"
|
||||
"@tsparticles/engine": "npm:4.0.0"
|
||||
"@tsparticles/interaction-particles-links": "npm:4.0.0"
|
||||
"@tsparticles/plugin-interactivity": "npm:4.0.0"
|
||||
checksum: 10/67d8c4c90c44a9f3f940aa249e2b86acef4485a54ac02005de5b947cc3c2d01d4b6b00695dc5ff47ab0a8594faa18ba2ccf05b7510f7fd84993939f7d137121e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-color@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/updater-color@npm:3.9.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/5c4cb7fc7f4767461abffd3ba90ee2c5dba8a7cd3a38d6278314bb9d074c7e5f4977844f4c84448af00e24c923e4635f9392b320fffbac644ae59f157c6ed5b0
|
||||
"@tsparticles/shape-circle@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/shape-circle@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/37f010c44ba9b82712532da32c2cb6e482d5f5205aaf3bcd7fb04c87925218cd732a31d69ae424613810f11b2651a80fbbd9ec9d6b9d0e58884022ec68cc9d5b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-opacity@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/updater-opacity@npm:3.9.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/c0ecfd623bdb9cf6ece47098403cb19ce4d9c6204b19ca65357c106f400b4faf2e8f3a51a7ae518b9f708ba549073b4ea8ad8f2282dc298016437a62659298f4
|
||||
"@tsparticles/updater-opacity@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/updater-opacity@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/b078a28175372246d6861562bcc013c03dff88f6aeaf9b16558e533477340e1b3cd07d57997bdf11b730636df26ef0117bef9ba4ab1b791a9b9a4cd1d2e6623e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-out-modes@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/updater-out-modes@npm:3.9.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/b74bb0987aacabaeabc981fbbeffb362263ad69cea2f1733b0f5b8753a5c7338ca51eb02164a41d0a70b75ae0bd6e2f2c16c0bc0b4805b367122536df110f21a
|
||||
"@tsparticles/updater-out-modes@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/updater-out-modes@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/3b27af0a68a7f320ae2481142ff9d6e342b75f245b94e875ea2bdc3f9f47c0b8e79b7ddbf45fea5eeaa9e4610dbdb7130e10056b1f26618958e7e6c28e25dbbe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-size@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@tsparticles/updater-size@npm:3.9.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
checksum: 10/211038b9cadd1df1a0fb747d2e1eeff9f1ce57fd4828e838ddfe6b5365feb3f625e7f713380bc819601d88c50ba4b122a401eae627449760d94c95cff85efdb3
|
||||
"@tsparticles/updater-paint@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/updater-paint@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/22f9c275ee3eb1409923c74b63fc5984d978b68473bd0f58418d6c28a9be0e9e2e010e338adb7deb482021ab444fab85b962745c06d94066dd40265cf016395d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-size@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@tsparticles/updater-size@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.0
|
||||
checksum: 10/4664fc5c4c961331d733d98287e9fef0079077def528b814034aea163eb95e2f6c0549e1b87e95eef5f376a7cd0983fbf18c2d77023b9f1f1b163698b03ec504
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8715,19 +8727,19 @@ __metadata:
|
||||
"@codemirror/lint": "npm:6.9.6"
|
||||
"@codemirror/search": "npm:6.7.0"
|
||||
"@codemirror/state": "npm:6.6.0"
|
||||
"@codemirror/view": "npm:6.42.1"
|
||||
"@codemirror/view": "npm:6.43.0"
|
||||
"@date-fns/tz": "npm:1.4.1"
|
||||
"@egjs/hammerjs": "npm:2.0.17"
|
||||
"@eslint/js": "npm:10.0.1"
|
||||
"@formatjs/intl-datetimeformat": "npm:7.4.4"
|
||||
"@formatjs/intl-displaynames": "npm:7.3.6"
|
||||
"@formatjs/intl-durationformat": "npm:0.10.10"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.2.7"
|
||||
"@formatjs/intl-listformat": "npm:8.3.6"
|
||||
"@formatjs/intl-locale": "npm:5.3.6"
|
||||
"@formatjs/intl-numberformat": "npm:9.3.7"
|
||||
"@formatjs/intl-pluralrules": "npm:6.3.6"
|
||||
"@formatjs/intl-relativetimeformat": "npm:12.3.6"
|
||||
"@formatjs/intl-datetimeformat": "npm:7.4.5"
|
||||
"@formatjs/intl-displaynames": "npm:7.3.7"
|
||||
"@formatjs/intl-durationformat": "npm:0.10.11"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.2.8"
|
||||
"@formatjs/intl-listformat": "npm:8.3.7"
|
||||
"@formatjs/intl-locale": "npm:5.3.7"
|
||||
"@formatjs/intl-numberformat": "npm:9.3.8"
|
||||
"@formatjs/intl-pluralrules": "npm:6.3.7"
|
||||
"@formatjs/intl-relativetimeformat": "npm:12.3.7"
|
||||
"@fullcalendar/core": "npm:6.1.20"
|
||||
"@fullcalendar/daygrid": "npm:6.1.20"
|
||||
"@fullcalendar/interaction": "npm:6.1.20"
|
||||
@@ -8762,8 +8774,8 @@ __metadata:
|
||||
"@rspack/dev-server": "npm:2.0.1"
|
||||
"@swc/helpers": "npm:0.5.21"
|
||||
"@thomasloven/round-slider": "npm:0.6.0"
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
"@tsparticles/preset-links": "npm:3.2.0"
|
||||
"@tsparticles/engine": "npm:4.0.0"
|
||||
"@tsparticles/preset-links": "npm:4.0.0"
|
||||
"@types/babel__plugin-transform-runtime": "npm:7.9.5"
|
||||
"@types/chromecast-caf-receiver": "npm:6.0.26"
|
||||
"@types/chromecast-caf-sender": "npm:1.0.11"
|
||||
@@ -8827,7 +8839,7 @@ __metadata:
|
||||
html-minifier-terser: "npm:7.2.0"
|
||||
husky: "npm:9.1.7"
|
||||
idb-keyval: "npm:6.2.2"
|
||||
intl-messageformat: "npm:11.2.5"
|
||||
intl-messageformat: "npm:11.2.6"
|
||||
js-yaml: "npm:4.1.1"
|
||||
jsdom: "npm:29.1.1"
|
||||
jszip: "npm:3.10.1"
|
||||
@@ -9163,13 +9175,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"intl-messageformat@npm:11.2.5":
|
||||
version: 11.2.5
|
||||
resolution: "intl-messageformat@npm:11.2.5"
|
||||
"intl-messageformat@npm:11.2.6":
|
||||
version: 11.2.6
|
||||
resolution: "intl-messageformat@npm:11.2.6"
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize": "npm:3.1.5"
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.5.8"
|
||||
checksum: 10/2064e4e4adf438b137241c34923fe9ae7017d0d569a7ea70e2538950d2c57e7b0621d234241abc75c74a657f9d8d13ed55d793d12008daa9422040535f848a6b
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.5.9"
|
||||
checksum: 10/a93a33c607be110715d76f532f74c0f34f1a4e39e28822333d8dd801d0e5e3f4f9a82e2d88895c179b7583d4a9135b1e6bb4044a8ce84c17fd67f2d78cfd84e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user