From 5bad99e942efcaf1ab02023ff6a514c9e6c7d9ec Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 15:17:45 +0300 Subject: [PATCH 01/14] chore(tooling): add package.json version linting and renovate trust policy - Add .npmpackagejsonlintrc.json enforcing absolute version pins for all dependencies and devDependencies - Add renovate minimumReleaseAge and trustPolicyExclude for semver@5/6 (old post-release versions that cannot yet be upgraded) - Wire npm-package-json-lint into pre-commit (staged check) and pre-push (full check) hooks - Update README to mention package.json version linting in hook descriptions --- .npmpackagejsonlintrc.json | 6 ++++ README.md | 4 +-- .../hass_datapoints/hass-datapoints-cards.js | 2 +- pnpm-workspace.yaml | 5 ++++ scripts/pre-commit | 29 ++++++++++++++----- scripts/pre-push | 3 ++ 6 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 .npmpackagejsonlintrc.json diff --git a/.npmpackagejsonlintrc.json b/.npmpackagejsonlintrc.json new file mode 100644 index 00000000..5fc6b120 --- /dev/null +++ b/.npmpackagejsonlintrc.json @@ -0,0 +1,6 @@ +{ + "rules": { + "prefer-absolute-version-dependencies": "error", + "prefer-absolute-version-devDependencies": "error" + } +} diff --git a/README.md b/README.md index 68b62afa..96973d88 100644 --- a/README.md +++ b/README.md @@ -1000,5 +1000,5 @@ pnpm dev:watch - CI checks build correctness and integration metadata. - The built frontend bundle is committed as `custom_components/hass_datapoints/hass-datapoints-cards.js`. -- Pre-commit hooks format staged files, validate frontend types, and rebuild the frontend when needed. -- Pre-push hooks run tests, lint checks, and frontend type validation before pushing. +- Pre-commit hooks format staged files, lint package.json versions, validate frontend types, and rebuild the frontend when needed. +- Pre-push hooks run tests, lint checks, package.json version linting, and frontend type validation before pushing. diff --git a/custom_components/hass_datapoints/hass-datapoints-cards.js b/custom_components/hass_datapoints/hass-datapoints-cards.js index b05dce2c..4dc9bd8f 100644 --- a/custom_components/hass_datapoints/hass-datapoints-cards.js +++ b/custom_components/hass_datapoints/hass-datapoints-cards.js @@ -2733,7 +2733,7 @@ return fmtDateTime(iso); } function esc(str) { - return String(str).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + return String(str).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } const HA_COMPONENT_LOAD_TIMEOUT_MS = 6e3; const HA_COMPONENT_LOADER_SUPPORTED_TAGS = /* @__PURE__ */ new Set([ diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e7361ee8..d46bcce2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,3 +3,8 @@ packages: minimumReleaseAge: 1440 trustPolicy: no-downgrade +trustPolicyExclude: + # Package: semver@6.3.1 AND semver@5.7.2 + # Reason: Old version released after higher-trust versions; dependency chain cannot yet be upgraded. Version and repo manually checked. + # Risk Assessment: Used only as version parser; no runtime execution path; no CVE exposure. + - semver@6.3.1 || 5.7.2 diff --git a/scripts/pre-commit b/scripts/pre-commit index 440a0937..0bbe08be 100755 --- a/scripts/pre-commit +++ b/scripts/pre-commit @@ -3,8 +3,9 @@ # pre-commit hook: # 1. Format all staged Prettier-supported files (ts, js, md, json, css, html…) # 2. Lint & format staged Python files with ruff -# 3. Validate TypeScript types when frontend sources/config change -# 4. Rebuild hass-datapoints-cards.js when any src/ file is staged +# 3. Lint package.json version specifiers when package metadata changes +# 4. Validate TypeScript types when frontend sources/config change +# 5. Rebuild hass-datapoints-cards.js when any src/ file is staged # # Install: pnpm hooks:install # (or manually: ln -sf ../../scripts/pre-commit .git/hooks/pre-commit) @@ -35,7 +36,11 @@ STAGED_SRC=$(git diff --cached --name-only --diff-filter=ACMR \ STAGED_TYPECHECK=$(git diff --cached --name-only --diff-filter=ACMR \ | grep -E "^(custom_components/hass_datapoints/src/.*\\.(ts|js)|tsconfig.*\\.json|package\\.json)$" || true) -if [ -z "$STAGED_PRETTIER" ] && [ -z "$STAGED_PY" ] && [ -z "$STAGED_SRC" ] && [ -z "$STAGED_TYPECHECK" ]; then +# Staged package metadata files that should trigger package.json linting +STAGED_PACKAGE_JSON=$(git diff --cached --name-only --diff-filter=ACMR \ + | grep -E "^package\\.json$" || true) + +if [ -z "$STAGED_PRETTIER" ] && [ -z "$STAGED_PY" ] && [ -z "$STAGED_SRC" ] && [ -z "$STAGED_TYPECHECK" ] && [ -z "$STAGED_PACKAGE_JSON" ]; then exit 0 fi @@ -45,8 +50,8 @@ if [ -n "$STAGED_PRETTIER" ]; then echo "[pre-commit] pnpm is required for Prettier." >&2 exit 1 fi - echo "[pre-commit] Formatting staged files with Prettier…" - echo "$STAGED_PRETTIER" | xargs pnpm prettier --cache --write -- + echo "[pre-commit] Formatting staged files with Prettier and Eslint…" + echo "$STAGED_PRETTIER" | xargs pnpm format -- # Re-stage any files Prettier modified echo "$STAGED_PRETTIER" | xargs git add fi @@ -71,7 +76,17 @@ if [ -n "$STAGED_PY" ]; then echo "$STAGED_PY" | xargs git add fi -# ── 3. Validate frontend types ───────────────────────────────────────────────── +# ── 3. Lint package.json versions ────────────────────────────────────────────── +if [ -n "$STAGED_PACKAGE_JSON" ]; then + if ! command -v pnpm >/dev/null 2>&1; then + echo "[pre-commit] pnpm is required to lint package.json." >&2 + exit 1 + fi + echo "[pre-commit] Linting package.json versions…" + pnpm lint:package-json +fi + +# ── 4. Validate frontend types ───────────────────────────────────────────────── if [ -n "$STAGED_TYPECHECK" ]; then if ! command -v pnpm >/dev/null 2>&1; then echo "[pre-commit] pnpm is required to validate TypeScript types." >&2 @@ -81,7 +96,7 @@ if [ -n "$STAGED_TYPECHECK" ]; then pnpm lint:types fi -# ── 4. Rebuild the bundle ────────────────────────────────────────────────────── +# ── 5. Rebuild the bundle ────────────────────────────────────────────────────── if [ -n "$STAGED_SRC" ]; then if ! command -v pnpm >/dev/null 2>&1; then echo "[pre-commit] pnpm is required to build frontend assets." >&2 diff --git a/scripts/pre-push b/scripts/pre-push index 5f10a7c0..71fc7f13 100755 --- a/scripts/pre-push +++ b/scripts/pre-push @@ -18,6 +18,9 @@ pnpm test echo "[pre-push] Running ESLint without fixes…" pnpm lint:eslint +echo "[pre-push] Linting package.json versions…" +pnpm lint:package-json + echo "[pre-push] Running Prettier checks…" pnpm lint:prettier From cd20d40fbe44dbbeae0678cd60ce3e7f252704d4 Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 15:18:31 +0300 Subject: [PATCH 02/14] fix(security): harden input validation across WS API and service schema - Add vol.Match / vol.Length / vol.In / vol.Range validators to ws_update_event, ws_get_anomalies, and ws_clear_cache schemas, capping free-text fields and constraining enum/duration/offset inputs - Validate entity_id with cv.entity_id on ws_get_anomalies and ws_clear_cache (already applied to ws_get_history) - Gate ws_clear_cache behind _require_admin; only admins may flush cache - Mirror the same icon/color/message length + format guards into SERVICE_RECORD_SCHEMA in __init__.py - Escape single-quotes in esc() helper (format.ts) via ' - Add voluptuous to requirements_dev.txt and lift the mock stub from conftest so validation-constant unit tests run against the real library - Add 22 new validator unit tests (DescribeValidationConstants) covering regex patterns, In/Range/Length validators, and the non-admin cache guard --- custom_components/hass_datapoints/__init__.py | 20 +- .../hass_datapoints/src/lib/util/format.ts | 3 +- .../hass_datapoints/websocket_api.py | 71 +++++-- requirements_dev.txt | 1 + tests/conftest.py | 11 +- tests/test_websocket_api.py | 180 ++++++++++++++++++ 6 files changed, 256 insertions(+), 30 deletions(-) diff --git a/custom_components/hass_datapoints/__init__.py b/custom_components/hass_datapoints/__init__.py index b7df8b29..3ebe40df 100644 --- a/custom_components/hass_datapoints/__init__.py +++ b/custom_components/hass_datapoints/__init__.py @@ -70,15 +70,27 @@ def _find_automation_id(hass: HomeAssistant, context) -> str | None: SERVICE_RECORD_SCHEMA = vol.Schema( { - vol.Required(ATTR_MESSAGE): cv.string, - vol.Optional(ATTR_ANNOTATION): cv.string, + vol.Required(ATTR_MESSAGE): vol.All( + cv.string, vol.Length(max=ws_api._MAX_LEN_MESSAGE) + ), + vol.Optional(ATTR_ANNOTATION): vol.All( + cv.string, vol.Length(max=ws_api._MAX_LEN_MESSAGE) + ), vol.Optional(ATTR_ENTITY_IDS): vol.All(cv.ensure_list, [cv.entity_id]), vol.Optional(ATTR_DEVICE_IDS): vol.All(cv.ensure_list, [cv.string]), vol.Optional(ATTR_AREA_IDS): vol.All(cv.ensure_list, [cv.string]), vol.Optional(ATTR_LABEL_IDS): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(ATTR_ICON): cv.string, - vol.Optional(ATTR_COLOR): vol.Any( + vol.Optional(ATTR_ICON): vol.All( cv.string, + vol.Length(max=ws_api._MAX_LEN_ICON), + vol.Match(ws_api._RE_MDI_ICON), + ), + vol.Optional(ATTR_COLOR): vol.Any( + vol.All( + cv.string, + vol.Length(max=ws_api._MAX_LEN_COLOR), + vol.Match(ws_api._RE_HEX_COLOR), + ), vol.All( [vol.Coerce(int)], vol.Length(min=3, max=3), diff --git a/custom_components/hass_datapoints/src/lib/util/format.ts b/custom_components/hass_datapoints/src/lib/util/format.ts index da08b512..18465614 100644 --- a/custom_components/hass_datapoints/src/lib/util/format.ts +++ b/custom_components/hass_datapoints/src/lib/util/format.ts @@ -42,5 +42,6 @@ export function esc(str: unknown): string { .replace(/&/g, "&") .replace(//g, ">") - .replace(/"/g, """); + .replace(/"/g, """) + .replace(/'/g, "'"); } diff --git a/custom_components/hass_datapoints/websocket_api.py b/custom_components/hass_datapoints/websocket_api.py index 275aa540..34c4de15 100644 --- a/custom_components/hass_datapoints/websocket_api.py +++ b/custom_components/hass_datapoints/websocket_api.py @@ -61,6 +61,22 @@ "persistence", "comparison_window", ] +_VALID_ANOMALY_SENSITIVITY = ["low", "medium", "high"] +_VALID_ANOMALY_OVERLAP_MODES = ["all", "highlight", "only"] +_VALID_TREND_METHODS = ["rolling_average", "linear_trend"] + +# Field-length caps for free-text event fields stored in .storage/ +_MAX_LEN_MESSAGE = 10_000 +_MAX_LEN_ICON = 255 +_MAX_LEN_COLOR = 20 +_MAX_LEN_WINDOW = 20 # interval strings like "24h", "30m" + +# Validator for MDI icon names (e.g. "mdi:bookmark", "mdi:home-outline") +_RE_MDI_ICON = r"^mdi:[a-z0-9][a-z0-9\-]*$" +# Validator for CSS hex colors (#rgb, #rrggbb, #rrggbbaa) +_RE_HEX_COLOR = r"^#[0-9a-fA-F]{3,8}$" +# Validator for duration strings accepted by parse_interval_seconds ("1h", "30m", "24h", …) +_RE_DURATION = r"^\d+[smhd]$" # Maximum date-range span accepted by ws_get_history. Requests beyond this # limit return an empty pts list so the frontend gracefully falls back to the @@ -305,14 +321,18 @@ def _valid_uuid(value: str) -> str: { vol.Required("type"): f"{DOMAIN}/events/update", vol.Required("event_id"): vol.All(str, _valid_uuid), - vol.Optional("message"): str, - vol.Optional("annotation"): str, + vol.Optional("message"): vol.All(str, vol.Length(max=_MAX_LEN_MESSAGE)), + vol.Optional("annotation"): vol.All(str, vol.Length(max=_MAX_LEN_MESSAGE)), vol.Optional("entity_ids"): [str], vol.Optional("device_ids"): [str], vol.Optional("area_ids"): [str], vol.Optional("label_ids"): [str], - vol.Optional("icon"): str, - vol.Optional("color"): str, + vol.Optional("icon"): vol.All( + str, vol.Length(max=_MAX_LEN_ICON), vol.Match(_RE_MDI_ICON) + ), + vol.Optional("color"): vol.All( + str, vol.Length(max=_MAX_LEN_COLOR), vol.Match(_RE_HEX_COLOR) + ), } ) @websocket_api.async_response @@ -386,7 +406,7 @@ async def ws_delete_dev_events( @websocket_api.websocket_command( { vol.Required("type"): f"{DOMAIN}/history", - vol.Required("entity_id"): str, + vol.Required("entity_id"): cv.entity_id, vol.Required("start_time"): str, vol.Required("end_time"): str, vol.Required("interval"): vol.In(_VALID_INTERVALS), @@ -488,23 +508,41 @@ def _run_detection(pts: list, config: dict, comparison_pts: list | None = None) @websocket_api.websocket_command( { vol.Required("type"): f"{DOMAIN}/anomalies", - vol.Required("entity_id"): str, + vol.Required("entity_id"): cv.entity_id, vol.Required("start_time"): str, vol.Required("end_time"): str, - vol.Required("anomaly_methods"): [str], - vol.Optional("anomaly_sensitivity", default="medium"): str, - vol.Optional("anomaly_overlap_mode", default="all"): str, - vol.Optional("anomaly_rate_window", default="1h"): str, - vol.Optional("anomaly_zscore_window", default="24h"): str, - vol.Optional("anomaly_persistence_window", default="1h"): str, - vol.Optional("trend_method", default="rolling_average"): str, - vol.Optional("trend_window", default="24h"): str, + vol.Required("anomaly_methods"): vol.All( + [vol.In(_VALID_ANOMALY_METHODS)], vol.Length(min=1) + ), + vol.Optional("anomaly_sensitivity", default="medium"): vol.In( + _VALID_ANOMALY_SENSITIVITY + ), + vol.Optional("anomaly_overlap_mode", default="all"): vol.In( + _VALID_ANOMALY_OVERLAP_MODES + ), + vol.Optional("anomaly_rate_window", default="1h"): vol.All( + str, vol.Length(max=_MAX_LEN_WINDOW), vol.Match(_RE_DURATION) + ), + vol.Optional("anomaly_zscore_window", default="24h"): vol.All( + str, vol.Length(max=_MAX_LEN_WINDOW), vol.Match(_RE_DURATION) + ), + vol.Optional("anomaly_persistence_window", default="1h"): vol.All( + str, vol.Length(max=_MAX_LEN_WINDOW), vol.Match(_RE_DURATION) + ), + vol.Optional("trend_method", default="rolling_average"): vol.In( + _VALID_TREND_METHODS + ), + vol.Optional("trend_window", default="24h"): vol.All( + str, vol.Length(max=_MAX_LEN_WINDOW), vol.Match(_RE_DURATION) + ), vol.Optional("sample_interval"): vol.In(_VALID_INTERVALS), vol.Optional("sample_aggregate", default="mean"): vol.In(_VALID_AGGREGATES), vol.Optional("comparison_entity_id"): cv.entity_id, vol.Optional("comparison_start_time"): str, vol.Optional("comparison_end_time"): str, - vol.Optional("comparison_time_offset_ms", default=0): int, + vol.Optional("comparison_time_offset_ms", default=0): vol.All( + int, vol.Range(min=-315_576_000_000, max=315_576_000_000) + ), } ) @websocket_api.async_response @@ -655,7 +693,7 @@ async def ws_get_anomalies( @websocket_api.websocket_command( { vol.Required("type"): f"{DOMAIN}/cache/clear", - vol.Optional("entity_id"): str, + vol.Optional("entity_id"): cv.entity_id, } ) @websocket_api.async_response @@ -665,6 +703,7 @@ async def ws_clear_cache( msg: dict, ) -> None: """Clear anomaly cache entries (all, or for a specific entity).""" + _require_admin(connection, msg) cache: AnomalyCache | None = hass.data.get(DOMAIN, {}).get("anomaly_cache") if cache is None: connection.send_result(msg["id"], {"cleared": 0}) diff --git a/requirements_dev.txt b/requirements_dev.txt index 92734702..3c0be922 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -2,3 +2,4 @@ pytest pytest-asyncio ruff +voluptuous diff --git a/tests/conftest.py b/tests/conftest.py index d5cfe19f..0d619d47 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,15 +28,8 @@ def _stub(name: str, obj: object | None = None) -> None: # -- voluptuous --------------------------------------------------------------- -_vol = MagicMock() -_vol.Schema = MagicMock(return_value=MagicMock()) -_vol.Required = MagicMock(return_value="required") -_vol.Optional = MagicMock(return_value="optional") -_vol.All = MagicMock(return_value=MagicMock()) -_vol.Any = MagicMock(return_value=MagicMock()) -_vol.Coerce = MagicMock(return_value=MagicMock()) -_vol.Length = MagicMock(return_value=MagicMock()) -_stub("voluptuous", _vol) +# voluptuous is a real dependency listed in requirements_dev.txt; do NOT stub +# it so that tests exercising vol.Match / vol.In / vol.Range work correctly. # -- sqlalchemy --------------------------------------------------------------- _sa = MagicMock() diff --git a/tests/test_websocket_api.py b/tests/test_websocket_api.py index faffcfd3..798588f4 100644 --- a/tests/test_websocket_api.py +++ b/tests/test_websocket_api.py @@ -6,10 +6,22 @@ from unittest.mock import AsyncMock, MagicMock import pytest +import voluptuous as vol from homeassistant.exceptions import Unauthorized from custom_components.hass_datapoints.const import DOMAIN from custom_components.hass_datapoints.websocket_api import ( + _MAX_LEN_COLOR, + _MAX_LEN_ICON, + _MAX_LEN_MESSAGE, + _MAX_LEN_WINDOW, + _RE_DURATION, + _RE_HEX_COLOR, + _RE_MDI_ICON, + _VALID_ANOMALY_METHODS, + _VALID_ANOMALY_OVERLAP_MODES, + _VALID_ANOMALY_SENSITIVITY, + _VALID_TREND_METHODS, _normalize_recorder_timestamp, _require_admin, ws_clear_cache, @@ -352,3 +364,171 @@ async def test_GIVEN_cache_present_and_entity_id_provided_WHEN_called_THEN_clear cache.clear_entity.assert_called_once_with("sensor.temp") result = connection.send_result.call_args[0][1] assert result["cleared"] == 2 + + async def test_GIVEN_non_admin_user_WHEN_called_THEN_raises_unauthorized(self): + cache = MagicMock() + hass = MagicMock() + hass.data = {DOMAIN: {"anomaly_cache": cache}} + connection = _make_connection(is_admin=False) + msg = {"id": 1} + + with pytest.raises(Unauthorized): + await ws_clear_cache(hass, connection, msg) + + cache.clear_all.assert_not_called() + connection.send_result.assert_not_called() + + +# --------------------------------------------------------------------------- +# Validation constants — unit tests for regex patterns and allowed values +# --------------------------------------------------------------------------- + + +class DescribeValidationConstants: + # --- _RE_MDI_ICON --- + + def test_GIVEN_valid_mdi_icon_WHEN_matched_THEN_passes(self): + validator = vol.Match(_RE_MDI_ICON) + assert validator("mdi:home") == "mdi:home" + assert validator("mdi:thermometer-lines") == "mdi:thermometer-lines" + assert validator("mdi:ab1") == "mdi:ab1" + + def test_GIVEN_invalid_icon_WHEN_matched_THEN_raises(self): + validator = vol.Match(_RE_MDI_ICON) + with pytest.raises(vol.Invalid): + validator("home") + with pytest.raises(vol.Invalid): + validator("fa:home") + with pytest.raises(vol.Invalid): + validator("mdi:") + with pytest.raises(vol.Invalid): + validator("mdi:Home") # uppercase not allowed + + def test_GIVEN_icon_exceeds_max_len_WHEN_validated_THEN_raises(self): + validator = vol.Length(max=_MAX_LEN_ICON) + with pytest.raises(vol.Invalid): + validator("x" * (_MAX_LEN_ICON + 1)) + + # --- _RE_HEX_COLOR --- + + def test_GIVEN_valid_hex_colors_WHEN_matched_THEN_passes(self): + validator = vol.Match(_RE_HEX_COLOR) + assert validator("#fff") == "#fff" + assert validator("#FF0000") == "#FF0000" + assert validator("#aabbccdd") == "#aabbccdd" + + def test_GIVEN_invalid_hex_color_WHEN_matched_THEN_raises(self): + validator = vol.Match(_RE_HEX_COLOR) + with pytest.raises(vol.Invalid): + validator("red") + with pytest.raises(vol.Invalid): + validator("ff0000") # missing # + with pytest.raises(vol.Invalid): + validator("#gg0000") # invalid hex chars + + def test_GIVEN_color_exceeds_max_len_WHEN_validated_THEN_raises(self): + validator = vol.Length(max=_MAX_LEN_COLOR) + with pytest.raises(vol.Invalid): + validator("#" + "0" * _MAX_LEN_COLOR) + + # --- _RE_DURATION --- + + def test_GIVEN_valid_duration_strings_WHEN_matched_THEN_passes(self): + validator = vol.Match(_RE_DURATION) + for value in ["1s", "30m", "24h", "7d", "100s"]: + assert validator(value) == value + + def test_GIVEN_invalid_duration_WHEN_matched_THEN_raises(self): + validator = vol.Match(_RE_DURATION) + with pytest.raises(vol.Invalid): + validator("1hour") + with pytest.raises(vol.Invalid): + validator("h") + with pytest.raises(vol.Invalid): + validator("1H") # uppercase not allowed + with pytest.raises(vol.Invalid): + validator("") + + def test_GIVEN_window_exceeds_max_len_WHEN_validated_THEN_raises(self): + validator = vol.Length(max=_MAX_LEN_WINDOW) + with pytest.raises(vol.Invalid): + validator("1" * (_MAX_LEN_WINDOW + 1) + "h") + + # --- _VALID_ANOMALY_SENSITIVITY --- + + def test_GIVEN_valid_sensitivity_WHEN_checked_THEN_passes(self): + validator = vol.In(_VALID_ANOMALY_SENSITIVITY) + for value in ["low", "medium", "high"]: + assert validator(value) == value + + def test_GIVEN_invalid_sensitivity_WHEN_checked_THEN_raises(self): + validator = vol.In(_VALID_ANOMALY_SENSITIVITY) + with pytest.raises(vol.Invalid): + validator("extreme") + + # --- _VALID_ANOMALY_OVERLAP_MODES --- + + def test_GIVEN_valid_overlap_mode_WHEN_checked_THEN_passes(self): + validator = vol.In(_VALID_ANOMALY_OVERLAP_MODES) + for value in ["all", "highlight", "only"]: + assert validator(value) == value + + def test_GIVEN_invalid_overlap_mode_WHEN_checked_THEN_raises(self): + validator = vol.In(_VALID_ANOMALY_OVERLAP_MODES) + with pytest.raises(vol.Invalid): + validator("none") + + # --- _VALID_TREND_METHODS --- + + def test_GIVEN_valid_trend_method_WHEN_checked_THEN_passes(self): + validator = vol.In(_VALID_TREND_METHODS) + for value in ["rolling_average", "linear_trend"]: + assert validator(value) == value + + def test_GIVEN_invalid_trend_method_WHEN_checked_THEN_raises(self): + validator = vol.In(_VALID_TREND_METHODS) + with pytest.raises(vol.Invalid): + validator("polynomial") + + # --- _VALID_ANOMALY_METHODS list --- + + def test_GIVEN_valid_anomaly_methods_WHEN_checked_THEN_all_pass(self): + for method in _VALID_ANOMALY_METHODS: + assert vol.In(_VALID_ANOMALY_METHODS)(method) == method + + def test_GIVEN_invalid_anomaly_method_in_list_WHEN_validated_THEN_raises(self): + schema = vol.All([vol.In(_VALID_ANOMALY_METHODS)], vol.Length(min=1)) + with pytest.raises(vol.Invalid): + schema(["not_a_method"]) + + def test_GIVEN_empty_anomaly_methods_list_WHEN_validated_THEN_raises(self): + schema = vol.All([vol.In(_VALID_ANOMALY_METHODS)], vol.Length(min=1)) + with pytest.raises(vol.Invalid): + schema([]) + + # --- comparison_time_offset_ms range --- + + def test_GIVEN_offset_within_range_WHEN_validated_THEN_passes(self): + validator = vol.Range(min=-315_576_000_000, max=315_576_000_000) + assert validator(0) == 0 + assert validator(-315_576_000_000) == -315_576_000_000 + assert validator(315_576_000_000) == 315_576_000_000 + + def test_GIVEN_offset_outside_range_WHEN_validated_THEN_raises(self): + validator = vol.Range(min=-315_576_000_000, max=315_576_000_000) + with pytest.raises(vol.Invalid): + validator(315_576_000_001) + with pytest.raises(vol.Invalid): + validator(-315_576_000_001) + + # --- message / annotation length cap --- + + def test_GIVEN_message_within_limit_WHEN_validated_THEN_passes(self): + validator = vol.Length(max=_MAX_LEN_MESSAGE) + assert validator("hello") == "hello" + assert validator("x" * _MAX_LEN_MESSAGE) == "x" * _MAX_LEN_MESSAGE + + def test_GIVEN_message_exceeds_limit_WHEN_validated_THEN_raises(self): + validator = vol.Length(max=_MAX_LEN_MESSAGE) + with pytest.raises(vol.Invalid): + validator("x" * (_MAX_LEN_MESSAGE + 1)) From 0969df725100f8169924741c6a78ba51be365498 Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 15:33:16 +0300 Subject: [PATCH 03/14] chore(eslint): migrate from airbnb-base to eslint-config-airbnb-extended eslint-config-airbnb-base no longer resolves after the dependency update to eslint-config-airbnb-extended@3 and ESLint 10. Migrate the flat config: - Replace compat.extends("airbnb-base") with configs.base.recommended from eslint-config-airbnb-extended; register plugins.stylistic and plugins.importX which the config uses but does not bundle - Switch import/ rule overrides to import-x/ to match the new plugin - Keep tseslint.configs.recommended for non-type-aware TypeScript rules (no parserOptions.project required) - Add includeIgnoreFile(.gitignore) via @eslint/compat - Remove unused FlatCompat import - Fix no-useless-assignment in dev-tool.ts: initial values for message, icon and color are always overwritten before use; drop them so the rule is satisfied - Auto-fix import-x/no-useless-path-segments and remove stale eslint-disable-next-line comments for wc/guard-super-call that were no longer needed under the new config - Bump package.json to exact version pins and add @eslint/compat - @eslint/eslintrc: only needed for FlatCompat/compat.extends(); both removed from eslint.config.mjs in the airbnb-extended migration --- .../hass_datapoints/hass-datapoints-cards.js | 56531 +++++++--------- .../range-timeline/range-timeline.ts | 1 - .../resizable-panes/resizable-panes.ts | 1 - .../dev-tool-windows/dev-tool-windows.ts | 1 - .../src/cards/dev-tool/dev-tool.ts | 6 +- .../history/history-chart/history-chart.ts | 2 +- .../hass_datapoints/src/cards/list/list.ts | 2 - .../cards/sensor/sensor-chart/sensor-chart.ts | 2 - .../sensor/sensor-records/sensor-records.ts | 1 - .../src/cards/sensor/sensor.ts | 2 - .../src/charts/base/chart-card-base.ts | 2 - .../collapsed-options-menu.ts | 1 - .../comparison-tab-rail.ts | 2 - .../molecules/floating-menu/floating-menu.ts | 2 - .../src/panels/datapoints/datapoints.ts | 3 +- eslint.config.mjs | 30 +- package.json | 69 +- pnpm-lock.yaml | 3164 +- 18 files changed, 27691 insertions(+), 32131 deletions(-) diff --git a/custom_components/hass_datapoints/hass-datapoints-cards.js b/custom_components/hass_datapoints/hass-datapoints-cards.js index 4dc9bd8f..0f317f09 100644 --- a/custom_components/hass_datapoints/hass-datapoints-cards.js +++ b/custom_components/hass_datapoints/hass-datapoints-cards.js @@ -1,506 +1,595 @@ (function() { - "use strict"; - const t$4 = globalThis, e$4 = t$4.ShadowRoot && (void 0 === t$4.ShadyCSS || t$4.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$3 = /* @__PURE__ */ Symbol(), o$4 = /* @__PURE__ */ new WeakMap(); - let n$3 = class n { - constructor(t2, e2, o2) { - if (this._$cssResult$ = true, o2 !== s$3) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); - this.cssText = t2, this.t = e2; - } - get styleSheet() { - let t2 = this.o; - const s2 = this.t; - if (e$4 && void 0 === t2) { - const e2 = void 0 !== s2 && 1 === s2.length; - e2 && (t2 = o$4.get(s2)), void 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), e2 && o$4.set(s2, t2)); - } - return t2; - } - toString() { - return this.cssText; - } - }; - const r$4 = (t2) => new n$3("string" == typeof t2 ? t2 : t2 + "", void 0, s$3), i$5 = (t2, ...e2) => { - const o2 = 1 === t2.length ? t2[0] : e2.reduce((e3, s2, o3) => e3 + ((t3) => { - if (true === t3._$cssResult$) return t3.cssText; - if ("number" == typeof t3) return t3; - throw Error("Value passed to 'css' function must be a 'css' function result: " + t3 + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); - })(s2) + t2[o3 + 1], t2[0]); - return new n$3(o2, t2, s$3); - }, S$1 = (s2, o2) => { - if (e$4) s2.adoptedStyleSheets = o2.map((t2) => t2 instanceof CSSStyleSheet ? t2 : t2.styleSheet); - else for (const e2 of o2) { - const o3 = document.createElement("style"), n2 = t$4.litNonce; - void 0 !== n2 && o3.setAttribute("nonce", n2), o3.textContent = e2.cssText, s2.appendChild(o3); - } - }, c$3 = e$4 ? (t2) => t2 : (t2) => t2 instanceof CSSStyleSheet ? ((t3) => { - let e2 = ""; - for (const s2 of t3.cssRules) e2 += s2.cssText; - return r$4(e2); - })(t2) : t2; - const { is: i$4, defineProperty: e$3, getOwnPropertyDescriptor: h$2, getOwnPropertyNames: r$3, getOwnPropertySymbols: o$3, getPrototypeOf: n$2 } = Object, a$1 = globalThis, c$2 = a$1.trustedTypes, l$1 = c$2 ? c$2.emptyScript : "", p$2 = a$1.reactiveElementPolyfillSupport, d$1 = (t2, s2) => t2, u$3 = { toAttribute(t2, s2) { - switch (s2) { - case Boolean: - t2 = t2 ? l$1 : null; - break; - case Object: - case Array: - t2 = null == t2 ? t2 : JSON.stringify(t2); - } - return t2; - }, fromAttribute(t2, s2) { - let i2 = t2; - switch (s2) { - case Boolean: - i2 = null !== t2; - break; - case Number: - i2 = null === t2 ? null : Number(t2); - break; - case Object: - case Array: - try { - i2 = JSON.parse(t2); - } catch (t3) { - i2 = null; - } - } - return i2; - } }, f$1 = (t2, s2) => !i$4(t2, s2), b$1 = { attribute: true, type: String, converter: u$3, reflect: false, useDefault: false, hasChanged: f$1 }; - Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), a$1.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); - let y$1 = class y extends HTMLElement { - static addInitializer(t2) { - this._$Ei(), (this.l ??= []).push(t2); - } - static get observedAttributes() { - return this.finalize(), this._$Eh && [...this._$Eh.keys()]; - } - static createProperty(t2, s2 = b$1) { - if (s2.state && (s2.attribute = false), this._$Ei(), this.prototype.hasOwnProperty(t2) && ((s2 = Object.create(s2)).wrapped = true), this.elementProperties.set(t2, s2), !s2.noAccessor) { - const i2 = /* @__PURE__ */ Symbol(), h2 = this.getPropertyDescriptor(t2, i2, s2); - void 0 !== h2 && e$3(this.prototype, t2, h2); - } - } - static getPropertyDescriptor(t2, s2, i2) { - const { get: e2, set: r2 } = h$2(this.prototype, t2) ?? { get() { - return this[s2]; - }, set(t3) { - this[s2] = t3; - } }; - return { get: e2, set(s3) { - const h2 = e2?.call(this); - r2?.call(this, s3), this.requestUpdate(t2, h2, i2); - }, configurable: true, enumerable: true }; - } - static getPropertyOptions(t2) { - return this.elementProperties.get(t2) ?? b$1; - } - static _$Ei() { - if (this.hasOwnProperty(d$1("elementProperties"))) return; - const t2 = n$2(this); - t2.finalize(), void 0 !== t2.l && (this.l = [...t2.l]), this.elementProperties = new Map(t2.elementProperties); - } - static finalize() { - if (this.hasOwnProperty(d$1("finalized"))) return; - if (this.finalized = true, this._$Ei(), this.hasOwnProperty(d$1("properties"))) { - const t3 = this.properties, s2 = [...r$3(t3), ...o$3(t3)]; - for (const i2 of s2) this.createProperty(i2, t3[i2]); - } - const t2 = this[Symbol.metadata]; - if (null !== t2) { - const s2 = litPropertyMetadata.get(t2); - if (void 0 !== s2) for (const [t3, i2] of s2) this.elementProperties.set(t3, i2); - } - this._$Eh = /* @__PURE__ */ new Map(); - for (const [t3, s2] of this.elementProperties) { - const i2 = this._$Eu(t3, s2); - void 0 !== i2 && this._$Eh.set(i2, t3); - } - this.elementStyles = this.finalizeStyles(this.styles); - } - static finalizeStyles(s2) { - const i2 = []; - if (Array.isArray(s2)) { - const e2 = new Set(s2.flat(1 / 0).reverse()); - for (const s3 of e2) i2.unshift(c$3(s3)); - } else void 0 !== s2 && i2.push(c$3(s2)); - return i2; - } - static _$Eu(t2, s2) { - const i2 = s2.attribute; - return false === i2 ? void 0 : "string" == typeof i2 ? i2 : "string" == typeof t2 ? t2.toLowerCase() : void 0; - } - constructor() { - super(), this._$Ep = void 0, this.isUpdatePending = false, this.hasUpdated = false, this._$Em = null, this._$Ev(); - } - _$Ev() { - this._$ES = new Promise((t2) => this.enableUpdating = t2), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t2) => t2(this)); - } - addController(t2) { - (this._$EO ??= /* @__PURE__ */ new Set()).add(t2), void 0 !== this.renderRoot && this.isConnected && t2.hostConnected?.(); - } - removeController(t2) { - this._$EO?.delete(t2); - } - _$E_() { - const t2 = /* @__PURE__ */ new Map(), s2 = this.constructor.elementProperties; - for (const i2 of s2.keys()) this.hasOwnProperty(i2) && (t2.set(i2, this[i2]), delete this[i2]); - t2.size > 0 && (this._$Ep = t2); - } - createRenderRoot() { - const t2 = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); - return S$1(t2, this.constructor.elementStyles), t2; - } - connectedCallback() { - this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(true), this._$EO?.forEach((t2) => t2.hostConnected?.()); - } - enableUpdating(t2) { - } - disconnectedCallback() { - this._$EO?.forEach((t2) => t2.hostDisconnected?.()); - } - attributeChangedCallback(t2, s2, i2) { - this._$AK(t2, i2); - } - _$ET(t2, s2) { - const i2 = this.constructor.elementProperties.get(t2), e2 = this.constructor._$Eu(t2, i2); - if (void 0 !== e2 && true === i2.reflect) { - const h2 = (void 0 !== i2.converter?.toAttribute ? i2.converter : u$3).toAttribute(s2, i2.type); - this._$Em = t2, null == h2 ? this.removeAttribute(e2) : this.setAttribute(e2, h2), this._$Em = null; - } - } - _$AK(t2, s2) { - const i2 = this.constructor, e2 = i2._$Eh.get(t2); - if (void 0 !== e2 && this._$Em !== e2) { - const t3 = i2.getPropertyOptions(e2), h2 = "function" == typeof t3.converter ? { fromAttribute: t3.converter } : void 0 !== t3.converter?.fromAttribute ? t3.converter : u$3; - this._$Em = e2; - const r2 = h2.fromAttribute(s2, t3.type); - this[e2] = r2 ?? this._$Ej?.get(e2) ?? r2, this._$Em = null; - } - } - requestUpdate(t2, s2, i2, e2 = false, h2) { - if (void 0 !== t2) { - const r2 = this.constructor; - if (false === e2 && (h2 = this[t2]), i2 ??= r2.getPropertyOptions(t2), !((i2.hasChanged ?? f$1)(h2, s2) || i2.useDefault && i2.reflect && h2 === this._$Ej?.get(t2) && !this.hasAttribute(r2._$Eu(t2, i2)))) return; - this.C(t2, s2, i2); - } - false === this.isUpdatePending && (this._$ES = this._$EP()); - } - C(t2, s2, { useDefault: i2, reflect: e2, wrapped: h2 }, r2) { - i2 && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t2) && (this._$Ej.set(t2, r2 ?? s2 ?? this[t2]), true !== h2 || void 0 !== r2) || (this._$AL.has(t2) || (this.hasUpdated || i2 || (s2 = void 0), this._$AL.set(t2, s2)), true === e2 && this._$Em !== t2 && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t2)); - } - async _$EP() { - this.isUpdatePending = true; - try { - await this._$ES; - } catch (t3) { - Promise.reject(t3); - } - const t2 = this.scheduleUpdate(); - return null != t2 && await t2, !this.isUpdatePending; - } - scheduleUpdate() { - return this.performUpdate(); - } - performUpdate() { - if (!this.isUpdatePending) return; - if (!this.hasUpdated) { - if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { - for (const [t4, s3] of this._$Ep) this[t4] = s3; - this._$Ep = void 0; - } - const t3 = this.constructor.elementProperties; - if (t3.size > 0) for (const [s3, i2] of t3) { - const { wrapped: t4 } = i2, e2 = this[s3]; - true !== t4 || this._$AL.has(s3) || void 0 === e2 || this.C(s3, void 0, i2, e2); - } - } - let t2 = false; - const s2 = this._$AL; - try { - t2 = this.shouldUpdate(s2), t2 ? (this.willUpdate(s2), this._$EO?.forEach((t3) => t3.hostUpdate?.()), this.update(s2)) : this._$EM(); - } catch (s3) { - throw t2 = false, this._$EM(), s3; - } - t2 && this._$AE(s2); - } - willUpdate(t2) { - } - _$AE(t2) { - this._$EO?.forEach((t3) => t3.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = true, this.firstUpdated(t2)), this.updated(t2); - } - _$EM() { - this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = false; - } - get updateComplete() { - return this.getUpdateComplete(); - } - getUpdateComplete() { - return this._$ES; - } - shouldUpdate(t2) { - return true; - } - update(t2) { - this._$Eq &&= this._$Eq.forEach((t3) => this._$ET(t3, this[t3])), this._$EM(); - } - updated(t2) { - } - firstUpdated(t2) { - } - }; - y$1.elementStyles = [], y$1.shadowRootOptions = { mode: "open" }, y$1[d$1("elementProperties")] = /* @__PURE__ */ new Map(), y$1[d$1("finalized")] = /* @__PURE__ */ new Map(), p$2?.({ ReactiveElement: y$1 }), (a$1.reactiveElementVersions ??= []).push("2.1.2"); - const t$3 = globalThis, i$3 = (t2) => t2, s$2 = t$3.trustedTypes, e$2 = s$2 ? s$2.createPolicy("lit-html", { createHTML: (t2) => t2 }) : void 0, h$1 = "$lit$", o$2 = `lit$${Math.random().toFixed(9).slice(2)}$`, n$1 = "?" + o$2, r$2 = `<${n$1}>`, l = document, c$1 = () => l.createComment(""), a = (t2) => null === t2 || "object" != typeof t2 && "function" != typeof t2, u$2 = Array.isArray, d = (t2) => u$2(t2) || "function" == typeof t2?.[Symbol.iterator], f = "[ \n\f\r]", v$1 = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m$1 = />/g, p$1 = RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`, "g"), g = /'/g, $ = /"/g, y = /^(?:script|style|textarea|title)$/i, x = (t2) => (i2, ...s2) => ({ _$litType$: t2, strings: i2, values: s2 }), b = x(1), E = /* @__PURE__ */ Symbol.for("lit-noChange"), A = /* @__PURE__ */ Symbol.for("lit-nothing"), C = /* @__PURE__ */ new WeakMap(), P = l.createTreeWalker(l, 129); - function V(t2, i2) { - if (!u$2(t2) || !t2.hasOwnProperty("raw")) throw Error("invalid template strings array"); - return void 0 !== e$2 ? e$2.createHTML(i2) : i2; - } - const N = (t2, i2) => { - const s2 = t2.length - 1, e2 = []; - let n2, l2 = 2 === i2 ? "" : 3 === i2 ? "" : "", c2 = v$1; - for (let i3 = 0; i3 < s2; i3++) { - const s3 = t2[i3]; - let a2, u2, d2 = -1, f2 = 0; - for (; f2 < s3.length && (c2.lastIndex = f2, u2 = c2.exec(s3), null !== u2); ) f2 = c2.lastIndex, c2 === v$1 ? "!--" === u2[1] ? c2 = _ : void 0 !== u2[1] ? c2 = m$1 : void 0 !== u2[2] ? (y.test(u2[2]) && (n2 = RegExp("" === u2[0] ? (c2 = n2 ?? v$1, d2 = -1) : void 0 === u2[1] ? d2 = -2 : (d2 = c2.lastIndex - u2[2].length, a2 = u2[1], c2 = void 0 === u2[3] ? p$1 : '"' === u2[3] ? $ : g) : c2 === $ || c2 === g ? c2 = p$1 : c2 === _ || c2 === m$1 ? c2 = v$1 : (c2 = p$1, n2 = void 0); - const x2 = c2 === p$1 && t2[i3 + 1].startsWith("/>") ? " " : ""; - l2 += c2 === v$1 ? s3 + r$2 : d2 >= 0 ? (e2.push(a2), s3.slice(0, d2) + h$1 + s3.slice(d2) + o$2 + x2) : s3 + o$2 + (-2 === d2 ? i3 : x2); - } - return [V(t2, l2 + (t2[s2] || "") + (2 === i2 ? "" : 3 === i2 ? "" : "")), e2]; - }; - class S { - constructor({ strings: t2, _$litType$: i2 }, e2) { - let r2; - this.parts = []; - let l2 = 0, a2 = 0; - const u2 = t2.length - 1, d2 = this.parts, [f2, v2] = N(t2, i2); - if (this.el = S.createElement(f2, e2), P.currentNode = this.el.content, 2 === i2 || 3 === i2) { - const t3 = this.el.content.firstChild; - t3.replaceWith(...t3.childNodes); - } - for (; null !== (r2 = P.nextNode()) && d2.length < u2; ) { - if (1 === r2.nodeType) { - if (r2.hasAttributes()) for (const t3 of r2.getAttributeNames()) if (t3.endsWith(h$1)) { - const i3 = v2[a2++], s2 = r2.getAttribute(t3).split(o$2), e3 = /([.?@])?(.*)/.exec(i3); - d2.push({ type: 1, index: l2, name: e3[2], strings: s2, ctor: "." === e3[1] ? I : "?" === e3[1] ? L : "@" === e3[1] ? z : H }), r2.removeAttribute(t3); - } else t3.startsWith(o$2) && (d2.push({ type: 6, index: l2 }), r2.removeAttribute(t3)); - if (y.test(r2.tagName)) { - const t3 = r2.textContent.split(o$2), i3 = t3.length - 1; - if (i3 > 0) { - r2.textContent = s$2 ? s$2.emptyScript : ""; - for (let s2 = 0; s2 < i3; s2++) r2.append(t3[s2], c$1()), P.nextNode(), d2.push({ type: 2, index: ++l2 }); - r2.append(t3[i3], c$1()); - } - } - } else if (8 === r2.nodeType) if (r2.data === n$1) d2.push({ type: 2, index: l2 }); - else { - let t3 = -1; - for (; -1 !== (t3 = r2.data.indexOf(o$2, t3 + 1)); ) d2.push({ type: 7, index: l2 }), t3 += o$2.length - 1; - } - l2++; - } - } - static createElement(t2, i2) { - const s2 = l.createElement("template"); - return s2.innerHTML = t2, s2; - } - } - function M$1(t2, i2, s2 = t2, e2) { - if (i2 === E) return i2; - let h2 = void 0 !== e2 ? s2._$Co?.[e2] : s2._$Cl; - const o2 = a(i2) ? void 0 : i2._$litDirective$; - return h2?.constructor !== o2 && (h2?._$AO?.(false), void 0 === o2 ? h2 = void 0 : (h2 = new o2(t2), h2._$AT(t2, s2, e2)), void 0 !== e2 ? (s2._$Co ??= [])[e2] = h2 : s2._$Cl = h2), void 0 !== h2 && (i2 = M$1(t2, h2._$AS(t2, i2.values), h2, e2)), i2; - } - class R { - constructor(t2, i2) { - this._$AV = [], this._$AN = void 0, this._$AD = t2, this._$AM = i2; - } - get parentNode() { - return this._$AM.parentNode; - } - get _$AU() { - return this._$AM._$AU; - } - u(t2) { - const { el: { content: i2 }, parts: s2 } = this._$AD, e2 = (t2?.creationScope ?? l).importNode(i2, true); - P.currentNode = e2; - let h2 = P.nextNode(), o2 = 0, n2 = 0, r2 = s2[0]; - for (; void 0 !== r2; ) { - if (o2 === r2.index) { - let i3; - 2 === r2.type ? i3 = new k(h2, h2.nextSibling, this, t2) : 1 === r2.type ? i3 = new r2.ctor(h2, r2.name, r2.strings, this, t2) : 6 === r2.type && (i3 = new Z(h2, this, t2)), this._$AV.push(i3), r2 = s2[++n2]; - } - o2 !== r2?.index && (h2 = P.nextNode(), o2++); - } - return P.currentNode = l, e2; - } - p(t2) { - let i2 = 0; - for (const s2 of this._$AV) void 0 !== s2 && (void 0 !== s2.strings ? (s2._$AI(t2, s2, i2), i2 += s2.strings.length - 2) : s2._$AI(t2[i2])), i2++; - } - } - class k { - get _$AU() { - return this._$AM?._$AU ?? this._$Cv; - } - constructor(t2, i2, s2, e2) { - this.type = 2, this._$AH = A, this._$AN = void 0, this._$AA = t2, this._$AB = i2, this._$AM = s2, this.options = e2, this._$Cv = e2?.isConnected ?? true; - } - get parentNode() { - let t2 = this._$AA.parentNode; - const i2 = this._$AM; - return void 0 !== i2 && 11 === t2?.nodeType && (t2 = i2.parentNode), t2; - } - get startNode() { - return this._$AA; - } - get endNode() { - return this._$AB; - } - _$AI(t2, i2 = this) { - t2 = M$1(this, t2, i2), a(t2) ? t2 === A || null == t2 || "" === t2 ? (this._$AH !== A && this._$AR(), this._$AH = A) : t2 !== this._$AH && t2 !== E && this._(t2) : void 0 !== t2._$litType$ ? this.$(t2) : void 0 !== t2.nodeType ? this.T(t2) : d(t2) ? this.k(t2) : this._(t2); - } - O(t2) { - return this._$AA.parentNode.insertBefore(t2, this._$AB); - } - T(t2) { - this._$AH !== t2 && (this._$AR(), this._$AH = this.O(t2)); - } - _(t2) { - this._$AH !== A && a(this._$AH) ? this._$AA.nextSibling.data = t2 : this.T(l.createTextNode(t2)), this._$AH = t2; - } - $(t2) { - const { values: i2, _$litType$: s2 } = t2, e2 = "number" == typeof s2 ? this._$AC(t2) : (void 0 === s2.el && (s2.el = S.createElement(V(s2.h, s2.h[0]), this.options)), s2); - if (this._$AH?._$AD === e2) this._$AH.p(i2); - else { - const t3 = new R(e2, this), s3 = t3.u(this.options); - t3.p(i2), this.T(s3), this._$AH = t3; - } - } - _$AC(t2) { - let i2 = C.get(t2.strings); - return void 0 === i2 && C.set(t2.strings, i2 = new S(t2)), i2; - } - k(t2) { - u$2(this._$AH) || (this._$AH = [], this._$AR()); - const i2 = this._$AH; - let s2, e2 = 0; - for (const h2 of t2) e2 === i2.length ? i2.push(s2 = new k(this.O(c$1()), this.O(c$1()), this, this.options)) : s2 = i2[e2], s2._$AI(h2), e2++; - e2 < i2.length && (this._$AR(s2 && s2._$AB.nextSibling, e2), i2.length = e2); - } - _$AR(t2 = this._$AA.nextSibling, s2) { - for (this._$AP?.(false, true, s2); t2 !== this._$AB; ) { - const s3 = i$3(t2).nextSibling; - i$3(t2).remove(), t2 = s3; - } - } - setConnected(t2) { - void 0 === this._$AM && (this._$Cv = t2, this._$AP?.(t2)); - } - } - class H { - get tagName() { - return this.element.tagName; - } - get _$AU() { - return this._$AM._$AU; - } - constructor(t2, i2, s2, e2, h2) { - this.type = 1, this._$AH = A, this._$AN = void 0, this.element = t2, this.name = i2, this._$AM = e2, this.options = h2, s2.length > 2 || "" !== s2[0] || "" !== s2[1] ? (this._$AH = Array(s2.length - 1).fill(new String()), this.strings = s2) : this._$AH = A; - } - _$AI(t2, i2 = this, s2, e2) { - const h2 = this.strings; - let o2 = false; - if (void 0 === h2) t2 = M$1(this, t2, i2, 0), o2 = !a(t2) || t2 !== this._$AH && t2 !== E, o2 && (this._$AH = t2); - else { - const e3 = t2; - let n2, r2; - for (t2 = h2[0], n2 = 0; n2 < h2.length - 1; n2++) r2 = M$1(this, e3[s2 + n2], i2, n2), r2 === E && (r2 = this._$AH[n2]), o2 ||= !a(r2) || r2 !== this._$AH[n2], r2 === A ? t2 = A : t2 !== A && (t2 += (r2 ?? "") + h2[n2 + 1]), this._$AH[n2] = r2; - } - o2 && !e2 && this.j(t2); - } - j(t2) { - t2 === A ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t2 ?? ""); - } - } - class I extends H { - constructor() { - super(...arguments), this.type = 3; - } - j(t2) { - this.element[this.name] = t2 === A ? void 0 : t2; - } - } - class L extends H { - constructor() { - super(...arguments), this.type = 4; - } - j(t2) { - this.element.toggleAttribute(this.name, !!t2 && t2 !== A); - } - } - class z extends H { - constructor(t2, i2, s2, e2, h2) { - super(t2, i2, s2, e2, h2), this.type = 5; - } - _$AI(t2, i2 = this) { - if ((t2 = M$1(this, t2, i2, 0) ?? A) === E) return; - const s2 = this._$AH, e2 = t2 === A && s2 !== A || t2.capture !== s2.capture || t2.once !== s2.once || t2.passive !== s2.passive, h2 = t2 !== A && (s2 === A || e2); - e2 && this.element.removeEventListener(this.name, this, s2), h2 && this.element.addEventListener(this.name, this, t2), this._$AH = t2; - } - handleEvent(t2) { - "function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t2) : this._$AH.handleEvent(t2); - } - } - class Z { - constructor(t2, i2, s2) { - this.element = t2, this.type = 6, this._$AN = void 0, this._$AM = i2, this.options = s2; - } - get _$AU() { - return this._$AM._$AU; - } - _$AI(t2) { - M$1(this, t2); - } - } - const j = { I: k }, B = t$3.litHtmlPolyfillSupport; - B?.(S, k), (t$3.litHtmlVersions ??= []).push("3.3.2"); - const D = (t2, i2, s2) => { - const e2 = s2?.renderBefore ?? i2; - let h2 = e2._$litPart$; - if (void 0 === h2) { - const t3 = s2?.renderBefore ?? null; - e2._$litPart$ = h2 = new k(i2.insertBefore(c$1(), t3), t3, void 0, s2 ?? {}); - } - return h2._$AI(t2), h2; - }; - const s$1 = globalThis; - let i$2 = class i extends y$1 { - constructor() { - super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; - } - createRenderRoot() { - const t2 = super.createRenderRoot(); - return this.renderOptions.renderBefore ??= t2.firstChild, t2; - } - update(t2) { - const r2 = this.render(); - this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t2), this._$Do = D(r2, this.renderRoot, this.renderOptions); - } - connectedCallback() { - super.connectedCallback(), this._$Do?.setConnected(true); - } - disconnectedCallback() { - super.disconnectedCallback(), this._$Do?.setConnected(false); - } - render() { - return E; - } - }; - i$2._$litElement$ = true, i$2["finalized"] = true, s$1.litElementHydrateSupport?.({ LitElement: i$2 }); - const o$1 = s$1.litElementPolyfillSupport; - o$1?.({ LitElement: i$2 }); - (s$1.litElementVersions ??= []).push("4.2.2"); - const styles$16 = i$5` + //#region \0rolldown/runtime.js + var __defProp = Object.defineProperty; + var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res); + var __exportAll = (all, no_symbols) => { + let target = {}; + for (var name in all) __defProp(target, name, { + get: all[name], + enumerable: true + }); + if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" }); + return target; + }; + //#endregion + //#region node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/css-tag.js + /** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + var t$4 = globalThis, e$5 = t$4.ShadowRoot && (void 0 === t$4.ShadyCSS || t$4.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$3 = Symbol(), o$4 = /* @__PURE__ */ new WeakMap(); + var n$4 = class { + constructor(t, e, o) { + if (this._$cssResult$ = !0, o !== s$3) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); + this.cssText = t, this.t = e; + } + get styleSheet() { + let t = this.o; + const s = this.t; + if (e$5 && void 0 === t) { + const e = void 0 !== s && 1 === s.length; + e && (t = o$4.get(s)), void 0 === t && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), e && o$4.set(s, t)); + } + return t; + } + toString() { + return this.cssText; + } + }; + var r$5 = (t) => new n$4("string" == typeof t ? t : t + "", void 0, s$3), i$5 = (t, ...e) => { + return new n$4(1 === t.length ? t[0] : e.reduce((e, s, o) => e + ((t) => { + if (!0 === t._$cssResult$) return t.cssText; + if ("number" == typeof t) return t; + throw Error("Value passed to 'css' function must be a 'css' function result: " + t + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); + })(s) + t[o + 1], t[0]), t, s$3); + }, S$1 = (s, o) => { + if (e$5) s.adoptedStyleSheets = o.map((t) => t instanceof CSSStyleSheet ? t : t.styleSheet); + else for (const e of o) { + const o = document.createElement("style"), n = t$4.litNonce; + void 0 !== n && o.setAttribute("nonce", n), o.textContent = e.cssText, s.appendChild(o); + } + }, c$4 = e$5 ? (t) => t : (t) => t instanceof CSSStyleSheet ? ((t) => { + let e = ""; + for (const s of t.cssRules) e += s.cssText; + return r$5(e); + })(t) : t; + //#endregion + //#region node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/reactive-element.js + /** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ var { is: i$4, defineProperty: e$4, getOwnPropertyDescriptor: h$2, getOwnPropertyNames: r$4, getOwnPropertySymbols: o$3, getPrototypeOf: n$3 } = Object, a$1 = globalThis, c$3 = a$1.trustedTypes, l$2 = c$3 ? c$3.emptyScript : "", p$2 = a$1.reactiveElementPolyfillSupport, d$2 = (t, s) => t, u$3 = { + toAttribute(t, s) { + switch (s) { + case Boolean: + t = t ? l$2 : null; + break; + case Object: + case Array: t = null == t ? t : JSON.stringify(t); + } + return t; + }, + fromAttribute(t, s) { + let i = t; + switch (s) { + case Boolean: + i = null !== t; + break; + case Number: + i = null === t ? null : Number(t); + break; + case Object: + case Array: try { + i = JSON.parse(t); + } catch (t) { + i = null; + } + } + return i; + } + }, f$2 = (t, s) => !i$4(t, s), b$1 = { + attribute: !0, + type: String, + converter: u$3, + reflect: !1, + useDefault: !1, + hasChanged: f$2 + }; + Symbol.metadata ??= Symbol("metadata"), a$1.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); + var y$1 = class extends HTMLElement { + static addInitializer(t) { + this._$Ei(), (this.l ??= []).push(t); + } + static get observedAttributes() { + return this.finalize(), this._$Eh && [...this._$Eh.keys()]; + } + static createProperty(t, s = b$1) { + if (s.state && (s.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((s = Object.create(s)).wrapped = !0), this.elementProperties.set(t, s), !s.noAccessor) { + const i = Symbol(), h = this.getPropertyDescriptor(t, i, s); + void 0 !== h && e$4(this.prototype, t, h); + } + } + static getPropertyDescriptor(t, s, i) { + const { get: e, set: r } = h$2(this.prototype, t) ?? { + get() { + return this[s]; + }, + set(t) { + this[s] = t; + } + }; + return { + get: e, + set(s) { + const h = e?.call(this); + r?.call(this, s), this.requestUpdate(t, h, i); + }, + configurable: !0, + enumerable: !0 + }; + } + static getPropertyOptions(t) { + return this.elementProperties.get(t) ?? b$1; + } + static _$Ei() { + if (this.hasOwnProperty(d$2("elementProperties"))) return; + const t = n$3(this); + t.finalize(), void 0 !== t.l && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties); + } + static finalize() { + if (this.hasOwnProperty(d$2("finalized"))) return; + if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(d$2("properties"))) { + const t = this.properties, s = [...r$4(t), ...o$3(t)]; + for (const i of s) this.createProperty(i, t[i]); + } + const t = this[Symbol.metadata]; + if (null !== t) { + const s = litPropertyMetadata.get(t); + if (void 0 !== s) for (const [t, i] of s) this.elementProperties.set(t, i); + } + this._$Eh = /* @__PURE__ */ new Map(); + for (const [t, s] of this.elementProperties) { + const i = this._$Eu(t, s); + void 0 !== i && this._$Eh.set(i, t); + } + this.elementStyles = this.finalizeStyles(this.styles); + } + static finalizeStyles(s) { + const i = []; + if (Array.isArray(s)) { + const e = new Set(s.flat(Infinity).reverse()); + for (const s of e) i.unshift(c$4(s)); + } else void 0 !== s && i.push(c$4(s)); + return i; + } + static _$Eu(t, s) { + const i = s.attribute; + return !1 === i ? void 0 : "string" == typeof i ? i : "string" == typeof t ? t.toLowerCase() : void 0; + } + constructor() { + super(), this._$Ep = void 0, this.isUpdatePending = !1, this.hasUpdated = !1, this._$Em = null, this._$Ev(); + } + _$Ev() { + this._$ES = new Promise((t) => this.enableUpdating = t), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t) => t(this)); + } + addController(t) { + (this._$EO ??= /* @__PURE__ */ new Set()).add(t), void 0 !== this.renderRoot && this.isConnected && t.hostConnected?.(); + } + removeController(t) { + this._$EO?.delete(t); + } + _$E_() { + const t = /* @__PURE__ */ new Map(), s = this.constructor.elementProperties; + for (const i of s.keys()) this.hasOwnProperty(i) && (t.set(i, this[i]), delete this[i]); + t.size > 0 && (this._$Ep = t); + } + createRenderRoot() { + const t = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); + return S$1(t, this.constructor.elementStyles), t; + } + connectedCallback() { + this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(!0), this._$EO?.forEach((t) => t.hostConnected?.()); + } + enableUpdating(t) {} + disconnectedCallback() { + this._$EO?.forEach((t) => t.hostDisconnected?.()); + } + attributeChangedCallback(t, s, i) { + this._$AK(t, i); + } + _$ET(t, s) { + const i = this.constructor.elementProperties.get(t), e = this.constructor._$Eu(t, i); + if (void 0 !== e && !0 === i.reflect) { + const h = (void 0 !== i.converter?.toAttribute ? i.converter : u$3).toAttribute(s, i.type); + this._$Em = t, null == h ? this.removeAttribute(e) : this.setAttribute(e, h), this._$Em = null; + } + } + _$AK(t, s) { + const i = this.constructor, e = i._$Eh.get(t); + if (void 0 !== e && this._$Em !== e) { + const t = i.getPropertyOptions(e), h = "function" == typeof t.converter ? { fromAttribute: t.converter } : void 0 !== t.converter?.fromAttribute ? t.converter : u$3; + this._$Em = e; + const r = h.fromAttribute(s, t.type); + this[e] = r ?? this._$Ej?.get(e) ?? r, this._$Em = null; + } + } + requestUpdate(t, s, i, e = !1, h) { + if (void 0 !== t) { + const r = this.constructor; + if (!1 === e && (h = this[t]), i ??= r.getPropertyOptions(t), !((i.hasChanged ?? f$2)(h, s) || i.useDefault && i.reflect && h === this._$Ej?.get(t) && !this.hasAttribute(r._$Eu(t, i)))) return; + this.C(t, s, i); + } + !1 === this.isUpdatePending && (this._$ES = this._$EP()); + } + C(t, s, { useDefault: i, reflect: e, wrapped: h }, r) { + i && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, r ?? s ?? this[t]), !0 !== h || void 0 !== r) || (this._$AL.has(t) || (this.hasUpdated || i || (s = void 0), this._$AL.set(t, s)), !0 === e && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); + } + async _$EP() { + this.isUpdatePending = !0; + try { + await this._$ES; + } catch (t) { + Promise.reject(t); + } + const t = this.scheduleUpdate(); + return null != t && await t, !this.isUpdatePending; + } + scheduleUpdate() { + return this.performUpdate(); + } + performUpdate() { + if (!this.isUpdatePending) return; + if (!this.hasUpdated) { + if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { + for (const [t, s] of this._$Ep) this[t] = s; + this._$Ep = void 0; + } + const t = this.constructor.elementProperties; + if (t.size > 0) for (const [s, i] of t) { + const { wrapped: t } = i, e = this[s]; + !0 !== t || this._$AL.has(s) || void 0 === e || this.C(s, void 0, i, e); + } + } + let t = !1; + const s = this._$AL; + try { + t = this.shouldUpdate(s), t ? (this.willUpdate(s), this._$EO?.forEach((t) => t.hostUpdate?.()), this.update(s)) : this._$EM(); + } catch (s) { + throw t = !1, this._$EM(), s; + } + t && this._$AE(s); + } + willUpdate(t) {} + _$AE(t) { + this._$EO?.forEach((t) => t.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = !0, this.firstUpdated(t)), this.updated(t); + } + _$EM() { + this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = !1; + } + get updateComplete() { + return this.getUpdateComplete(); + } + getUpdateComplete() { + return this._$ES; + } + shouldUpdate(t) { + return !0; + } + update(t) { + this._$Eq &&= this._$Eq.forEach((t) => this._$ET(t, this[t])), this._$EM(); + } + updated(t) {} + firstUpdated(t) {} + }; + y$1.elementStyles = [], y$1.shadowRootOptions = { mode: "open" }, y$1[d$2("elementProperties")] = /* @__PURE__ */ new Map(), y$1[d$2("finalized")] = /* @__PURE__ */ new Map(), p$2?.({ ReactiveElement: y$1 }), (a$1.reactiveElementVersions ??= []).push("2.1.2"); + //#endregion + //#region node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/lit-html.js + /** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + var t$3 = globalThis, i$3 = (t) => t, s$2 = t$3.trustedTypes, e$3 = s$2 ? s$2.createPolicy("lit-html", { createHTML: (t) => t }) : void 0, h$1 = "$lit$", o$2 = `lit$${Math.random().toFixed(9).slice(2)}$`, n$2 = "?" + o$2, r$3 = `<${n$2}>`, l$1 = document, c$2 = () => l$1.createComment(""), a = (t) => null === t || "object" != typeof t && "function" != typeof t, u$2 = Array.isArray, d$1 = (t) => u$2(t) || "function" == typeof t?.[Symbol.iterator], f$1 = "[ \n\f\r]", v$1 = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m$1 = />/g, p$1 = RegExp(`>|${f$1}(?:([^\\s"'>=/]+)(${f$1}*=${f$1}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`, "g"), g = /'/g, $ = /"/g, y = /^(?:script|style|textarea|title)$/i, x = (t) => (i, ...s) => ({ + _$litType$: t, + strings: i, + values: s + }), b = x(1); + x(2); + x(3); + var E = Symbol.for("lit-noChange"), A = Symbol.for("lit-nothing"), C = /* @__PURE__ */ new WeakMap(), P = l$1.createTreeWalker(l$1, 129); + function V(t, i) { + if (!u$2(t) || !t.hasOwnProperty("raw")) throw Error("invalid template strings array"); + return void 0 !== e$3 ? e$3.createHTML(i) : i; + } + var N = (t, i) => { + const s = t.length - 1, e = []; + let n, l = 2 === i ? "" : 3 === i ? "" : "", c = v$1; + for (let i = 0; i < s; i++) { + const s = t[i]; + let a, u, d = -1, f = 0; + for (; f < s.length && (c.lastIndex = f, u = c.exec(s), null !== u);) f = c.lastIndex, c === v$1 ? "!--" === u[1] ? c = _ : void 0 !== u[1] ? c = m$1 : void 0 !== u[2] ? (y.test(u[2]) && (n = RegExp("" === u[0] ? (c = n ?? v$1, d = -1) : void 0 === u[1] ? d = -2 : (d = c.lastIndex - u[2].length, a = u[1], c = void 0 === u[3] ? p$1 : "\"" === u[3] ? $ : g) : c === $ || c === g ? c = p$1 : c === _ || c === m$1 ? c = v$1 : (c = p$1, n = void 0); + const x = c === p$1 && t[i + 1].startsWith("/>") ? " " : ""; + l += c === v$1 ? s + r$3 : d >= 0 ? (e.push(a), s.slice(0, d) + h$1 + s.slice(d) + o$2 + x) : s + o$2 + (-2 === d ? i : x); + } + return [V(t, l + (t[s] || "") + (2 === i ? "" : 3 === i ? "" : "")), e]; + }; + var S = class S { + constructor({ strings: t, _$litType$: i }, e) { + let r; + this.parts = []; + let l = 0, a = 0; + const u = t.length - 1, d = this.parts, [f, v] = N(t, i); + if (this.el = S.createElement(f, e), P.currentNode = this.el.content, 2 === i || 3 === i) { + const t = this.el.content.firstChild; + t.replaceWith(...t.childNodes); + } + for (; null !== (r = P.nextNode()) && d.length < u;) { + if (1 === r.nodeType) { + if (r.hasAttributes()) for (const t of r.getAttributeNames()) if (t.endsWith(h$1)) { + const i = v[a++], s = r.getAttribute(t).split(o$2), e = /([.?@])?(.*)/.exec(i); + d.push({ + type: 1, + index: l, + name: e[2], + strings: s, + ctor: "." === e[1] ? I : "?" === e[1] ? L : "@" === e[1] ? z : H + }), r.removeAttribute(t); + } else t.startsWith(o$2) && (d.push({ + type: 6, + index: l + }), r.removeAttribute(t)); + if (y.test(r.tagName)) { + const t = r.textContent.split(o$2), i = t.length - 1; + if (i > 0) { + r.textContent = s$2 ? s$2.emptyScript : ""; + for (let s = 0; s < i; s++) r.append(t[s], c$2()), P.nextNode(), d.push({ + type: 2, + index: ++l + }); + r.append(t[i], c$2()); + } + } + } else if (8 === r.nodeType) if (r.data === n$2) d.push({ + type: 2, + index: l + }); + else { + let t = -1; + for (; -1 !== (t = r.data.indexOf(o$2, t + 1));) d.push({ + type: 7, + index: l + }), t += o$2.length - 1; + } + l++; + } + } + static createElement(t, i) { + const s = l$1.createElement("template"); + return s.innerHTML = t, s; + } + }; + function M$1(t, i, s = t, e) { + if (i === E) return i; + let h = void 0 !== e ? s._$Co?.[e] : s._$Cl; + const o = a(i) ? void 0 : i._$litDirective$; + return h?.constructor !== o && (h?._$AO?.(!1), void 0 === o ? h = void 0 : (h = new o(t), h._$AT(t, s, e)), void 0 !== e ? (s._$Co ??= [])[e] = h : s._$Cl = h), void 0 !== h && (i = M$1(t, h._$AS(t, i.values), h, e)), i; + } + var R = class { + constructor(t, i) { + this._$AV = [], this._$AN = void 0, this._$AD = t, this._$AM = i; + } + get parentNode() { + return this._$AM.parentNode; + } + get _$AU() { + return this._$AM._$AU; + } + u(t) { + const { el: { content: i }, parts: s } = this._$AD, e = (t?.creationScope ?? l$1).importNode(i, !0); + P.currentNode = e; + let h = P.nextNode(), o = 0, n = 0, r = s[0]; + for (; void 0 !== r;) { + if (o === r.index) { + let i; + 2 === r.type ? i = new k(h, h.nextSibling, this, t) : 1 === r.type ? i = new r.ctor(h, r.name, r.strings, this, t) : 6 === r.type && (i = new Z(h, this, t)), this._$AV.push(i), r = s[++n]; + } + o !== r?.index && (h = P.nextNode(), o++); + } + return P.currentNode = l$1, e; + } + p(t) { + let i = 0; + for (const s of this._$AV) void 0 !== s && (void 0 !== s.strings ? (s._$AI(t, s, i), i += s.strings.length - 2) : s._$AI(t[i])), i++; + } + }; + var k = class k { + get _$AU() { + return this._$AM?._$AU ?? this._$Cv; + } + constructor(t, i, s, e) { + this.type = 2, this._$AH = A, this._$AN = void 0, this._$AA = t, this._$AB = i, this._$AM = s, this.options = e, this._$Cv = e?.isConnected ?? !0; + } + get parentNode() { + let t = this._$AA.parentNode; + const i = this._$AM; + return void 0 !== i && 11 === t?.nodeType && (t = i.parentNode), t; + } + get startNode() { + return this._$AA; + } + get endNode() { + return this._$AB; + } + _$AI(t, i = this) { + t = M$1(this, t, i), a(t) ? t === A || null == t || "" === t ? (this._$AH !== A && this._$AR(), this._$AH = A) : t !== this._$AH && t !== E && this._(t) : void 0 !== t._$litType$ ? this.$(t) : void 0 !== t.nodeType ? this.T(t) : d$1(t) ? this.k(t) : this._(t); + } + O(t) { + return this._$AA.parentNode.insertBefore(t, this._$AB); + } + T(t) { + this._$AH !== t && (this._$AR(), this._$AH = this.O(t)); + } + _(t) { + this._$AH !== A && a(this._$AH) ? this._$AA.nextSibling.data = t : this.T(l$1.createTextNode(t)), this._$AH = t; + } + $(t) { + const { values: i, _$litType$: s } = t, e = "number" == typeof s ? this._$AC(t) : (void 0 === s.el && (s.el = S.createElement(V(s.h, s.h[0]), this.options)), s); + if (this._$AH?._$AD === e) this._$AH.p(i); + else { + const t = new R(e, this), s = t.u(this.options); + t.p(i), this.T(s), this._$AH = t; + } + } + _$AC(t) { + let i = C.get(t.strings); + return void 0 === i && C.set(t.strings, i = new S(t)), i; + } + k(t) { + u$2(this._$AH) || (this._$AH = [], this._$AR()); + const i = this._$AH; + let s, e = 0; + for (const h of t) e === i.length ? i.push(s = new k(this.O(c$2()), this.O(c$2()), this, this.options)) : s = i[e], s._$AI(h), e++; + e < i.length && (this._$AR(s && s._$AB.nextSibling, e), i.length = e); + } + _$AR(t = this._$AA.nextSibling, s) { + for (this._$AP?.(!1, !0, s); t !== this._$AB;) { + const s = i$3(t).nextSibling; + i$3(t).remove(), t = s; + } + } + setConnected(t) { + void 0 === this._$AM && (this._$Cv = t, this._$AP?.(t)); + } + }; + var H = class { + get tagName() { + return this.element.tagName; + } + get _$AU() { + return this._$AM._$AU; + } + constructor(t, i, s, e, h) { + this.type = 1, this._$AH = A, this._$AN = void 0, this.element = t, this.name = i, this._$AM = e, this.options = h, s.length > 2 || "" !== s[0] || "" !== s[1] ? (this._$AH = Array(s.length - 1).fill(/* @__PURE__ */ new String()), this.strings = s) : this._$AH = A; + } + _$AI(t, i = this, s, e) { + const h = this.strings; + let o = !1; + if (void 0 === h) t = M$1(this, t, i, 0), o = !a(t) || t !== this._$AH && t !== E, o && (this._$AH = t); + else { + const e = t; + let n, r; + for (t = h[0], n = 0; n < h.length - 1; n++) r = M$1(this, e[s + n], i, n), r === E && (r = this._$AH[n]), o ||= !a(r) || r !== this._$AH[n], r === A ? t = A : t !== A && (t += (r ?? "") + h[n + 1]), this._$AH[n] = r; + } + o && !e && this.j(t); + } + j(t) { + t === A ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); + } + }; + var I = class extends H { + constructor() { + super(...arguments), this.type = 3; + } + j(t) { + this.element[this.name] = t === A ? void 0 : t; + } + }; + var L = class extends H { + constructor() { + super(...arguments), this.type = 4; + } + j(t) { + this.element.toggleAttribute(this.name, !!t && t !== A); + } + }; + var z = class extends H { + constructor(t, i, s, e, h) { + super(t, i, s, e, h), this.type = 5; + } + _$AI(t, i = this) { + if ((t = M$1(this, t, i, 0) ?? A) === E) return; + const s = this._$AH, e = t === A && s !== A || t.capture !== s.capture || t.once !== s.once || t.passive !== s.passive, h = t !== A && (s === A || e); + e && this.element.removeEventListener(this.name, this, s), h && this.element.addEventListener(this.name, this, t), this._$AH = t; + } + handleEvent(t) { + "function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t); + } + }; + var Z = class { + constructor(t, i, s) { + this.element = t, this.type = 6, this._$AN = void 0, this._$AM = i, this.options = s; + } + get _$AU() { + return this._$AM._$AU; + } + _$AI(t) { + M$1(this, t); + } + }; + var j$1 = { + M: h$1, + P: o$2, + A: n$2, + C: 1, + L: N, + R, + D: d$1, + V: M$1, + I: k, + H, + N: L, + U: z, + B: I, + F: Z + }, B = t$3.litHtmlPolyfillSupport; + B?.(S, k), (t$3.litHtmlVersions ??= []).push("3.3.2"); + var D = (t, i, s) => { + const e = s?.renderBefore ?? i; + let h = e._$litPart$; + if (void 0 === h) { + const t = s?.renderBefore ?? null; + e._$litPart$ = h = new k(i.insertBefore(c$2(), t), t, void 0, s ?? {}); + } + return h._$AI(t), h; + }; + //#endregion + //#region node_modules/.pnpm/lit-element@4.2.2/node_modules/lit-element/lit-element.js + /** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ var s$1 = globalThis; + var i$2 = class extends y$1 { + constructor() { + super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; + } + createRenderRoot() { + const t = super.createRenderRoot(); + return this.renderOptions.renderBefore ??= t.firstChild, t; + } + update(t) { + const r = this.render(); + this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = D(r, this.renderRoot, this.renderOptions); + } + connectedCallback() { + super.connectedCallback(), this._$Do?.setConnected(!0); + } + disconnectedCallback() { + super.disconnectedCallback(), this._$Do?.setConnected(!1); + } + render() { + return E; + } + }; + i$2._$litElement$ = !0, i$2["finalized"] = !0, s$1.litElementHydrateSupport?.({ LitElement: i$2 }); + var o$1 = s$1.litElementPolyfillSupport; + o$1?.({ LitElement: i$2 }); + (s$1.litElementVersions ??= []).push("4.2.2"); + //#endregion + //#region custom_components/hass_datapoints/src/cards/action/action.styles.ts + var styles$70 = i$5` :host { display: block; } @@ -592,48 +681,79 @@ font-size: 0.875rem; } `; - const DOMAIN = "hass_datapoints"; - const PANEL_URL_PATH = "hass-datapoints-history"; - const COLORS = [ - "#3b82f6", - "#ef4444", - "#10b981", - "#f59e0b", - "#8b5cf6", - "#ec4899" - ]; - const AMBER = "#ff9800"; - const o = { attribute: true, type: String, converter: u$3, reflect: false, hasChanged: f$1 }, r$1 = (t2 = o, e2, r2) => { - const { kind: n2, metadata: i2 } = r2; - let s2 = globalThis.litPropertyMetadata.get(i2); - if (void 0 === s2 && globalThis.litPropertyMetadata.set(i2, s2 = /* @__PURE__ */ new Map()), "setter" === n2 && ((t2 = Object.create(t2)).wrapped = true), s2.set(r2.name, t2), "accessor" === n2) { - const { name: o2 } = r2; - return { set(r3) { - const n3 = e2.get.call(this); - e2.set.call(this, r3), this.requestUpdate(o2, n3, t2, true, r3); - }, init(e3) { - return void 0 !== e3 && this.C(o2, void 0, t2, e3), e3; - } }; - } - if ("setter" === n2) { - const { name: o2 } = r2; - return function(r3) { - const n3 = this[o2]; - e2.call(this, r3), this.requestUpdate(o2, n3, t2, true, r3); - }; - } - throw Error("Unsupported decorator location: " + n2); - }; - function n(t2) { - return (e2, o2) => "object" == typeof o2 ? r$1(t2, e2, o2) : ((t3, e3, o3) => { - const r2 = e3.hasOwnProperty(o3); - return e3.constructor.createProperty(o3, t3), r2 ? Object.getOwnPropertyDescriptor(e3, o3) : void 0; - })(t2, e2, o2); - } - function r(r2) { - return n({ ...r2, state: true, attribute: false }); - } - const styles$15 = i$5` + //#endregion + //#region custom_components/hass_datapoints/src/constants.ts + /** + * Shared constants used by all Hass Records cards. + */ + var DOMAIN = "hass_datapoints"; + var PANEL_URL_PATH = "hass-datapoints-history"; + var COLORS = [ + "#3b82f6", + "#ef4444", + "#10b981", + "#f59e0b", + "#8b5cf6", + "#ec4899" + ]; + //#endregion + //#region node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/property.js + /** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ var o = { + attribute: !0, + type: String, + converter: u$3, + reflect: !1, + hasChanged: f$2 + }, r$2 = (t = o, e, r) => { + const { kind: n, metadata: i } = r; + let s = globalThis.litPropertyMetadata.get(i); + if (void 0 === s && globalThis.litPropertyMetadata.set(i, s = /* @__PURE__ */ new Map()), "setter" === n && ((t = Object.create(t)).wrapped = !0), s.set(r.name, t), "accessor" === n) { + const { name: o } = r; + return { + set(r) { + const n = e.get.call(this); + e.set.call(this, r), this.requestUpdate(o, n, t, !0, r); + }, + init(e) { + return void 0 !== e && this.C(o, void 0, t, e), e; + } + }; + } + if ("setter" === n) { + const { name: o } = r; + return function(r) { + const n = this[o]; + e.call(this, r), this.requestUpdate(o, n, t, !0, r); + }; + } + throw Error("Unsupported decorator location: " + n); + }; + function n$1(t) { + return (e, o) => "object" == typeof o ? r$2(t, e, o) : ((t, e, o) => { + const r = e.hasOwnProperty(o); + return e.constructor.createProperty(o, t), r ? Object.getOwnPropertyDescriptor(e, o) : void 0; + })(t, e, o); + } + //#endregion + //#region node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/state.js + /** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ function r$1(r) { + return n$1({ + ...r, + state: !0, + attribute: !1 + }); + } + //#endregion + //#region custom_components/hass_datapoints/src/atoms/display/color-swatch/color-swatch.styles.ts + var styles$69 = i$5` :host { display: block; } @@ -678,70 +798,59 @@ pointer-events: none; } `; - var __create$V = Object.create; - var __defProp$1b = Object.defineProperty; - var __getOwnPropDesc$V = Object.getOwnPropertyDescriptor; - var __knownSymbol$V = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$V = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$1b = (obj, key, value) => key in obj ? __defProp$1b(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$V = (base) => [, , , __create$V(base?.[__knownSymbol$V("metadata")] ?? null)]; - var __decoratorStrings$V = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$V = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$V("Function expected") : fn; - var __decoratorContext$V = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$V[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$V("Already initialized") : fns.push(__expectFn$V(fn || null)) }); - var __decoratorMetadata$V = (array, target) => __defNormalProp$1b(target, __knownSymbol$V("metadata"), array[3]); - var __runInitializers$V = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$V = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$V[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$V({ get [name]() { - return __privateGet$U(this, extra); - }, set [name](x2) { - return __privateSet$U(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$V(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$V(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$V("Object expected"); - else __expectFn$V(fn = it.get) && (desc.get = fn), __expectFn$V(fn = it.set) && (desc.set = fn), __expectFn$V(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$1b(target, name, desc), target; - }; - var __publicField$1b = (obj, key, value) => __defNormalProp$1b(obj, key + "", value); - var __accessCheck$U = (obj, member, msg2) => member.has(obj) || __typeError$V("Cannot " + msg2); - var __privateGet$U = (obj, member, getter) => (__accessCheck$U(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$U = (obj, member, value) => member.has(obj) ? __typeError$V("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$U = (obj, member, value, setter) => (__accessCheck$U(obj, member, "write to private field"), member.set(obj, value), value); - var _label_dec$e, _color_dec$2, _a$V, _init$V, _color$2, _label$e; - class ColorSwatch extends (_a$V = i$2, _color_dec$2 = [n({ type: String })], _label_dec$e = [n({ type: String })], _a$V) { - constructor() { - super(...arguments); - __privateAdd$U(this, _color$2, __runInitializers$V(_init$V, 8, this, "#ff9800")), __runInitializers$V(_init$V, 11, this); - __privateAdd$U(this, _label$e, __runInitializers$V(_init$V, 12, this, "")), __runInitializers$V(_init$V, 15, this); - } - _onInput(e2) { - const newColor = e2.target.value; - this.dispatchEvent( - new CustomEvent("dp-color-change", { - detail: { color: newColor }, - bubbles: true, - composed: true - }) - ); - } - render() { - return b` + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/typeof.js + function _typeof(o) { + "@babel/helpers - typeof"; + return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) { + return typeof o; + } : function(o) { + return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; + }, _typeof(o); + } + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/toPrimitive.js + function toPrimitive(t, r) { + if ("object" != _typeof(t) || !t) return t; + var e = t[Symbol.toPrimitive]; + if (void 0 !== e) { + var i = e.call(t, r || "default"); + if ("object" != _typeof(i)) return i; + throw new TypeError("@@toPrimitive must return a primitive value."); + } + return ("string" === r ? String : Number)(t); + } + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/toPropertyKey.js + function toPropertyKey(t) { + var i = toPrimitive(t, "string"); + return "symbol" == _typeof(i) ? i : i + ""; + } + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/defineProperty.js + function _defineProperty(e, r, t) { + return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, { + value: t, + enumerable: !0, + configurable: !0, + writable: !0 + }) : e[r] = t, e; + } + //#endregion + //#region custom_components/hass_datapoints/src/atoms/display/color-swatch/color-swatch.ts + var ColorSwatch = class extends i$2 { + @n$1({ type: String }) accessor color = "#ff9800"; + @n$1({ type: String }) accessor label = ""; + _onInput(e) { + const newColor = e.target.value; + this.dispatchEvent(new CustomEvent("dp-color-change", { + detail: { color: newColor }, + bubbles: true, + composed: true + })); + } + render() { + return b`
${this.label ? b`${this.label}` : ""}
`; - } - } - _init$V = __decoratorStart$V(_a$V); - _color$2 = /* @__PURE__ */ new WeakMap(); - _label$e = /* @__PURE__ */ new WeakMap(); - __decorateElement$V(_init$V, 4, "color", _color_dec$2, ColorSwatch, _color$2); - __decorateElement$V(_init$V, 4, "label", _label_dec$e, ColorSwatch, _label$e); - __decoratorMetadata$V(_init$V, ColorSwatch); - __publicField$1b(ColorSwatch, "styles", styles$15); - customElements.define("color-swatch", ColorSwatch); - const styles$14 = i$5` + } + }; + _defineProperty(ColorSwatch, "styles", styles$69); + customElements.define("color-swatch", ColorSwatch); + //#endregion + //#region custom_components/hass_datapoints/src/atoms/display/feedback-banner/feedback-banner.styles.ts + var styles$68 = i$5` :host { display: block; } @@ -790,65 +895,16 @@ color: var(--error-color, #f44336); } `; - var __create$U = Object.create; - var __defProp$1a = Object.defineProperty; - var __getOwnPropDesc$U = Object.getOwnPropertyDescriptor; - var __knownSymbol$U = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$U = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$1a = (obj, key, value) => key in obj ? __defProp$1a(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$U = (base) => [, , , __create$U(base?.[__knownSymbol$U("metadata")] ?? null)]; - var __decoratorStrings$U = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$U = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$U("Function expected") : fn; - var __decoratorContext$U = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$U[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$U("Already initialized") : fns.push(__expectFn$U(fn || null)) }); - var __decoratorMetadata$U = (array, target) => __defNormalProp$1a(target, __knownSymbol$U("metadata"), array[3]); - var __runInitializers$U = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$U = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$U[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$U({ get [name]() { - return __privateGet$T(this, extra); - }, set [name](x2) { - return __privateSet$T(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$U(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$U(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$U("Object expected"); - else __expectFn$U(fn = it.get) && (desc.get = fn), __expectFn$U(fn = it.set) && (desc.set = fn), __expectFn$U(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$1a(target, name, desc), target; - }; - var __publicField$1a = (obj, key, value) => __defNormalProp$1a(obj, key + "", value); - var __accessCheck$T = (obj, member, msg2) => member.has(obj) || __typeError$U("Cannot " + msg2); - var __privateGet$T = (obj, member, getter) => (__accessCheck$T(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$T = (obj, member, value) => member.has(obj) ? __typeError$U("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$T = (obj, member, value, setter) => (__accessCheck$T(obj, member, "write to private field"), member.set(obj, value), value); - var _variant_dec, _visible_dec$1, _text_dec$1, _kind_dec, _a$U, _init$U, _kind, _text$1, _visible$1, _variant; - class FeedbackBanner extends (_a$U = i$2, _kind_dec = [n({ type: String })], _text_dec$1 = [n({ type: String })], _visible_dec$1 = [n({ type: Boolean })], _variant_dec = [n({ type: String })], _a$U) { - constructor() { - super(...arguments); - __privateAdd$T(this, _kind, __runInitializers$U(_init$U, 8, this, "")), __runInitializers$U(_init$U, 11, this); - __privateAdd$T(this, _text$1, __runInitializers$U(_init$U, 12, this, "")), __runInitializers$U(_init$U, 15, this); - __privateAdd$T(this, _visible$1, __runInitializers$U(_init$U, 16, this, false)), __runInitializers$U(_init$U, 19, this); - __privateAdd$T(this, _variant, __runInitializers$U(_init$U, 20, this, "default")), __runInitializers$U(_init$U, 23, this); - } - render() { - if (!this.text) { - return A; - } - return b` + //#endregion + //#region custom_components/hass_datapoints/src/atoms/display/feedback-banner/feedback-banner.ts + var FeedbackBanner = class extends i$2 { + @n$1({ type: String }) accessor kind = ""; + @n$1({ type: String }) accessor text = ""; + @n$1({ type: Boolean }) accessor visible = false; + @n$1({ type: String }) accessor variant = "default"; + render() { + if (!this.text) return A; + return b` `; - const entityPicker = this.shadowRoot.getElementById( - "entity-picker" - ); - if (entityPicker) { - entityPicker.selector = { entity: { multiple: true } }; - entityPicker.value = []; - this._entities = []; - this._suppressEntityChange = false; - entityPicker.addEventListener("value-changed", (event) => { - if (this._suppressEntityChange) { - return; - } - const value = event.detail.value; - if (Array.isArray(value)) { - this._entities = value; - } else if (value) { - this._entities = [value]; - } else { - this._entities = []; - } - }); - } - this.shadowRoot.getElementById("analyze-btn").addEventListener( - "click", - () => { - this._analyzeHistory(); - } - ); - this.shadowRoot.getElementById("delete-dev-btn").addEventListener( - "click", - () => { - this._deleteAllDev(); - } - ); - this.shadowRoot.getElementById("results-container").addEventListener( - "dp-record-selected-request", - (event) => { - const detail = event.detail; - this._recordSelected(detail.items); - } - ); - } - _readWindowConfigs() { - const windowsEditor = this.shadowRoot.getElementById( - "windows-editor" - ); - return windowsEditor.getWindowConfigs().map((windowConfig, index) => ({ - ...windowConfig, - label: windowConfig.label.trim() || `Window ${index + 1}` - })); - } - async _analyzeHistory() { - if (!this._entities.length) { - this._showFeedback( - "analyze-status", - "err", - "Please select at least one entity." - ); - return; - } - const windowConfigs = this._readWindowConfigs(); - const button = this.shadowRoot.getElementById( - "analyze-btn" - ); - button.disabled = true; - this._results = []; - this._showFeedback( - "analyze-status", - "ok", - `Fetching history for ${windowConfigs.length} window${windowConfigs.length === 1 ? "" : "s"}…` - ); - try { - const now = /* @__PURE__ */ new Date(); - this._results = await Promise.all( - windowConfigs.map(async (windowConfig) => { - const start = windowConfig.startDt ? new Date(windowConfig.startDt) : now; - const end = windowConfig.endDt ? new Date(windowConfig.endDt) : now; - const raw = await this._hass.connection.sendMessagePromise({ - type: "history/history_during_period", - start_time: start.toISOString(), - end_time: end.toISOString(), - entity_ids: this._entities, - include_start_time_state: false, - significant_changes_only: false, - no_attributes: false - }); - const changes = this._detectChanges( - raw || {} - ); - return { - id: windowConfig.id, - label: windowConfig.label, - startDt: windowConfig.startDt, - endDt: windowConfig.endDt, - changes, - selected: changes.map((_2, index) => index) - }; - }) - ); - this._renderResults(); - this._hideFeedback("analyze-status"); - } catch (err) { - this._showFeedback( - "analyze-status", - "err", - `Error: ${err.message || "Failed to fetch history"}` - ); - logger$1.error("[hass-datapoints dev-tool]", err); - } - button.disabled = false; - } - _detectChanges(histResult) { - const changes = []; - for (const [entityId, statesRaw] of Object.entries(histResult)) { - const states = statesRaw; - if (!states?.length) { - continue; - } - const domain = entityId.split(".")[0]; - const entityState = this._hass?.states?.[entityId]; - const deviceClass = entityState?.attributes?.device_class || ""; - const friendlyName = entityState?.attributes?.friendly_name || entityId; - const unit = entityState?.attributes?.unit_of_measurement || ""; - for (let i2 = 0; i2 < states.length; i2 += 1) { - const state = states[i2]; - const previous = i2 > 0 ? states[i2 - 1] : null; - const currentValue = state.s; - const previousValue = previous?.s ?? null; - if (currentValue === "unavailable" || currentValue === "unknown") { - continue; - } - if (previous && previousValue === currentValue) { - if (domain !== "climate") { - continue; - } - } - const timestampRaw = state.lc ?? state.lu; - const timestamp = timestampRaw != null ? new Date(timestampRaw * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString(); - let message = null; - let icon = "mdi:bookmark"; - let color = "#03a9f4"; - if (domain === "binary_sensor" || domain === "input_boolean") { - message = `${friendlyName}: ${this._binaryLabel(deviceClass, currentValue)}`; - icon = currentValue === "on" ? "mdi:toggle-switch" : "mdi:toggle-switch-off"; - color = currentValue === "on" ? "#4caf50" : "#9e9e9e"; - } else if (domain === "switch") { - message = `${friendlyName}: turned ${currentValue === "on" ? "on" : "off"}`; - icon = currentValue === "on" ? "mdi:power-plug" : "mdi:power-plug-off"; - color = currentValue === "on" ? "#ff9800" : "#9e9e9e"; - } else if (domain === "light") { - message = `${friendlyName}: ${currentValue === "on" ? "on" : "off"}`; - icon = currentValue === "on" ? "mdi:lightbulb" : "mdi:lightbulb-off"; - color = currentValue === "on" ? "#ffee58" : "#9e9e9e"; - } else if (domain === "cover") { - const labels = { - open: "opened", - closed: "closed", - opening: "opening", - closing: "closing" - }; - if (!labels[currentValue]) { - continue; - } - message = `${friendlyName}: ${labels[currentValue]}`; - icon = currentValue === "open" || currentValue === "opening" ? "mdi:garage-open" : "mdi:garage"; - color = currentValue === "open" ? "#4caf50" : "#795548"; - } else if (domain === "climate") { - const stateAttributes = state.a; - const previousAttributes = previous?.a; - const currentTemperature = stateAttributes?.temperature; - const previousTemperature = previousAttributes?.temperature; - if (currentTemperature != null && currentTemperature !== previousTemperature) { - const temperatureUnit = stateAttributes?.temperature_unit || unit || "°"; - message = `${friendlyName}: setpoint → ${currentTemperature}${temperatureUnit}`; - icon = "mdi:thermostat"; - color = "#ff5722"; - } else if (!previous || previousValue !== currentValue) { - const modes = { - heat: "heating", - cool: "cooling", - auto: "auto", - off: "off", - heat_cool: "heat/cool", - fan_only: "fan only", - dry: "dry" - }; - message = `${friendlyName}: mode → ${modes[currentValue] || currentValue}`; - icon = "mdi:thermostat"; - color = "#ff5722"; - } else { - continue; - } - } else if (domain === "sensor") { - const currentNumber = parseFloat(currentValue); - const previousNumber = previousValue != null ? parseFloat(previousValue) : Number.NaN; - if (Number.isNaN(currentNumber)) { - continue; - } - if (!Number.isNaN(previousNumber) && Math.abs(currentNumber - previousNumber) < 0.5) { - continue; - } - message = `${friendlyName}: ${currentValue}${unit}`; - icon = "mdi:gauge"; - color = "#2196f3"; - } else if (domain === "input_number" || domain === "number") { - const currentNumber = parseFloat(currentValue); - const previousNumber = previousValue != null ? parseFloat(previousValue) : Number.NaN; - if (Number.isNaN(currentNumber)) { - continue; - } - if (!Number.isNaN(previousNumber) && currentNumber === previousNumber) { - continue; - } - message = `${friendlyName}: → ${currentValue}${unit}`; - icon = "mdi:numeric"; - color = "#9c27b0"; - } else if (domain === "input_select" || domain === "select") { - if (!previous || previousValue === currentValue) { - continue; - } - message = `${friendlyName}: → ${currentValue}`; - icon = "mdi:form-select"; - color = "#009688"; - } else { - if (!previous || previousValue === currentValue) { - continue; - } - message = `${friendlyName}: ${previousValue} → ${currentValue}`; - icon = "mdi:swap-horizontal"; - color = "#607d8b"; - } - if (!message) { - continue; - } - changes.push({ - timestamp, - message, - entity_id: entityId, - icon, - color - }); - } - } - changes.sort((a2, b2) => a2.timestamp < b2.timestamp ? -1 : 1); - return changes; - } - _binaryLabel(deviceClass, state) { - const on = state === "on"; - const map = { - door: ["opened", "closed"], - window: ["opened", "closed"], - garage_door: ["opened", "closed"], - opening: ["opened", "closed"], - lock: ["locked", "unlocked"], - motion: ["motion detected", "motion cleared"], - occupancy: ["occupied", "vacant"], - presence: ["home", "away"], - vibration: ["vibrating", "still"], - plug: ["plugged in", "unplugged"], - outlet: ["on", "off"], - smoke: ["smoke detected", "smoke cleared"], - moisture: ["wet", "dry"], - running: ["running", "stopped"], - connectivity: ["connected", "disconnected"], - power: ["on", "off"], - battery_charging: ["charging", "not charging"], - battery: ["low battery", "battery normal"], - cold: ["cold", "temperature normal"], - heat: ["heat", "temperature normal"], - light: ["light detected", "dark"], - sound: ["sound detected", "quiet"] - }; - const pair = map[deviceClass]; - if (pair) { - return on ? pair[0] : pair[1]; - } - return on ? "on" : "off"; - } - _renderResults() { - const resultsContainer = this.shadowRoot.getElementById( - "results-container" - ); - resultsContainer.results = [...this._results]; - resultsContainer.statusKind = ""; - resultsContainer.statusText = ""; - resultsContainer.statusVisible = false; - } - async _recordSelected(items) { - if (!items.length) { - this._showResultsStatus("err", "No items selected."); - return; - } - this._showResultsStatus( - "ok", - `Recording ${items.length} data point${items.length === 1 ? "" : "s"}…` - ); - const results = await Promise.allSettled( - items.map( - (item) => this._hass.callService(DOMAIN, "record", { - message: item.message, - entity_ids: [item.entity_id], - icon: item.icon, - color: item.color, - date: item.timestamp, - dev: true - }) - ) - ); - const ok = results.filter((result) => result.status === "fulfilled").length; - const fail = results.filter( - (result) => result.status === "rejected" - ).length; - if (fail) { - this._showResultsStatus("err", `Recorded ${ok}, failed ${fail}.`); - } else { - this._showResultsStatus( - "ok", - `Recorded ${ok} dev data point${ok === 1 ? "" : "s"}!` - ); - } - await this._refreshDevCount(); - window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); - } - async _deleteAllDev() { - const devCountEl = this.shadowRoot.getElementById("dev-count"); - const count = parseInt(devCountEl?.textContent ?? "0", 10) || 0; - if (count === 0) { - this._showFeedback( - "delete-status", - "err", - "No dev datapoints to delete." - ); - return; - } - const confirmed = await confirmDestructiveAction(this, { - title: "Delete dev datapoints", - message: `Delete all ${count} dev data point${count === 1 ? "" : "s"}?`, - confirmLabel: "Delete all" - }); - if (!confirmed) { - return; - } - const button = this.shadowRoot.getElementById( - "delete-dev-btn" - ); - button.disabled = true; - try { - const result = await this._hass.connection.sendMessagePromise({ - type: `${DOMAIN}/events/delete_dev` - }); - const deleted = result.deleted; - this._showFeedback( - "delete-status", - "ok", - `Deleted ${deleted} dev data point${deleted === 1 ? "" : "s"}.` - ); - await this._refreshDevCount(); - window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); - } catch (err) { - this._showFeedback( - "delete-status", - "err", - `Error: ${err.message || "failed"}` - ); - } - button.disabled = false; - } - async _refreshDevCount() { - try { - const result = await this._hass.connection.sendMessagePromise({ - type: `${DOMAIN}/events` - }); - const events = result.events || []; - const count = events.filter((event) => event.dev).length; - const countEl = this.shadowRoot.getElementById("dev-count"); - const pluralEl = this.shadowRoot.getElementById("dev-count-plural"); - if (countEl) { - countEl.textContent = String(count); - } - if (pluralEl) { - pluralEl.textContent = count === 1 ? "" : "s"; - } - } catch (error) { - logger$1.warn("[hass-datapoints dev-tool] refresh dev count failed", error); - } - } - _showFeedback(id, kind, text) { - const el = this.shadowRoot.getElementById(id); - if (!el) { - return; - } - el.kind = kind; - el.text = text; - el.visible = true; - } - _hideFeedback(id) { - const el = this.shadowRoot.getElementById(id); - if (!el) { - return; - } - el.visible = false; - } - _showResultsStatus(kind, text) { - const el = this.shadowRoot.getElementById( - "results-container" - ); - el.statusKind = kind; - el.statusText = text; - el.statusVisible = true; - } - static getStubConfig() { - return { title: "Dev Tool" }; - } - static getConfigElement() { - return document.createElement("hass-datapoints-dev-tool-card-editor"); - } - } - var __defProp$X = Object.defineProperty; - var __defNormalProp$X = (obj, key, value) => key in obj ? __defProp$X(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$X = (obj, key, value) => __defNormalProp$X(obj, typeof key !== "symbol" ? key + "" : key, value); - class ChartCardBase extends i$2 { - constructor() { - super(...arguments); - __publicField$X(this, "_hass"); - __publicField$X(this, "_config", {}); - __publicField$X(this, "_loadRequestId", 0); - __publicField$X(this, "_lastDrawArgs", []); - __publicField$X(this, "_previousSeriesEndpoints", /* @__PURE__ */ new Map()); - __publicField$X(this, "_unsubscribe", null); - __publicField$X(this, "_resizeObserver", null); - __publicField$X(this, "_loadRaf", null); - __publicField$X(this, "_loadInFlight", false); - __publicField$X(this, "_hasStartedInitialLoad", false); - __publicField$X(this, "_windowListener", null); - __publicField$X(this, "_initialized", false); - } - // ── Lovelace API ────────────────────────────────────────────────────────── - setConfig(config) { - this._config = config ?? {}; - this.requestUpdate(); - } - set hass(hass) { - this._hass = hass; - this.requestUpdate(); - if (this._hasStartedInitialLoad) { - this._scheduleLoad(); - } - } - get hass() { - return this._hass; - } - // ── Lifecycle ───────────────────────────────────────────────────────────── - connectedCallback() { - super.connectedCallback(); - if (this._initialized) { - if (!this._unsubscribe || !this._windowListener) { - this._setupAutoRefresh(); - } - this._setupResizeObserver(); - } - if (this._hass) { - this._scheduleLoad(); - } - } - updated() { - if (!this._initialized && this._hass) { - this._initialized = true; - this._setupAutoRefresh(); - this._setupResizeObserver(); - this._scheduleLoad(); - } - } - disconnectedCallback() { - super.disconnectedCallback(); - this._cleanup(); - } - // ── Scheduling ──────────────────────────────────────────────────────────── - _scheduleLoad() { - if (!this._hass || this._loadRaf !== null || this._loadInFlight) return; - this._loadRaf = window.requestAnimationFrame(() => { - this._loadRaf = null; - if (!this._hass || !this.isConnected || this._loadInFlight) return; - this._hasStartedInitialLoad = true; - this._loadInFlight = true; - Promise.resolve(this._load()).catch((err) => { - logger$1.error("[hass-datapoints chart-base] load failed", err); - }).finally(() => { - this._loadInFlight = false; - }); - }); - } - // ── Setup helpers ───────────────────────────────────────────────────────── - _setupAutoRefresh() { - if (!this._hass) return; - if (this._unsubscribe || this._windowListener) return; - this._hass.connection.subscribeEvents(() => { - this._scheduleLoad(); - }, `${DOMAIN}_event_recorded`).then((unsub) => { - this._unsubscribe = unsub; - }).catch(() => { - }); - this._windowListener = () => { - this._scheduleLoad(); - }; - window.addEventListener( - "hass-datapoints-event-recorded", - this._windowListener - ); - } - _setupResizeObserver() { - if (this._resizeObserver) return; - const wrap = this.shadowRoot?.querySelector(".chart-wrap") ?? this.shadowRoot?.querySelector("hass-datapoints-history-chart"); - if (!wrap || !window.ResizeObserver) return; - this._resizeObserver = new ResizeObserver(() => { - if (this._lastDrawArgs.length) { - this._drawChart(...this._lastDrawArgs); - } - }); - this._resizeObserver.observe(wrap); - } - // ── Cleanup ─────────────────────────────────────────────────────────────── - _cleanup() { - if (this._loadRaf !== null) { - window.cancelAnimationFrame(this._loadRaf); - this._loadRaf = null; - } - if (this._unsubscribe) { - this._unsubscribe(); - this._unsubscribe = null; - } - if (this._windowListener) { - window.removeEventListener( - "hass-datapoints-event-recorded", - this._windowListener - ); - this._windowListener = null; - } - if (this._resizeObserver) { - this._resizeObserver.disconnect(); - this._resizeObserver = null; - } - } - // ── Static API ──────────────────────────────────────────────────────────── - static getStubConfig() { - return { title: "" }; - } - } - const styles$T = i$5` + const entityPicker = this.shadowRoot.getElementById("entity-picker"); + if (entityPicker) { + entityPicker.selector = { entity: { multiple: true } }; + entityPicker.value = []; + this._entities = []; + this._suppressEntityChange = false; + entityPicker.addEventListener("value-changed", (event) => { + if (this._suppressEntityChange) return; + const value = event.detail.value; + if (Array.isArray(value)) this._entities = value; + else if (value) this._entities = [value]; + else this._entities = []; + }); + } + this.shadowRoot.getElementById("analyze-btn").addEventListener("click", () => { + this._analyzeHistory(); + }); + this.shadowRoot.getElementById("delete-dev-btn").addEventListener("click", () => { + this._deleteAllDev(); + }); + this.shadowRoot.getElementById("results-container").addEventListener("dp-record-selected-request", (event) => { + const detail = event.detail; + this._recordSelected(detail.items); + }); + } + _readWindowConfigs() { + return this.shadowRoot.getElementById("windows-editor").getWindowConfigs().map((windowConfig, index) => ({ + ...windowConfig, + label: windowConfig.label.trim() || `Window ${index + 1}` + })); + } + async _analyzeHistory() { + if (!this._entities.length) { + this._showFeedback("analyze-status", "err", "Please select at least one entity."); + return; + } + const windowConfigs = this._readWindowConfigs(); + const button = this.shadowRoot.getElementById("analyze-btn"); + button.disabled = true; + this._results = []; + this._showFeedback("analyze-status", "ok", `Fetching history for ${windowConfigs.length} window${windowConfigs.length === 1 ? "" : "s"}…`); + try { + const now = /* @__PURE__ */ new Date(); + this._results = await Promise.all(windowConfigs.map(async (windowConfig) => { + const start = windowConfig.startDt ? new Date(windowConfig.startDt) : now; + const end = windowConfig.endDt ? new Date(windowConfig.endDt) : now; + const raw = await this._hass.connection.sendMessagePromise({ + type: "history/history_during_period", + start_time: start.toISOString(), + end_time: end.toISOString(), + entity_ids: this._entities, + include_start_time_state: false, + significant_changes_only: false, + no_attributes: false + }); + const changes = this._detectChanges(raw || {}); + return { + id: windowConfig.id, + label: windowConfig.label, + startDt: windowConfig.startDt, + endDt: windowConfig.endDt, + changes, + selected: changes.map((_, index) => index) + }; + })); + this._renderResults(); + this._hideFeedback("analyze-status"); + } catch (err) { + this._showFeedback("analyze-status", "err", `Error: ${err.message || "Failed to fetch history"}`); + logger$1.error("[hass-datapoints dev-tool]", err); + } + button.disabled = false; + } + _detectChanges(histResult) { + const changes = []; + for (const [entityId, statesRaw] of Object.entries(histResult)) { + const states = statesRaw; + if (!states?.length) continue; + const domain = entityId.split(".")[0]; + const entityState = this._hass?.states?.[entityId]; + const deviceClass = entityState?.attributes?.device_class || ""; + const friendlyName = entityState?.attributes?.friendly_name || entityId; + const unit = entityState?.attributes?.unit_of_measurement || ""; + for (let i = 0; i < states.length; i += 1) { + const state = states[i]; + const previous = i > 0 ? states[i - 1] : null; + const currentValue = state.s; + const previousValue = previous?.s ?? null; + if (currentValue === "unavailable" || currentValue === "unknown") continue; + if (previous && previousValue === currentValue) { + if (domain !== "climate") continue; + } + const timestampRaw = state.lc ?? state.lu; + const timestamp = timestampRaw != null ? (/* @__PURE__ */ new Date(timestampRaw * 1e3)).toISOString() : (/* @__PURE__ */ new Date()).toISOString(); + let message; + let icon; + let color; + if (domain === "binary_sensor" || domain === "input_boolean") { + message = `${friendlyName}: ${this._binaryLabel(deviceClass, currentValue)}`; + icon = currentValue === "on" ? "mdi:toggle-switch" : "mdi:toggle-switch-off"; + color = currentValue === "on" ? "#4caf50" : "#9e9e9e"; + } else if (domain === "switch") { + message = `${friendlyName}: turned ${currentValue === "on" ? "on" : "off"}`; + icon = currentValue === "on" ? "mdi:power-plug" : "mdi:power-plug-off"; + color = currentValue === "on" ? "#ff9800" : "#9e9e9e"; + } else if (domain === "light") { + message = `${friendlyName}: ${currentValue === "on" ? "on" : "off"}`; + icon = currentValue === "on" ? "mdi:lightbulb" : "mdi:lightbulb-off"; + color = currentValue === "on" ? "#ffee58" : "#9e9e9e"; + } else if (domain === "cover") { + const labels = { + open: "opened", + closed: "closed", + opening: "opening", + closing: "closing" + }; + if (!labels[currentValue]) continue; + message = `${friendlyName}: ${labels[currentValue]}`; + icon = currentValue === "open" || currentValue === "opening" ? "mdi:garage-open" : "mdi:garage"; + color = currentValue === "open" ? "#4caf50" : "#795548"; + } else if (domain === "climate") { + const stateAttributes = state.a; + const previousAttributes = previous?.a; + const currentTemperature = stateAttributes?.temperature; + const previousTemperature = previousAttributes?.temperature; + if (currentTemperature != null && currentTemperature !== previousTemperature) { + message = `${friendlyName}: setpoint → ${currentTemperature}${stateAttributes?.temperature_unit || unit || "°"}`; + icon = "mdi:thermostat"; + color = "#ff5722"; + } else if (!previous || previousValue !== currentValue) { + message = `${friendlyName}: mode → ${{ + heat: "heating", + cool: "cooling", + auto: "auto", + off: "off", + heat_cool: "heat/cool", + fan_only: "fan only", + dry: "dry" + }[currentValue] || currentValue}`; + icon = "mdi:thermostat"; + color = "#ff5722"; + } else continue; + } else if (domain === "sensor") { + const currentNumber = parseFloat(currentValue); + const previousNumber = previousValue != null ? parseFloat(previousValue) : NaN; + if (Number.isNaN(currentNumber)) continue; + if (!Number.isNaN(previousNumber) && Math.abs(currentNumber - previousNumber) < .5) continue; + message = `${friendlyName}: ${currentValue}${unit}`; + icon = "mdi:gauge"; + color = "#2196f3"; + } else if (domain === "input_number" || domain === "number") { + const currentNumber = parseFloat(currentValue); + const previousNumber = previousValue != null ? parseFloat(previousValue) : NaN; + if (Number.isNaN(currentNumber)) continue; + if (!Number.isNaN(previousNumber) && currentNumber === previousNumber) continue; + message = `${friendlyName}: → ${currentValue}${unit}`; + icon = "mdi:numeric"; + color = "#9c27b0"; + } else if (domain === "input_select" || domain === "select") { + if (!previous || previousValue === currentValue) continue; + message = `${friendlyName}: → ${currentValue}`; + icon = "mdi:form-select"; + color = "#009688"; + } else { + if (!previous || previousValue === currentValue) continue; + message = `${friendlyName}: ${previousValue} → ${currentValue}`; + icon = "mdi:swap-horizontal"; + color = "#607d8b"; + } + if (!message) continue; + changes.push({ + timestamp, + message, + entity_id: entityId, + icon, + color + }); + } + } + changes.sort((a, b) => a.timestamp < b.timestamp ? -1 : 1); + return changes; + } + _binaryLabel(deviceClass, state) { + const on = state === "on"; + const pair = { + door: ["opened", "closed"], + window: ["opened", "closed"], + garage_door: ["opened", "closed"], + opening: ["opened", "closed"], + lock: ["locked", "unlocked"], + motion: ["motion detected", "motion cleared"], + occupancy: ["occupied", "vacant"], + presence: ["home", "away"], + vibration: ["vibrating", "still"], + plug: ["plugged in", "unplugged"], + outlet: ["on", "off"], + smoke: ["smoke detected", "smoke cleared"], + moisture: ["wet", "dry"], + running: ["running", "stopped"], + connectivity: ["connected", "disconnected"], + power: ["on", "off"], + battery_charging: ["charging", "not charging"], + battery: ["low battery", "battery normal"], + cold: ["cold", "temperature normal"], + heat: ["heat", "temperature normal"], + light: ["light detected", "dark"], + sound: ["sound detected", "quiet"] + }[deviceClass]; + if (pair) return on ? pair[0] : pair[1]; + return on ? "on" : "off"; + } + _renderResults() { + const resultsContainer = this.shadowRoot.getElementById("results-container"); + resultsContainer.results = [...this._results]; + resultsContainer.statusKind = ""; + resultsContainer.statusText = ""; + resultsContainer.statusVisible = false; + } + async _recordSelected(items) { + if (!items.length) { + this._showResultsStatus("err", "No items selected."); + return; + } + this._showResultsStatus("ok", `Recording ${items.length} data point${items.length === 1 ? "" : "s"}…`); + const results = await Promise.allSettled(items.map((item) => this._hass.callService(DOMAIN, "record", { + message: item.message, + entity_ids: [item.entity_id], + icon: item.icon, + color: item.color, + date: item.timestamp, + dev: true + }))); + const ok = results.filter((result) => result.status === "fulfilled").length; + const fail = results.filter((result) => result.status === "rejected").length; + if (fail) this._showResultsStatus("err", `Recorded ${ok}, failed ${fail}.`); + else this._showResultsStatus("ok", `Recorded ${ok} dev data point${ok === 1 ? "" : "s"}!`); + await this._refreshDevCount(); + window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); + } + async _deleteAllDev() { + const devCountEl = this.shadowRoot.getElementById("dev-count"); + const count = parseInt(devCountEl?.textContent ?? "0", 10) || 0; + if (count === 0) { + this._showFeedback("delete-status", "err", "No dev datapoints to delete."); + return; + } + if (!await confirmDestructiveAction(this, { + title: "Delete dev datapoints", + message: `Delete all ${count} dev data point${count === 1 ? "" : "s"}?`, + confirmLabel: "Delete all" + })) return; + const button = this.shadowRoot.getElementById("delete-dev-btn"); + button.disabled = true; + try { + const deleted = (await this._hass.connection.sendMessagePromise({ type: `${DOMAIN}/events/delete_dev` })).deleted; + this._showFeedback("delete-status", "ok", `Deleted ${deleted} dev data point${deleted === 1 ? "" : "s"}.`); + await this._refreshDevCount(); + window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); + } catch (err) { + this._showFeedback("delete-status", "err", `Error: ${err.message || "failed"}`); + } + button.disabled = false; + } + async _refreshDevCount() { + try { + const count = ((await this._hass.connection.sendMessagePromise({ type: `hass_datapoints/events` })).events || []).filter((event) => event.dev).length; + const countEl = this.shadowRoot.getElementById("dev-count"); + const pluralEl = this.shadowRoot.getElementById("dev-count-plural"); + if (countEl) countEl.textContent = String(count); + if (pluralEl) pluralEl.textContent = count === 1 ? "" : "s"; + } catch (error) { + logger$1.warn("[hass-datapoints dev-tool] refresh dev count failed", error); + } + } + _showFeedback(id, kind, text) { + const el = this.shadowRoot.getElementById(id); + if (!el) return; + el.kind = kind; + el.text = text; + el.visible = true; + } + _hideFeedback(id) { + const el = this.shadowRoot.getElementById(id); + if (!el) return; + el.visible = false; + } + _showResultsStatus(kind, text) { + const el = this.shadowRoot.getElementById("results-container"); + el.statusKind = kind; + el.statusText = text; + el.statusVisible = true; + } + static getStubConfig() { + return { title: "Dev Tool" }; + } + static getConfigElement() { + return document.createElement("hass-datapoints-dev-tool-card-editor"); + } + }; + //#endregion + //#region custom_components/hass_datapoints/src/charts/base/chart-card-base.ts + /** + * ChartCardBase – shared LitElement base class for history and statistics + * chart cards. + * + * Handles: + * • hass setter + requestUpdate plumbing + * • Auto-refresh via HA domain event subscription + window custom event + * • ResizeObserver to redraw the chart when the container resizes + * • _scheduleLoad() — rAF-deferred load with in-flight guard + * + * Subclasses must implement: + * • render() — Lit template (must include a `.chart-wrap` element) + * • _load() — async data fetch + draw + * • _drawChart() — (re-)draw the canvas chart; called by ResizeObserver + */ + var ChartCardBase = class extends i$2 { + constructor(..._args) { + super(..._args); + _defineProperty(this, "_hass", void 0); + _defineProperty(this, "_config", {}); + _defineProperty(this, "_loadRequestId", 0); + _defineProperty( + this, + /** Subclasses store their last draw call arguments here so the + * ResizeObserver can re-invoke _drawChart with the same data. */ + "_lastDrawArgs", + [] + ); + _defineProperty( + this, + /** Tracks the last data point per series to detect new points for blip animations. + * Map of entityId → { t: timestamp, v: value } */ + "_previousSeriesEndpoints", + /* @__PURE__ */ new Map() + ); + _defineProperty(this, "_unsubscribe", null); + _defineProperty(this, "_resizeObserver", null); + _defineProperty(this, "_loadRaf", null); + _defineProperty(this, "_loadInFlight", false); + _defineProperty(this, "_hasStartedInitialLoad", false); + _defineProperty(this, "_windowListener", null); + _defineProperty( + this, + /** True once _setupAutoRefresh and _setupResizeObserver have been called. */ + "_initialized", + false + ); + } + setConfig(config) { + this._config = config ?? {}; + this.requestUpdate(); + } + set hass(hass) { + this._hass = hass; + this.requestUpdate(); + if (this._hasStartedInitialLoad) this._scheduleLoad(); + } + get hass() { + return this._hass; + } + connectedCallback() { + super.connectedCallback(); + if (this._initialized) { + if (!this._unsubscribe || !this._windowListener) this._setupAutoRefresh(); + this._setupResizeObserver(); + } + if (this._hass) this._scheduleLoad(); + } + updated() { + if (!this._initialized && this._hass) { + this._initialized = true; + this._setupAutoRefresh(); + this._setupResizeObserver(); + this._scheduleLoad(); + } + } + disconnectedCallback() { + super.disconnectedCallback(); + this._cleanup(); + } + _scheduleLoad() { + if (!this._hass || this._loadRaf !== null || this._loadInFlight) return; + this._loadRaf = window.requestAnimationFrame(() => { + this._loadRaf = null; + if (!this._hass || !this.isConnected || this._loadInFlight) return; + this._hasStartedInitialLoad = true; + this._loadInFlight = true; + Promise.resolve(this._load()).catch((err) => { + logger$1.error("[hass-datapoints chart-base] load failed", err); + }).finally(() => { + this._loadInFlight = false; + }); + }); + } + _setupAutoRefresh() { + if (!this._hass) return; + if (this._unsubscribe || this._windowListener) return; + this._hass.connection.subscribeEvents(() => { + this._scheduleLoad(); + }, `${DOMAIN}_event_recorded`).then((unsub) => { + this._unsubscribe = unsub; + }).catch(() => {}); + this._windowListener = () => { + this._scheduleLoad(); + }; + window.addEventListener("hass-datapoints-event-recorded", this._windowListener); + } + _setupResizeObserver() { + if (this._resizeObserver) return; + const wrap = this.shadowRoot?.querySelector(".chart-wrap") ?? this.shadowRoot?.querySelector("hass-datapoints-history-chart"); + if (!wrap || !window.ResizeObserver) return; + this._resizeObserver = new ResizeObserver(() => { + if (this._lastDrawArgs.length) this._drawChart(...this._lastDrawArgs); + }); + this._resizeObserver.observe(wrap); + } + _cleanup() { + if (this._loadRaf !== null) { + window.cancelAnimationFrame(this._loadRaf); + this._loadRaf = null; + } + if (this._unsubscribe) { + this._unsubscribe(); + this._unsubscribe = null; + } + if (this._windowListener) { + window.removeEventListener("hass-datapoints-event-recorded", this._windowListener); + this._windowListener = null; + } + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + this._resizeObserver = null; + } + } + static getStubConfig() { + return { title: "" }; + } + }; + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/history.styles.ts + var styles$55 = i$5` :host { display: block; height: 100%; @@ -4383,1964 +6803,1577 @@ min-height: 0; } `; - const SAMPLE_INTERVAL_MS = { - "1s": 1e3, - "5s": 5e3, - "10s": 1e4, - "15s": 15e3, - "30s": 3e4, - "1m": 6e4, - "2m": 2 * 6e4, - "5m": 5 * 6e4, - "10m": 10 * 6e4, - "15m": 15 * 6e4, - "30m": 30 * 6e4, - "1h": 60 * 6e4, - "2h": 2 * 60 * 6e4, - "3h": 3 * 60 * 6e4, - "4h": 4 * 60 * 6e4, - "6h": 6 * 60 * 6e4, - "12h": 12 * 60 * 6e4, - "24h": 24 * 60 * 6e4 - }; - function binaryOnLabel(deviceClass) { - const labels = { - battery: "low", - battery_charging: "charging", - carbon_monoxide: "detected", - cold: "cold", - connectivity: "connected", - door: "open", - garage_door: "open", - gas: "detected", - heat: "hot", - lock: "unlocked", - moisture: "wet", - motion: "motion", - moving: "moving", - occupancy: "occupied", - opening: "open", - plug: "plugged in", - power: "power", - presence: "present", - problem: "problem", - running: "running", - safety: "unsafe", - smoke: "smoke", - sound: "sound", - tamper: "tampered", - update: "update available", - vibration: "vibration", - window: "open" - }; - return labels[deviceClass] || "on"; - } - function binaryOffLabel(deviceClass) { - const labels = { - battery: "normal", - battery_charging: "not charging", - carbon_monoxide: "clear", - cold: "normal", - connectivity: "disconnected", - door: "closed", - garage_door: "closed", - gas: "clear", - heat: "normal", - lock: "locked", - moisture: "dry", - motion: "clear", - moving: "still", - occupancy: "clear", - opening: "closed", - plug: "unplugged", - power: "off", - presence: "away", - problem: "ok", - running: "idle", - safety: "safe", - smoke: "clear", - sound: "quiet", - tamper: "clear", - update: "up to date", - vibration: "still", - window: "closed" - }; - return labels[deviceClass] || "off"; - } - function normalizeNumericHistory(_entityId2, histStates) { - return (Array.isArray(histStates) ? histStates : []).map((state) => { - const value = parseFloat(state?.s ?? ""); - if (Number.isNaN(value)) { - return null; - } - const rawTimestamp = state?.lu ?? state?.lc ?? state?.last_changed ?? state?.last_updated; - const timeSec = typeof rawTimestamp === "number" ? rawTimestamp : new Date(rawTimestamp || 0).getTime() / 1e3; - if (!Number.isFinite(timeSec)) { - return null; - } - return { - lu: Math.round(timeSec * 1e3) / 1e3, - s: String(value) - }; - }).filter((entry) => entry !== null); - } - function getHistoryStatesForEntity$1(histResult, entityId, entityIds = []) { - if (!histResult) { - return []; - } - const result = histResult; - if (Array.isArray(result[entityId])) { - return result[entityId]; - } - if (Array.isArray(histResult)) { - const entries = histResult; - const entityIndex = entityIds.indexOf(entityId); - if (entityIndex >= 0 && Array.isArray(entries[entityIndex])) { - return entries[entityIndex]; - } - if (entries.every( - (entry) => entry && typeof entry === "object" && !Array.isArray(entry) - )) { - return entries.filter( - (entry) => entry.entity_id === entityId - ); - } - } - if (histResult && typeof histResult === "object") { - const wrapped = histResult; - if (Array.isArray(wrapped.result?.[entityId])) { - return wrapped.result[entityId]; - } - if (Array.isArray(wrapped.result)) { - if (wrapped.result.every( - (entry) => entry && typeof entry === "object" && !Array.isArray(entry) - )) { - return wrapped.result.filter( - (entry) => entry.entity_id === entityId - ); - } - const entityIndex = entityIds.indexOf(entityId); - if (entityIndex >= 0 && Array.isArray(wrapped.result[entityIndex])) { - return wrapped.result[entityIndex]; - } - } - } - return []; - } - function normalizeStatisticsHistory(entityId, statsData) { - const statEntries = statsData && typeof statsData === "object" ? statsData[entityId] ?? [] : []; - return (Array.isArray(statEntries) ? statEntries : []).map((entry) => { - const value = Number(entry?.mean); - if (!Number.isFinite(value)) { - return null; - } - const rawTimestamp = entry?.start; - let timestamp; - if (typeof rawTimestamp === "number") { - if (rawTimestamp > 1e11) { - timestamp = rawTimestamp; - } else { - timestamp = rawTimestamp * 1e3; - } - } else { - timestamp = new Date(rawTimestamp).getTime(); - } - if (!Number.isFinite(timestamp)) { - return null; - } - return { - lu: Math.round(timestamp) / 1e3, - s: String(value) - }; - }).filter((entry) => entry !== null).sort((a2, b2) => a2.lu - b2.lu); - } - function mergeNumericHistoryWithStatistics(histPts, statsPts) { - const raw = Array.isArray(histPts) ? histPts : []; - const stats = Array.isArray(statsPts) ? statsPts : []; - if (!raw.length) { - return [...stats]; - } - if (!stats.length) { - return [...raw]; - } - const firstRawMs = raw[0].lu * 1e3; - const lastRawMs = raw[raw.length - 1].lu * 1e3; - const merged2 = [ - ...stats.filter((entry) => { - const timeMs = entry.lu * 1e3; - return timeMs < firstRawMs || timeMs > lastRawMs; - }), - ...raw - ]; - merged2.sort((a2, b2) => a2.lu - b2.lu); - return merged2; - } - function getAxisValueExtent(allValues) { - let min = Infinity; - let max = -Infinity; - for (const value of allValues) { - const numeric = Number(value); - if (!Number.isFinite(numeric)) { - continue; - } - if (numeric < min) { - min = numeric; - } - if (numeric > max) { - max = numeric; - } - } - if (!Number.isFinite(min) || !Number.isFinite(max)) { - return null; - } - return { min, max }; - } - function hexToRgba(hex, alpha) { - const h2 = hex.replace("#", ""); - const r2 = Number.parseInt(h2.substring(0, 2), 16); - const g2 = Number.parseInt(h2.substring(2, 4), 16); - const b2 = Number.parseInt(h2.substring(4, 6), 16); - return `rgba(${r2},${g2},${b2},${alpha})`; - } - function contrastColor(hex) { - if (!hex || typeof hex !== "string") { - return "#fff"; - } - const h2 = hex.replace("#", ""); - if (h2.length !== 6) { - return "#fff"; - } - const r2 = Number.parseInt(h2.substring(0, 2), 16) / 255; - const g2 = Number.parseInt(h2.substring(2, 4), 16) / 255; - const b2 = Number.parseInt(h2.substring(4, 6), 16) / 255; - const lin = (c2) => c2 <= 0.04045 ? c2 / 12.92 : ((c2 + 0.055) / 1.055) ** 2.4; - const luminance = 0.2126 * lin(r2) + 0.7152 * lin(g2) + 0.0722 * lin(b2); - return luminance > 0.179 ? "#000" : "#fff"; - } - var __defProp$W = Object.defineProperty; - var __defNormalProp$W = (obj, key, value) => key in obj ? __defProp$W(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$W = (obj, key, value) => __defNormalProp$W(obj, typeof key !== "symbol" ? key + "" : key, value); - function createFallbackCanvasContext() { - return { - beginPath() { - }, - moveTo() { - }, - lineTo() { - }, - stroke() { - }, - fill() { - }, - fillRect() { - }, - clearRect() { - }, - rect() { - }, - clip() { - }, - save() { - }, - restore() { - }, - scale() { - }, - setLineDash() { - }, - fillText() { - }, - closePath() { - }, - bezierCurveTo() { - }, - arc() { - }, - measureText() { - return { width: 0 }; - } - }; - } - class ChartRenderer { - constructor(canvas, cssWidth, cssHeight) { - __publicField$W(this, "canvas"); - __publicField$W(this, "ctx"); - __publicField$W(this, "cssW"); - __publicField$W(this, "cssH"); - __publicField$W(this, "basePad"); - __publicField$W(this, "pad"); - __publicField$W(this, "labelColor"); - __publicField$W(this, "_activeAxes", []); - this.canvas = canvas; - this.ctx = canvas.getContext("2d") || createFallbackCanvasContext(); - this.cssW = cssWidth; - this.cssH = cssHeight; - this.basePad = { top: 24, right: 12, bottom: 48, left: 12 }; - this.pad = { ...this.basePad }; - this.labelColor = "rgba(214,218,224,0.92)"; - } - static get AXIS_SLOT_WIDTH() { - return 30; - } - get cw() { - return this.cssW - this.pad.left - this.pad.right; - } - get ch() { - return this.cssH - this.pad.top - this.pad.bottom; - } - xOf(t2, t0, t1) { - return this.pad.left + (t2 - t0) / (t1 - t0) * this.cw; - } - yOf(v2, vMin, vMax) { - return this.pad.top + this.ch - (v2 - vMin) / (vMax - vMin) * this.ch; - } - clear() { - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - } - _normalizeAxes(vMinOrAxes, vMax) { - const axisColumnWidth = ChartRenderer.AXIS_SLOT_WIDTH; - const inputAxes = Array.isArray(vMinOrAxes) ? vMinOrAxes : [ - { - key: "default", - min: vMinOrAxes, - max: vMax ?? vMinOrAxes, - side: "left", - unit: "", - color: null - } - ]; - const leftAxes = []; - const rightAxes = []; - const axes = inputAxes.map((axis, index) => { - const normalized = { - key: axis.key || `axis-${index}`, - min: axis.min, - max: axis.max, - side: axis.side === "right" ? "right" : "left", - unit: axis.unit || "", - color: axis.color || "rgba(128,128,128,0.85)" - }; - const bucket = normalized.side === "right" ? rightAxes : leftAxes; - normalized.slot = bucket.length; - bucket.push(normalized); - return normalized; - }); - this.pad = { - top: this.basePad.top, - bottom: this.basePad.bottom, - left: this.basePad.left + Math.max(1, leftAxes.length) * axisColumnWidth, - right: this.basePad.right + rightAxes.length * axisColumnWidth - }; - this._activeAxes = axes; - return axes; - } - _formatAxisTick(v2, _unit2) { - const numeric = Math.abs(v2) >= 1e3 ? `${(v2 / 1e3).toFixed(1).replace(/\.0$/, "")}k` : v2.toFixed(v2 % 1 !== 0 ? 1 : 0); - return numeric; - } - _axisLabelX(axis) { - const columnWidth = ChartRenderer.AXIS_SLOT_WIDTH; - const leftAxisX = this.pad.left; - const rightAxisX = this.pad.left + this.cw; - if (axis.side === "right") { - return rightAxisX + 10 + (axis.slot ?? 0) * columnWidth; - } - return leftAxisX - 10 - (axis.slot ?? 0) * columnWidth; - } - _formatTimeTick(t2, t0, t1, tickSpanMs = null) { - const value = new Date(t2); - const spanMs = Math.max(0, t1 - t0); - const detailSpanMs = Number.isFinite(tickSpanMs) && (tickSpanMs ?? 0) > 0 ? tickSpanMs : spanMs; - const start = new Date(t0); - const end = new Date(t1); - const sameDay = start.getFullYear() === end.getFullYear() && start.getMonth() === end.getMonth() && start.getDate() === end.getDate(); - const sameMonth = start.getFullYear() === end.getFullYear() && start.getMonth() === end.getMonth(); - if (detailSpanMs <= 2 * 60 * 60 * 1e3) { - return value.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit" - }); - } - if (detailSpanMs <= 12 * 60 * 60 * 1e3) { - return value.toLocaleString([], { - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit" - }); - } - if (detailSpanMs <= 2 * 24 * 60 * 60 * 1e3) { - return value.toLocaleString([], { - month: "short", - day: "numeric", - hour: "2-digit" - }); - } - if (sameDay) { - return value.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit" - }); - } - if (detailSpanMs <= 6 * 60 * 60 * 1e3) { - return value.toLocaleString([], { - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit" - }); - } - if (detailSpanMs <= 24 * 60 * 60 * 1e3) { - return value.toLocaleString([], { - month: "short", - day: "numeric", - hour: "2-digit" - }); - } - if (sameMonth && spanMs <= 14 * 24 * 60 * 60 * 1e3) { - return value.toLocaleDateString([], { day: "numeric" }); - } - if (spanMs >= 2 * 24 * 60 * 60 * 1e3) { - return value.toLocaleDateString([], { month: "short", day: "numeric" }); - } - if (spanMs >= 24 * 60 * 60 * 1e3) { - return value.toLocaleString([], { - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit" - }); - } - return fmtTime(value.toISOString()); - } - _niceNumber(value, round) { - if (!Number.isFinite(value) || value <= 0) { - return 1; - } - const exponent = Math.floor(Math.log10(value)); - const fraction = value / 10 ** exponent; - let niceFraction; - if (round) { - if (fraction < 1.5) { - niceFraction = 1; - } else if (fraction < 3) { - niceFraction = 2; - } else if (fraction < 7) { - niceFraction = 5; - } else { - niceFraction = 10; - } - } else if (fraction <= 1) { - niceFraction = 1; - } else if (fraction <= 2) { - niceFraction = 2; - } else if (fraction <= 5) { - niceFraction = 5; - } else { - niceFraction = 10; - } - return niceFraction * 10 ** exponent; - } - _buildNiceAxisScale(axis, tickCount) { - const rawMin = Number.isFinite(axis.min) ? axis.min : 0; - const rawMax = Number.isFinite(axis.max) ? axis.max : 1; - if (rawMin === rawMax) { - const pad = Math.abs(rawMin || 1); - const step2 = this._niceNumber(pad * 2 / Math.max(1, tickCount), true); - const niceMin2 = Math.floor((rawMin - pad) / step2) * step2; - const niceMax2 = Math.ceil((rawMax + pad) / step2) * step2; - const ticks2 = []; - for (let value = niceMin2; value <= niceMax2 + step2 * 0.5; value += step2) { - ticks2.push(Number(value.toFixed(10))); - } - return { min: niceMin2, max: niceMax2, step: step2, ticks: ticks2 }; - } - const range = this._niceNumber(rawMax - rawMin, false); - const step = this._niceNumber(range / Math.max(1, tickCount), true); - const niceMin = Math.floor(rawMin / step) * step; - const niceMax = Math.ceil(rawMax / step) * step; - const ticks = []; - for (let value = niceMin; value <= niceMax + step * 0.5; value += step) { - ticks.push(Number(value.toFixed(10))); - } - return { min: niceMin, max: niceMax, step, ticks }; - } - _alignTimeTick(timestamp, stepMs) { - const date = new Date(timestamp); - if (stepMs < 60 * 1e3) { - return Math.floor(timestamp / stepMs) * stepMs; - } - if (stepMs < 60 * 60 * 1e3) { - const minutes = Math.max(1, Math.round(stepMs / (60 * 1e3))); - date.setSeconds(0, 0); - date.setMinutes(Math.floor(date.getMinutes() / minutes) * minutes); - return date.getTime(); - } - if (stepMs < 24 * 60 * 60 * 1e3) { - const hours = Math.max(1, Math.round(stepMs / (60 * 60 * 1e3))); - date.setMinutes(0, 0, 0); - date.setHours(Math.floor(date.getHours() / hours) * hours); - return date.getTime(); - } - if (stepMs < 7 * 24 * 60 * 60 * 1e3) { - const days = Math.max(1, Math.round(stepMs / (24 * 60 * 60 * 1e3))); - date.setHours(0, 0, 0, 0); - const dayOfMonth = date.getDate(); - date.setDate(dayOfMonth - (dayOfMonth - 1) % days); - return date.getTime(); - } - if (stepMs < 30 * 24 * 60 * 60 * 1e3) { - date.setHours(0, 0, 0, 0); - const day = date.getDay(); - const offset = (day + 6) % 7; - date.setDate(date.getDate() - offset); - return date.getTime(); - } - date.setHours(0, 0, 0, 0); - date.setDate(1); - return date.getTime(); - } - _getTimeTickStep(targetStepMs) { - const candidates = [ - 5 * 60 * 1e3, - 10 * 60 * 1e3, - 15 * 60 * 1e3, - 30 * 60 * 1e3, - 60 * 60 * 1e3, - 2 * 60 * 60 * 1e3, - 3 * 60 * 60 * 1e3, - 6 * 60 * 60 * 1e3, - 12 * 60 * 60 * 1e3, - 24 * 60 * 60 * 1e3, - 2 * 24 * 60 * 60 * 1e3, - 7 * 24 * 60 * 60 * 1e3, - 14 * 24 * 60 * 60 * 1e3, - 30 * 24 * 60 * 60 * 1e3 - ]; - return candidates.find((step) => step >= targetStepMs) || candidates[candidates.length - 1]; - } - _buildTimeTicks(t0, t1) { - const approxTickCount = Math.max( - 2, - Math.min(96, Math.floor(this.cw / 120)) - ); - const stepMs = this._getTimeTickStep( - (t1 - t0) / Math.max(1, approxTickCount) - ); - const ticks = []; - let tick = this._alignTimeTick(t0, stepMs); - if (tick < t0) { - tick += stepMs; - } - while (tick <= t1) { - ticks.push(tick); - tick += stepMs; - } - if (!ticks.length) { - ticks.push(t0, t1); - } - return { ticks, stepMs }; - } - drawGrid(t0, t1, vMin, vMax, yTicks = 5, options = {}) { - const { ctx, pad } = this; - const gridColor = "rgba(128,128,128,0.15)"; - const labelColor = this.labelColor; - const fixedAxisOverlay = !!options.fixedAxisOverlay; - const hideTimeLabels = !!options.hideTimeLabels; - const axes = this._normalizeAxes(vMin, vMax); - const unitCounts = axes.reduce((counts, axis) => { - if (!axis.unit) { - return counts; - } - counts.set(axis.unit, (counts.get(axis.unit) || 0) + 1); - return counts; - }, /* @__PURE__ */ new Map()); - const axisLabelColor = (axis) => { - const duplicateUnit = !!axis?.unit && (unitCounts.get(axis.unit) || 0) > 1; - if (!duplicateUnit || !axis?.color) { - return labelColor; - } - return axis.color; - }; - axes.forEach((axis) => { - const scale = this._buildNiceAxisScale(axis, yTicks); - axis.min = scale.min; - axis.max = scale.max; - axis.ticks = scale.ticks; - }); - const primaryAxis = axes[0]; - ctx.font = "12px sans-serif"; - for (const v2 of primaryAxis.ticks || []) { - const y2 = this.yOf(v2, primaryAxis.min, primaryAxis.max); - ctx.strokeStyle = gridColor; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(pad.left, y2); - ctx.lineTo(pad.left + this.cw, y2); - ctx.stroke(); - if (!fixedAxisOverlay) { - ctx.fillStyle = axisLabelColor(primaryAxis); - ctx.textAlign = "right"; - ctx.textBaseline = "middle"; - ctx.fillText( - this._formatAxisTick(v2, primaryAxis.unit), - this._axisLabelX(primaryAxis), - y2 - ); - } - } - if (!fixedAxisOverlay) { - for (const axis of axes.slice(1)) { - for (const v2 of axis.ticks || []) { - const y2 = this.yOf(v2, axis.min, axis.max); - ctx.fillStyle = axisLabelColor(axis); - ctx.textAlign = axis.side === "right" ? "left" : "right"; - ctx.textBaseline = "middle"; - ctx.fillText( - this._formatAxisTick(v2, axis.unit), - this._axisLabelX(axis), - y2 - ); - } - } - } - if (!fixedAxisOverlay) { - for (const axis of axes) { - if (!axis.unit) { - continue; - } - ctx.fillStyle = axisLabelColor(axis); - ctx.textAlign = axis.side === "right" ? "left" : "right"; - ctx.textBaseline = "bottom"; - ctx.fillText(axis.unit, this._axisLabelX(axis), pad.top - 6); - } - } - const { ticks: timeTicks, stepMs: tickSpanMs } = this._buildTimeTicks( - t0, - t1 - ); - for (const t2 of timeTicks) { - const x2 = this.xOf(t2, t0, t1); - const label = this._formatTimeTick(t2, t0, t1, tickSpanMs); - ctx.strokeStyle = "rgba(128,128,128,0.08)"; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(x2, pad.top); - ctx.lineTo(x2, pad.top + this.ch); - ctx.stroke(); - if (!hideTimeLabels) { - ctx.fillStyle = labelColor; - ctx.textAlign = "center"; - ctx.textBaseline = "top"; - const labelWidth = ctx.measureText(label).width; - const labelX = Math.min( - pad.left + this.cw - labelWidth / 2, - Math.max(pad.left + labelWidth / 2, x2) - ); - ctx.fillText(label, labelX, pad.top + this.ch + 6); - } - } - ctx.strokeStyle = "rgba(128,128,128,0.35)"; - ctx.lineWidth = 1; - ctx.beginPath(); - if (!fixedAxisOverlay) { - ctx.moveTo(pad.left, pad.top); - ctx.lineTo(pad.left, pad.top + this.ch); - } - ctx.moveTo(pad.left, pad.top + this.ch); - ctx.lineTo(pad.left + this.cw, pad.top + this.ch); - if (axes.some((axis) => axis.side === "right") && !fixedAxisOverlay) { - ctx.moveTo(pad.left + this.cw, pad.top); - ctx.lineTo(pad.left + this.cw, pad.top + this.ch); - } - ctx.stroke(); - } - drawRowLabel(text, color = "rgba(214,218,224,0.85)") { - if (!text) { - return; - } - const { ctx, pad } = this; - ctx.save(); - ctx.font = "bold 11px sans-serif"; - ctx.fillStyle = color; - ctx.textAlign = "left"; - ctx.textBaseline = "top"; - ctx.fillText(text, pad.left + 6, pad.top + 5); - ctx.restore(); - } - /** - * Append Catmull-Rom bezier segments to the current path, starting from pts[0] - * (caller must have already called moveTo(pts[0])). Each segment passes exactly - * through the data points with tangents derived from neighbouring points. - */ - static _catmullRomPath(ctx, pts) { - for (let i2 = 1; i2 < pts.length; i2++) { - const pm1 = pts[Math.max(0, i2 - 2)]; - const p0 = pts[i2 - 1]; - const p1 = pts[i2]; - const p2 = pts[Math.min(pts.length - 1, i2 + 1)]; - const cp1x = p0[0] + (p1[0] - pm1[0]) / 6; - const cp1y = p0[1] + (p1[1] - pm1[1]) / 6; - const cp2x = p1[0] - (p2[0] - p0[0]) / 6; - const cp2y = p1[1] - (p2[1] - p0[1]) / 6; - ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p1[0], p1[1]); - } - } - static _steppedPath(ctx, pts) { - for (let i2 = 1; i2 < pts.length; i2++) { - const previous = pts[i2 - 1]; - const current = pts[i2]; - ctx.lineTo(current[0], previous[1]); - ctx.lineTo(current[0], current[1]); - } - } - drawLine(points, color, t0, t1, vMin, vMax, options = {}) { - if (!points.length) { - return; - } - const { ctx, pad } = this; - const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : 0; - const smooth = !!options.smooth; - const stepped = options.stepped === true; - const dashed = !!options.dashed; - const dotted = !!options.dotted; - const dashPattern = Array.isArray(options.dashPattern) ? options.dashPattern.filter( - (entry) => Number.isFinite(entry) && entry > 0 - ) : null; - const lineOpacity = Number.isFinite(options.lineOpacity) ? options.lineOpacity : 1; - const lineWidth = Number.isFinite(options.lineWidth) ? options.lineWidth : 1.75; - const px = points.map(([t2, v2]) => [ - this.xOf(t2, t0, t1), - this.yOf(v2, vMin, vMax) - ]); - const bottom = pad.top + this.ch; - ctx.save(); - ctx.beginPath(); - ctx.rect(pad.left, pad.top, this.cw, this.ch); - ctx.clip(); - if (dashPattern && dashPattern.length) { - ctx.setLineDash(dashPattern); - } else if (dotted) { - ctx.setLineDash([1, 3]); - ctx.lineCap = "round"; - } else if (dashed) { - ctx.setLineDash([6, 4]); - } - if (lineOpacity < 1) { - ctx.globalAlpha = lineOpacity; - } - if (fillAlpha > 0) { - ctx.beginPath(); - ctx.moveTo(px[0][0], bottom); - ctx.lineTo(px[0][0], px[0][1]); - if (stepped) { - ChartRenderer._steppedPath(ctx, px); - } else if (smooth) { - ChartRenderer._catmullRomPath(ctx, px); - } else { - for (let i2 = 1; i2 < px.length; i2++) { - ctx.lineTo(px[i2][0], px[i2][1]); - } - } - ctx.lineTo(px[px.length - 1][0], bottom); - ctx.closePath(); - ctx.fillStyle = hexToRgba(color, fillAlpha); - ctx.fill(); - } - ctx.beginPath(); - ctx.moveTo(px[0][0], px[0][1]); - if (stepped) { - ChartRenderer._steppedPath(ctx, px); - } else if (smooth) { - ChartRenderer._catmullRomPath(ctx, px); - } else { - for (let i2 = 1; i2 < px.length; i2++) { - ctx.lineTo(px[i2][0], px[i2][1]); - } - } - ctx.strokeStyle = color; - ctx.lineWidth = lineWidth; - if (stepped) { - ctx.lineJoin = "miter"; - ctx.lineCap = "butt"; - } else { - ctx.lineJoin = "round"; - ctx.lineCap = "round"; - } - ctx.stroke(); - ctx.restore(); - } - drawBars(points, color, t0, t1, vMin, vMax, options = {}) { - if (!points.length) { - return; - } - const { ctx } = this; - const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : 0.78; - const widthFactor = Number.isFinite(options.widthFactor) ? options.widthFactor : 0.72; - const baselineY = this.yOf(Math.max(vMin, 0), vMin, vMax); - const xs = points.map(([t2]) => this.xOf(t2, t0, t1)); - let minGap = this.cw / Math.max(points.length, 1); - for (let i2 = 1; i2 < xs.length; i2++) { - minGap = Math.min(minGap, xs[i2] - xs[i2 - 1]); - } - const barWidth = Math.max(3, Math.min(28, minGap * widthFactor)); - ctx.save(); - ctx.fillStyle = hexToRgba(color, fillAlpha); - ctx.strokeStyle = color; - ctx.lineWidth = 1; - for (let i2 = 0; i2 < points.length; i2++) { - const [, v2] = points[i2]; - const x2 = xs[i2]; - const y2 = this.yOf(v2, vMin, vMax); - const top = Math.min(y2, baselineY); - const height = Math.max(1, Math.abs(baselineY - y2)); - const left = x2 - barWidth / 2; - ctx.fillRect(left, top, barWidth, height); - } - ctx.restore(); - } - drawStateBands(spans, t0, t1, color = "#03a9f4", alpha = 0.12) { - if (!spans?.length) { - return; - } - const { ctx, pad } = this; - ctx.save(); - ctx.fillStyle = hexToRgba(color, alpha); - for (const span of spans) { - const start = Math.max(t0, span.start); - const end = Math.min(t1, span.end); - if (!(start < end)) { - continue; - } - const x0 = this.xOf(start, t0, t1); - const x1 = this.xOf(end, t0, t1); - ctx.fillRect(x0, pad.top, Math.max(1, x1 - x0), this.ch); - } - ctx.restore(); - } - drawAnnotations(events, t0, t1, options = {}) { - const { ctx, pad } = this; - const hits = []; - const showLines = options.showLines !== false; - const showMarkers = options.showMarkers !== false; - for (const event of events) { - const t2 = new Date(event.timestamp).getTime(); - if (t2 < t0 || t2 > t1) { - continue; - } - const x2 = this.xOf(t2, t0, t1); - const color = event.color || "#03a9f4"; - if (showLines) { - ctx.save(); - ctx.setLineDash([4, 3]); - ctx.strokeStyle = color; - ctx.lineWidth = 1.5; - ctx.globalAlpha = 0.75; - ctx.beginPath(); - ctx.moveTo(x2, pad.top + 8); - ctx.lineTo(x2, pad.top + this.ch); - ctx.stroke(); - ctx.restore(); - } - if (showMarkers) { - const d2 = 5; - ctx.save(); - ctx.fillStyle = color; - ctx.strokeStyle = "rgba(255,255,255,0.8)"; - ctx.lineWidth = 1.5; - ctx.beginPath(); - ctx.moveTo(x2, pad.top - d2); - ctx.lineTo(x2 + d2, pad.top); - ctx.lineTo(x2, pad.top + d2); - ctx.lineTo(x2 - d2, pad.top); - ctx.closePath(); - ctx.fill(); - ctx.stroke(); - ctx.restore(); - } - hits.push({ event, x: x2, y: pad.top }); - } - return hits; - } - /** - * Draw sensor-style vertical annotation lines that terminate on the data line - * with a small circle marker. - */ - drawAnnotationLinesOnLine(events, allSeries, t0, t1, vMin, vMax) { - const { ctx, pad } = this; - const firstPts = allSeries.length ? allSeries[0].pts : []; - const hits = []; - for (const event of events) { - const t2 = new Date(event.timestamp).getTime(); - if (t2 < t0 || t2 > t1) { - continue; - } - const x2 = this.xOf(t2, t0, t1); - const value = this._interpolateValue(firstPts, t2); - if (value === null) { - continue; - } - const y2 = this.yOf(value, vMin, vMax); - const color = event.color || "#03a9f4"; - ctx.save(); - ctx.setLineDash([4, 3]); - ctx.strokeStyle = color; - ctx.lineWidth = 1.5; - ctx.globalAlpha = 0.75; - ctx.beginPath(); - ctx.moveTo(x2, pad.top + this.ch); - ctx.lineTo(x2, y2); - ctx.stroke(); - ctx.restore(); - ctx.save(); - ctx.beginPath(); - ctx.arc(x2, y2, 4, 0, Math.PI * 2); - ctx.fillStyle = color; - ctx.fill(); - ctx.strokeStyle = "rgba(255,255,255,0.9)"; - ctx.lineWidth = 1.5; - ctx.stroke(); - ctx.restore(); - hits.push({ event, x: x2, y: y2, value }); - } - return hits; - } - /** - * Interpolate the Y pixel position on a data series at a given timestamp. - * Uses linear interpolation between surrounding data points. - */ - _interpolateY(seriesPoints, t2, _t0, _t1, vMin, vMax) { - if (!seriesPoints.length) { - return null; - } - if (t2 <= seriesPoints[0][0]) { - return this.yOf(seriesPoints[0][1], vMin, vMax); - } - if (t2 >= seriesPoints[seriesPoints.length - 1][0]) { - return this.yOf(seriesPoints[seriesPoints.length - 1][1], vMin, vMax); - } - for (let i2 = 0; i2 < seriesPoints.length - 1; i2++) { - const [t1p, v1p] = seriesPoints[i2]; - const [t2p, v2p] = seriesPoints[i2 + 1]; - if (t2 >= t1p && t2 <= t2p) { - const frac = (t2 - t1p) / (t2p - t1p); - const v2 = v1p + frac * (v2p - v1p); - return this.yOf(v2, vMin, vMax); - } - } - return null; - } - _interpolateValue(seriesPoints, t2) { - const len = seriesPoints.length; - if (!len) { - return null; - } - if (t2 < seriesPoints[0][0]) { - return null; - } - if (t2 > seriesPoints[len - 1][0]) { - return null; - } - let lo = 0; - let hi = len - 1; - while (lo + 1 < hi) { - const mid = Math.floor((lo + hi) / 2); - if (seriesPoints[mid][0] <= t2) { - lo = mid; - } else { - hi = mid; - } - } - const [t0, v0] = seriesPoints[lo]; - const [t1, v1] = seriesPoints[hi]; - if (t0 === t1) return v0; - return v0 + (v1 - v0) * ((t2 - t0) / (t1 - t0)); - } - /** - * Draw annotation markers directly on a sensor data line. - * No vertical dotted line — only a coloured circle on the line. - * - * @param {Array} events Recorded events array - * @param {Array} allSeries Array of {pts} objects — first series used for Y - * @param {number} t0 Start time ms - * @param {number} t1 End time ms - * @param {number} vMin Y axis min - * @param {number} vMax Y axis max - * @returns {Array} Array of {event, x, y} for hit-testing - */ - drawAnnotationsOnLine(events, allSeries, t0, t1, vMin, vMax) { - const { ctx } = this; - const firstPts = allSeries.length ? allSeries[0].pts : []; - const hits = []; - for (const event of events) { - const t2 = new Date(event.timestamp).getTime(); - if (t2 < t0 || t2 > t1) { - continue; - } - const x2 = this.xOf(t2, t0, t1); - const value = this._interpolateValue(firstPts, t2); - if (value === null) { - continue; - } - const y2 = this.yOf(value, vMin, vMax); - const color = event.color || "#03a9f4"; - const r2 = 10; - ctx.save(); - ctx.beginPath(); - ctx.arc(x2, y2, r2 + 1.5, 0, Math.PI * 2); - ctx.fillStyle = "rgba(255,255,255,0.9)"; - ctx.fill(); - ctx.restore(); - ctx.save(); - ctx.beginPath(); - ctx.arc(x2, y2, r2, 0, Math.PI * 2); - ctx.fillStyle = color; - ctx.fill(); - ctx.restore(); - hits.push({ event, x: x2, y: y2, value }); - } - return hits; - } - /** - * Draw a gradient-filled band between two data values, fading from the edge - * value toward the midpoint value. Used for min/max shading that fades toward - * the mean line. - * - * @param {number} valueEdge Data value at the opaque edge (the min or max line) - * @param {number} valueMid Data value at the transparent end (the mean line) - * @param {string} color Hex color string (e.g. "#03a9f4") - * @param {number} t0 Render start time ms - * @param {number} t1 Render end time ms - * @param {number} vMin Y-axis minimum data value - * @param {number} vMax Y-axis maximum data value - * @param {object} options { fillAlpha } - */ - drawGradientBand(valueEdge, valueMid, color, _t0, _t1, vMin, vMax, options = {}) { - const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : 0.08; - if (fillAlpha <= 0) { - return; - } - const hexMatch = String(color || "").match( - /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i - ); - if (!hexMatch) { - return; - } - const r2 = parseInt(hexMatch[1], 16); - const g2 = parseInt(hexMatch[2], 16); - const b2 = parseInt(hexMatch[3], 16); - const yEdge = this.yOf(valueEdge, vMin, vMax); - const yMid = this.yOf(valueMid, vMin, vMax); - if (Math.abs(yMid - yEdge) < 1) { - return; - } - const { ctx, pad } = this; - const grad = ctx.createLinearGradient(0, yEdge, 0, yMid); - grad.addColorStop(0, `rgba(${r2}, ${g2}, ${b2}, ${fillAlpha})`); - grad.addColorStop(1, `rgba(${r2}, ${g2}, ${b2}, 0)`); - ctx.save(); - ctx.beginPath(); - ctx.rect(pad.left, pad.top, this.cw, this.ch); - ctx.clip(); - ctx.fillStyle = grad; - ctx.fillRect( - pad.left, - Math.min(yEdge, yMid), - this.cw, - Math.abs(yMid - yEdge) - ); - ctx.restore(); - } - drawThresholdArea(points, thresholdValue, color, t0, t1, vMin, vMax, options = {}) { - if (!Array.isArray(points) || points.length < 2) { - return; - } - if (!Number.isFinite(thresholdValue)) { - return; - } - const mode = options.mode === "below" ? "below" : "above"; - const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : 0.12; - if (fillAlpha <= 0) { - return; - } - const segments = []; - let currentSegment = []; - const isInside = (value) => { - if (mode === "below") { - return value <= thresholdValue; - } - return value >= thresholdValue; - }; - const flushSegment = () => { - if (currentSegment.length >= 2) { - segments.push(currentSegment); - } - currentSegment = []; - }; - for (let index = 0; index < points.length - 1; index += 1) { - const startPoint = points[index]; - const endPoint = points[index + 1]; - const startInside = isInside(startPoint[1]); - const endInside = isInside(endPoint[1]); - if (startInside && currentSegment.length === 0) { - currentSegment.push(startPoint); - } - if (startInside && endInside) { - currentSegment.push(endPoint); - continue; - } - if (startInside !== endInside) { - const deltaValue = endPoint[1] - startPoint[1]; - if (deltaValue === 0) { - flushSegment(); - continue; - } - const fraction = (thresholdValue - startPoint[1]) / deltaValue; - const crossingTime = startPoint[0] + (endPoint[0] - startPoint[0]) * fraction; - const crossingPoint = [crossingTime, thresholdValue]; - if (startInside) { - currentSegment.push(crossingPoint); - flushSegment(); - } else { - currentSegment.push(crossingPoint); - currentSegment.push(endPoint); - } - continue; - } - if (!startInside && !endInside) { - flushSegment(); - } - } - flushSegment(); - if (!segments.length) { - return; - } - const { ctx, pad } = this; - const thresholdY = this.yOf(thresholdValue, vMin, vMax); - ctx.save(); - ctx.beginPath(); - ctx.rect(pad.left, pad.top, this.cw, this.ch); - ctx.clip(); - ctx.fillStyle = hexToRgba(color, fillAlpha); - segments.forEach((segment) => { - if (!Array.isArray(segment) || segment.length < 2) { - return; - } - ctx.beginPath(); - const firstPoint = segment[0]; - ctx.moveTo(this.xOf(firstPoint[0], t0, t1), thresholdY); - segment.forEach((point) => { - ctx.lineTo(this.xOf(point[0], t0, t1), this.yOf(point[1], vMin, vMax)); - }); - const lastPoint = segment[segment.length - 1]; - ctx.lineTo(this.xOf(lastPoint[0], t0, t1), thresholdY); - ctx.closePath(); - ctx.fill(); - }); - ctx.restore(); - } - /** - * Draw diagonal hash marks at gap boundary points to indicate the start/end - * of contiguous data ranges. - * - * @param {Array} boundaryPoints Array of [timeMs, value] pairs at gap edges - * @param {string} color Stroke colour - * @param {number} t0 Start time ms - * @param {number} t1 End time ms - * @param {number} vMin Y axis min - * @param {number} vMax Y axis max - */ - drawGapMarkers(boundaryPoints, color, t0, t1, vMin, vMax) { - if (!boundaryPoints.length) { - return; - } - const { ctx, pad } = this; - const h2 = 7; - const w = 3; - const gap = 2; - ctx.save(); - ctx.beginPath(); - ctx.rect(pad.left, pad.top, this.cw, this.ch); - ctx.clip(); - ctx.strokeStyle = color; - ctx.lineWidth = 1.5; - ctx.globalAlpha = 0.55; - for (let i2 = 0; i2 < boundaryPoints.length; i2++) { - const [t2, v2] = boundaryPoints[i2]; - const x2 = this.xOf(t2, t0, t1); - const y2 = this.yOf(v2, vMin, vMax); - const dir = i2 % 2 === 0 ? 1 : -1; - for (let d2 = -gap; d2 <= gap; d2 += gap * 2) { - ctx.beginPath(); - ctx.moveTo(x2 + d2 - w * dir, y2 - h2); - ctx.lineTo(x2 + d2 + w * dir, y2 + h2); - ctx.stroke(); - } - } - ctx.restore(); - } - drawAnomalyClusters(clusters, color, t0, t1, vMin, vMax, options = {}) { - if (!Array.isArray(clusters) || clusters.length === 0) { - return; - } - const strokeAlpha = Number.isFinite(options.strokeAlpha) ? options.strokeAlpha : 0.92; - const lineWidth = Number.isFinite(options.lineWidth) ? options.lineWidth : 2; - const haloWidth = Number.isFinite(options.haloWidth) ? options.haloWidth : Math.max(2.5, lineWidth + 1.5); - const haloColor = typeof options.haloColor === "string" && options.haloColor ? options.haloColor : "rgba(255,255,255,0.9)"; - const haloAlpha = Number.isFinite(options.haloAlpha) ? options.haloAlpha : 0.9; - const fillColor = typeof options.fillColor === "string" && options.fillColor ? options.fillColor : null; - const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : 0; - const pointPadding = Number.isFinite(options.pointPadding) ? options.pointPadding : 10; - const minRadiusX = Number.isFinite(options.minRadiusX) ? options.minRadiusX : 10; - const minRadiusY = Number.isFinite(options.minRadiusY) ? options.minRadiusY : 10; - const clusterRegions = this.getAnomalyClusterRegions( - clusters, - t0, - t1, - vMin, - vMax, - { - pointPadding, - minRadiusX, - minRadiusY - } - ); - const { ctx, pad } = this; - ctx.save(); - ctx.beginPath(); - ctx.rect(pad.left, pad.top, this.cw, this.ch); - ctx.clip(); - clusterRegions.forEach((region) => { - ctx.save(); - ctx.setLineDash([]); - if (fillColor && fillAlpha > 0) { - ctx.globalAlpha = fillAlpha; - ctx.fillStyle = fillColor; - ctx.beginPath(); - ctx.ellipse( - region.centerX, - region.centerY, - region.radiusX, - region.radiusY, - 0, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - ctx.globalAlpha = haloAlpha; - ctx.strokeStyle = haloColor; - ctx.lineWidth = haloWidth; - ctx.beginPath(); - ctx.ellipse( - region.centerX, - region.centerY, - region.radiusX, - region.radiusY, - 0, - 0, - Math.PI * 2 - ); - ctx.stroke(); - ctx.globalAlpha = strokeAlpha; - ctx.strokeStyle = color; - ctx.lineWidth = lineWidth; - ctx.beginPath(); - ctx.ellipse( - region.centerX, - region.centerY, - region.radiusX, - region.radiusY, - 0, - 0, - Math.PI * 2 - ); - ctx.stroke(); - ctx.restore(); - }); - ctx.restore(); - } - /** - * Animate a "blip" circle at the given canvas coordinates. - * The circle expands with a bouncy overshoot, holds briefly, then shrinks to nothing. - * Uses a separate overlay canvas so it doesn't interfere with the main chart. - */ - drawBlip(cx, cy, color, options = {}) { - const resolvedOptions = options; - const maxRadius = resolvedOptions.maxRadius || 6; - const duration = resolvedOptions.duration || 600; - const canvas = this.canvas; - const parent = canvas.parentElement; - if (!parent) { - return; - } - const overlay = document.createElement("canvas"); - overlay.width = canvas.width; - overlay.height = canvas.height; - overlay.style.cssText = `position:absolute;top:0;left:0;width:${canvas.style.width || `${canvas.offsetWidth}px`};height:${canvas.style.height || `${canvas.offsetHeight}px`};pointer-events:none;z-index:2;`; - parent.style.position = parent.style.position || "relative"; - parent.appendChild(overlay); - const ctx = overlay.getContext("2d"); - if (!ctx) { - overlay.remove(); - return; - } - const dpr = window.devicePixelRatio || 1; - const pxCx = cx * dpr; - const pxCy = cy * dpr; - const pxMaxR = maxRadius * dpr; - const start = performance.now(); - const animate = (now) => { - const elapsed = now - start; - const t2 = Math.min(elapsed / duration, 1); - ctx.clearRect(0, 0, overlay.width, overlay.height); - let radius; - let alpha; - if (t2 < 0.35) { - const p2 = t2 / 0.35; - const bounce = p2 < 0.6 ? p2 / 0.6 * 1.3 : 1.3 - 0.3 * ((p2 - 0.6) / 0.4); - radius = pxMaxR * Math.min(bounce, 1.3); - alpha = Math.min(p2 * 2.5, 0.85); - } else if (t2 < 0.6) { - radius = pxMaxR; - alpha = 0.85; - } else { - const p2 = (t2 - 0.6) / 0.4; - const ease = 1 - (1 - p2) ** 3; - radius = pxMaxR * (1 - ease); - alpha = 0.85 * (1 - ease); - } - if (radius > 0.2 && alpha > 0.01) { - ctx.save(); - ctx.globalAlpha = alpha; - ctx.beginPath(); - ctx.arc(pxCx, pxCy, radius, 0, Math.PI * 2); - ctx.fillStyle = color; - ctx.fill(); - ctx.beginPath(); - ctx.arc(pxCx, pxCy, radius * 1.6, 0, Math.PI * 2); - ctx.strokeStyle = color; - ctx.lineWidth = 1.2 * dpr; - ctx.globalAlpha = alpha * 0.4; - ctx.stroke(); - ctx.restore(); - } - if (t2 < 1) { - requestAnimationFrame(animate); - } else { - overlay.remove(); - } - }; - requestAnimationFrame(animate); - } - getAnomalyClusterRegions(clusters, t0, t1, vMin, vMax, options = {}) { - if (!Array.isArray(clusters) || clusters.length === 0) { - return []; - } - const pointPadding = Number.isFinite(options.pointPadding) ? options.pointPadding : 10; - const minRadiusX = Number.isFinite(options.minRadiusX) ? options.minRadiusX : 10; - const minRadiusY = Number.isFinite(options.minRadiusY) ? options.minRadiusY : 10; - return clusters.flatMap((cluster) => { - if (!Array.isArray(cluster?.points) || cluster.points.length === 0) { - return []; - } - const xs = []; - const ys = []; - cluster.points.forEach((point) => { - xs.push(this.xOf(point.timeMs, t0, t1)); - ys.push(this.yOf(point.value, vMin, vMax)); - }); - const minX = Math.min(...xs); - const maxX = Math.max(...xs); - const minY = Math.min(...ys); - const maxY = Math.max(...ys); - return [ - { - centerX: (minX + maxX) / 2, - centerY: (minY + maxY) / 2, - radiusX: Math.max(minRadiusX, (maxX - minX) / 2 + pointPadding), - radiusY: Math.max(minRadiusY, (maxY - minY) / 2 + pointPadding), - cluster - } - ]; - }); - } - } - function getRoot$1(card) { - const rootNode = card.shadowRoot ?? card.getRootNode(); - if (rootNode instanceof ShadowRoot || rootNode instanceof Document) { - return rootNode; - } - return document; - } - function resolveChartLabelColor(el) { - if (!el) { - return "rgba(214,218,224,0.92)"; - } - const raw = getComputedStyle(el).getPropertyValue("--secondary-text-color").trim(); - if (raw) { - return raw; - } - return "rgba(214,218,224,0.92)"; - } - function setupCanvas(canvas, container, cssHeight, cssWidth = null) { - const dpr = window.devicePixelRatio || 1; - const maxCssDim = Math.floor(16383 / dpr); - const styles2 = getComputedStyle(container); - const paddingX = (Number.parseFloat(styles2.paddingLeft || "0") || 0) + (Number.parseFloat(styles2.paddingRight || "0") || 0); - const paddingY = (Number.parseFloat(styles2.paddingTop || "0") || 0) + (Number.parseFloat(styles2.paddingBottom || "0") || 0); - const measuredWidth = cssWidth ?? (container.clientWidth || 360); - const w = Math.min( - maxCssDim, - Math.max(1, Math.round(measuredWidth - paddingX)) - ); - const requestedHeight = cssHeight ?? container.clientHeight ?? 220; - const h2 = Math.min( - maxCssDim, - Math.max(40, Math.round(requestedHeight - paddingY)) - ); - canvas.width = w * dpr; - canvas.height = h2 * dpr; - canvas.style.width = `${w}px`; - canvas.style.height = `${h2}px`; - const ctx = canvas.getContext("2d"); - if (ctx && typeof ctx.scale === "function") { - ctx.scale(dpr, dpr); - } - return { w, h: h2 }; - } - function renderChartAxisOverlays(card, renderer, axes = []) { - const leftEl = getRoot$1(card)?.getElementById("chart-axis-left"); - const rightEl = getRoot$1(card)?.getElementById("chart-axis-right"); - if (!leftEl || !rightEl || !renderer) { - return; - } - const leftWidth = Math.max(0, renderer.pad.left); - const rightWidth = Math.max(0, renderer.pad.right); - leftEl.style.width = `${leftWidth}px`; - rightEl.style.width = `${rightWidth}px`; - const chartWrap = getRoot$1(card).querySelector(".chart-wrap"); - const chartWrapEl = chartWrap instanceof HTMLElement ? chartWrap : card; - if (chartWrapEl) { - chartWrapEl.style.setProperty( - "--dp-chart-axis-left-width", - `${leftWidth}px` - ); - chartWrapEl.style.setProperty( - "--dp-chart-axis-right-width", - `${rightWidth}px` - ); - } - const axisSlotWidth = ChartRenderer.AXIS_SLOT_WIDTH; - const axisOffset = (axis) => 10 + (axis.slot ?? 0) * axisSlotWidth; - const unitCounts = axes.reduce((counts, axis) => { - if (!axis?.unit) { - return counts; - } - counts.set(axis.unit, (counts.get(axis.unit) || 0) + 1); - return counts; - }, /* @__PURE__ */ new Map()); - const axisTextStyle = (axis) => { - const duplicateUnit = !!axis?.unit && (unitCounts.get(axis.unit) || 0) > 1; - if (!duplicateUnit || !axis?.color) { - return ""; - } - return `color:${esc(axis.color)};`; - }; - const buildAxisMarkup = (axis) => { - const labels = (axis.ticks || []).map((tick) => { - const y2 = renderer.yOf(tick, axis.min, axis.max); - return `
${esc(renderer._formatAxisTick(tick, axis.unit))}
`; - }).join(""); - const unit = axis.unit ? `
${esc(axis.unit)}
` : ""; - return `${labels}${unit}`; - }; - const leftAxes = axes.filter((axis) => axis.side !== "right"); - const rightAxes = axes.filter((axis) => axis.side === "right"); - leftEl.innerHTML = leftAxes.length ? `
${leftAxes.map((axis) => buildAxisMarkup(axis)).join("")}` : ""; - rightEl.innerHTML = rightAxes.length ? `
${rightAxes.map((axis) => buildAxisMarkup(axis)).join("")}` : ""; - leftEl.classList.toggle("visible", !!leftAxes.length); - rightEl.classList.toggle("visible", !!rightAxes.length); - } - function renderChartAxisHoverDots(card, hoverValues = []) { - const root = getRoot$1(card); - const leftEl = root.getElementById("chart-axis-left"); - const rightEl = root.getElementById("chart-axis-right"); - const scrollViewport = root.getElementById("chart-scroll-viewport"); - if (!leftEl || !rightEl) { - return; - } - leftEl.querySelectorAll(".chart-axis-hover-dot").forEach((el) => el.remove()); - rightEl.querySelectorAll(".chart-axis-hover-dot").forEach((el) => el.remove()); - const verticalOffset = scrollViewport?.offsetTop || 0; - hoverValues.filter( - (entry) => entry?.hasValue !== false && Number.isFinite(entry?.y) - ).forEach((entry) => { - const target = entry.axisSide === "right" ? rightEl : leftEl; - const dot = document.createElement("span"); - dot.className = `chart-axis-hover-dot ${entry.axisSide === "right" ? "right" : "left"}`; - dot.style.top = `${verticalOffset + entry.y}px`; - dot.style.background = entry.color || "#03a9f4"; - dot.style.opacity = `${Number.isFinite(entry.opacity) ? entry.opacity : 1}`; - target.appendChild(dot); - }); - } - function positionTooltip(tooltip, clientX, clientY, bounds = null) { - tooltip.style.display = "block"; - const tipRect = tooltip.getBoundingClientRect(); - const tipW = tipRect.width || 220; - const tipH = tipRect.height || 64; - const gap = 12; - const minLeft = Number.isFinite(bounds?.left) ? bounds?.left : gap; - const maxLeft = Number.isFinite(bounds?.right) ? bounds?.right : window.innerWidth - gap; - const minTop = Number.isFinite(bounds?.top) ? bounds?.top : gap; - const maxTop = Number.isFinite(bounds?.bottom) ? bounds?.bottom : window.innerHeight - gap; - let left = clientX + gap; - if (left + tipW > maxLeft) { - left = clientX - tipW - gap; - } - let top = clientY - tipH - gap; - if (top < minTop) { - top = clientY + gap; - } - if (top + tipH > maxTop) { - top = Math.max(minTop, clientY - tipH - gap); - } - left = Math.min(Math.max(left, minLeft), Math.max(minLeft, maxLeft - tipW)); - top = Math.min(Math.max(top, minTop), Math.max(minTop, maxTop - tipH)); - tooltip.style.left = `${left}px`; - tooltip.style.top = `${top}px`; - } - function clampChartValue(value, min, max) { - return Math.min(max, Math.max(min, value)); - } - function formatTooltipValue(value, unit = "") { - if (value == null || value === "" || Number.isNaN(Number(value))) { - return ""; - } - return `${Number(value).toFixed(2).replace(/\.00$/, "")}${unit ? ` ${unit}` : ""}`; - } - function formatTooltipDisplayValue(value, unit = "") { - if (value == null || value === "") { - return "No value"; - } - if (typeof value === "string") { - return unit ? `${value} ${unit}` : value; - } - return formatTooltipValue(value, unit); - } - function entityName(hass, entityId) { - if (!hass || !entityId) { - return entityId || ""; - } - const state = hass.states?.[entityId]; - return state && state.attributes && state.attributes.friendly_name || entityId; - } - function entityIcon(hass, entityId) { - if (!hass || !entityId) { - return "mdi:link-variant"; - } - const state = hass.states?.[entityId]; - if (state?.attributes?.icon) { - return state.attributes.icon; - } - const domain = String(entityId).split(".")[0]; - const entry = hass.entities?.[entityId]; - if (entry?.icon) { - return entry.icon; - } - switch (domain) { - case "light": - return "mdi:lightbulb"; - case "switch": - return "mdi:toggle-switch"; - case "binary_sensor": - return "mdi:radiobox-marked"; - case "sensor": - return "mdi:chart-line"; - case "climate": - return "mdi:thermostat"; - case "cover": - return "mdi:window-shutter"; - case "lock": - return "mdi:lock"; - case "media_player": - return "mdi:play-box"; - case "person": - return "mdi:account"; - case "device_tracker": - return "mdi:crosshairs-gps"; - default: - return "mdi:link-variant"; - } - } - function entityRegistryEntries(hass) { - return Object.entries(hass?.entities || {}); - } - function firstRelatedEntityId(hass, matcher) { - return entityRegistryEntries(hass).find( - ([, entry]) => entry && typeof entry === "object" && matcher(entry) - )?.[0] || ""; - } - function deviceName(hass, deviceId) { - if (!hass || !deviceId) { - return deviceId || ""; - } - return hass.devices?.[deviceId]?.name ?? deviceId; - } - function deviceIcon(hass, deviceId) { - if (!hass || !deviceId) { - return "mdi:devices"; - } - const entityId = firstRelatedEntityId( - hass, - (entry) => (entry.device_id || entry.deviceId) === deviceId - ); - return entityId ? entityIcon(hass, entityId) : "mdi:devices"; - } - function areaName(hass, areaId) { - if (!hass || !areaId) { - return areaId || ""; - } - return hass.areas?.[areaId]?.name ?? areaId; - } - function areaIcon(hass, areaId) { - if (!hass || !areaId) { - return "mdi:floor-plan"; - } - const entityId = firstRelatedEntityId( - hass, - (entry) => (entry.area_id || entry.areaId) === areaId - ); - return entityId ? entityIcon(hass, entityId) : "mdi:floor-plan"; - } - function labelName(hass, labelId) { - if (!hass || !labelId) { - return labelId || ""; - } - return hass.labels?.[labelId]?.name ?? labelId; - } - function labelIcon(hass, labelId) { - if (!hass || !labelId) { - return "mdi:label-outline"; - } - const entityId = firstRelatedEntityId(hass, (entry) => { - const labels = [ - ...Array.isArray(entry.labels) ? entry.labels : [], - ...Array.isArray(entry.label_ids) ? entry.label_ids : [] - ]; - return labels.includes(labelId); - }); - return entityId ? entityIcon(hass, entityId) : "mdi:label-outline"; - } - function getRoot(card) { - const rootNode = card.shadowRoot ?? card.getRootNode(); - if (rootNode instanceof ShadowRoot || rootNode instanceof Document) { - return rootNode; - } - return document; - } - function getInteractionState(card) { - return card; - } - function toChartBounds(bounds) { - if (!bounds) { - return null; - } - return { - left: bounds.left + 8, - right: bounds.right - 8, - top: bounds.top + 8, - bottom: bounds.bottom - 8 - }; - } - function formatTooltipDateTimeFromMs(timeMs) { - if (!Number.isFinite(timeMs)) { - return ""; - } - return fmtDateTime(new Date(timeMs).toISOString()); - } - function t$2(key, ...values) { - let s2 = msg(key); - values.forEach((v2, i2) => { - s2 = s2.replace(new RegExp(`\\{${i2}\\}`, "g"), v2); - }); - return s2; - } - function getAnomalyMethodLabels() { - return { - trend_residual: msg("Trend deviation"), - rate_of_change: msg("Sudden change"), - iqr: msg("Statistical outlier (IQR)"), - rolling_zscore: msg("Rolling Z-score"), - persistence: msg("Flat-line / stuck"), - comparison_window: msg("Comparison window") - }; - } - function buildAnomalyMethodSection(region) { - if (!region?.cluster?.points?.length) { - return null; - } - const points = region.cluster.points; - const startPoint = points[0]; - const endPoint = points[points.length - 1]; - const peakPoint = points.reduce( - (peak, p2) => !peak || Math.abs(p2.residual) > Math.abs(peak.residual) ? p2 : peak, - null - ); - if (!peakPoint) { - return null; - } - const label = region.label || region.relatedEntityId || "Series"; - const unit = region.unit || ""; - const cluster = region.cluster; - const method = cluster.anomalyMethod ?? "trend_residual"; - const methodLabel = getAnomalyMethodLabels()[method] || method; - let description; - let alert; - if (method === "rate_of_change") { - const rateUnit = unit ? `${unit}/h` : "units/h"; - description = t$2( - "{0} shows an unusual rate of change between {1} and {2}.", - label, - formatTooltipDateTimeFromMs(startPoint.timeMs), - formatTooltipDateTimeFromMs(endPoint.timeMs) - ); - alert = t$2( - "Peak rate deviation: {0} from a typical rate of {1} at {2}.", - formatTooltipValue(peakPoint.residual, rateUnit), - formatTooltipValue(peakPoint.baselineValue, rateUnit), - formatTooltipDateTimeFromMs(peakPoint.timeMs) - ); - } else if (method === "iqr") { - description = t$2( - "{0} contains statistical outliers between {1} and {2}.", - label, - formatTooltipDateTimeFromMs(startPoint.timeMs), - formatTooltipDateTimeFromMs(endPoint.timeMs) - ); - alert = t$2( - "Peak value: {0}, deviating {1} from the median at {2}.", - formatTooltipValue(peakPoint.value, unit), - formatTooltipValue(Math.abs(peakPoint.residual), unit), - formatTooltipDateTimeFromMs(peakPoint.timeMs) - ); - } else if (method === "rolling_zscore") { - description = t$2( - "{0} shows statistically unusual values between {1} and {2}.", - label, - formatTooltipDateTimeFromMs(startPoint.timeMs), - formatTooltipDateTimeFromMs(endPoint.timeMs) - ); - alert = t$2( - "Peak deviation: {0} from a rolling mean of {1} at {2}.", - formatTooltipValue(peakPoint.residual, unit), - formatTooltipValue(peakPoint.baselineValue, unit), - formatTooltipDateTimeFromMs(peakPoint.timeMs) - ); - } else if (method === "persistence") { - const flatRange = typeof cluster.flatRange === "number" ? cluster.flatRange : null; - const rangeStr = flatRange !== null ? t$2(" (range: {0})", formatTooltipValue(flatRange, unit)) : ""; - description = t$2( - "{0} appears stuck or flat between {1} and {2}{3}.", - label, - formatTooltipDateTimeFromMs(startPoint.timeMs), - formatTooltipDateTimeFromMs(endPoint.timeMs), - rangeStr - ); - alert = t$2( - "Value remained near {0} for an unusually long period.", - formatTooltipValue(peakPoint.baselineValue, unit) - ); - } else if (method === "comparison_window") { - description = t$2( - "{0} deviates significantly from the comparison window between {1} and {2}.", - label, - formatTooltipDateTimeFromMs(startPoint.timeMs), - formatTooltipDateTimeFromMs(endPoint.timeMs) - ); - alert = t$2( - "Peak deviation from comparison: {0} at {1}.", - formatTooltipValue(peakPoint.residual, unit), - formatTooltipDateTimeFromMs(peakPoint.timeMs) - ); - } else { - description = t$2( - "{0} deviates from its expected trend between {1} and {2}.", - label, - formatTooltipDateTimeFromMs(startPoint.timeMs), - formatTooltipDateTimeFromMs(endPoint.timeMs) - ); - alert = t$2( - "Peak deviation: {0} from a baseline of {1} at {2}.", - formatTooltipValue(peakPoint.residual, unit), - formatTooltipValue(peakPoint.baselineValue, unit), - formatTooltipDateTimeFromMs(peakPoint.timeMs) - ); - } - return { methodLabel, description, alert }; - } - function buildAnomalyTooltipContent(regions) { - let regionsArray; - if (Array.isArray(regions)) { - regionsArray = regions; - } else if (regions) { - regionsArray = [regions]; - } else { - regionsArray = []; - } - if (regionsArray.length === 0) { - return null; - } - const sections = regionsArray.map(buildAnomalyMethodSection).filter((section) => section !== null); - if (sections.length === 0) { - return null; - } - const instruction = msg( - "Click the highlighted circle to add an annotation.", - { id: "Click the highlighted circle to add an annotation." } - ); - if (sections.length === 1) { - const section = sections[0]; - const cluster = regionsArray[0]?.cluster; - const detectedByMethods = Array.isArray(cluster?.detectedByMethods) && cluster.detectedByMethods.length > 1 ? cluster.detectedByMethods : null; - const isMultiMethod = detectedByMethods !== null; - const title = isMultiMethod ? msg("⚠️ Multi-method Anomaly") : msg("⚠️ Anomaly Insight"); - const labels = getAnomalyMethodLabels(); - const confirmedNote = isMultiMethod ? ` -${msg("Confirmed by")} ${detectedByMethods.length} ${msg("methods:")} ${detectedByMethods.map( - (method) => labels[method] || method - ).join(", ")}.` : ""; - return { - title, - description: section.description + confirmedNote, - alert: `${msg("Alert:")} ${section.alert}`, - instruction - }; - } - const description = sections.map((s2) => `${s2.methodLabel}: -${s2.description}`).join("\n\n"); - const alert = sections.map((s2) => `${s2.methodLabel}: ${s2.alert}`).join("\n"); - return { - title: msg("⚠️ Multi-method Anomaly"), - description, - alert, - instruction - }; - } - function positionAnomalyTooltip(tooltip, clientX, clientY, mainTooltip, bounds = null) { - if (!tooltip) { - return; - } - tooltip.style.display = "block"; - const tipRect = tooltip.getBoundingClientRect(); - const tipW = tipRect.width || 220; - const tipH = tipRect.height || 64; - const gap = 12; - const minLeft = Number.isFinite(bounds?.left) ? bounds?.left : gap; - const maxLeft = Number.isFinite(bounds?.right) ? bounds?.right : window.innerWidth - gap; - const minTop = Number.isFinite(bounds?.top) ? bounds?.top : gap; - const maxTop = Number.isFinite(bounds?.bottom) ? bounds?.bottom : window.innerHeight - gap; - let left = clientX - gap - tipW; - if (left < minLeft) { - const mainRect2 = mainTooltip ? mainTooltip.getBoundingClientRect() : null; - left = mainRect2 ? mainRect2.right + gap : clientX + gap; - } - const mainRect = mainTooltip ? mainTooltip.getBoundingClientRect() : null; - let top = mainRect ? mainRect.top : clientY - tipH - gap; - if (top + tipH > maxTop) { - top = Math.max(minTop, maxTop - tipH); - } - left = Math.min(Math.max(left, minLeft), Math.max(minLeft, maxLeft - tipW)); - top = Math.min(Math.max(top, minTop), Math.max(minTop, maxTop - tipH)); - tooltip.style.left = `${left}px`; - tooltip.style.top = `${top}px`; - } - function positionSecondaryTooltip(tooltip, anchorTooltip, bounds = null) { - if (!tooltip || !anchorTooltip) { - return; - } - tooltip.style.display = "block"; - const anchorRect = anchorTooltip.getBoundingClientRect(); - const tipRect = tooltip.getBoundingClientRect(); - const gap = 10; - const minLeft = Number.isFinite(bounds?.left) ? bounds?.left : gap; - const maxLeft = Number.isFinite(bounds?.right) ? bounds?.right : window.innerWidth - gap; - const minTop = Number.isFinite(bounds?.top) ? bounds?.top : gap; - const maxTop = Number.isFinite(bounds?.bottom) ? bounds?.bottom : window.innerHeight - gap; - let left = anchorRect.right + gap; - if (left + tipRect.width > maxLeft) { - left = anchorRect.left - tipRect.width - gap; - } - let top = anchorRect.top; - if (top + tipRect.height > maxTop) { - top = Math.max(minTop, maxTop - tipRect.height); - } - left = Math.min( - Math.max(left, minLeft), - Math.max(minLeft, maxLeft - tipRect.width) - ); - top = Math.min( - Math.max(top, minTop), - Math.max(minTop, maxTop - tipRect.height) - ); - tooltip.style.left = `${left}px`; - tooltip.style.top = `${top}px`; - } - function positionTooltipBelow(tooltip, anchorTooltip, bounds = null) { - if (!tooltip || !anchorTooltip) { - return; - } - tooltip.style.display = "block"; - const anchorRect = anchorTooltip.getBoundingClientRect(); - const tipRect = tooltip.getBoundingClientRect(); - const gap = 8; - const minLeft = Number.isFinite(bounds?.left) ? bounds?.left : gap; - const maxLeft = Number.isFinite(bounds?.right) ? bounds?.right : window.innerWidth - gap; - const minTop = Number.isFinite(bounds?.top) ? bounds?.top : gap; - const maxTop = Number.isFinite(bounds?.bottom) ? bounds?.bottom : window.innerHeight - gap; - let left = anchorRect.left; - if (left + tipRect.width > maxLeft) { - left = Math.max(minLeft, maxLeft - tipRect.width); - } - let top = anchorRect.bottom + gap; - if (top + tipRect.height > maxTop) { - top = Math.max(minTop, anchorRect.top - tipRect.height - gap); - } - left = Math.min( - Math.max(left, minLeft), - Math.max(minLeft, maxLeft - tipRect.width) - ); - top = Math.min( - Math.max(top, minTop), - Math.max(minTop, maxTop - tipRect.height) - ); - tooltip.style.left = `${left}px`; - tooltip.style.top = `${top}px`; - } - function getAnnotationTooltipContainer(card) { - if (!card || !getRoot(card)) { - return null; - } - return getRoot(card).getElementById("annotation-tooltips"); - } - function clearAnnotationTooltips(card) { - const container = getAnnotationTooltipContainer(card); - if (!container) { - return; - } - container.innerHTML = ""; - } - function buildAnnotationTooltip(card, event) { - const interactionState = getInteractionState(card); - const tooltip = document.createElement("div"); - tooltip.className = "tooltip secondary annotation-tooltip"; - const hasValue = event?.chart_value != null && event.chart_value !== ""; - const valueMarkup = hasValue ? `
${esc(formatTooltipValue(event.chart_value, event.chart_unit))}
` : ""; - const message = event?.message || "Data point"; - const annotation = event?.annotation && event.annotation !== event.message ? event.annotation : ""; - const relatedMarkup = buildTooltipRelatedChips(interactionState._hass, event); - tooltip.innerHTML = ` + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/data/downsampling.ts + /** + * Map of human-readable interval strings to milliseconds. + * Used when configuring downsampling bucket widths. + */ + var SAMPLE_INTERVAL_MS = { + "1s": 1e3, + "5s": 5e3, + "10s": 1e4, + "15s": 15e3, + "30s": 3e4, + "1m": 6e4, + "2m": 2 * 6e4, + "5m": 5 * 6e4, + "10m": 10 * 6e4, + "15m": 15 * 6e4, + "30m": 30 * 6e4, + "1h": 60 * 6e4, + "2h": 120 * 6e4, + "3h": 180 * 6e4, + "4h": 240 * 6e4, + "6h": 360 * 6e4, + "12h": 720 * 6e4, + "24h": 1440 * 6e4 + }; + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/data/binary-labels.ts + /** + * Returns the human-readable "on" label for a binary sensor device class. + * Pass the lower-cased `device_class` attribute value. + */ + function binaryOnLabel(deviceClass) { + return { + battery: "low", + battery_charging: "charging", + carbon_monoxide: "detected", + cold: "cold", + connectivity: "connected", + door: "open", + garage_door: "open", + gas: "detected", + heat: "hot", + lock: "unlocked", + moisture: "wet", + motion: "motion", + moving: "moving", + occupancy: "occupied", + opening: "open", + plug: "plugged in", + power: "power", + presence: "present", + problem: "problem", + running: "running", + safety: "unsafe", + smoke: "smoke", + sound: "sound", + tamper: "tampered", + update: "update available", + vibration: "vibration", + window: "open" + }[deviceClass] || "on"; + } + /** + * Returns the human-readable "off" label for a binary sensor device class. + * Pass the lower-cased `device_class` attribute value. + */ + function binaryOffLabel(deviceClass) { + return { + battery: "normal", + battery_charging: "not charging", + carbon_monoxide: "clear", + cold: "normal", + connectivity: "disconnected", + door: "closed", + garage_door: "closed", + gas: "clear", + heat: "normal", + lock: "locked", + moisture: "dry", + motion: "clear", + moving: "still", + occupancy: "clear", + opening: "closed", + plug: "unplugged", + power: "off", + presence: "away", + problem: "ok", + running: "idle", + safety: "safe", + smoke: "clear", + sound: "quiet", + tamper: "clear", + update: "up to date", + vibration: "still", + window: "closed" + }[deviceClass] || "off"; + } + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/data/history-normalization.ts + /** + * Normalises a raw numeric sensor state list from the HA history API into an + * array of `{ lu, s }` records (non-finite values filtered out). + */ + function normalizeNumericHistory(_entityId, histStates) { + return (Array.isArray(histStates) ? histStates : []).map((state) => { + const value = parseFloat(state?.s ?? ""); + if (Number.isNaN(value)) return null; + const rawTimestamp = state?.lu ?? state?.lc ?? state?.last_changed ?? state?.last_updated; + const timeSec = typeof rawTimestamp === "number" ? rawTimestamp : new Date(rawTimestamp || 0).getTime() / 1e3; + if (!Number.isFinite(timeSec)) return null; + return { + lu: Math.round(timeSec * 1e3) / 1e3, + s: String(value) + }; + }).filter((entry) => entry !== null); + } + /** + * Extracts the state array for a given entity from the various response shapes + * returned by different versions of the HA history WebSocket API. + */ + function getHistoryStatesForEntity$1(histResult, entityId, entityIds = []) { + if (!histResult) return []; + const result = histResult; + if (Array.isArray(result[entityId])) return result[entityId]; + if (Array.isArray(histResult)) { + const entries = histResult; + const entityIndex = entityIds.indexOf(entityId); + if (entityIndex >= 0 && Array.isArray(entries[entityIndex])) return entries[entityIndex]; + if (entries.every((entry) => entry && typeof entry === "object" && !Array.isArray(entry))) return entries.filter((entry) => entry.entity_id === entityId); + } + if (histResult && typeof histResult === "object") { + const wrapped = histResult; + if (Array.isArray(wrapped.result?.[entityId])) return wrapped.result[entityId]; + if (Array.isArray(wrapped.result)) { + if (wrapped.result.every((entry) => entry && typeof entry === "object" && !Array.isArray(entry))) return wrapped.result.filter((entry) => entry.entity_id === entityId); + const entityIndex = entityIds.indexOf(entityId); + if (entityIndex >= 0 && Array.isArray(wrapped.result[entityIndex])) return wrapped.result[entityIndex]; + } + } + return []; + } + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/data/statistics-normalization.ts + /** + * Normalises statistics data from the HA statistics API for a single entity + * into a sorted array of `{ lu, s }` records. + */ + function normalizeStatisticsHistory(entityId, statsData) { + const statEntries = statsData && typeof statsData === "object" ? statsData[entityId] ?? [] : []; + return (Array.isArray(statEntries) ? statEntries : []).map((entry) => { + const value = Number(entry?.mean); + if (!Number.isFinite(value)) return null; + const rawTimestamp = entry?.start; + let timestamp; + if (typeof rawTimestamp === "number") if (rawTimestamp > 1e11) timestamp = rawTimestamp; + else timestamp = rawTimestamp * 1e3; + else timestamp = new Date(rawTimestamp).getTime(); + if (!Number.isFinite(timestamp)) return null; + return { + lu: Math.round(timestamp) / 1e3, + s: String(value) + }; + }).filter((entry) => entry !== null).sort((a, b) => a.lu - b.lu); + } + /** + * Merges a raw (high-resolution) history point list with a statistics + * (long-term) point list, preferring raw data for the period it covers and + * filling in statistics data outside that period. + */ + function mergeNumericHistoryWithStatistics(histPts, statsPts) { + const raw = Array.isArray(histPts) ? histPts : []; + const stats = Array.isArray(statsPts) ? statsPts : []; + if (!raw.length) return [...stats]; + if (!stats.length) return [...raw]; + const firstRawMs = raw[0].lu * 1e3; + const lastRawMs = raw[raw.length - 1].lu * 1e3; + const merged = [...stats.filter((entry) => { + const timeMs = entry.lu * 1e3; + return timeMs < firstRawMs || timeMs > lastRawMs; + }), ...raw]; + merged.sort((a, b) => a.lu - b.lu); + return merged; + } + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/data/axis-extent.ts + /** + * Computes the finite minimum and maximum of an array of values. + * Returns `null` if there are no finite values. + */ + function getAxisValueExtent(allValues) { + let min = Infinity; + let max = -Infinity; + for (const value of allValues) { + const numeric = Number(value); + if (!Number.isFinite(numeric)) continue; + if (numeric < min) min = numeric; + if (numeric > max) max = numeric; + } + if (!Number.isFinite(min) || !Number.isFinite(max)) return null; + return { + min, + max + }; + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/util/color.ts + function hexToRgba(hex, alpha) { + const h = hex.replace("#", ""); + return `rgba(${Number.parseInt(h.substring(0, 2), 16)},${Number.parseInt(h.substring(2, 4), 16)},${Number.parseInt(h.substring(4, 6), 16)},${alpha})`; + } + /** + * Return "#fff" or "#000" whichever has better contrast against the given hex + * background colour, using the WCAG relative-luminance formula. + */ + function contrastColor(hex) { + if (!hex || typeof hex !== "string") return "#fff"; + const h = hex.replace("#", ""); + if (h.length !== 6) return "#fff"; + const r = Number.parseInt(h.substring(0, 2), 16) / 255; + const g = Number.parseInt(h.substring(2, 4), 16) / 255; + const b = Number.parseInt(h.substring(4, 6), 16) / 255; + const lin = (c) => c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4; + return .2126 * lin(r) + .7152 * lin(g) + .0722 * lin(b) > .179 ? "#000" : "#fff"; + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/chart/chart-renderer.ts + function createFallbackCanvasContext() { + return { + beginPath() {}, + moveTo() {}, + lineTo() {}, + stroke() {}, + fill() {}, + fillRect() {}, + clearRect() {}, + rect() {}, + clip() {}, + save() {}, + restore() {}, + scale() {}, + setLineDash() {}, + fillText() {}, + closePath() {}, + bezierCurveTo() {}, + arc() {}, + measureText() { + return { width: 0 }; + } + }; + } + /** + * Canvas-based chart renderer – grids, lines, annotations. + */ + var ChartRenderer = class ChartRenderer { + constructor(canvas, cssWidth, cssHeight) { + _defineProperty(this, "canvas", void 0); + _defineProperty(this, "ctx", void 0); + _defineProperty(this, "cssW", void 0); + _defineProperty(this, "cssH", void 0); + _defineProperty(this, "basePad", void 0); + _defineProperty(this, "pad", void 0); + _defineProperty(this, "labelColor", void 0); + _defineProperty(this, "_activeAxes", []); + this.canvas = canvas; + this.ctx = canvas.getContext("2d") || createFallbackCanvasContext(); + this.cssW = cssWidth; + this.cssH = cssHeight; + this.basePad = { + top: 24, + right: 12, + bottom: 48, + left: 12 + }; + this.pad = { ...this.basePad }; + this.labelColor = "rgba(214,218,224,0.92)"; + } + static get AXIS_SLOT_WIDTH() { + return 30; + } + get cw() { + return this.cssW - this.pad.left - this.pad.right; + } + get ch() { + return this.cssH - this.pad.top - this.pad.bottom; + } + xOf(t, t0, t1) { + return this.pad.left + (t - t0) / (t1 - t0) * this.cw; + } + yOf(v, vMin, vMax) { + return this.pad.top + this.ch - (v - vMin) / (vMax - vMin) * this.ch; + } + clear() { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + _normalizeAxes(vMinOrAxes, vMax) { + const axisColumnWidth = ChartRenderer.AXIS_SLOT_WIDTH; + const inputAxes = Array.isArray(vMinOrAxes) ? vMinOrAxes : [{ + key: "default", + min: vMinOrAxes, + max: vMax ?? vMinOrAxes, + side: "left", + unit: "", + color: null + }]; + const leftAxes = []; + const rightAxes = []; + const axes = inputAxes.map((axis, index) => { + const normalized = { + key: axis.key || `axis-${index}`, + min: axis.min, + max: axis.max, + side: axis.side === "right" ? "right" : "left", + unit: axis.unit || "", + color: axis.color || "rgba(128,128,128,0.85)" + }; + const bucket = normalized.side === "right" ? rightAxes : leftAxes; + normalized.slot = bucket.length; + bucket.push(normalized); + return normalized; + }); + this.pad = { + top: this.basePad.top, + bottom: this.basePad.bottom, + left: this.basePad.left + Math.max(1, leftAxes.length) * axisColumnWidth, + right: this.basePad.right + rightAxes.length * axisColumnWidth + }; + this._activeAxes = axes; + return axes; + } + _formatAxisTick(v, _unit) { + return Math.abs(v) >= 1e3 ? `${(v / 1e3).toFixed(1).replace(/\.0$/, "")}k` : v.toFixed(v % 1 !== 0 ? 1 : 0); + } + _axisLabelX(axis) { + const columnWidth = ChartRenderer.AXIS_SLOT_WIDTH; + const leftAxisX = this.pad.left; + const rightAxisX = this.pad.left + this.cw; + if (axis.side === "right") return rightAxisX + 10 + (axis.slot ?? 0) * columnWidth; + return leftAxisX - 10 - (axis.slot ?? 0) * columnWidth; + } + _formatTimeTick(t, t0, t1, tickSpanMs = null) { + const value = new Date(t); + const spanMs = Math.max(0, t1 - t0); + const detailSpanMs = Number.isFinite(tickSpanMs) && (tickSpanMs ?? 0) > 0 ? tickSpanMs : spanMs; + const start = new Date(t0); + const end = new Date(t1); + const sameDay = start.getFullYear() === end.getFullYear() && start.getMonth() === end.getMonth() && start.getDate() === end.getDate(); + const sameMonth = start.getFullYear() === end.getFullYear() && start.getMonth() === end.getMonth(); + if (detailSpanMs <= 7200 * 1e3) return value.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit" + }); + if (detailSpanMs <= 720 * 60 * 1e3) return value.toLocaleString([], { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit" + }); + if (detailSpanMs <= 2880 * 60 * 1e3) return value.toLocaleString([], { + month: "short", + day: "numeric", + hour: "2-digit" + }); + if (sameDay) return value.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit" + }); + if (detailSpanMs <= 360 * 60 * 1e3) return value.toLocaleString([], { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit" + }); + if (detailSpanMs <= 1440 * 60 * 1e3) return value.toLocaleString([], { + month: "short", + day: "numeric", + hour: "2-digit" + }); + if (sameMonth && spanMs <= 336 * 60 * 60 * 1e3) return value.toLocaleDateString([], { day: "numeric" }); + if (spanMs >= 2880 * 60 * 1e3) return value.toLocaleDateString([], { + month: "short", + day: "numeric" + }); + if (spanMs >= 1440 * 60 * 1e3) return value.toLocaleString([], { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit" + }); + return fmtTime(value.toISOString()); + } + _niceNumber(value, round) { + if (!Number.isFinite(value) || value <= 0) return 1; + const exponent = Math.floor(Math.log10(value)); + const fraction = value / 10 ** exponent; + let niceFraction; + if (round) if (fraction < 1.5) niceFraction = 1; + else if (fraction < 3) niceFraction = 2; + else if (fraction < 7) niceFraction = 5; + else niceFraction = 10; + else if (fraction <= 1) niceFraction = 1; + else if (fraction <= 2) niceFraction = 2; + else if (fraction <= 5) niceFraction = 5; + else niceFraction = 10; + return niceFraction * 10 ** exponent; + } + _buildNiceAxisScale(axis, tickCount) { + const rawMin = Number.isFinite(axis.min) ? axis.min : 0; + const rawMax = Number.isFinite(axis.max) ? axis.max : 1; + if (rawMin === rawMax) { + const pad = Math.abs(rawMin || 1); + const step = this._niceNumber(pad * 2 / Math.max(1, tickCount), true); + const niceMin = Math.floor((rawMin - pad) / step) * step; + const niceMax = Math.ceil((rawMax + pad) / step) * step; + const ticks = []; + for (let value = niceMin; value <= niceMax + step * .5; value += step) ticks.push(Number(value.toFixed(10))); + return { + min: niceMin, + max: niceMax, + step, + ticks + }; + } + const range = this._niceNumber(rawMax - rawMin, false); + const step = this._niceNumber(range / Math.max(1, tickCount), true); + const niceMin = Math.floor(rawMin / step) * step; + const niceMax = Math.ceil(rawMax / step) * step; + const ticks = []; + for (let value = niceMin; value <= niceMax + step * .5; value += step) ticks.push(Number(value.toFixed(10))); + return { + min: niceMin, + max: niceMax, + step, + ticks + }; + } + _alignTimeTick(timestamp, stepMs) { + const date = new Date(timestamp); + if (stepMs < 60 * 1e3) return Math.floor(timestamp / stepMs) * stepMs; + if (stepMs < 3600 * 1e3) { + const minutes = Math.max(1, Math.round(stepMs / (60 * 1e3))); + date.setSeconds(0, 0); + date.setMinutes(Math.floor(date.getMinutes() / minutes) * minutes); + return date.getTime(); + } + if (stepMs < 1440 * 60 * 1e3) { + const hours = Math.max(1, Math.round(stepMs / (3600 * 1e3))); + date.setMinutes(0, 0, 0); + date.setHours(Math.floor(date.getHours() / hours) * hours); + return date.getTime(); + } + if (stepMs < 10080 * 60 * 1e3) { + const days = Math.max(1, Math.round(stepMs / (1440 * 60 * 1e3))); + date.setHours(0, 0, 0, 0); + const dayOfMonth = date.getDate(); + date.setDate(dayOfMonth - (dayOfMonth - 1) % days); + return date.getTime(); + } + if (stepMs < 720 * 60 * 60 * 1e3) { + date.setHours(0, 0, 0, 0); + const offset = (date.getDay() + 6) % 7; + date.setDate(date.getDate() - offset); + return date.getTime(); + } + date.setHours(0, 0, 0, 0); + date.setDate(1); + return date.getTime(); + } + _getTimeTickStep(targetStepMs) { + const candidates = [ + 300 * 1e3, + 600 * 1e3, + 900 * 1e3, + 1800 * 1e3, + 3600 * 1e3, + 7200 * 1e3, + 10800 * 1e3, + 360 * 60 * 1e3, + 720 * 60 * 1e3, + 1440 * 60 * 1e3, + 2880 * 60 * 1e3, + 10080 * 60 * 1e3, + 336 * 60 * 60 * 1e3, + 720 * 60 * 60 * 1e3 + ]; + return candidates.find((step) => step >= targetStepMs) || candidates[candidates.length - 1]; + } + _buildTimeTicks(t0, t1) { + const approxTickCount = Math.max(2, Math.min(96, Math.floor(this.cw / 120))); + const stepMs = this._getTimeTickStep((t1 - t0) / Math.max(1, approxTickCount)); + const ticks = []; + let tick = this._alignTimeTick(t0, stepMs); + if (tick < t0) tick += stepMs; + while (tick <= t1) { + ticks.push(tick); + tick += stepMs; + } + if (!ticks.length) ticks.push(t0, t1); + return { + ticks, + stepMs + }; + } + drawGrid(t0, t1, vMin, vMax, yTicks = 5, options = {}) { + const { ctx, pad } = this; + const gridColor = "rgba(128,128,128,0.15)"; + const labelColor = this.labelColor; + const fixedAxisOverlay = !!options.fixedAxisOverlay; + const hideTimeLabels = !!options.hideTimeLabels; + const axes = this._normalizeAxes(vMin, vMax); + const unitCounts = axes.reduce((counts, axis) => { + if (!axis.unit) return counts; + counts.set(axis.unit, (counts.get(axis.unit) || 0) + 1); + return counts; + }, /* @__PURE__ */ new Map()); + const axisLabelColor = (axis) => { + if (!(!!axis?.unit && (unitCounts.get(axis.unit) || 0) > 1) || !axis?.color) return labelColor; + return axis.color; + }; + axes.forEach((axis) => { + const scale = this._buildNiceAxisScale(axis, yTicks); + axis.min = scale.min; + axis.max = scale.max; + axis.ticks = scale.ticks; + }); + const primaryAxis = axes[0]; + ctx.font = "12px sans-serif"; + for (const v of primaryAxis.ticks || []) { + const y = this.yOf(v, primaryAxis.min, primaryAxis.max); + ctx.strokeStyle = gridColor; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(pad.left, y); + ctx.lineTo(pad.left + this.cw, y); + ctx.stroke(); + if (!fixedAxisOverlay) { + ctx.fillStyle = axisLabelColor(primaryAxis); + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + ctx.fillText(this._formatAxisTick(v, primaryAxis.unit), this._axisLabelX(primaryAxis), y); + } + } + if (!fixedAxisOverlay) for (const axis of axes.slice(1)) for (const v of axis.ticks || []) { + const y = this.yOf(v, axis.min, axis.max); + ctx.fillStyle = axisLabelColor(axis); + ctx.textAlign = axis.side === "right" ? "left" : "right"; + ctx.textBaseline = "middle"; + ctx.fillText(this._formatAxisTick(v, axis.unit), this._axisLabelX(axis), y); + } + if (!fixedAxisOverlay) for (const axis of axes) { + if (!axis.unit) continue; + ctx.fillStyle = axisLabelColor(axis); + ctx.textAlign = axis.side === "right" ? "left" : "right"; + ctx.textBaseline = "bottom"; + ctx.fillText(axis.unit, this._axisLabelX(axis), pad.top - 6); + } + const { ticks: timeTicks, stepMs: tickSpanMs } = this._buildTimeTicks(t0, t1); + for (const t of timeTicks) { + const x = this.xOf(t, t0, t1); + const label = this._formatTimeTick(t, t0, t1, tickSpanMs); + ctx.strokeStyle = "rgba(128,128,128,0.08)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(x, pad.top); + ctx.lineTo(x, pad.top + this.ch); + ctx.stroke(); + if (!hideTimeLabels) { + ctx.fillStyle = labelColor; + ctx.textAlign = "center"; + ctx.textBaseline = "top"; + const labelWidth = ctx.measureText(label).width; + const labelX = Math.min(pad.left + this.cw - labelWidth / 2, Math.max(pad.left + labelWidth / 2, x)); + ctx.fillText(label, labelX, pad.top + this.ch + 6); + } + } + ctx.strokeStyle = "rgba(128,128,128,0.35)"; + ctx.lineWidth = 1; + ctx.beginPath(); + if (!fixedAxisOverlay) { + ctx.moveTo(pad.left, pad.top); + ctx.lineTo(pad.left, pad.top + this.ch); + } + ctx.moveTo(pad.left, pad.top + this.ch); + ctx.lineTo(pad.left + this.cw, pad.top + this.ch); + if (axes.some((axis) => axis.side === "right") && !fixedAxisOverlay) { + ctx.moveTo(pad.left + this.cw, pad.top); + ctx.lineTo(pad.left + this.cw, pad.top + this.ch); + } + ctx.stroke(); + } + drawRowLabel(text, color = "rgba(214,218,224,0.85)") { + if (!text) return; + const { ctx, pad } = this; + ctx.save(); + ctx.font = "bold 11px sans-serif"; + ctx.fillStyle = color; + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.fillText(text, pad.left + 6, pad.top + 5); + ctx.restore(); + } + /** + * Append Catmull-Rom bezier segments to the current path, starting from pts[0] + * (caller must have already called moveTo(pts[0])). Each segment passes exactly + * through the data points with tangents derived from neighbouring points. + */ + static _catmullRomPath(ctx, pts) { + for (let i = 1; i < pts.length; i++) { + const pm1 = pts[Math.max(0, i - 2)]; + const p0 = pts[i - 1]; + const p1 = pts[i]; + const p2 = pts[Math.min(pts.length - 1, i + 1)]; + const cp1x = p0[0] + (p1[0] - pm1[0]) / 6; + const cp1y = p0[1] + (p1[1] - pm1[1]) / 6; + const cp2x = p1[0] - (p2[0] - p0[0]) / 6; + const cp2y = p1[1] - (p2[1] - p0[1]) / 6; + ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p1[0], p1[1]); + } + } + static _steppedPath(ctx, pts) { + for (let i = 1; i < pts.length; i++) { + const previous = pts[i - 1]; + const current = pts[i]; + ctx.lineTo(current[0], previous[1]); + ctx.lineTo(current[0], current[1]); + } + } + drawLine(points, color, t0, t1, vMin, vMax, options = {}) { + if (!points.length) return; + const { ctx, pad } = this; + const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : 0; + const smooth = !!options.smooth; + const stepped = options.stepped === true; + const dashed = !!options.dashed; + const dotted = !!options.dotted; + const dashPattern = Array.isArray(options.dashPattern) ? options.dashPattern.filter((entry) => Number.isFinite(entry) && entry > 0) : null; + const lineOpacity = Number.isFinite(options.lineOpacity) ? options.lineOpacity : 1; + const lineWidth = Number.isFinite(options.lineWidth) ? options.lineWidth : 1.75; + const px = points.map(([t, v]) => [this.xOf(t, t0, t1), this.yOf(v, vMin, vMax)]); + const bottom = pad.top + this.ch; + ctx.save(); + ctx.beginPath(); + ctx.rect(pad.left, pad.top, this.cw, this.ch); + ctx.clip(); + if (dashPattern && dashPattern.length) ctx.setLineDash(dashPattern); + else if (dotted) { + ctx.setLineDash([1, 3]); + ctx.lineCap = "round"; + } else if (dashed) ctx.setLineDash([6, 4]); + if (lineOpacity < 1) ctx.globalAlpha = lineOpacity; + if (fillAlpha > 0) { + ctx.beginPath(); + ctx.moveTo(px[0][0], bottom); + ctx.lineTo(px[0][0], px[0][1]); + if (stepped) ChartRenderer._steppedPath(ctx, px); + else if (smooth) ChartRenderer._catmullRomPath(ctx, px); + else for (let i = 1; i < px.length; i++) ctx.lineTo(px[i][0], px[i][1]); + ctx.lineTo(px[px.length - 1][0], bottom); + ctx.closePath(); + ctx.fillStyle = hexToRgba(color, fillAlpha); + ctx.fill(); + } + ctx.beginPath(); + ctx.moveTo(px[0][0], px[0][1]); + if (stepped) ChartRenderer._steppedPath(ctx, px); + else if (smooth) ChartRenderer._catmullRomPath(ctx, px); + else for (let i = 1; i < px.length; i++) ctx.lineTo(px[i][0], px[i][1]); + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth; + if (stepped) { + ctx.lineJoin = "miter"; + ctx.lineCap = "butt"; + } else { + ctx.lineJoin = "round"; + ctx.lineCap = "round"; + } + ctx.stroke(); + ctx.restore(); + } + drawBars(points, color, t0, t1, vMin, vMax, options = {}) { + if (!points.length) return; + const { ctx } = this; + const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : .78; + const widthFactor = Number.isFinite(options.widthFactor) ? options.widthFactor : .72; + const baselineY = this.yOf(Math.max(vMin, 0), vMin, vMax); + const xs = points.map(([t]) => this.xOf(t, t0, t1)); + let minGap = this.cw / Math.max(points.length, 1); + for (let i = 1; i < xs.length; i++) minGap = Math.min(minGap, xs[i] - xs[i - 1]); + const barWidth = Math.max(3, Math.min(28, minGap * widthFactor)); + ctx.save(); + ctx.fillStyle = hexToRgba(color, fillAlpha); + ctx.strokeStyle = color; + ctx.lineWidth = 1; + for (let i = 0; i < points.length; i++) { + const [, v] = points[i]; + const x = xs[i]; + const y = this.yOf(v, vMin, vMax); + const top = Math.min(y, baselineY); + const height = Math.max(1, Math.abs(baselineY - y)); + const left = x - barWidth / 2; + ctx.fillRect(left, top, barWidth, height); + } + ctx.restore(); + } + drawStateBands(spans, t0, t1, color = "#03a9f4", alpha = .12) { + if (!spans?.length) return; + const { ctx, pad } = this; + ctx.save(); + ctx.fillStyle = hexToRgba(color, alpha); + for (const span of spans) { + const start = Math.max(t0, span.start); + const end = Math.min(t1, span.end); + if (!(start < end)) continue; + const x0 = this.xOf(start, t0, t1); + const x1 = this.xOf(end, t0, t1); + ctx.fillRect(x0, pad.top, Math.max(1, x1 - x0), this.ch); + } + ctx.restore(); + } + drawAnnotations(events, t0, t1, options = {}) { + const { ctx, pad } = this; + const hits = []; + const showLines = options.showLines !== false; + const showMarkers = options.showMarkers !== false; + for (const event of events) { + const t = new Date(event.timestamp).getTime(); + if (t < t0 || t > t1) continue; + const x = this.xOf(t, t0, t1); + const color = event.color || "#03a9f4"; + if (showLines) { + ctx.save(); + ctx.setLineDash([4, 3]); + ctx.strokeStyle = color; + ctx.lineWidth = 1.5; + ctx.globalAlpha = .75; + ctx.beginPath(); + ctx.moveTo(x, pad.top + 8); + ctx.lineTo(x, pad.top + this.ch); + ctx.stroke(); + ctx.restore(); + } + if (showMarkers) { + const d = 5; + ctx.save(); + ctx.fillStyle = color; + ctx.strokeStyle = "rgba(255,255,255,0.8)"; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.moveTo(x, pad.top - d); + ctx.lineTo(x + d, pad.top); + ctx.lineTo(x, pad.top + d); + ctx.lineTo(x - d, pad.top); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + ctx.restore(); + } + hits.push({ + event, + x, + y: pad.top + }); + } + return hits; + } + /** + * Draw sensor-style vertical annotation lines that terminate on the data line + * with a small circle marker. + */ + drawAnnotationLinesOnLine(events, allSeries, t0, t1, vMin, vMax) { + const { ctx, pad } = this; + const firstPts = allSeries.length ? allSeries[0].pts : []; + const hits = []; + for (const event of events) { + const t = new Date(event.timestamp).getTime(); + if (t < t0 || t > t1) continue; + const x = this.xOf(t, t0, t1); + const value = this._interpolateValue(firstPts, t); + if (value === null) continue; + const y = this.yOf(value, vMin, vMax); + const color = event.color || "#03a9f4"; + ctx.save(); + ctx.setLineDash([4, 3]); + ctx.strokeStyle = color; + ctx.lineWidth = 1.5; + ctx.globalAlpha = .75; + ctx.beginPath(); + ctx.moveTo(x, pad.top + this.ch); + ctx.lineTo(x, y); + ctx.stroke(); + ctx.restore(); + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, 4, 0, Math.PI * 2); + ctx.fillStyle = color; + ctx.fill(); + ctx.strokeStyle = "rgba(255,255,255,0.9)"; + ctx.lineWidth = 1.5; + ctx.stroke(); + ctx.restore(); + hits.push({ + event, + x, + y, + value + }); + } + return hits; + } + /** + * Interpolate the Y pixel position on a data series at a given timestamp. + * Uses linear interpolation between surrounding data points. + */ + _interpolateY(seriesPoints, t, _t0, _t1, vMin, vMax) { + if (!seriesPoints.length) return null; + if (t <= seriesPoints[0][0]) return this.yOf(seriesPoints[0][1], vMin, vMax); + if (t >= seriesPoints[seriesPoints.length - 1][0]) return this.yOf(seriesPoints[seriesPoints.length - 1][1], vMin, vMax); + for (let i = 0; i < seriesPoints.length - 1; i++) { + const [t1p, v1p] = seriesPoints[i]; + const [t2p, v2p] = seriesPoints[i + 1]; + if (t >= t1p && t <= t2p) { + const v = v1p + (t - t1p) / (t2p - t1p) * (v2p - v1p); + return this.yOf(v, vMin, vMax); + } + } + return null; + } + _interpolateValue(seriesPoints, t) { + const len = seriesPoints.length; + if (!len) return null; + if (t < seriesPoints[0][0]) return null; + if (t > seriesPoints[len - 1][0]) return null; + let lo = 0; + let hi = len - 1; + while (lo + 1 < hi) { + const mid = Math.floor((lo + hi) / 2); + if (seriesPoints[mid][0] <= t) lo = mid; + else hi = mid; + } + const [t0, v0] = seriesPoints[lo]; + const [t1, v1] = seriesPoints[hi]; + if (t0 === t1) return v0; + return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); + } + /** + * Draw annotation markers directly on a sensor data line. + * No vertical dotted line — only a coloured circle on the line. + * + * @param {Array} events Recorded events array + * @param {Array} allSeries Array of {pts} objects — first series used for Y + * @param {number} t0 Start time ms + * @param {number} t1 End time ms + * @param {number} vMin Y axis min + * @param {number} vMax Y axis max + * @returns {Array} Array of {event, x, y} for hit-testing + */ + drawAnnotationsOnLine(events, allSeries, t0, t1, vMin, vMax) { + const { ctx } = this; + const firstPts = allSeries.length ? allSeries[0].pts : []; + const hits = []; + for (const event of events) { + const t = new Date(event.timestamp).getTime(); + if (t < t0 || t > t1) continue; + const x = this.xOf(t, t0, t1); + const value = this._interpolateValue(firstPts, t); + if (value === null) continue; + const y = this.yOf(value, vMin, vMax); + const color = event.color || "#03a9f4"; + const r = 10; + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, r + 1.5, 0, Math.PI * 2); + ctx.fillStyle = "rgba(255,255,255,0.9)"; + ctx.fill(); + ctx.restore(); + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, r, 0, Math.PI * 2); + ctx.fillStyle = color; + ctx.fill(); + ctx.restore(); + hits.push({ + event, + x, + y, + value + }); + } + return hits; + } + /** + * Draw a gradient-filled band between two data values, fading from the edge + * value toward the midpoint value. Used for min/max shading that fades toward + * the mean line. + * + * @param {number} valueEdge Data value at the opaque edge (the min or max line) + * @param {number} valueMid Data value at the transparent end (the mean line) + * @param {string} color Hex color string (e.g. "#03a9f4") + * @param {number} t0 Render start time ms + * @param {number} t1 Render end time ms + * @param {number} vMin Y-axis minimum data value + * @param {number} vMax Y-axis maximum data value + * @param {object} options { fillAlpha } + */ + drawGradientBand(valueEdge, valueMid, color, _t0, _t1, vMin, vMax, options = {}) { + const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : .08; + if (fillAlpha <= 0) return; + const hexMatch = String(color || "").match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i); + if (!hexMatch) return; + const r = parseInt(hexMatch[1], 16); + const g = parseInt(hexMatch[2], 16); + const b = parseInt(hexMatch[3], 16); + const yEdge = this.yOf(valueEdge, vMin, vMax); + const yMid = this.yOf(valueMid, vMin, vMax); + if (Math.abs(yMid - yEdge) < 1) return; + const { ctx, pad } = this; + const grad = ctx.createLinearGradient(0, yEdge, 0, yMid); + grad.addColorStop(0, `rgba(${r}, ${g}, ${b}, ${fillAlpha})`); + grad.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`); + ctx.save(); + ctx.beginPath(); + ctx.rect(pad.left, pad.top, this.cw, this.ch); + ctx.clip(); + ctx.fillStyle = grad; + ctx.fillRect(pad.left, Math.min(yEdge, yMid), this.cw, Math.abs(yMid - yEdge)); + ctx.restore(); + } + drawThresholdArea(points, thresholdValue, color, t0, t1, vMin, vMax, options = {}) { + if (!Array.isArray(points) || points.length < 2) return; + if (!Number.isFinite(thresholdValue)) return; + const mode = options.mode === "below" ? "below" : "above"; + const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : .12; + if (fillAlpha <= 0) return; + const segments = []; + let currentSegment = []; + const isInside = (value) => { + if (mode === "below") return value <= thresholdValue; + return value >= thresholdValue; + }; + const flushSegment = () => { + if (currentSegment.length >= 2) segments.push(currentSegment); + currentSegment = []; + }; + for (let index = 0; index < points.length - 1; index += 1) { + const startPoint = points[index]; + const endPoint = points[index + 1]; + const startInside = isInside(startPoint[1]); + const endInside = isInside(endPoint[1]); + if (startInside && currentSegment.length === 0) currentSegment.push(startPoint); + if (startInside && endInside) { + currentSegment.push(endPoint); + continue; + } + if (startInside !== endInside) { + const deltaValue = endPoint[1] - startPoint[1]; + if (deltaValue === 0) { + flushSegment(); + continue; + } + const fraction = (thresholdValue - startPoint[1]) / deltaValue; + const crossingPoint = [startPoint[0] + (endPoint[0] - startPoint[0]) * fraction, thresholdValue]; + if (startInside) { + currentSegment.push(crossingPoint); + flushSegment(); + } else { + currentSegment.push(crossingPoint); + currentSegment.push(endPoint); + } + continue; + } + if (!startInside && !endInside) flushSegment(); + } + flushSegment(); + if (!segments.length) return; + const { ctx, pad } = this; + const thresholdY = this.yOf(thresholdValue, vMin, vMax); + ctx.save(); + ctx.beginPath(); + ctx.rect(pad.left, pad.top, this.cw, this.ch); + ctx.clip(); + ctx.fillStyle = hexToRgba(color, fillAlpha); + segments.forEach((segment) => { + if (!Array.isArray(segment) || segment.length < 2) return; + ctx.beginPath(); + const firstPoint = segment[0]; + ctx.moveTo(this.xOf(firstPoint[0], t0, t1), thresholdY); + segment.forEach((point) => { + ctx.lineTo(this.xOf(point[0], t0, t1), this.yOf(point[1], vMin, vMax)); + }); + const lastPoint = segment[segment.length - 1]; + ctx.lineTo(this.xOf(lastPoint[0], t0, t1), thresholdY); + ctx.closePath(); + ctx.fill(); + }); + ctx.restore(); + } + /** + * Draw diagonal hash marks at gap boundary points to indicate the start/end + * of contiguous data ranges. + * + * @param {Array} boundaryPoints Array of [timeMs, value] pairs at gap edges + * @param {string} color Stroke colour + * @param {number} t0 Start time ms + * @param {number} t1 End time ms + * @param {number} vMin Y axis min + * @param {number} vMax Y axis max + */ + drawGapMarkers(boundaryPoints, color, t0, t1, vMin, vMax) { + if (!boundaryPoints.length) return; + const { ctx, pad } = this; + const h = 7; + const w = 3; + const gap = 2; + ctx.save(); + ctx.beginPath(); + ctx.rect(pad.left, pad.top, this.cw, this.ch); + ctx.clip(); + ctx.strokeStyle = color; + ctx.lineWidth = 1.5; + ctx.globalAlpha = .55; + for (let i = 0; i < boundaryPoints.length; i++) { + const [t, v] = boundaryPoints[i]; + const x = this.xOf(t, t0, t1); + const y = this.yOf(v, vMin, vMax); + const dir = i % 2 === 0 ? 1 : -1; + for (let d = -gap; d <= gap; d += gap * 2) { + ctx.beginPath(); + ctx.moveTo(x + d - w * dir, y - h); + ctx.lineTo(x + d + w * dir, y + h); + ctx.stroke(); + } + } + ctx.restore(); + } + drawAnomalyClusters(clusters, color, t0, t1, vMin, vMax, options = {}) { + if (!Array.isArray(clusters) || clusters.length === 0) return; + const strokeAlpha = Number.isFinite(options.strokeAlpha) ? options.strokeAlpha : .92; + const lineWidth = Number.isFinite(options.lineWidth) ? options.lineWidth : 2; + const haloWidth = Number.isFinite(options.haloWidth) ? options.haloWidth : Math.max(2.5, lineWidth + 1.5); + const haloColor = typeof options.haloColor === "string" && options.haloColor ? options.haloColor : "rgba(255,255,255,0.9)"; + const haloAlpha = Number.isFinite(options.haloAlpha) ? options.haloAlpha : .9; + const fillColor = typeof options.fillColor === "string" && options.fillColor ? options.fillColor : null; + const fillAlpha = Number.isFinite(options.fillAlpha) ? options.fillAlpha : 0; + const pointPadding = Number.isFinite(options.pointPadding) ? options.pointPadding : 10; + const minRadiusX = Number.isFinite(options.minRadiusX) ? options.minRadiusX : 10; + const minRadiusY = Number.isFinite(options.minRadiusY) ? options.minRadiusY : 10; + const clusterRegions = this.getAnomalyClusterRegions(clusters, t0, t1, vMin, vMax, { + pointPadding, + minRadiusX, + minRadiusY + }); + const { ctx, pad } = this; + ctx.save(); + ctx.beginPath(); + ctx.rect(pad.left, pad.top, this.cw, this.ch); + ctx.clip(); + clusterRegions.forEach((region) => { + ctx.save(); + ctx.setLineDash([]); + if (fillColor && fillAlpha > 0) { + ctx.globalAlpha = fillAlpha; + ctx.fillStyle = fillColor; + ctx.beginPath(); + ctx.ellipse(region.centerX, region.centerY, region.radiusX, region.radiusY, 0, 0, Math.PI * 2); + ctx.fill(); + } + ctx.globalAlpha = haloAlpha; + ctx.strokeStyle = haloColor; + ctx.lineWidth = haloWidth; + ctx.beginPath(); + ctx.ellipse(region.centerX, region.centerY, region.radiusX, region.radiusY, 0, 0, Math.PI * 2); + ctx.stroke(); + ctx.globalAlpha = strokeAlpha; + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.ellipse(region.centerX, region.centerY, region.radiusX, region.radiusY, 0, 0, Math.PI * 2); + ctx.stroke(); + ctx.restore(); + }); + ctx.restore(); + } + /** + * Animate a "blip" circle at the given canvas coordinates. + * The circle expands with a bouncy overshoot, holds briefly, then shrinks to nothing. + * Uses a separate overlay canvas so it doesn't interfere with the main chart. + */ + drawBlip(cx, cy, color, options = {}) { + const resolvedOptions = options; + const maxRadius = resolvedOptions.maxRadius || 6; + const duration = resolvedOptions.duration || 600; + const canvas = this.canvas; + const parent = canvas.parentElement; + if (!parent) return; + const overlay = document.createElement("canvas"); + overlay.width = canvas.width; + overlay.height = canvas.height; + overlay.style.cssText = `position:absolute;top:0;left:0;width:${canvas.style.width || `${canvas.offsetWidth}px`};height:${canvas.style.height || `${canvas.offsetHeight}px`};pointer-events:none;z-index:2;`; + parent.style.position = parent.style.position || "relative"; + parent.appendChild(overlay); + const ctx = overlay.getContext("2d"); + if (!ctx) { + overlay.remove(); + return; + } + const dpr = window.devicePixelRatio || 1; + const pxCx = cx * dpr; + const pxCy = cy * dpr; + const pxMaxR = maxRadius * dpr; + const start = performance.now(); + const animate = (now) => { + const elapsed = now - start; + const t = Math.min(elapsed / duration, 1); + ctx.clearRect(0, 0, overlay.width, overlay.height); + let radius; + let alpha; + if (t < .35) { + const p = t / .35; + const bounce = p < .6 ? p / .6 * 1.3 : 1.3 - .3 * ((p - .6) / .4); + radius = pxMaxR * Math.min(bounce, 1.3); + alpha = Math.min(p * 2.5, .85); + } else if (t < .6) { + radius = pxMaxR; + alpha = .85; + } else { + const ease = 1 - (1 - (t - .6) / .4) ** 3; + radius = pxMaxR * (1 - ease); + alpha = .85 * (1 - ease); + } + if (radius > .2 && alpha > .01) { + ctx.save(); + ctx.globalAlpha = alpha; + ctx.beginPath(); + ctx.arc(pxCx, pxCy, radius, 0, Math.PI * 2); + ctx.fillStyle = color; + ctx.fill(); + ctx.beginPath(); + ctx.arc(pxCx, pxCy, radius * 1.6, 0, Math.PI * 2); + ctx.strokeStyle = color; + ctx.lineWidth = 1.2 * dpr; + ctx.globalAlpha = alpha * .4; + ctx.stroke(); + ctx.restore(); + } + if (t < 1) requestAnimationFrame(animate); + else overlay.remove(); + }; + requestAnimationFrame(animate); + } + getAnomalyClusterRegions(clusters, t0, t1, vMin, vMax, options = {}) { + if (!Array.isArray(clusters) || clusters.length === 0) return []; + const pointPadding = Number.isFinite(options.pointPadding) ? options.pointPadding : 10; + const minRadiusX = Number.isFinite(options.minRadiusX) ? options.minRadiusX : 10; + const minRadiusY = Number.isFinite(options.minRadiusY) ? options.minRadiusY : 10; + return clusters.flatMap((cluster) => { + if (!Array.isArray(cluster?.points) || cluster.points.length === 0) return []; + const xs = []; + const ys = []; + cluster.points.forEach((point) => { + xs.push(this.xOf(point.timeMs, t0, t1)); + ys.push(this.yOf(point.value, vMin, vMax)); + }); + const minX = Math.min(...xs); + const maxX = Math.max(...xs); + const minY = Math.min(...ys); + const maxY = Math.max(...ys); + return [{ + centerX: (minX + maxX) / 2, + centerY: (minY + maxY) / 2, + radiusX: Math.max(minRadiusX, (maxX - minX) / 2 + pointPadding), + radiusY: Math.max(minRadiusY, (maxY - minY) / 2 + pointPadding), + cluster + }]; + }); + } + }; + //#endregion + //#region custom_components/hass_datapoints/src/charts/utils/chart-dom.ts + function getRoot$1(card) { + const rootNode = card.shadowRoot ?? card.getRootNode(); + if (rootNode instanceof ShadowRoot || rootNode instanceof Document) return rootNode; + return document; + } + /** + * Reads the HA `--secondary-text-color` CSS variable from the given element so + * canvas-drawn axis labels use the correct colour for the active light/dark theme. + * Falls back to the dark-mode default when the variable is unavailable. + */ + function resolveChartLabelColor(el) { + if (!el) return "rgba(214,218,224,0.92)"; + const raw = getComputedStyle(el).getPropertyValue("--secondary-text-color").trim(); + if (raw) return raw; + return "rgba(214,218,224,0.92)"; + } + function setupCanvas(canvas, container, cssHeight, cssWidth = null) { + const dpr = window.devicePixelRatio || 1; + const maxCssDim = Math.floor(16383 / dpr); + const styles = getComputedStyle(container); + const paddingX = (Number.parseFloat(styles.paddingLeft || "0") || 0) + (Number.parseFloat(styles.paddingRight || "0") || 0); + const paddingY = (Number.parseFloat(styles.paddingTop || "0") || 0) + (Number.parseFloat(styles.paddingBottom || "0") || 0); + const measuredWidth = cssWidth ?? (container.clientWidth || 360); + const w = Math.min(maxCssDim, Math.max(1, Math.round(measuredWidth - paddingX))); + const requestedHeight = cssHeight ?? container.clientHeight ?? 220; + const h = Math.min(maxCssDim, Math.max(40, Math.round(requestedHeight - paddingY))); + canvas.width = w * dpr; + canvas.height = h * dpr; + canvas.style.width = `${w}px`; + canvas.style.height = `${h}px`; + const ctx = canvas.getContext("2d"); + if (ctx && typeof ctx.scale === "function") ctx.scale(dpr, dpr); + return { + w, + h + }; + } + function renderChartAxisOverlays(card, renderer, axes = []) { + const leftEl = getRoot$1(card)?.getElementById("chart-axis-left"); + const rightEl = getRoot$1(card)?.getElementById("chart-axis-right"); + if (!leftEl || !rightEl || !renderer) return; + const leftWidth = Math.max(0, renderer.pad.left); + const rightWidth = Math.max(0, renderer.pad.right); + leftEl.style.width = `${leftWidth}px`; + rightEl.style.width = `${rightWidth}px`; + const chartWrap = getRoot$1(card).querySelector(".chart-wrap"); + const chartWrapEl = chartWrap instanceof HTMLElement ? chartWrap : card; + if (chartWrapEl) { + chartWrapEl.style.setProperty("--dp-chart-axis-left-width", `${leftWidth}px`); + chartWrapEl.style.setProperty("--dp-chart-axis-right-width", `${rightWidth}px`); + } + const axisSlotWidth = ChartRenderer.AXIS_SLOT_WIDTH; + const axisOffset = (axis) => 10 + (axis.slot ?? 0) * axisSlotWidth; + const unitCounts = axes.reduce((counts, axis) => { + if (!axis?.unit) return counts; + counts.set(axis.unit, (counts.get(axis.unit) || 0) + 1); + return counts; + }, /* @__PURE__ */ new Map()); + const axisTextStyle = (axis) => { + if (!(!!axis?.unit && (unitCounts.get(axis.unit) || 0) > 1) || !axis?.color) return ""; + return `color:${esc(axis.color)};`; + }; + const buildAxisMarkup = (axis) => { + return `${(axis.ticks || []).map((tick) => { + const y = renderer.yOf(tick, axis.min, axis.max); + return `
${esc(renderer._formatAxisTick(tick, axis.unit))}
`; + }).join("")}${axis.unit ? `
${esc(axis.unit)}
` : ""}`; + }; + const leftAxes = axes.filter((axis) => axis.side !== "right"); + const rightAxes = axes.filter((axis) => axis.side === "right"); + leftEl.innerHTML = leftAxes.length ? `
${leftAxes.map((axis) => buildAxisMarkup(axis)).join("")}` : ""; + rightEl.innerHTML = rightAxes.length ? `
${rightAxes.map((axis) => buildAxisMarkup(axis)).join("")}` : ""; + leftEl.classList.toggle("visible", !!leftAxes.length); + rightEl.classList.toggle("visible", !!rightAxes.length); + } + function renderChartAxisHoverDots(card, hoverValues = []) { + const root = getRoot$1(card); + const leftEl = root.getElementById("chart-axis-left"); + const rightEl = root.getElementById("chart-axis-right"); + const scrollViewport = root.getElementById("chart-scroll-viewport"); + if (!leftEl || !rightEl) return; + leftEl.querySelectorAll(".chart-axis-hover-dot").forEach((el) => el.remove()); + rightEl.querySelectorAll(".chart-axis-hover-dot").forEach((el) => el.remove()); + const verticalOffset = scrollViewport?.offsetTop || 0; + hoverValues.filter((entry) => entry?.hasValue !== false && Number.isFinite(entry?.y)).forEach((entry) => { + const target = entry.axisSide === "right" ? rightEl : leftEl; + const dot = document.createElement("span"); + dot.className = `chart-axis-hover-dot ${entry.axisSide === "right" ? "right" : "left"}`; + dot.style.top = `${verticalOffset + entry.y}px`; + dot.style.background = entry.color || "#03a9f4"; + dot.style.opacity = `${Number.isFinite(entry.opacity) ? entry.opacity : 1}`; + target.appendChild(dot); + }); + } + function positionTooltip(tooltip, clientX, clientY, bounds = null) { + tooltip.style.display = "block"; + const tipRect = tooltip.getBoundingClientRect(); + const tipW = tipRect.width || 220; + const tipH = tipRect.height || 64; + const gap = 12; + const minLeft = Number.isFinite(bounds?.left) ? bounds?.left : gap; + const maxLeft = Number.isFinite(bounds?.right) ? bounds?.right : window.innerWidth - gap; + const minTop = Number.isFinite(bounds?.top) ? bounds?.top : gap; + const maxTop = Number.isFinite(bounds?.bottom) ? bounds?.bottom : window.innerHeight - gap; + let left = clientX + gap; + if (left + tipW > maxLeft) left = clientX - tipW - gap; + let top = clientY - tipH - gap; + if (top < minTop) top = clientY + gap; + if (top + tipH > maxTop) top = Math.max(minTop, clientY - tipH - gap); + left = Math.min(Math.max(left, minLeft), Math.max(minLeft, maxLeft - tipW)); + top = Math.min(Math.max(top, minTop), Math.max(minTop, maxTop - tipH)); + tooltip.style.left = `${left}px`; + tooltip.style.top = `${top}px`; + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/chart/chart-shell.ts + function clampChartValue(value, min, max) { + return Math.min(max, Math.max(min, value)); + } + function formatTooltipValue(value, unit = "") { + if (value == null || value === "" || Number.isNaN(Number(value))) return ""; + return `${Number(value).toFixed(2).replace(/\.00$/, "")}${unit ? ` ${unit}` : ""}`; + } + function formatTooltipDisplayValue(value, unit = "") { + if (value == null || value === "") return "No value"; + if (typeof value === "string") return unit ? `${value} ${unit}` : value; + return formatTooltipValue(value, unit); + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/ha/entity-name.ts + function entityName(hass, entityId) { + if (!hass || !entityId) return entityId || ""; + const state = hass.states?.[entityId]; + return state && state.attributes && state.attributes.friendly_name || entityId; + } + function entityIcon(hass, entityId) { + if (!hass || !entityId) return "mdi:link-variant"; + const state = hass.states?.[entityId]; + if (state?.attributes?.icon) return state.attributes.icon; + const domain = String(entityId).split(".")[0]; + const entry = hass.entities?.[entityId]; + if (entry?.icon) return entry.icon; + switch (domain) { + case "light": return "mdi:lightbulb"; + case "switch": return "mdi:toggle-switch"; + case "binary_sensor": return "mdi:radiobox-marked"; + case "sensor": return "mdi:chart-line"; + case "climate": return "mdi:thermostat"; + case "cover": return "mdi:window-shutter"; + case "lock": return "mdi:lock"; + case "media_player": return "mdi:play-box"; + case "person": return "mdi:account"; + case "device_tracker": return "mdi:crosshairs-gps"; + default: return "mdi:link-variant"; + } + } + function entityRegistryEntries(hass) { + return Object.entries(hass?.entities || {}); + } + function firstRelatedEntityId(hass, matcher) { + return entityRegistryEntries(hass).find(([, entry]) => entry && typeof entry === "object" && matcher(entry))?.[0] || ""; + } + function deviceName(hass, deviceId) { + if (!hass || !deviceId) return deviceId || ""; + return hass.devices?.[deviceId]?.name ?? deviceId; + } + function deviceIcon(hass, deviceId) { + if (!hass || !deviceId) return "mdi:devices"; + const entityId = firstRelatedEntityId(hass, (entry) => (entry.device_id || entry.deviceId) === deviceId); + return entityId ? entityIcon(hass, entityId) : "mdi:devices"; + } + function areaName(hass, areaId) { + if (!hass || !areaId) return areaId || ""; + return hass.areas?.[areaId]?.name ?? areaId; + } + function areaIcon(hass, areaId) { + if (!hass || !areaId) return "mdi:floor-plan"; + const entityId = firstRelatedEntityId(hass, (entry) => (entry.area_id || entry.areaId) === areaId); + return entityId ? entityIcon(hass, entityId) : "mdi:floor-plan"; + } + function labelName(hass, labelId) { + if (!hass || !labelId) return labelId || ""; + return hass.labels?.[labelId]?.name ?? labelId; + } + function labelIcon(hass, labelId) { + if (!hass || !labelId) return "mdi:label-outline"; + const entityId = firstRelatedEntityId(hass, (entry) => { + return [...Array.isArray(entry.labels) ? entry.labels : [], ...Array.isArray(entry.label_ids) ? entry.label_ids : []].includes(labelId); + }); + return entityId ? entityIcon(hass, entityId) : "mdi:label-outline"; + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/chart/chart-interaction.ts + /** + * Returns the DOM root that contains the chart's elements. + * For cards with a shadow root (legacy HTMLElement-based cards) this is + * `card.shadowRoot`. For sub-components that render into the parent's shadow + * DOM (like `hass-datapoints-history-chart`) there is no own shadow root, so we fall back + * to `getRootNode()` which returns the ancestor ShadowRoot. + */ + function getRoot(card) { + const rootNode = card.shadowRoot ?? card.getRootNode(); + if (rootNode instanceof ShadowRoot || rootNode instanceof Document) return rootNode; + return document; + } + function getInteractionState(card) { + return card; + } + function toChartBounds(bounds) { + if (!bounds) return null; + return { + left: bounds.left + 8, + right: bounds.right - 8, + top: bounds.top + 8, + bottom: bounds.bottom - 8 + }; + } + function formatTooltipDateTimeFromMs(timeMs) { + if (!Number.isFinite(timeMs)) return ""; + return fmtDateTime(new Date(timeMs).toISOString()); + } + function t$2(key, ...values) { + let s = msg(key); + values.forEach((v, i) => { + s = s.replace(new RegExp(`\\{${i}\\}`, "g"), v); + }); + return s; + } + function getAnomalyMethodLabels() { + return { + trend_residual: msg("Trend deviation"), + rate_of_change: msg("Sudden change"), + iqr: msg("Statistical outlier (IQR)"), + rolling_zscore: msg("Rolling Z-score"), + persistence: msg("Flat-line / stuck"), + comparison_window: msg("Comparison window") + }; + } + function buildAnomalyMethodSection(region) { + if (!region?.cluster?.points?.length) return null; + const points = region.cluster.points; + const startPoint = points[0]; + const endPoint = points[points.length - 1]; + const peakPoint = points.reduce((peak, p) => !peak || Math.abs(p.residual) > Math.abs(peak.residual) ? p : peak, null); + if (!peakPoint) return null; + const label = region.label || region.relatedEntityId || "Series"; + const unit = region.unit || ""; + const cluster = region.cluster; + const method = cluster.anomalyMethod ?? "trend_residual"; + const methodLabel = getAnomalyMethodLabels()[method] || method; + let description; + let alert; + if (method === "rate_of_change") { + const rateUnit = unit ? `${unit}/h` : "units/h"; + description = t$2("{0} shows an unusual rate of change between {1} and {2}.", label, formatTooltipDateTimeFromMs(startPoint.timeMs), formatTooltipDateTimeFromMs(endPoint.timeMs)); + alert = t$2("Peak rate deviation: {0} from a typical rate of {1} at {2}.", formatTooltipValue(peakPoint.residual, rateUnit), formatTooltipValue(peakPoint.baselineValue, rateUnit), formatTooltipDateTimeFromMs(peakPoint.timeMs)); + } else if (method === "iqr") { + description = t$2("{0} contains statistical outliers between {1} and {2}.", label, formatTooltipDateTimeFromMs(startPoint.timeMs), formatTooltipDateTimeFromMs(endPoint.timeMs)); + alert = t$2("Peak value: {0}, deviating {1} from the median at {2}.", formatTooltipValue(peakPoint.value, unit), formatTooltipValue(Math.abs(peakPoint.residual), unit), formatTooltipDateTimeFromMs(peakPoint.timeMs)); + } else if (method === "rolling_zscore") { + description = t$2("{0} shows statistically unusual values between {1} and {2}.", label, formatTooltipDateTimeFromMs(startPoint.timeMs), formatTooltipDateTimeFromMs(endPoint.timeMs)); + alert = t$2("Peak deviation: {0} from a rolling mean of {1} at {2}.", formatTooltipValue(peakPoint.residual, unit), formatTooltipValue(peakPoint.baselineValue, unit), formatTooltipDateTimeFromMs(peakPoint.timeMs)); + } else if (method === "persistence") { + const flatRange = typeof cluster.flatRange === "number" ? cluster.flatRange : null; + const rangeStr = flatRange !== null ? t$2(" (range: {0})", formatTooltipValue(flatRange, unit)) : ""; + description = t$2("{0} appears stuck or flat between {1} and {2}{3}.", label, formatTooltipDateTimeFromMs(startPoint.timeMs), formatTooltipDateTimeFromMs(endPoint.timeMs), rangeStr); + alert = t$2("Value remained near {0} for an unusually long period.", formatTooltipValue(peakPoint.baselineValue, unit)); + } else if (method === "comparison_window") { + description = t$2("{0} deviates significantly from the comparison window between {1} and {2}.", label, formatTooltipDateTimeFromMs(startPoint.timeMs), formatTooltipDateTimeFromMs(endPoint.timeMs)); + alert = t$2("Peak deviation from comparison: {0} at {1}.", formatTooltipValue(peakPoint.residual, unit), formatTooltipDateTimeFromMs(peakPoint.timeMs)); + } else { + description = t$2("{0} deviates from its expected trend between {1} and {2}.", label, formatTooltipDateTimeFromMs(startPoint.timeMs), formatTooltipDateTimeFromMs(endPoint.timeMs)); + alert = t$2("Peak deviation: {0} from a baseline of {1} at {2}.", formatTooltipValue(peakPoint.residual, unit), formatTooltipValue(peakPoint.baselineValue, unit), formatTooltipDateTimeFromMs(peakPoint.timeMs)); + } + return { + methodLabel, + description, + alert + }; + } + function buildAnomalyTooltipContent(regions) { + let regionsArray; + if (Array.isArray(regions)) regionsArray = regions; + else if (regions) regionsArray = [regions]; + else regionsArray = []; + if (regionsArray.length === 0) return null; + const sections = regionsArray.map(buildAnomalyMethodSection).filter((section) => section !== null); + if (sections.length === 0) return null; + const instruction = msg("Click the highlighted circle to add an annotation.", { id: "Click the highlighted circle to add an annotation." }); + if (sections.length === 1) { + const section = sections[0]; + const cluster = regionsArray[0]?.cluster; + const detectedByMethods = Array.isArray(cluster?.detectedByMethods) && cluster.detectedByMethods.length > 1 ? cluster.detectedByMethods : null; + const isMultiMethod = detectedByMethods !== null; + const title = isMultiMethod ? msg("⚠️ Multi-method Anomaly") : msg("⚠️ Anomaly Insight"); + const labels = getAnomalyMethodLabels(); + const confirmedNote = isMultiMethod ? `\n${msg("Confirmed by")} ${detectedByMethods.length} ${msg("methods:")} ${detectedByMethods.map((method) => labels[method] || method).join(", ")}.` : ""; + return { + title, + description: section.description + confirmedNote, + alert: `${msg("Alert:")} ${section.alert}`, + instruction + }; + } + const description = sections.map((s) => `${s.methodLabel}:\n${s.description}`).join("\n\n"); + const alert = sections.map((s) => `${s.methodLabel}: ${s.alert}`).join("\n"); + return { + title: msg("⚠️ Multi-method Anomaly"), + description, + alert, + instruction + }; + } + function positionAnomalyTooltip(tooltip, clientX, clientY, mainTooltip, bounds = null) { + if (!tooltip) return; + tooltip.style.display = "block"; + const tipRect = tooltip.getBoundingClientRect(); + const tipW = tipRect.width || 220; + const tipH = tipRect.height || 64; + const gap = 12; + const minLeft = Number.isFinite(bounds?.left) ? bounds?.left : gap; + const maxLeft = Number.isFinite(bounds?.right) ? bounds?.right : window.innerWidth - gap; + const minTop = Number.isFinite(bounds?.top) ? bounds?.top : gap; + const maxTop = Number.isFinite(bounds?.bottom) ? bounds?.bottom : window.innerHeight - gap; + let left = clientX - gap - tipW; + if (left < minLeft) { + const mainRect = mainTooltip ? mainTooltip.getBoundingClientRect() : null; + left = mainRect ? mainRect.right + gap : clientX + gap; + } + const mainRect = mainTooltip ? mainTooltip.getBoundingClientRect() : null; + let top = mainRect ? mainRect.top : clientY - tipH - gap; + if (top + tipH > maxTop) top = Math.max(minTop, maxTop - tipH); + left = Math.min(Math.max(left, minLeft), Math.max(minLeft, maxLeft - tipW)); + top = Math.min(Math.max(top, minTop), Math.max(minTop, maxTop - tipH)); + tooltip.style.left = `${left}px`; + tooltip.style.top = `${top}px`; + } + function positionSecondaryTooltip(tooltip, anchorTooltip, bounds = null) { + if (!tooltip || !anchorTooltip) return; + tooltip.style.display = "block"; + const anchorRect = anchorTooltip.getBoundingClientRect(); + const tipRect = tooltip.getBoundingClientRect(); + const gap = 10; + const minLeft = Number.isFinite(bounds?.left) ? bounds?.left : gap; + const maxLeft = Number.isFinite(bounds?.right) ? bounds?.right : window.innerWidth - gap; + const minTop = Number.isFinite(bounds?.top) ? bounds?.top : gap; + const maxTop = Number.isFinite(bounds?.bottom) ? bounds?.bottom : window.innerHeight - gap; + let left = anchorRect.right + gap; + if (left + tipRect.width > maxLeft) left = anchorRect.left - tipRect.width - gap; + let top = anchorRect.top; + if (top + tipRect.height > maxTop) top = Math.max(minTop, maxTop - tipRect.height); + left = Math.min(Math.max(left, minLeft), Math.max(minLeft, maxLeft - tipRect.width)); + top = Math.min(Math.max(top, minTop), Math.max(minTop, maxTop - tipRect.height)); + tooltip.style.left = `${left}px`; + tooltip.style.top = `${top}px`; + } + function positionTooltipBelow(tooltip, anchorTooltip, bounds = null) { + if (!tooltip || !anchorTooltip) return; + tooltip.style.display = "block"; + const anchorRect = anchorTooltip.getBoundingClientRect(); + const tipRect = tooltip.getBoundingClientRect(); + const gap = 8; + const minLeft = Number.isFinite(bounds?.left) ? bounds?.left : gap; + const maxLeft = Number.isFinite(bounds?.right) ? bounds?.right : window.innerWidth - gap; + const minTop = Number.isFinite(bounds?.top) ? bounds?.top : gap; + const maxTop = Number.isFinite(bounds?.bottom) ? bounds?.bottom : window.innerHeight - gap; + let left = anchorRect.left; + if (left + tipRect.width > maxLeft) left = Math.max(minLeft, maxLeft - tipRect.width); + let top = anchorRect.bottom + gap; + if (top + tipRect.height > maxTop) top = Math.max(minTop, anchorRect.top - tipRect.height - gap); + left = Math.min(Math.max(left, minLeft), Math.max(minLeft, maxLeft - tipRect.width)); + top = Math.min(Math.max(top, minTop), Math.max(minTop, maxTop - tipRect.height)); + tooltip.style.left = `${left}px`; + tooltip.style.top = `${top}px`; + } + function getAnnotationTooltipContainer(card) { + if (!card || !getRoot(card)) return null; + return getRoot(card).getElementById("annotation-tooltips"); + } + function clearAnnotationTooltips(card) { + const container = getAnnotationTooltipContainer(card); + if (!container) return; + container.innerHTML = ""; + } + function buildAnnotationTooltip(card, event) { + const interactionState = getInteractionState(card); + const tooltip = document.createElement("div"); + tooltip.className = "tooltip secondary annotation-tooltip"; + const valueMarkup = event?.chart_value != null && event.chart_value !== "" ? `
${esc(formatTooltipValue(event.chart_value, event.chart_unit))}
` : ""; + const message = event?.message || "Data point"; + const annotation = event?.annotation && event.annotation !== event.message ? event.annotation : ""; + const relatedMarkup = buildTooltipRelatedChips(interactionState._hass, event); + tooltip.innerHTML = `
${esc(fmtDateTime(event.timestamp))}
${valueMarkup}
@@ -6350,389 +8383,295 @@ ${s2.description}`).join("\n\n");
${esc(annotation)}
${relatedMarkup}
`; - return tooltip; - } - function renderAnnotationTooltips(card, hover, anchorTooltip, bounds = null) { - const container = getAnnotationTooltipContainer(card); - if (!container) { - return []; - } - clearAnnotationTooltips(card); - const annotationEvents = Array.isArray(hover?.events) ? hover.events : []; - if (!annotationEvents.length) { - return []; - } - const renderedTooltips = []; - let anchorEl = anchorTooltip; - for (const event of annotationEvents) { - const tooltip = buildAnnotationTooltip(card, event); - container.appendChild(tooltip); - if (renderedTooltips.length === 0) { - positionSecondaryTooltip(tooltip, anchorEl, bounds); - } else { - positionTooltipBelow(tooltip, anchorEl, bounds); - } - renderedTooltips.push(tooltip); - anchorEl = tooltip; - } - return renderedTooltips; - } - function hideTooltip(card) { - const tooltip = getRoot(card).getElementById("tooltip"); - const anomalyTooltip = getRoot(card).getElementById("anomaly-tooltip"); - if (tooltip) { - tooltip.style.display = "none"; - } - if (anomalyTooltip) { - anomalyTooltip.style.display = "none"; - } - clearAnnotationTooltips(card); - } - function resolveTooltipSeriesLabel(entry) { - const isSubordinate = entry.grouped === true && entry.rawVisible === true; - const isComparisonDerived = entry.comparisonDerived === true && entry.grouped === true; - if (entry.comparison === true) { - const windowLabel = String(entry.windowLabel || msg("Date window")); - if (entry.grouped === true) { - return windowLabel; - } - return `${windowLabel}: ${String(entry.label || "")}`; - } - if (entry.trend === true) { - const trendLabel = msg("Trend"); - if (isSubordinate || isComparisonDerived) { - return trendLabel; - } - return `${trendLabel}: ${entry.baseLabel || entry.label || ""}`; - } - if (entry.rate === true) { - const rateLabel = msg("Rate"); - if (isSubordinate || isComparisonDerived) { - return rateLabel; - } - return `${rateLabel}: ${entry.baseLabel || entry.label || ""}`; - } - if (entry.delta === true) { - const deltaLabel = msg("Delta"); - if (isSubordinate || isComparisonDerived) { - return deltaLabel; - } - return `${deltaLabel}: ${entry.baseLabel || entry.label || ""}`; - } - if (entry.summary === true) { - const summaryLabel = String(entry.summaryType || "").toUpperCase(); - if (isSubordinate || isComparisonDerived) { - return summaryLabel; - } - return `${summaryLabel}: ${entry.baseLabel || entry.label || ""}`; - } - if (entry.threshold === true) { - const thresholdLabel = msg("Threshold"); - if (isSubordinate || isComparisonDerived) { - return thresholdLabel; - } - return `${thresholdLabel}: ${entry.baseLabel || entry.label || ""}`; - } - return String(entry.label || ""); - } - function showLineChartTooltip(card, hover, clientX, clientY) { - const root = getRoot(card); - const tooltip = root.getElementById("tooltip"); - const ttTime = root.getElementById("tt-time"); - const ttValue = root.getElementById("tt-value"); - const ttSeries = root.getElementById("tt-series"); - const anomalyTooltip = root.getElementById( - "anomaly-tooltip" - ); - const ttSecondaryTitle = root.getElementById("tt-secondary-title"); - const ttSecondaryDescription = root.getElementById( - "tt-secondary-description" - ); - const ttSecondaryAlert = root.getElementById("tt-secondary-alert"); - const ttSecondaryInstruction = root.getElementById( - "tt-secondary-instruction" - ); - const ttMessageRow = root.getElementById( - "tt-message-row" - ); - const ttMsg = root.getElementById("tt-message"); - const ttAnn = root.getElementById("tt-annotation"); - const ttEntities = root.getElementById( - "tt-entities" - ); - if (!tooltip || !ttTime || !ttValue || !ttMessageRow || !ttMsg || !ttAnn || !ttEntities) { - return; - } - const rangeStartMs = Number.isFinite(hover.rangeStartMs) ? hover.rangeStartMs : hover.timeMs; - const rangeEndMs = Number.isFinite(hover.rangeEndMs) ? hover.rangeEndMs : hover.timeMs; - ttTime.textContent = rangeStartMs === rangeEndMs ? fmtDateTime(new Date(hover.timeMs).toISOString()) : `${fmtDateTime(new Date(rangeStartMs).toISOString())} - ${fmtDateTime(new Date(rangeEndMs).toISOString())}`; - const values = Array.isArray(hover.values) ? hover.values : []; - const trendValues = Array.isArray(hover.trendValues) ? hover.trendValues : []; - const rateValues = Array.isArray(hover.rateValues) ? hover.rateValues : []; - const deltaValues = Array.isArray(hover.deltaValues) ? hover.deltaValues : []; - const summaryValues = Array.isArray(hover.summaryValues) ? hover.summaryValues : []; - const thresholdValues = Array.isArray(hover.thresholdValues) ? hover.thresholdValues : []; - const binaryValues = Array.isArray(hover.binaryValues) ? hover.binaryValues : []; - const comparisonValues = Array.isArray(hover.comparisonValues) ? hover.comparisonValues : []; - const displayRows = []; - const usedTrendRows = /* @__PURE__ */ new Set(); - const usedRateRows = /* @__PURE__ */ new Set(); - const usedDeltaRows = /* @__PURE__ */ new Set(); - const usedSummaryRows = /* @__PURE__ */ new Set(); - const usedThresholdRows = /* @__PURE__ */ new Set(); - const usedComparisonRows = /* @__PURE__ */ new Set(); - const pushComparisonDerivedRows = (comparisonEntry, comparisonIndex) => { - trendValues.forEach((trendEntry, trendIndex) => { - if (usedTrendRows.has(trendIndex)) { - return; - } - if (trendEntry.comparisonParentId !== comparisonEntry.entityId && !(trendEntry.relatedEntityId === comparisonEntry.relatedEntityId && trendEntry.windowLabel === comparisonEntry.windowLabel)) { - return; - } - usedTrendRows.add(trendIndex); - displayRows.push({ - ...trendEntry, - rawVisible: true, - comparisonDerived: true, - grouped: true, - key: `comparison-trend-${comparisonIndex}-${trendIndex}` - }); - }); - rateValues.forEach((rateEntry, rateIndex) => { - if (usedRateRows.has(rateIndex)) { - return; - } - if (rateEntry.comparisonParentId !== comparisonEntry.entityId && !(rateEntry.relatedEntityId === comparisonEntry.relatedEntityId && rateEntry.windowLabel === comparisonEntry.windowLabel)) { - return; - } - usedRateRows.add(rateIndex); - displayRows.push({ - ...rateEntry, - rawVisible: true, - comparisonDerived: true, - grouped: true, - key: `comparison-rate-${comparisonIndex}-${rateIndex}` - }); - }); - summaryValues.forEach((summaryEntry, summaryIndex) => { - if (usedSummaryRows.has(summaryIndex)) { - return; - } - if (summaryEntry.comparisonParentId !== comparisonEntry.entityId && !(summaryEntry.relatedEntityId === comparisonEntry.relatedEntityId && summaryEntry.windowLabel === comparisonEntry.windowLabel)) { - return; - } - usedSummaryRows.add(summaryIndex); - displayRows.push({ - ...summaryEntry, - rawVisible: true, - comparisonDerived: true, - grouped: true, - key: `comparison-summary-${comparisonIndex}-${summaryIndex}` - }); - }); - thresholdValues.forEach((thresholdEntry, thresholdIndex) => { - if (usedThresholdRows.has(thresholdIndex)) { - return; - } - if (thresholdEntry.comparisonParentId !== comparisonEntry.entityId && !(thresholdEntry.relatedEntityId === comparisonEntry.relatedEntityId && thresholdEntry.windowLabel === comparisonEntry.windowLabel)) { - return; - } - usedThresholdRows.add(thresholdIndex); - displayRows.push({ - ...thresholdEntry, - rawVisible: true, - comparisonDerived: true, - grouped: true, - key: `comparison-threshold-${comparisonIndex}-${thresholdIndex}` - }); - }); - }; - values.forEach((entry, index) => { - displayRows.push(entry); - trendValues.forEach((trendEntry, trendIndex) => { - if (usedTrendRows.has(trendIndex)) { - return; - } - const sameEntity = trendEntry.relatedEntityId && trendEntry.relatedEntityId === entry.entityId; - const sameLabel = !trendEntry.relatedEntityId && trendEntry.baseLabel && trendEntry.baseLabel === entry.label; - if (!sameEntity && !sameLabel) { - return; - } - usedTrendRows.add(trendIndex); - displayRows.push({ - ...trendEntry, - rawVisible: trendEntry.rawVisible !== false, - grouped: true, - key: `trend-${index}-${trendIndex}` - }); - }); - rateValues.forEach((rateEntry, rateIndex) => { - if (usedRateRows.has(rateIndex)) { - return; - } - const sameEntity = rateEntry.relatedEntityId && rateEntry.relatedEntityId === entry.entityId; - const sameLabel = !rateEntry.relatedEntityId && rateEntry.baseLabel && rateEntry.baseLabel === entry.label; - if (!sameEntity && !sameLabel) { - return; - } - usedRateRows.add(rateIndex); - displayRows.push({ - ...rateEntry, - rawVisible: rateEntry.rawVisible !== false, - grouped: true, - key: `rate-${index}-${rateIndex}` - }); - }); - deltaValues.forEach((deltaEntry, deltaIndex) => { - if (usedDeltaRows.has(deltaIndex)) { - return; - } - const sameEntity = deltaEntry.relatedEntityId && deltaEntry.relatedEntityId === entry.entityId; - const sameLabel = !deltaEntry.relatedEntityId && deltaEntry.baseLabel && deltaEntry.baseLabel === entry.label; - if (!sameEntity && !sameLabel) { - return; - } - usedDeltaRows.add(deltaIndex); - displayRows.push({ - ...deltaEntry, - rawVisible: deltaEntry.rawVisible !== false, - grouped: true, - key: `delta-${index}-${deltaIndex}` - }); - }); - summaryValues.forEach((summaryEntry, summaryIndex) => { - if (usedSummaryRows.has(summaryIndex)) { - return; - } - const sameEntity = summaryEntry.relatedEntityId && summaryEntry.relatedEntityId === entry.entityId; - const sameLabel = !summaryEntry.relatedEntityId && summaryEntry.baseLabel && summaryEntry.baseLabel === entry.label; - if (!sameEntity && !sameLabel) { - return; - } - usedSummaryRows.add(summaryIndex); - displayRows.push({ - ...summaryEntry, - rawVisible: summaryEntry.rawVisible !== false, - grouped: true, - key: `summary-${index}-${summaryIndex}` - }); - }); - thresholdValues.forEach((thresholdEntry, thresholdIndex) => { - if (usedThresholdRows.has(thresholdIndex)) { - return; - } - const sameEntity = thresholdEntry.relatedEntityId && thresholdEntry.relatedEntityId === entry.entityId; - const sameLabel = !thresholdEntry.relatedEntityId && thresholdEntry.baseLabel && thresholdEntry.baseLabel === entry.label; - if (!sameEntity && !sameLabel) { - return; - } - usedThresholdRows.add(thresholdIndex); - displayRows.push({ - ...thresholdEntry, - rawVisible: thresholdEntry.rawVisible !== false, - grouped: true, - key: `threshold-${index}-${thresholdIndex}` - }); - }); - comparisonValues.forEach((compEntry, compIndex) => { - if (usedComparisonRows.has(compIndex)) { - return; - } - if (!compEntry.relatedEntityId || compEntry.relatedEntityId !== entry.entityId) { - return; - } - usedComparisonRows.add(compIndex); - const groupedEntry = { - ...compEntry, - grouped: true, - comparison: true, - key: `comparison-${index}-${compIndex}` - }; - displayRows.push(groupedEntry); - pushComparisonDerivedRows(groupedEntry, compIndex); - }); - }); - trendValues.forEach((trendEntry, trendIndex) => { - if (usedTrendRows.has(trendIndex)) { - return; - } - if (trendEntry.comparisonDerived === true || typeof trendEntry.comparisonParentId === "string") { - return; - } - displayRows.push({ - ...trendEntry, - rawVisible: trendEntry.rawVisible !== false - }); - }); - rateValues.forEach((rateEntry, rateIndex) => { - if (usedRateRows.has(rateIndex)) { - return; - } - if (rateEntry.comparisonDerived === true || typeof rateEntry.comparisonParentId === "string") { - return; - } - displayRows.push({ - ...rateEntry, - rawVisible: rateEntry.rawVisible !== false - }); - }); - deltaValues.forEach((deltaEntry, deltaIndex) => { - if (usedDeltaRows.has(deltaIndex)) { - return; - } - displayRows.push({ - ...deltaEntry, - rawVisible: deltaEntry.rawVisible !== false - }); - }); - summaryValues.forEach((summaryEntry, summaryIndex) => { - if (usedSummaryRows.has(summaryIndex)) { - return; - } - if (summaryEntry.comparisonDerived === true || typeof summaryEntry.comparisonParentId === "string") { - return; - } - displayRows.push({ - ...summaryEntry, - rawVisible: summaryEntry.rawVisible !== false - }); - }); - thresholdValues.forEach((thresholdEntry, thresholdIndex) => { - if (usedThresholdRows.has(thresholdIndex)) { - return; - } - if (thresholdEntry.comparisonDerived === true || typeof thresholdEntry.comparisonParentId === "string") { - return; - } - displayRows.push({ - ...thresholdEntry, - rawVisible: thresholdEntry.rawVisible !== false - }); - }); - comparisonValues.forEach((compEntry, compIndex) => { - if (usedComparisonRows.has(compIndex)) { - return; - } - const groupedEntry = { ...compEntry, comparison: true }; - displayRows.push(groupedEntry); - pushComparisonDerivedRows(groupedEntry, compIndex); - }); - displayRows.push(...binaryValues); - const useSingleValueMode = displayRows.length === 1 && trendValues.length === 0 && rateValues.length === 0 && deltaValues.length === 0 && summaryValues.length === 0 && thresholdValues.length === 0 && comparisonValues.length === 0 && binaryValues.length === 0 && displayRows[0]?.comparison !== true; - if (useSingleValueMode) { - const value = displayRows[0]; - ttValue.textContent = value ? formatTooltipDisplayValue(value.value, value.unit) : ""; - ttValue.style.display = value ? "block" : "none"; - if (ttSeries) { - ttSeries.innerHTML = ""; - ttSeries.style.display = "none"; - } - } else { - ttValue.textContent = ""; - ttValue.style.display = "none"; - if (ttSeries) { - ttSeries.innerHTML = displayRows.map( - (entry) => ` + return tooltip; + } + function renderAnnotationTooltips(card, hover, anchorTooltip, bounds = null) { + const container = getAnnotationTooltipContainer(card); + if (!container) return []; + clearAnnotationTooltips(card); + const annotationEvents = Array.isArray(hover?.events) ? hover.events : []; + if (!annotationEvents.length) return []; + const renderedTooltips = []; + let anchorEl = anchorTooltip; + for (const event of annotationEvents) { + const tooltip = buildAnnotationTooltip(card, event); + container.appendChild(tooltip); + if (renderedTooltips.length === 0) positionSecondaryTooltip(tooltip, anchorEl, bounds); + else positionTooltipBelow(tooltip, anchorEl, bounds); + renderedTooltips.push(tooltip); + anchorEl = tooltip; + } + return renderedTooltips; + } + function hideTooltip(card) { + const tooltip = getRoot(card).getElementById("tooltip"); + const anomalyTooltip = getRoot(card).getElementById("anomaly-tooltip"); + if (tooltip) tooltip.style.display = "none"; + if (anomalyTooltip) anomalyTooltip.style.display = "none"; + clearAnnotationTooltips(card); + } + function resolveTooltipSeriesLabel(entry) { + const isSubordinate = entry.grouped === true && entry.rawVisible === true; + const isComparisonDerived = entry.comparisonDerived === true && entry.grouped === true; + if (entry.comparison === true) { + const windowLabel = String(entry.windowLabel || msg("Date window")); + if (entry.grouped === true) return windowLabel; + return `${windowLabel}: ${String(entry.label || "")}`; + } + if (entry.trend === true) { + const trendLabel = msg("Trend"); + if (isSubordinate || isComparisonDerived) return trendLabel; + return `${trendLabel}: ${entry.baseLabel || entry.label || ""}`; + } + if (entry.rate === true) { + const rateLabel = msg("Rate"); + if (isSubordinate || isComparisonDerived) return rateLabel; + return `${rateLabel}: ${entry.baseLabel || entry.label || ""}`; + } + if (entry.delta === true) { + const deltaLabel = msg("Delta"); + if (isSubordinate || isComparisonDerived) return deltaLabel; + return `${deltaLabel}: ${entry.baseLabel || entry.label || ""}`; + } + if (entry.summary === true) { + const summaryLabel = String(entry.summaryType || "").toUpperCase(); + if (isSubordinate || isComparisonDerived) return summaryLabel; + return `${summaryLabel}: ${entry.baseLabel || entry.label || ""}`; + } + if (entry.threshold === true) { + const thresholdLabel = msg("Threshold"); + if (isSubordinate || isComparisonDerived) return thresholdLabel; + return `${thresholdLabel}: ${entry.baseLabel || entry.label || ""}`; + } + return String(entry.label || ""); + } + function showLineChartTooltip(card, hover, clientX, clientY) { + const root = getRoot(card); + const tooltip = root.getElementById("tooltip"); + const ttTime = root.getElementById("tt-time"); + const ttValue = root.getElementById("tt-value"); + const ttSeries = root.getElementById("tt-series"); + const anomalyTooltip = root.getElementById("anomaly-tooltip"); + const ttSecondaryTitle = root.getElementById("tt-secondary-title"); + const ttSecondaryDescription = root.getElementById("tt-secondary-description"); + const ttSecondaryAlert = root.getElementById("tt-secondary-alert"); + const ttSecondaryInstruction = root.getElementById("tt-secondary-instruction"); + const ttMessageRow = root.getElementById("tt-message-row"); + const ttMsg = root.getElementById("tt-message"); + const ttAnn = root.getElementById("tt-annotation"); + const ttEntities = root.getElementById("tt-entities"); + if (!tooltip || !ttTime || !ttValue || !ttMessageRow || !ttMsg || !ttAnn || !ttEntities) return; + const rangeStartMs = Number.isFinite(hover.rangeStartMs) ? hover.rangeStartMs : hover.timeMs; + const rangeEndMs = Number.isFinite(hover.rangeEndMs) ? hover.rangeEndMs : hover.timeMs; + ttTime.textContent = rangeStartMs === rangeEndMs ? fmtDateTime(new Date(hover.timeMs).toISOString()) : `${fmtDateTime(new Date(rangeStartMs).toISOString())} - ${fmtDateTime(new Date(rangeEndMs).toISOString())}`; + const values = Array.isArray(hover.values) ? hover.values : []; + const trendValues = Array.isArray(hover.trendValues) ? hover.trendValues : []; + const rateValues = Array.isArray(hover.rateValues) ? hover.rateValues : []; + const deltaValues = Array.isArray(hover.deltaValues) ? hover.deltaValues : []; + const summaryValues = Array.isArray(hover.summaryValues) ? hover.summaryValues : []; + const thresholdValues = Array.isArray(hover.thresholdValues) ? hover.thresholdValues : []; + const binaryValues = Array.isArray(hover.binaryValues) ? hover.binaryValues : []; + const comparisonValues = Array.isArray(hover.comparisonValues) ? hover.comparisonValues : []; + const displayRows = []; + const usedTrendRows = /* @__PURE__ */ new Set(); + const usedRateRows = /* @__PURE__ */ new Set(); + const usedDeltaRows = /* @__PURE__ */ new Set(); + const usedSummaryRows = /* @__PURE__ */ new Set(); + const usedThresholdRows = /* @__PURE__ */ new Set(); + const usedComparisonRows = /* @__PURE__ */ new Set(); + const pushComparisonDerivedRows = (comparisonEntry, comparisonIndex) => { + trendValues.forEach((trendEntry, trendIndex) => { + if (usedTrendRows.has(trendIndex)) return; + if (trendEntry.comparisonParentId !== comparisonEntry.entityId && !(trendEntry.relatedEntityId === comparisonEntry.relatedEntityId && trendEntry.windowLabel === comparisonEntry.windowLabel)) return; + usedTrendRows.add(trendIndex); + displayRows.push({ + ...trendEntry, + rawVisible: true, + comparisonDerived: true, + grouped: true, + key: `comparison-trend-${comparisonIndex}-${trendIndex}` + }); + }); + rateValues.forEach((rateEntry, rateIndex) => { + if (usedRateRows.has(rateIndex)) return; + if (rateEntry.comparisonParentId !== comparisonEntry.entityId && !(rateEntry.relatedEntityId === comparisonEntry.relatedEntityId && rateEntry.windowLabel === comparisonEntry.windowLabel)) return; + usedRateRows.add(rateIndex); + displayRows.push({ + ...rateEntry, + rawVisible: true, + comparisonDerived: true, + grouped: true, + key: `comparison-rate-${comparisonIndex}-${rateIndex}` + }); + }); + summaryValues.forEach((summaryEntry, summaryIndex) => { + if (usedSummaryRows.has(summaryIndex)) return; + if (summaryEntry.comparisonParentId !== comparisonEntry.entityId && !(summaryEntry.relatedEntityId === comparisonEntry.relatedEntityId && summaryEntry.windowLabel === comparisonEntry.windowLabel)) return; + usedSummaryRows.add(summaryIndex); + displayRows.push({ + ...summaryEntry, + rawVisible: true, + comparisonDerived: true, + grouped: true, + key: `comparison-summary-${comparisonIndex}-${summaryIndex}` + }); + }); + thresholdValues.forEach((thresholdEntry, thresholdIndex) => { + if (usedThresholdRows.has(thresholdIndex)) return; + if (thresholdEntry.comparisonParentId !== comparisonEntry.entityId && !(thresholdEntry.relatedEntityId === comparisonEntry.relatedEntityId && thresholdEntry.windowLabel === comparisonEntry.windowLabel)) return; + usedThresholdRows.add(thresholdIndex); + displayRows.push({ + ...thresholdEntry, + rawVisible: true, + comparisonDerived: true, + grouped: true, + key: `comparison-threshold-${comparisonIndex}-${thresholdIndex}` + }); + }); + }; + values.forEach((entry, index) => { + displayRows.push(entry); + trendValues.forEach((trendEntry, trendIndex) => { + if (usedTrendRows.has(trendIndex)) return; + const sameEntity = trendEntry.relatedEntityId && trendEntry.relatedEntityId === entry.entityId; + const sameLabel = !trendEntry.relatedEntityId && trendEntry.baseLabel && trendEntry.baseLabel === entry.label; + if (!sameEntity && !sameLabel) return; + usedTrendRows.add(trendIndex); + displayRows.push({ + ...trendEntry, + rawVisible: trendEntry.rawVisible !== false, + grouped: true, + key: `trend-${index}-${trendIndex}` + }); + }); + rateValues.forEach((rateEntry, rateIndex) => { + if (usedRateRows.has(rateIndex)) return; + const sameEntity = rateEntry.relatedEntityId && rateEntry.relatedEntityId === entry.entityId; + const sameLabel = !rateEntry.relatedEntityId && rateEntry.baseLabel && rateEntry.baseLabel === entry.label; + if (!sameEntity && !sameLabel) return; + usedRateRows.add(rateIndex); + displayRows.push({ + ...rateEntry, + rawVisible: rateEntry.rawVisible !== false, + grouped: true, + key: `rate-${index}-${rateIndex}` + }); + }); + deltaValues.forEach((deltaEntry, deltaIndex) => { + if (usedDeltaRows.has(deltaIndex)) return; + const sameEntity = deltaEntry.relatedEntityId && deltaEntry.relatedEntityId === entry.entityId; + const sameLabel = !deltaEntry.relatedEntityId && deltaEntry.baseLabel && deltaEntry.baseLabel === entry.label; + if (!sameEntity && !sameLabel) return; + usedDeltaRows.add(deltaIndex); + displayRows.push({ + ...deltaEntry, + rawVisible: deltaEntry.rawVisible !== false, + grouped: true, + key: `delta-${index}-${deltaIndex}` + }); + }); + summaryValues.forEach((summaryEntry, summaryIndex) => { + if (usedSummaryRows.has(summaryIndex)) return; + const sameEntity = summaryEntry.relatedEntityId && summaryEntry.relatedEntityId === entry.entityId; + const sameLabel = !summaryEntry.relatedEntityId && summaryEntry.baseLabel && summaryEntry.baseLabel === entry.label; + if (!sameEntity && !sameLabel) return; + usedSummaryRows.add(summaryIndex); + displayRows.push({ + ...summaryEntry, + rawVisible: summaryEntry.rawVisible !== false, + grouped: true, + key: `summary-${index}-${summaryIndex}` + }); + }); + thresholdValues.forEach((thresholdEntry, thresholdIndex) => { + if (usedThresholdRows.has(thresholdIndex)) return; + const sameEntity = thresholdEntry.relatedEntityId && thresholdEntry.relatedEntityId === entry.entityId; + const sameLabel = !thresholdEntry.relatedEntityId && thresholdEntry.baseLabel && thresholdEntry.baseLabel === entry.label; + if (!sameEntity && !sameLabel) return; + usedThresholdRows.add(thresholdIndex); + displayRows.push({ + ...thresholdEntry, + rawVisible: thresholdEntry.rawVisible !== false, + grouped: true, + key: `threshold-${index}-${thresholdIndex}` + }); + }); + comparisonValues.forEach((compEntry, compIndex) => { + if (usedComparisonRows.has(compIndex)) return; + if (!compEntry.relatedEntityId || compEntry.relatedEntityId !== entry.entityId) return; + usedComparisonRows.add(compIndex); + const groupedEntry = { + ...compEntry, + grouped: true, + comparison: true, + key: `comparison-${index}-${compIndex}` + }; + displayRows.push(groupedEntry); + pushComparisonDerivedRows(groupedEntry, compIndex); + }); + }); + trendValues.forEach((trendEntry, trendIndex) => { + if (usedTrendRows.has(trendIndex)) return; + if (trendEntry.comparisonDerived === true || typeof trendEntry.comparisonParentId === "string") return; + displayRows.push({ + ...trendEntry, + rawVisible: trendEntry.rawVisible !== false + }); + }); + rateValues.forEach((rateEntry, rateIndex) => { + if (usedRateRows.has(rateIndex)) return; + if (rateEntry.comparisonDerived === true || typeof rateEntry.comparisonParentId === "string") return; + displayRows.push({ + ...rateEntry, + rawVisible: rateEntry.rawVisible !== false + }); + }); + deltaValues.forEach((deltaEntry, deltaIndex) => { + if (usedDeltaRows.has(deltaIndex)) return; + displayRows.push({ + ...deltaEntry, + rawVisible: deltaEntry.rawVisible !== false + }); + }); + summaryValues.forEach((summaryEntry, summaryIndex) => { + if (usedSummaryRows.has(summaryIndex)) return; + if (summaryEntry.comparisonDerived === true || typeof summaryEntry.comparisonParentId === "string") return; + displayRows.push({ + ...summaryEntry, + rawVisible: summaryEntry.rawVisible !== false + }); + }); + thresholdValues.forEach((thresholdEntry, thresholdIndex) => { + if (usedThresholdRows.has(thresholdIndex)) return; + if (thresholdEntry.comparisonDerived === true || typeof thresholdEntry.comparisonParentId === "string") return; + displayRows.push({ + ...thresholdEntry, + rawVisible: thresholdEntry.rawVisible !== false + }); + }); + comparisonValues.forEach((compEntry, compIndex) => { + if (usedComparisonRows.has(compIndex)) return; + const groupedEntry = { + ...compEntry, + comparison: true + }; + displayRows.push(groupedEntry); + pushComparisonDerivedRows(groupedEntry, compIndex); + }); + displayRows.push(...binaryValues); + if (displayRows.length === 1 && trendValues.length === 0 && rateValues.length === 0 && deltaValues.length === 0 && summaryValues.length === 0 && thresholdValues.length === 0 && comparisonValues.length === 0 && binaryValues.length === 0 && displayRows[0]?.comparison !== true) { + const value = displayRows[0]; + ttValue.textContent = value ? formatTooltipDisplayValue(value.value, value.unit) : ""; + ttValue.style.display = value ? "block" : "none"; + if (ttSeries) { + ttSeries.innerHTML = ""; + ttSeries.style.display = "none"; + } + } else { + ttValue.textContent = ""; + ttValue.style.display = "none"; + if (ttSeries) { + ttSeries.innerHTML = displayRows.map((entry) => `
${entry.grouped === true && entry.rawVisible === true ? "" : ``} @@ -6740,1740 +8679,1417 @@ ${s2.description}`).join("\n\n");
${esc(formatTooltipDisplayValue(entry.value, entry.unit))}
- ` - ).join(""); - ttSeries.style.display = displayRows.length ? "grid" : "none"; - } - } - ttMessageRow.style.display = "none"; - ttMsg.textContent = ""; - ttAnn.textContent = ""; - ttAnn.style.display = "none"; - ttEntities.innerHTML = ""; - ttEntities.style.display = "none"; - if (anomalyTooltip && ttSecondaryTitle && ttSecondaryDescription && ttSecondaryAlert && ttSecondaryInstruction) { - const anomalyContent = buildAnomalyTooltipContent(hover.anomalyRegions); - if (anomalyContent) { - ttSecondaryTitle.textContent = anomalyContent.title; - ttSecondaryDescription.textContent = anomalyContent.description; - ttSecondaryAlert.textContent = anomalyContent.alert; - ttSecondaryInstruction.textContent = anomalyContent.instruction; - } else { - ttSecondaryTitle.textContent = ""; - ttSecondaryDescription.textContent = ""; - ttSecondaryAlert.textContent = ""; - ttSecondaryInstruction.textContent = ""; - anomalyTooltip.style.display = "none"; - } - } - const chartBounds = (root.querySelector(".chart-wrap") ?? card).getBoundingClientRect(); - positionTooltip(tooltip, clientX, clientY, toChartBounds(chartBounds)); - if (anomalyTooltip && (hover.anomalyRegions?.length ?? 0) > 0) { - positionAnomalyTooltip( - anomalyTooltip, - clientX, - clientY, - tooltip, - toChartBounds(chartBounds) - ); - } - if (Array.isArray(hover.events) && hover.events.length > 0) { - renderAnnotationTooltips(card, hover, tooltip, toChartBounds(chartBounds)); - } else { - clearAnnotationTooltips(card); - } - } - function buildTooltipRelatedChips(hass, event) { - const entities = Array.isArray(event?.entity_ids) ? event.entity_ids : []; - const devices = Array.isArray(event?.device_ids) ? event.device_ids : []; - const areas = Array.isArray(event?.area_ids) ? event.area_ids : []; - const labels = Array.isArray(event?.label_ids) ? event.label_ids : []; - const chips = [ - ...entities.map((id) => ({ - icon: entityIcon(hass, id), - label: entityName(hass, id) - })), - ...devices.map((id) => ({ - icon: deviceIcon(hass, id), - label: deviceName(hass, id) - })), - ...areas.map((id) => ({ - icon: areaIcon(hass, id), - label: areaName(hass, id) - })), - ...labels.map((id) => ({ - icon: labelIcon(hass, id), - label: labelName(hass, id) - })) - ].filter((chip) => chip.label); - if (!chips.length) return ""; - return chips.map( - (chip) => ` + `).join(""); + ttSeries.style.display = displayRows.length ? "grid" : "none"; + } + } + ttMessageRow.style.display = "none"; + ttMsg.textContent = ""; + ttAnn.textContent = ""; + ttAnn.style.display = "none"; + ttEntities.innerHTML = ""; + ttEntities.style.display = "none"; + if (anomalyTooltip && ttSecondaryTitle && ttSecondaryDescription && ttSecondaryAlert && ttSecondaryInstruction) { + const anomalyContent = buildAnomalyTooltipContent(hover.anomalyRegions); + if (anomalyContent) { + ttSecondaryTitle.textContent = anomalyContent.title; + ttSecondaryDescription.textContent = anomalyContent.description; + ttSecondaryAlert.textContent = anomalyContent.alert; + ttSecondaryInstruction.textContent = anomalyContent.instruction; + } else { + ttSecondaryTitle.textContent = ""; + ttSecondaryDescription.textContent = ""; + ttSecondaryAlert.textContent = ""; + ttSecondaryInstruction.textContent = ""; + anomalyTooltip.style.display = "none"; + } + } + const chartBounds = (root.querySelector(".chart-wrap") ?? card).getBoundingClientRect(); + positionTooltip(tooltip, clientX, clientY, toChartBounds(chartBounds)); + if (anomalyTooltip && (hover.anomalyRegions?.length ?? 0) > 0) positionAnomalyTooltip(anomalyTooltip, clientX, clientY, tooltip, toChartBounds(chartBounds)); + if (Array.isArray(hover.events) && hover.events.length > 0) renderAnnotationTooltips(card, hover, tooltip, toChartBounds(chartBounds)); + else clearAnnotationTooltips(card); + } + function buildTooltipRelatedChips(hass, event) { + const entities = Array.isArray(event?.entity_ids) ? event.entity_ids : []; + const devices = Array.isArray(event?.device_ids) ? event.device_ids : []; + const areas = Array.isArray(event?.area_ids) ? event.area_ids : []; + const labels = Array.isArray(event?.label_ids) ? event.label_ids : []; + const chips = [ + ...entities.map((id) => ({ + icon: entityIcon(hass, id), + label: entityName(hass, id) + })), + ...devices.map((id) => ({ + icon: deviceIcon(hass, id), + label: deviceName(hass, id) + })), + ...areas.map((id) => ({ + icon: areaIcon(hass, id), + label: areaName(hass, id) + })), + ...labels.map((id) => ({ + icon: labelIcon(hass, id), + label: labelName(hass, id) + })) + ].filter((chip) => chip.label); + if (!chips.length) return ""; + return chips.map((chip) => ` ${esc(chip.label)} - ` - ).join(""); - } - function showLineChartCrosshair(card, renderer, hover) { - const overlay = getRoot(card).getElementById("chart-crosshair"); - const vertical = getRoot(card).getElementById("crosshair-vertical"); - const horizontal = getRoot(card).getElementById("crosshair-horizontal"); - const points = getRoot(card).getElementById("crosshair-points"); - const addButton = getRoot(card).getElementById("chart-add-annotation"); - if (!overlay || !vertical || !horizontal || !points) return; - overlay.hidden = false; - vertical.style.left = `${hover.x}px`; - if (hover.splitVertical) { - vertical.style.top = `${hover.splitVertical.top}px`; - vertical.style.height = `${hover.splitVertical.height}px`; - } else { - vertical.style.top = `${renderer.pad.top}px`; - vertical.style.height = `${renderer.ch}px`; - } - horizontal.hidden = true; - const crosshairValues = [ - ...hover.values || [], - ...hover.showTrendCrosshairs === true ? (hover.trendValues || []).filter( - (entry) => entry.showCrosshair === true - ) : [], - ...hover.showRateCrosshairs === true ? (hover.rateValues || []).filter((entry) => entry.showCrosshair === true) : [], - ...hover.comparisonValues || [] - ]; - points.innerHTML = ` - ${crosshairValues.filter((entry) => entry.hasValue !== false).map( - (entry) => ` + `).join(""); + } + function showLineChartCrosshair(card, renderer, hover) { + const overlay = getRoot(card).getElementById("chart-crosshair"); + const vertical = getRoot(card).getElementById("crosshair-vertical"); + const horizontal = getRoot(card).getElementById("crosshair-horizontal"); + const points = getRoot(card).getElementById("crosshair-points"); + const addButton = getRoot(card).getElementById("chart-add-annotation"); + if (!overlay || !vertical || !horizontal || !points) return; + overlay.hidden = false; + vertical.style.left = `${hover.x}px`; + if (hover.splitVertical) { + vertical.style.top = `${hover.splitVertical.top}px`; + vertical.style.height = `${hover.splitVertical.height}px`; + } else { + vertical.style.top = `${renderer.pad.top}px`; + vertical.style.height = `${renderer.ch}px`; + } + horizontal.hidden = true; + const crosshairValues = [ + ...hover.values || [], + ...hover.showTrendCrosshairs === true ? (hover.trendValues || []).filter((entry) => entry.showCrosshair === true) : [], + ...hover.showRateCrosshairs === true ? (hover.rateValues || []).filter((entry) => entry.showCrosshair === true) : [], + ...hover.comparisonValues || [] + ]; + points.innerHTML = ` + ${crosshairValues.filter((entry) => entry.hasValue !== false).map((entry) => ` - ` - ).join("")} - ${crosshairValues.filter((entry) => entry.hasValue !== false).map( - (entry) => ` + `).join("")} + ${crosshairValues.filter((entry) => entry.hasValue !== false).map((entry) => ` - ` - ).join("")} + `).join("")} `; - renderChartAxisHoverDots(card, crosshairValues); - if (addButton && addButton.dataset.allowAddAnnotation !== "false") { - addButton.hidden = false; - addButton.style.left = `${hover.x}px`; - if (hover.splitVertical) { - addButton.style.top = `${hover.splitVertical.top + hover.splitVertical.height}px`; - } else { - addButton.style.top = `${renderer.pad.top + renderer.ch}px`; - } - } - } - function dispatchLineChartHover(card, hover) { - card.dispatchEvent( - new CustomEvent("hass-datapoints-chart-hover", { - bubbles: true, - composed: true, - detail: hover ? { timeMs: hover.timeMs } : { timeMs: null } - }) - ); + renderChartAxisHoverDots(card, crosshairValues); + if (addButton && addButton.dataset.allowAddAnnotation !== "false") { + addButton.hidden = false; + addButton.style.left = `${hover.x}px`; + if (hover.splitVertical) addButton.style.top = `${hover.splitVertical.top + hover.splitVertical.height}px`; + else addButton.style.top = `${renderer.pad.top + renderer.ch}px`; + } + } + function dispatchLineChartHover(card, hover) { + card.dispatchEvent(new CustomEvent("hass-datapoints-chart-hover", { + bubbles: true, + composed: true, + detail: hover ? { timeMs: hover.timeMs } : { timeMs: null } + })); + } + function findNearestSeriesPointTime(seriesPoints, timeMs) { + if (!Array.isArray(seriesPoints) || seriesPoints.length === 0) return null; + let lo = 0; + let hi = seriesPoints.length - 1; + while (lo + 1 < hi) { + const mid = Math.floor((lo + hi) / 2); + if (seriesPoints[mid][0] <= timeMs) lo = mid; + else hi = mid; + } + const left = seriesPoints[lo]?.[0]; + const right = seriesPoints[hi]?.[0]; + if (!Number.isFinite(left) && !Number.isFinite(right)) return null; + if (!Number.isFinite(left)) return right; + if (!Number.isFinite(right)) return left; + return Math.abs(left - timeMs) <= Math.abs(right - timeMs) ? left : right; + } + function resolveLineChartHoverTime(series, timeMs, mode = "follow_series") { + if (mode !== "snap_to_data_points") return timeMs; + let bestTime = null; + let bestDistance = Infinity; + for (const seriesItem of Array.isArray(series) ? series : []) { + const candidateTime = findNearestSeriesPointTime(seriesItem?.pts, timeMs); + if (candidateTime == null || !Number.isFinite(candidateTime)) continue; + const distance = Math.abs(candidateTime - timeMs); + if (distance < bestDistance) { + bestDistance = distance; + bestTime = candidateTime; + } + } + return bestTime != null && Number.isFinite(bestTime) ? bestTime : timeMs; + } + function hideLineChartHover(card) { + dispatchLineChartHover(card, null); + hideTooltip(card); + const overlay = getRoot(card).getElementById("chart-crosshair"); + const points = getRoot(card).getElementById("crosshair-points"); + const addButton = getRoot(card).getElementById("chart-add-annotation"); + if (overlay) overlay.hidden = true; + if (points) points.innerHTML = ""; + renderChartAxisHoverDots(card, []); + const horizontal = getRoot(card).getElementById("crosshair-horizontal"); + if (horizontal) horizontal.hidden = true; + if (addButton) addButton.hidden = true; + } + function attachLineChartHover(card, canvas, renderer, series, events, t0, t1, vMin, vMax, axes = null, options = {}) { + const interactionState = getInteractionState(card); + if (!canvas || !renderer) return; + if (interactionState._chartHoverCleanup) { + interactionState._chartHoverCleanup(); + interactionState._chartHoverCleanup = null; + } + const resolvedSeries = Array.isArray(series) ? series : []; + const eventThresholdMs = renderer.cw ? 14 * ((t1 - t0) / renderer.cw) : 0; + const binaryStates = Array.isArray(options.binaryStates) ? options.binaryStates : []; + const comparisonSeries = Array.isArray(options.comparisonSeries) ? options.comparisonSeries : []; + const trendSeries = Array.isArray(options.trendSeries) ? options.trendSeries : []; + const rateSeries = Array.isArray(options.rateSeries) ? options.rateSeries : []; + const deltaSeries = Array.isArray(options.deltaSeries) ? options.deltaSeries : []; + const summarySeries = Array.isArray(options.summarySeries) ? options.summarySeries : []; + const thresholdSeries = Array.isArray(options.thresholdSeries) ? options.thresholdSeries : []; + const anomalyRegions = Array.isArray(options.anomalyRegions) ? options.anomalyRegions : []; + if (!resolvedSeries.length && !binaryStates.length && !comparisonSeries.length && !trendSeries.length && !rateSeries.length && !deltaSeries.length && !summarySeries.length && !thresholdSeries.length && !anomalyRegions.length) return; + const hoverSurfaceEl = options.hoverSurfaceEl || null; + const addAnnotationButton = getRoot(card)?.getElementById("chart-add-annotation") || null; + const resolveHoverAxis = (seriesItem) => seriesItem.axis || axes && axes[0] || { + min: vMin, + max: vMax + }; + const buildHoverValueEntry = (seriesItem, value, axis, extra = {}, entryOpts = {}) => { + const hasNumericValue = typeof value === "number" && Number.isFinite(value); + const includePosition = entryOpts.includePosition === true && hasNumericValue; + return { + entityId: seriesItem.entityId || "", + comparisonParentId: seriesItem.comparisonParentId || "", + relatedEntityId: seriesItem.relatedEntityId || "", + label: seriesItem.label || seriesItem.entityId || "", + baseLabel: seriesItem.baseLabel || "", + windowLabel: seriesItem.windowLabel || "", + value: hasNumericValue ? value : value ?? null, + unit: seriesItem.unit || "", + color: seriesItem.color, + opacity: Number.isFinite(seriesItem.hoverOpacity) ? seriesItem.hoverOpacity : 1, + hasValue: hasNumericValue || value != null, + x: includePosition ? entryOpts.x : void 0, + y: includePosition ? renderer.yOf(value, axis.min, axis.max) : void 0, + axisSide: axis.side === "right" ? "right" : "left", + axisSlot: Number.isFinite(axis.slot) ? axis.slot : 0, + rawVisible: seriesItem.rawVisible !== false, + comparisonDerived: seriesItem.comparisonDerived === true, + showCrosshair: seriesItem.showCrosshair === true, + ...extra + }; + }; + const findAnomalyRegions = (clientX, clientY) => { + const rect = canvas.getBoundingClientRect(); + if (!rect.width || !rect.height) return []; + const localX = clientX - rect.left; + const localY = clientY - rect.top; + const hits = []; + for (const region of anomalyRegions) { + const radiusX = Number(region?.radiusX) || 0; + const radiusY = Number(region?.radiusY) || 0; + if (radiusX <= 0 || radiusY <= 0) continue; + const dx = (localX - region.centerX) / radiusX; + const dy = (localY - region.centerY) / radiusY; + if (dx * dx + dy * dy <= 1) hits.push(region); + } + return hits; + }; + const buildHoverState = (clientX, clientY) => { + const rect = canvas.getBoundingClientRect(); + if (!rect.width || !rect.height || !renderer.cw || !renderer.ch) return null; + const localX = clampChartValue(clientX - rect.left, renderer.pad.left, renderer.pad.left + renderer.cw); + const localY = clampChartValue(clientY - rect.top, renderer.pad.top, renderer.pad.top + renderer.ch); + const timeMs = resolveLineChartHoverTime(resolvedSeries, t0 + (renderer.cw ? (localX - renderer.pad.left) / renderer.cw : 0) * (t1 - t0), options.hoverSnapMode || "follow_series"); + const x = renderer.xOf(timeMs, t0, t1); + const values = resolvedSeries.map((seriesItem) => { + const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); + return buildHoverValueEntry(seriesItem, value, resolveHoverAxis(seriesItem), {}, { + includePosition: value != null, + x + }); + }); + const comparisonValues = comparisonSeries.map((seriesItem) => { + const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); + return buildHoverValueEntry(seriesItem, value, resolveHoverAxis(seriesItem), { comparison: true }, { + includePosition: value != null, + x + }); + }); + const trendValues = trendSeries.map((seriesItem) => { + const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); + return buildHoverValueEntry(seriesItem, value, resolveHoverAxis(seriesItem), { trend: true }, { + includePosition: value != null, + x + }); + }); + const rateValues = rateSeries.map((seriesItem) => { + const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); + return buildHoverValueEntry(seriesItem, value, resolveHoverAxis(seriesItem), { rate: true }, { + includePosition: value != null, + x + }); + }); + const deltaValues = deltaSeries.map((seriesItem) => { + const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); + return buildHoverValueEntry(seriesItem, value, resolveHoverAxis(seriesItem), { delta: true }, { + includePosition: value != null, + x + }); + }); + const summaryValues = summarySeries.map((seriesItem) => { + const axis = resolveHoverAxis(seriesItem); + return buildHoverValueEntry(seriesItem, Number(seriesItem.value), axis, { + summary: true, + summaryType: seriesItem.summaryType || "" + }); + }); + const thresholdValues = thresholdSeries.map((seriesItem) => { + const axis = resolveHoverAxis(seriesItem); + return buildHoverValueEntry(seriesItem, Number(seriesItem.value), axis, { threshold: true }); + }); + const plottedValues = [ + ...values.filter((entry) => entry?.hasValue !== false), + ...comparisonValues.filter((entry) => entry?.hasValue !== false), + ...rateValues.filter((entry) => entry?.hasValue !== false), + ...options.showTrendCrosshairs === true ? trendValues.filter((entry) => entry?.hasValue !== false && entry.showCrosshair === true) : [] + ]; + let rangeStartMs = timeMs; + let rangeEndMs = timeMs; + let primary = plottedValues[0] || null; + if (primary) { + for (const entry of plottedValues) if (Number.isFinite(entry.y) && Number.isFinite(primary.y) && Math.abs(entry.y - localY) < Math.abs(primary.y - localY)) primary = entry; + } + const activePrimarySeries = primary ? resolvedSeries.find((seriesItem) => seriesItem.entityId === primary.entityId) || null : null; + if (activePrimarySeries?.pts?.length) { + const pts = activePrimarySeries.pts; + const pLen = pts.length; + let lo = 0; + let hi = pLen - 1; + let previousIndex = -1; + if (pts[0][0] <= timeMs) { + while (lo + 1 < hi) { + const mid = Math.floor((lo + hi) / 2); + if (pts[mid][0] <= timeMs) lo = mid; + else hi = mid; + } + previousIndex = pts[hi][0] <= timeMs ? hi : lo; + } + const nextIndex = previousIndex < pLen - 1 ? previousIndex + 1 : -1; + const previous = previousIndex >= 0 ? pts[previousIndex] : null; + let next = null; + if (nextIndex >= 0) next = pts[nextIndex]; + else if (previousIndex < 0) next = pts[0]; + if (previous && next) { + const prevPrev = pts[Math.max(0, previousIndex - 1)] || previous; + const nextNext = pts[Math.min(pLen - 1, nextIndex + 1)] || next; + rangeStartMs = previous === next ? previous[0] : Math.round((previous[0] + prevPrev[0]) / 2); + rangeEndMs = previous === next ? next[0] : Math.round((next[0] + nextNext[0]) / 2); + } else if (previous) { + rangeStartMs = previous[0]; + rangeEndMs = previous[0]; + } else if (next) { + rangeStartMs = next[0]; + rangeEndMs = next[0]; + } + } + const binaryValues = binaryStates.map((entry) => { + const activeSpan = (entry.spans || []).find((span) => timeMs >= span.start && timeMs <= span.end); + return { + entityId: entry.entityId || "", + label: entry.label || entry.entityId || "", + value: activeSpan ? entry.onLabel || "on" : entry.offLabel || "off", + unit: "", + color: entry.color, + hasValue: true, + active: !!activeSpan + }; + }).filter((entry) => Boolean(entry.label)); + if (!values.length && !binaryValues.length && !trendValues.length && !rateValues.length && !deltaValues.length && !summaryValues.length && !thresholdValues.length && !comparisonValues.length) return null; + const fallbackY = renderer.pad.top + 12; + const hoverY = primary ? primary.y : fallbackY; + const hoveredEvents = []; + for (const event of events || []) { + const eventTime = new Date(event.timestamp).getTime(); + if (eventTime < t0 || eventTime > t1) continue; + const distance = Math.abs(eventTime - timeMs); + if (distance <= eventThresholdMs) hoveredEvents.push({ + ...event, + _hoverDistanceMs: distance + }); + } + hoveredEvents.sort((left, right) => { + const distanceDelta = (left._hoverDistanceMs || 0) - (right._hoverDistanceMs || 0); + if (distanceDelta !== 0) return distanceDelta; + return new Date(left.timestamp).getTime() - new Date(right.timestamp).getTime(); + }); + const normalizedHoveredEvents = hoveredEvents.map((event) => { + const { _hoverDistanceMs: _, ...normalizedEvent } = event; + return normalizedEvent; + }); + return { + x, + y: hoverY, + timeMs, + rangeStartMs, + rangeEndMs, + values, + trendValues, + rateValues, + deltaValues: options.showDeltaTooltip === true ? deltaValues : [], + summaryValues, + thresholdValues, + comparisonValues, + binaryValues, + primary, + event: normalizedHoveredEvents[0] || null, + events: normalizedHoveredEvents, + emphasizeGuides: options.emphasizeHoverGuides === true, + showTrendCrosshairs: options.showTrendCrosshairs === true, + showRateCrosshairs: options.showRateCrosshairs === true, + hideRawData: options.hideRawData === true + }; + }; + const showFromPointer = (clientX, clientY) => { + if (interactionState._chartZoomDragging) return; + const anomalyRegionsHit = findAnomalyRegions(clientX, clientY); + const hover = buildHoverState(clientX, clientY); + if (!hover) { + interactionState._chartLastHover = null; + hideLineChartHover(card); + canvas.style.cursor = "default"; + return; + } + hover.anomalyRegions = anomalyRegionsHit; + interactionState._chartLastHover = hover; + showLineChartCrosshair(card, renderer, hover); + if (options.showTooltip !== false || Array.isArray(hover.events) && hover.events.length > 0) showLineChartTooltip(card, hover, clientX, clientY); + else hideTooltip(card); + dispatchLineChartHover(card, hover); + canvas.style.cursor = anomalyRegionsHit.length > 0 ? "pointer" : "crosshair"; + }; + const hideHover = () => { + interactionState._chartLastHover = null; + hideLineChartHover(card); + canvas.style.cursor = "default"; + }; + let _rafHandle = null; + let _pendingX = 0; + let _pendingY = 0; + const onMouseMove = (ev) => { + _pendingX = ev.clientX; + _pendingY = ev.clientY; + if (_rafHandle !== null) return; + _rafHandle = requestAnimationFrame(() => { + _rafHandle = null; + showFromPointer(_pendingX, _pendingY); + }); + }; + const onMouseLeave = (ev) => { + const nextTarget = ev.relatedTarget; + if (nextTarget instanceof Node && hoverSurfaceEl && hoverSurfaceEl.contains(nextTarget)) return; + if (nextTarget instanceof Node && addAnnotationButton && addAnnotationButton.contains(nextTarget)) return; + hideHover(); + }; + const onOverlayMove = (ev) => { + showFromPointer(ev.clientX, ev.clientY); + }; + const onOverlayLeave = (ev) => { + const nextTarget = ev.relatedTarget; + if (nextTarget instanceof Node && canvas.contains(nextTarget)) return; + if (nextTarget instanceof Node && addAnnotationButton && addAnnotationButton.contains(nextTarget)) return; + hideHover(); + }; + const onAddButtonLeave = (ev) => { + const nextTarget = ev.relatedTarget; + if (nextTarget instanceof Node && (canvas.contains(nextTarget) || hoverSurfaceEl && hoverSurfaceEl.contains(nextTarget))) return; + hideHover(); + }; + const onAddButtonClick = (ev) => { + if (typeof options.onAddAnnotation !== "function" || !interactionState._chartLastHover) return; + ev.preventDefault(); + ev.stopPropagation(); + options.onAddAnnotation(interactionState._chartLastHover, ev); + }; + const onContextMenu = (ev) => { + if (typeof options.onContextMenu !== "function") return; + const hover = buildHoverState(ev.clientX, ev.clientY); + if (!hover) return; + ev.preventDefault(); + interactionState._chartLastHover = hover; + showLineChartCrosshair(card, renderer, hover); + showLineChartTooltip(card, hover, ev.clientX, ev.clientY); + dispatchLineChartHover(card, hover); + options.onContextMenu(hover, ev); + }; + const onClick = (ev) => { + if (typeof options.onAnomalyClick !== "function") return; + const regions = findAnomalyRegions(ev.clientX, ev.clientY); + if (!regions.length) return; + ev.preventDefault(); + ev.stopPropagation(); + options.onAnomalyClick(regions, ev); + }; + let touchTimer = null; + const scheduleTouchHide = () => { + if (touchTimer) window.clearTimeout(touchTimer); + touchTimer = window.setTimeout(() => hideHover(), 1800); + }; + const onTouchStart = (ev) => { + ev.preventDefault(); + const touch = ev.touches[0]; + if (!touch) return; + showFromPointer(touch.clientX, touch.clientY); + scheduleTouchHide(); + }; + const onTouchMove = (ev) => { + ev.preventDefault(); + const touch = ev.touches[0]; + if (!touch) return; + showFromPointer(touch.clientX, touch.clientY); + scheduleTouchHide(); + }; + const onTouchEnd = () => scheduleTouchHide(); + canvas.addEventListener("mousemove", onMouseMove); + canvas.addEventListener("mouseleave", onMouseLeave); + canvas.addEventListener("click", onClick); + canvas.addEventListener("contextmenu", onContextMenu); + canvas.addEventListener("touchstart", onTouchStart, { passive: false }); + canvas.addEventListener("touchmove", onTouchMove, { passive: false }); + canvas.addEventListener("touchend", onTouchEnd); + canvas.addEventListener("touchcancel", onTouchEnd); + hoverSurfaceEl?.addEventListener("mousemove", onOverlayMove); + hoverSurfaceEl?.addEventListener("mouseleave", onOverlayLeave); + addAnnotationButton?.addEventListener("mouseleave", onAddButtonLeave); + addAnnotationButton?.addEventListener("click", onAddButtonClick); + interactionState._chartHoverCleanup = () => { + canvas.removeEventListener("mousemove", onMouseMove); + canvas.removeEventListener("mouseleave", onMouseLeave); + canvas.removeEventListener("click", onClick); + canvas.removeEventListener("contextmenu", onContextMenu); + canvas.removeEventListener("touchstart", onTouchStart); + canvas.removeEventListener("touchmove", onTouchMove); + canvas.removeEventListener("touchend", onTouchEnd); + canvas.removeEventListener("touchcancel", onTouchEnd); + hoverSurfaceEl?.removeEventListener("mousemove", onOverlayMove); + hoverSurfaceEl?.removeEventListener("mouseleave", onOverlayLeave); + addAnnotationButton?.removeEventListener("mouseleave", onAddButtonLeave); + addAnnotationButton?.removeEventListener("click", onAddButtonClick); + if (_rafHandle !== null) { + cancelAnimationFrame(_rafHandle); + _rafHandle = null; + } + if (touchTimer) { + window.clearTimeout(touchTimer); + touchTimer = null; + } + hideHover(); + }; + } + function attachLineChartRangeZoom(card, canvas, renderer, t0, t1, options = {}) { + const interactionState = getInteractionState(card); + if (!canvas || !renderer) return; + if (interactionState._chartZoomCleanup) { + interactionState._chartZoomCleanup(); + interactionState._chartZoomCleanup = null; + } + const selection = getRoot(card).getElementById("chart-zoom-selection"); + if (!selection) return; + let pointerId = null; + let startX = 0; + let currentX = 0; + let dragging = false; + const hideSelection = () => { + selection.hidden = true; + selection.classList.remove("visible"); + }; + const clientXToTime = (clientX) => { + const localX = clampChartValue(clientX - canvas.getBoundingClientRect().left, renderer.pad.left, renderer.pad.left + renderer.cw); + return t0 + (renderer.cw ? (localX - renderer.pad.left) / renderer.cw : 0) * (t1 - t0); + }; + const inPlotBounds = (clientX, clientY) => { + const rect = canvas.getBoundingClientRect(); + const localX = clientX - rect.left; + const localY = clientY - rect.top; + return localX >= renderer.pad.left && localX <= renderer.pad.left + renderer.cw && localY >= renderer.pad.top && localY <= renderer.pad.top + renderer.ch; + }; + const renderSelection = () => { + const left = Math.min(startX, currentX); + const width = Math.abs(currentX - startX); + selection.style.left = `${left}px`; + selection.style.top = `${renderer.pad.top}px`; + selection.style.width = `${width}px`; + selection.style.height = `${renderer.ch}px`; + selection.hidden = false; + selection.classList.add("visible"); + }; + const emitPreview = () => { + if (!dragging || Math.abs(currentX - startX) < 8) { + options.onPreview?.(null); + return; + } + const rectLeft = canvas.getBoundingClientRect().left; + const startTime = Math.min(clientXToTime(rectLeft + startX), clientXToTime(rectLeft + currentX)); + const endTime = Math.max(clientXToTime(rectLeft + startX), clientXToTime(rectLeft + currentX)); + options.onPreview?.({ + startTime, + endTime + }); + }; + const resetDragging = (clearPreview = true) => { + pointerId = null; + dragging = false; + interactionState._chartZoomDragging = false; + hideSelection(); + if (clearPreview) options.onPreview?.(null); + }; + const onPointerMove = (ev) => { + if (pointerId == null || ev.pointerId !== pointerId) return; + currentX = clampChartValue(ev.clientX - canvas.getBoundingClientRect().left, renderer.pad.left, renderer.pad.left + renderer.cw); + const movedPx = Math.abs(currentX - startX); + if (!dragging && movedPx < 6) return; + dragging = true; + interactionState._chartZoomDragging = true; + hideLineChartHover(card); + renderSelection(); + emitPreview(); + ev.preventDefault(); + }; + const finish = (ev) => { + if (pointerId == null || ev.pointerId !== pointerId) return; + const didDrag = dragging; + const endX = currentX; + window.removeEventListener("pointermove", onPointerMove); + window.removeEventListener("pointerup", finish); + window.removeEventListener("pointercancel", finish); + if (!didDrag || Math.abs(endX - startX) < 8) { + resetDragging(true); + return; + } + const rectLeft = canvas.getBoundingClientRect().left; + const startTime = Math.min(clientXToTime(rectLeft + startX), clientXToTime(rectLeft + endX)); + const endTime = Math.max(clientXToTime(rectLeft + startX), clientXToTime(rectLeft + endX)); + options.onZoom?.({ + startTime, + endTime + }); + resetDragging(false); + }; + const onPointerDown = (ev) => { + if (ev.button !== 0 || !inPlotBounds(ev.clientX, ev.clientY)) return; + pointerId = ev.pointerId; + const rect = canvas.getBoundingClientRect(); + startX = clampChartValue(ev.clientX - rect.left, renderer.pad.left, renderer.pad.left + renderer.cw); + currentX = startX; + dragging = false; + interactionState._chartZoomDragging = false; + options.onPreview?.(null); + window.addEventListener("pointermove", onPointerMove); + window.addEventListener("pointerup", finish); + window.addEventListener("pointercancel", finish); + }; + const onDoubleClick = (ev) => { + if (!inPlotBounds(ev.clientX, ev.clientY)) return; + if (!options.onReset) return; + ev.preventDefault(); + options.onReset(); + }; + canvas.addEventListener("pointerdown", onPointerDown); + canvas.addEventListener("dblclick", onDoubleClick); + interactionState._chartZoomCleanup = () => { + canvas.removeEventListener("pointerdown", onPointerDown); + canvas.removeEventListener("dblclick", onDoubleClick); + window.removeEventListener("pointermove", onPointerMove); + window.removeEventListener("pointerup", finish); + window.removeEventListener("pointercancel", finish); + resetDragging(); + }; + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/data/cache.ts + /** + * Shared cache utilities for history/statistics/event lookups. + */ + var DATA_RANGE_CACHE_TTL_MS = 600 * 1e3; + var DATA_RANGE_CACHE_LIVE_EDGE_MS = 300 * 1e3; + var dataRangeCache = /* @__PURE__ */ new Map(); + function normalizeCacheIdList(values) { + return [...new Set((Array.isArray(values) ? values : []).filter(Boolean))].sort(); + } + function shouldUseStableRangeCache(endTime) { + const endMs = new Date(endTime || 0).getTime(); + if (!Number.isFinite(endMs)) return false; + return endMs < Date.now() - DATA_RANGE_CACHE_LIVE_EDGE_MS; + } + function getCachedRangePromise(key) { + const entry = dataRangeCache.get(key); + if (!entry) return null; + if (entry.expiresAt <= Date.now()) { + dataRangeCache.delete(key); + return null; + } + return entry.promise; + } + function setCachedRangePromise(key, promise) { + dataRangeCache.set(key, { + promise, + expiresAt: Date.now() + DATA_RANGE_CACHE_TTL_MS + }); + return promise; + } + function withStableRangeCache(key, endTime, loader) { + if (!shouldUseStableRangeCache(endTime)) return Promise.resolve().then(loader); + const cached = getCachedRangePromise(key); + if (cached) return cached; + return setCachedRangePromise(key, Promise.resolve().then(loader).catch((err) => { + dataRangeCache.delete(key); + throw err; + })); + } + function clearStableRangeCacheMatching(predicate) { + if (typeof predicate !== "function") return 0; + let deletedCount = 0; + [...dataRangeCache.keys()].forEach((key) => { + if (predicate(key) === true) { + dataRangeCache.delete(key); + deletedCount += 1; + } + }); + return deletedCount; + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/data/history-api.ts + var MAX_DOWNSAMPLED_HISTORY_RANGE_MS = 2160 * 60 * 60 * 1e3; + function parseIsoTimeMs(value) { + const timeMs = Date.parse(value); + if (!Number.isFinite(timeMs)) return null; + return timeMs; + } + function buildDownsampledHistoryChunks(startTime, endTime) { + const startTimeMs = parseIsoTimeMs(startTime); + const endTimeMs = parseIsoTimeMs(endTime); + if (startTimeMs == null || endTimeMs == null || endTimeMs <= startTimeMs || endTimeMs - startTimeMs <= MAX_DOWNSAMPLED_HISTORY_RANGE_MS) return [{ + startTime, + endTime + }]; + const chunks = []; + let chunkStartMs = startTimeMs; + while (chunkStartMs < endTimeMs) { + const chunkEndMs = Math.min(endTimeMs, chunkStartMs + MAX_DOWNSAMPLED_HISTORY_RANGE_MS); + chunks.push({ + startTime: new Date(chunkStartMs).toISOString(), + endTime: new Date(chunkEndMs).toISOString() + }); + if (chunkEndMs >= endTimeMs) break; + chunkStartMs = chunkEndMs + 1; + } + return chunks; + } + function fetchDownsampledHistory(hass, entityId, startTime, endTime, interval, aggregate) { + return withStableRangeCache(JSON.stringify({ + type: "hass_datapoints/history", + entity_id: entityId, + start_time: startTime, + end_time: endTime, + interval, + aggregate + }), endTime, async () => { + const chunks = buildDownsampledHistoryChunks(startTime, endTime); + const mergedPoints = (await Promise.all(chunks.map(async (chunk) => hass.connection.sendMessagePromise({ + type: "hass_datapoints/history", + entity_id: entityId, + start_time: chunk.startTime, + end_time: chunk.endTime, + interval, + aggregate + })))).flatMap((result) => result.pts || []); + if (!mergedPoints.length) return []; + const dedupedPoints = /* @__PURE__ */ new Map(); + for (const point of mergedPoints) if (Array.isArray(point) && point.length > 0) dedupedPoints.set(String(point[0]), point); + else dedupedPoints.set(JSON.stringify(point), point); + return [...dedupedPoints.values()]; + }); + } + function fetchAnomaliesFromBackend(hass, entityId, startTime, endTime, config) { + return hass.connection.sendMessagePromise({ + type: "hass_datapoints/anomalies", + entity_id: entityId, + start_time: startTime, + end_time: endTime, + anomaly_methods: config.anomaly_methods || [], + anomaly_sensitivity: config.anomaly_sensitivity || "medium", + anomaly_overlap_mode: config.anomaly_overlap_mode || "all", + anomaly_rate_window: config.anomaly_rate_window || "1h", + anomaly_zscore_window: config.anomaly_zscore_window || "24h", + anomaly_persistence_window: config.anomaly_persistence_window || "1h", + trend_method: config.trend_method || "rolling_average", + trend_window: config.trend_window || "24h", + ...config.anomaly_use_sampled_data !== false && config.sample_interval && config.sample_interval !== "raw" ? { + sample_interval: config.sample_interval, + sample_aggregate: config.sample_aggregate || "mean" + } : {}, + ...config.comparison_entity_id ? { + comparison_entity_id: config.comparison_entity_id, + comparison_start_time: config.comparison_start_time, + comparison_end_time: config.comparison_end_time, + comparison_time_offset_ms: config.comparison_time_offset_ms || 0 + } : {} + }).then((result) => result.anomaly_clusters || []); + } + async function fetchHistoryDuringPeriod(hass, startTime, endTime, entityIds, options = {}) { + const normalizedEntityIds = normalizeCacheIdList(entityIds); + return withStableRangeCache(JSON.stringify({ + type: "history/history_during_period", + start_time: startTime, + end_time: endTime, + entity_ids: normalizedEntityIds, + include_start_time_state: options.include_start_time_state !== false, + significant_changes_only: !!options.significant_changes_only, + no_attributes: options.no_attributes !== false + }), endTime, () => hass.connection.sendMessagePromise({ + type: "history/history_during_period", + start_time: startTime, + end_time: endTime, + entity_ids: normalizedEntityIds, + include_start_time_state: options.include_start_time_state !== false, + significant_changes_only: !!options.significant_changes_only, + no_attributes: options.no_attributes !== false + })); + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/domain/history-series.ts + var VALID_ANOMALY_METHODS = [ + "trend_residual", + "rate_of_change", + "iqr", + "rolling_zscore", + "persistence", + "comparison_window" + ]; + var VALID_SAMPLE_INTERVALS = [ + "raw", + "1s", + "5s", + "10s", + "15s", + "30s", + "1m", + "2m", + "5m", + "10m", + "15m", + "30m", + "1h", + "2h", + "3h", + "4h", + "6h", + "12h", + "24h" + ]; + var VALID_SAMPLE_AGGREGATES = [ + "mean", + "min", + "max", + "median", + "first", + "last" + ]; + function normalizeHistorySeriesAnalysis(analysis) { + const source = analysis && typeof analysis === "object" ? analysis : {}; + return { + expanded: source.expanded === true, + show_trend_lines: source.show_trend_lines === true, + trend_method: source.trend_method === "linear_trend" ? "linear_trend" : "rolling_average", + trend_window: typeof source.trend_window === "string" && source.trend_window ? source.trend_window : "24h", + show_trend_crosshairs: source.show_trend_crosshairs !== false, + show_summary_stats: source.show_summary_stats === true, + show_summary_stats_shading: source.show_summary_stats_shading === true, + show_rate_of_change: source.show_rate_of_change === true, + show_rate_crosshairs: source.show_rate_crosshairs !== false, + rate_window: typeof source.rate_window === "string" && source.rate_window ? source.rate_window : "1h", + show_threshold_analysis: source.show_threshold_analysis === true, + show_threshold_shading: source.show_threshold_shading === true, + threshold_value: typeof source.threshold_value === "string" || typeof source.threshold_value === "number" ? String(source.threshold_value).trim() : "", + threshold_direction: source.threshold_direction === "below" ? "below" : "above", + show_anomalies: source.show_anomalies === true, + anomaly_methods: (() => { + if (Array.isArray(source.anomaly_methods)) return source.anomaly_methods.filter((method) => typeof method === "string" && VALID_ANOMALY_METHODS.includes(method)); + const legacy = typeof source.anomaly_method === "string" && VALID_ANOMALY_METHODS.includes(source.anomaly_method) ? source.anomaly_method : null; + return legacy ? [legacy] : []; + })(), + anomaly_overlap_mode: source.anomaly_overlap_mode === "only" ? "only" : "all", + anomaly_sensitivity: typeof source.anomaly_sensitivity === "string" && source.anomaly_sensitivity ? source.anomaly_sensitivity : "medium", + anomaly_rate_window: typeof source.anomaly_rate_window === "string" && source.anomaly_rate_window ? source.anomaly_rate_window : "1h", + anomaly_zscore_window: typeof source.anomaly_zscore_window === "string" && source.anomaly_zscore_window ? source.anomaly_zscore_window : "24h", + anomaly_persistence_window: typeof source.anomaly_persistence_window === "string" && source.anomaly_persistence_window ? source.anomaly_persistence_window : "1h", + anomaly_comparison_window_id: typeof source.anomaly_comparison_window_id === "string" && source.anomaly_comparison_window_id ? source.anomaly_comparison_window_id : null, + show_delta_analysis: source.show_delta_analysis === true, + show_delta_tooltip: source.show_delta_tooltip !== false, + show_delta_lines: source.show_delta_lines === true, + hide_source_series: source.hide_source_series === true, + sample_interval: typeof source.sample_interval === "string" && VALID_SAMPLE_INTERVALS.includes(source.sample_interval) ? source.sample_interval : "1m", + sample_aggregate: typeof source.sample_aggregate === "string" && VALID_SAMPLE_AGGREGATES.includes(source.sample_aggregate) ? source.sample_aggregate : "mean", + stepped_series: source.stepped_series === true, + anomaly_use_sampled_data: source.anomaly_use_sampled_data !== false + }; + } + function normalizeHistorySeriesRows(rows) { + if (!Array.isArray(rows)) return []; + const seen = /* @__PURE__ */ new Set(); + const normalized = []; + rows.forEach((row, index) => { + const entityId = typeof row?.entity_id === "string" ? row.entity_id.trim() : ""; + if (!entityId || seen.has(entityId)) return; + seen.add(entityId); + normalized.push({ + entity_id: entityId, + color: typeof row?.color === "string" && /^#[0-9a-f]{6}$/i.test(row.color) ? row.color : COLORS[index % COLORS.length], + visible: row?.visible !== false, + analysis: normalizeHistorySeriesAnalysis(row?.analysis) + }); + }); + return normalized; + } + function buildHistorySeriesRows(entityIds, previousRows = []) { + const normalizedPrevious = normalizeHistorySeriesRows(previousRows); + const previousMap = new Map(normalizedPrevious.map((row) => [row.entity_id, row])); + const intervals = normalizedPrevious.map((row) => row.analysis.sample_interval); + const inheritedSampleSettings = intervals.length > 0 && intervals.every((interval) => interval === intervals[0]) ? { + sample_interval: intervals[0], + sample_aggregate: normalizedPrevious[0].analysis.sample_aggregate + } : null; + return normalizeEntityIds(entityIds).map((entityId, index) => { + const existing = previousMap.get(entityId); + if (existing) return existing; + return { + entity_id: entityId, + color: COLORS[index % COLORS.length], + visible: true, + analysis: normalizeHistorySeriesAnalysis(inheritedSampleSettings) + }; + }); + } + function slugifySeriesName(value) { + return String(value || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); + } + function parseSeriesColorsParam(value) { + if (!value || typeof value !== "string") return {}; + return value.split(",").reduce((acc, entry) => { + const [rawKey, rawColor] = entry.split(":"); + const key = decodeURIComponent(rawKey || "").trim(); + const color = String(rawColor || "").trim(); + if (!key || !/^#[0-9a-f]{6}$/i.test(color)) return acc; + acc[key] = color; + return acc; + }, {}); + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/workers/history-analysis.worker.ts?worker&inline + var jsContent$1 = "(function() {\n //#region custom_components/hass_datapoints/src/lib/workers/history-analysis.worker.ts\n const HOUR_MS = 3600 * 1e3;\n function getTrendWindowMs(value) {\n const windows = {\n \"1h\": 3600 * 1e3,\n \"6h\": 360 * 60 * 1e3,\n \"24h\": 1440 * 60 * 1e3,\n \"7d\": 10080 * 60 * 1e3,\n \"14d\": 336 * 60 * 60 * 1e3,\n \"21d\": 504 * 60 * 60 * 1e3,\n \"28d\": 672 * 60 * 60 * 1e3\n };\n return windows[value] || windows[\"24h\"];\n }\n function buildRollingAverageTrend(points, windowMs) {\n if (!Array.isArray(points) || points.length < 2 || !Number.isFinite(windowMs) || windowMs <= 0) return [];\n const trendPoints = [];\n let windowStartIndex = 0;\n let windowSum = 0;\n for (let index = 0; index < points.length; index += 1) {\n const [time, value] = points[index];\n windowSum += value;\n while (windowStartIndex < index && time - points[windowStartIndex][0] > windowMs) {\n windowSum -= points[windowStartIndex][1];\n windowStartIndex += 1;\n }\n const count = index - windowStartIndex + 1;\n if (count > 0) trendPoints.push([time, windowSum / count]);\n }\n return trendPoints;\n }\n function buildLinearTrend(points) {\n if (!Array.isArray(points) || points.length < 2) return [];\n const origin = points[0][0];\n let sumX = 0;\n let sumY = 0;\n let sumXX = 0;\n let sumXY = 0;\n for (const [time, value] of points) {\n const x = (time - origin) / HOUR_MS;\n sumX += x;\n sumY += value;\n sumXX += x * x;\n sumXY += x * value;\n }\n const count = points.length;\n const denominator = count * sumXX - sumX * sumX;\n if (!Number.isFinite(denominator) || Math.abs(denominator) < 1e-9) return [];\n const slope = (count * sumXY - sumX * sumY) / denominator;\n const intercept = (sumY - slope * sumX) / count;\n const firstTime = points[0][0];\n const lastTime = points[points.length - 1][0];\n const firstX = (firstTime - origin) / HOUR_MS;\n const lastX = (lastTime - origin) / HOUR_MS;\n return [[firstTime, intercept + slope * firstX], [lastTime, intercept + slope * lastX]];\n }\n function buildTrendPoints(points, method, trendWindow) {\n if (!Array.isArray(points) || points.length < 2) return [];\n if (method === \"linear_trend\") return buildLinearTrend(points);\n return buildRollingAverageTrend(points, getTrendWindowMs(trendWindow));\n }\n function normalizeSeriesAnalysis(analysis) {\n const source = analysis && typeof analysis === \"object\" ? analysis : {};\n return {\n show_trend_lines: source.show_trend_lines === true,\n trend_method: source.trend_method === \"linear_trend\" ? \"linear_trend\" : \"rolling_average\",\n trend_window: typeof source.trend_window === \"string\" && source.trend_window ? source.trend_window : \"24h\",\n show_summary_stats: source.show_summary_stats === true,\n show_rate_of_change: source.show_rate_of_change === true,\n rate_window: typeof source.rate_window === \"string\" && source.rate_window ? source.rate_window : \"1h\",\n show_delta_analysis: source.show_delta_analysis === true\n };\n }\n function interpolateSeriesValue(points, timeMs) {\n if (!Array.isArray(points) || points.length === 0) return null;\n if (timeMs < points[0][0] || timeMs > points[points.length - 1][0]) return null;\n if (timeMs === points[0][0]) return points[0][1];\n if (timeMs === points[points.length - 1][0]) return points[points.length - 1][1];\n for (let index = 0; index < points.length - 1; index += 1) {\n const [startTime, startValue] = points[index];\n const [endTime, endValue] = points[index + 1];\n if (timeMs >= startTime && timeMs <= endTime) {\n const fraction = (timeMs - startTime) / (endTime - startTime);\n return startValue + (endValue - startValue) * fraction;\n }\n }\n return null;\n }\n function buildRateOfChangePoints(points, rateWindow) {\n if (!Array.isArray(points) || points.length < 2) return [];\n const ratePoints = [];\n for (let index = 1; index < points.length; index += 1) {\n const [timeMs, value] = points[index];\n let comparisonPoint = null;\n if (rateWindow === \"point_to_point\") comparisonPoint = points[index - 1];\n else {\n const windowMs = getTrendWindowMs(rateWindow);\n if (!Number.isFinite(windowMs) || windowMs <= 0) continue;\n for (let candidateIndex = index - 1; candidateIndex >= 0; candidateIndex -= 1) {\n const candidatePoint = points[candidateIndex];\n if (timeMs - candidatePoint[0] >= windowMs) {\n comparisonPoint = candidatePoint;\n break;\n }\n }\n if (!comparisonPoint) comparisonPoint = points[0];\n }\n if (!Array.isArray(comparisonPoint) || comparisonPoint.length < 2) continue;\n const deltaMs = timeMs - comparisonPoint[0];\n if (!Number.isFinite(deltaMs) || deltaMs <= 0) continue;\n const deltaHours = deltaMs / HOUR_MS;\n if (!Number.isFinite(deltaHours) || deltaHours <= 0) continue;\n const rateValue = (value - comparisonPoint[1]) / deltaHours;\n if (!Number.isFinite(rateValue)) continue;\n ratePoints.push([timeMs, rateValue]);\n }\n return ratePoints;\n }\n function buildDeltaPoints(sourcePoints, comparisonPoints) {\n if (!Array.isArray(sourcePoints) || sourcePoints.length < 2 || !Array.isArray(comparisonPoints) || comparisonPoints.length < 2) return [];\n const deltaPoints = [];\n for (const [timeMs, value] of sourcePoints) {\n const comparisonValue = interpolateSeriesValue(comparisonPoints, timeMs);\n if (comparisonValue == null) continue;\n deltaPoints.push([timeMs, value - comparisonValue]);\n }\n return deltaPoints;\n }\n function buildSummaryStats(points) {\n if (!Array.isArray(points) || points.length === 0) return null;\n let min = Infinity;\n let max = -Infinity;\n let sum = 0;\n let count = 0;\n for (const point of points) {\n const value = Number(point?.[1]);\n if (!Number.isFinite(value)) continue;\n if (value < min) min = value;\n if (value > max) max = value;\n sum += value;\n count += 1;\n }\n if (!Number.isFinite(min) || !Number.isFinite(max) || count === 0) return null;\n return {\n min,\n max,\n mean: sum / count\n };\n }\n function computeHistoryAnalysis(payload) {\n const series = (Array.isArray(payload?.series) ? payload.series : []).map((seriesItem) => ({\n ...seriesItem,\n analysis: normalizeSeriesAnalysis(seriesItem?.analysis)\n }));\n const comparisonSeries = new Map((Array.isArray(payload?.comparisonSeries) ? payload.comparisonSeries : []).filter((entry) => entry?.entityId).map((entry) => [entry.entityId, entry]));\n const result = {\n trendSeries: [],\n rateSeries: [],\n deltaSeries: [],\n summaryStats: [],\n anomalySeries: [],\n comparisonWindowResults: {}\n };\n for (const seriesItem of series) {\n const points = Array.isArray(seriesItem?.pts) ? seriesItem.pts : [];\n const analysis = normalizeSeriesAnalysis(seriesItem?.analysis);\n if (points.length < 2) continue;\n if (analysis.show_trend_lines === true) {\n const trendPoints = buildTrendPoints(points, analysis.trend_method, analysis.trend_window);\n if (trendPoints.length >= 2) result.trendSeries.push({\n entityId: seriesItem.entityId,\n pts: trendPoints\n });\n }\n if (analysis.show_rate_of_change === true) {\n const ratePoints = buildRateOfChangePoints(points, analysis.rate_window);\n if (ratePoints.length >= 2) result.rateSeries.push({\n entityId: seriesItem.entityId,\n pts: ratePoints\n });\n }\n if (analysis.show_summary_stats === true) {\n const summaryStats = buildSummaryStats(points);\n if (summaryStats) result.summaryStats.push({\n entityId: seriesItem.entityId,\n ...summaryStats\n });\n }\n if (analysis.show_delta_analysis === true && payload?.hasSelectedComparisonWindow === true) {\n const comparisonPoints = comparisonSeries.get(seriesItem.entityId)?.pts ?? [];\n if (comparisonPoints.length >= 2) {\n const deltaPoints = buildDeltaPoints(points, comparisonPoints);\n if (deltaPoints.length >= 2) result.deltaSeries.push({\n entityId: seriesItem.entityId,\n pts: deltaPoints\n });\n }\n }\n }\n const seriesAnalysisConfigs = typeof payload?.seriesAnalysisConfigs === \"object\" && payload.seriesAnalysisConfigs !== null ? payload.seriesAnalysisConfigs : {};\n const allComparisonWindowsData = typeof payload?.allComparisonWindowsData === \"object\" && payload.allComparisonWindowsData !== null ? payload.allComparisonWindowsData : {};\n for (const [windowId, entityPtsMap] of Object.entries(allComparisonWindowsData)) {\n result.comparisonWindowResults[windowId] = {};\n for (const [entityId, pts] of Object.entries(entityPtsMap)) {\n const winAnalysis = normalizeSeriesAnalysis(seriesAnalysisConfigs[entityId]);\n result.comparisonWindowResults[windowId][entityId] = {\n trendPts: winAnalysis.show_trend_lines && pts.length >= 2 ? buildTrendPoints(pts, winAnalysis.trend_method, winAnalysis.trend_window) : [],\n ratePts: winAnalysis.show_rate_of_change && pts.length >= 2 ? buildRateOfChangePoints(pts, winAnalysis.rate_window) : [],\n summaryStats: winAnalysis.show_summary_stats ? buildSummaryStats(pts) : null\n };\n }\n }\n return result;\n }\n const workerScope = globalThis;\n workerScope.onmessage = (event) => {\n const { id, payload } = event.data || {};\n try {\n const result = computeHistoryAnalysis(payload);\n workerScope.postMessage({\n id,\n result\n });\n } catch (error) {\n workerScope.postMessage({\n id,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n };\n //#endregion\n})();\n"; + var blob$1 = typeof self !== "undefined" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", jsContent$1], { type: "text/javascript;charset=utf-8" }); + function WorkerWrapper$1(options) { + let objURL; + try { + objURL = blob$1 && (self.URL || self.webkitURL).createObjectURL(blob$1); + if (!objURL) throw ""; + const worker = new Worker(objURL, { name: options?.name }); + worker.addEventListener("error", () => { + (self.URL || self.webkitURL).revokeObjectURL(objURL); + }); + return worker; + } catch (e) { + return new Worker("data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent$1), { name: options?.name }); + } + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/workers/history-analysis-client.ts + var workerInstance$1 = null; + var requestId$1 = 0; + var pending$1 = /* @__PURE__ */ new Map(); + function getHistoryAnalysisWorker() { + if (workerInstance$1) return workerInstance$1; + workerInstance$1 = new WorkerWrapper$1(); + workerInstance$1.addEventListener("message", (event) => { + const { id, result, error } = event.data || {}; + const handlers = pending$1.get(id || -1); + if (!handlers) return; + pending$1.delete(id || -1); + if (error) { + handlers.reject(new Error(error)); + return; + } + handlers.resolve(result); + }); + workerInstance$1.addEventListener("error", (error) => { + pending$1.forEach((handlers) => { + handlers.reject(error); + }); + pending$1.clear(); + workerInstance$1 = null; + }); + return workerInstance$1; + } + /** + * Abort all in-flight analysis requests and terminate the worker. + * Called when a new draw request supersedes an in-progress one so the worker + * is not left computing a result that will be thrown away. + */ + function terminateHistoryAnalysisWorker() { + if (pending$1.size > 0) { + pending$1.forEach(({ reject }) => { + reject(/* @__PURE__ */ new Error("Aborted: superseded by newer analysis")); + }); + pending$1.clear(); + } + if (workerInstance$1) { + workerInstance$1.terminate(); + workerInstance$1 = null; + } + } + function computeHistoryAnalysisInWorker(payload) { + const worker = getHistoryAnalysisWorker(); + return new Promise((resolve, reject) => { + const id = ++requestId$1; + pending$1.set(id, { + resolve, + reject + }); + worker.postMessage({ + id, + payload + }); + }); + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/workers/chart-data.worker.ts?worker&inline + var jsContent = "(function() {\n //#region custom_components/hass_datapoints/src/lib/workers/chart-data.worker.ts\n function downsamplePts(pts, intervalMs, aggregate) {\n if (!pts.length || intervalMs <= 0) return pts;\n const buckets = /* @__PURE__ */ new Map();\n const bucketRepTime = /* @__PURE__ */ new Map();\n for (const [time, value] of pts) {\n const idx = Math.floor(time / intervalMs);\n if (!buckets.has(idx)) {\n buckets.set(idx, []);\n bucketRepTime.set(idx, time);\n }\n buckets.get(idx)?.push(value);\n }\n const result = [];\n for (const idx of [...buckets.keys()].sort((a, b) => a - b)) {\n const values = buckets.get(idx) || [];\n const repTime = bucketRepTime.get(idx) || 0;\n let agg;\n if (aggregate === \"min\") agg = Math.min(...values);\n else if (aggregate === \"max\") agg = Math.max(...values);\n else if (aggregate === \"median\") {\n const sorted = [...values].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n agg = sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;\n } else if (aggregate === \"first\") agg = values[0];\n else if (aggregate === \"last\") agg = values[values.length - 1];\n else agg = values.reduce((sum, current) => sum + current, 0) / values.length;\n result.push([repTime, agg]);\n }\n return result;\n }\n self.onmessage = ({ data }) => {\n const { id, type, payload } = data || {};\n try {\n let result;\n if (type === \"downsample\") result = downsamplePts(payload.pts, payload.intervalMs, payload.aggregate);\n else throw new Error(`Unknown message type: ${type}`);\n self.postMessage({\n id,\n result\n });\n } catch (err) {\n self.postMessage({\n id,\n error: String(err)\n });\n }\n };\n //#endregion\n})();\n"; + var blob = typeof self !== "undefined" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", jsContent], { type: "text/javascript;charset=utf-8" }); + function WorkerWrapper(options) { + let objURL; + try { + objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob); + if (!objURL) throw ""; + const worker = new Worker(objURL, { name: options?.name }); + worker.addEventListener("error", () => { + (self.URL || self.webkitURL).revokeObjectURL(objURL); + }); + return worker; + } catch (e) { + return new Worker("data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent), { name: options?.name }); + } + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/workers/chart-data-client.ts + var workerInstance; + var requestId = 0; + var pending = /* @__PURE__ */ new Map(); + function getChartDataWorker() { + if (workerInstance !== void 0) return workerInstance; + try { + workerInstance = new WorkerWrapper(); + workerInstance.addEventListener("message", (event) => { + const { id, result, error } = event.data || {}; + const handlers = pending.get(id || -1); + if (!handlers) return; + pending.delete(id || -1); + if (error) handlers.reject(new Error(error)); + else handlers.resolve(result || []); + }); + workerInstance.addEventListener("error", (err) => { + pending.forEach(({ reject }) => { + reject(err); + }); + pending.clear(); + workerInstance = null; + }); + } catch { + workerInstance = null; + } + return workerInstance; + } + /** + * Downsample [[timeMs, value], ...] pts in a worker. + * Returns a Promise that resolves with the downsampled pts array. + */ + function downsampleInWorker(pts, intervalMs, aggregate) { + if (pts.length === 0) return Promise.resolve([]); + const worker = getChartDataWorker(); + if (!worker) return Promise.reject(/* @__PURE__ */ new Error("Worker not available")); + return new Promise((resolve, reject) => { + const id = ++requestId; + pending.set(id, { + resolve, + reject + }); + worker.postMessage({ + id, + type: "downsample", + payload: { + pts, + intervalMs, + aggregate + } + }); + }); + } + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/analysis/windows.ts + var HOUR_MS$1 = 3600 * 1e3; + function getTrendWindowMs(value) { + const windows = { + "1h": HOUR_MS$1, + "6h": 6 * HOUR_MS$1, + "24h": 24 * HOUR_MS$1, + "7d": 168 * HOUR_MS$1, + "14d": 336 * HOUR_MS$1, + "21d": 504 * HOUR_MS$1, + "28d": 672 * HOUR_MS$1 + }; + return windows[value] ?? windows["24h"]; + } + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/analysis/series.ts + function buildRollingAverageTrend(points, windowMs) { + if (!Array.isArray(points) || points.length < 2 || !Number.isFinite(windowMs) || windowMs <= 0) return []; + const trendPoints = []; + let windowStartIndex = 0; + let windowSum = 0; + for (let index = 0; index < points.length; index += 1) { + const [time, value] = points[index]; + windowSum += value; + while (windowStartIndex < index && time - points[windowStartIndex][0] > windowMs) { + windowSum -= points[windowStartIndex][1]; + windowStartIndex += 1; + } + const count = index - windowStartIndex + 1; + if (count > 0) trendPoints.push([time, windowSum / count]); + } + return trendPoints; + } + function buildLinearTrend(points) { + if (!Array.isArray(points) || points.length < 2) return []; + const origin = points[0][0]; + let sumX = 0; + let sumY = 0; + let sumXX = 0; + let sumXY = 0; + for (const [time, value] of points) { + const x = (time - origin) / (3600 * 1e3); + sumX += x; + sumY += value; + sumXX += x * x; + sumXY += x * value; + } + const count = points.length; + const denominator = count * sumXX - sumX * sumX; + if (!Number.isFinite(denominator) || Math.abs(denominator) < 1e-9) return []; + const slope = (count * sumXY - sumX * sumY) / denominator; + const intercept = (sumY - slope * sumX) / count; + const firstTime = points[0][0]; + const lastTime = points[points.length - 1][0]; + const firstX = (firstTime - origin) / (3600 * 1e3); + const lastX = (lastTime - origin) / (3600 * 1e3); + return [[firstTime, intercept + slope * firstX], [lastTime, intercept + slope * lastX]]; + } + function interpolateSeriesValue(points, timeMs) { + if (!Array.isArray(points) || !points.length) return null; + if (timeMs < points[0][0] || timeMs > points[points.length - 1][0]) return null; + if (timeMs === points[0][0]) return points[0][1]; + if (timeMs === points[points.length - 1][0]) return points[points.length - 1][1]; + for (let index = 0; index < points.length - 1; index += 1) { + const [startTime, startValue] = points[index]; + const [endTime, endValue] = points[index + 1]; + if (timeMs >= startTime && timeMs <= endTime) return startValue + (timeMs - startTime) / (endTime - startTime) * (endValue - startValue); + } + return null; + } + function buildRateOfChangePoints(points, rateWindow = "1h") { + if (!Array.isArray(points) || points.length < 2) return []; + const ratePoints = []; + for (let index = 1; index < points.length; index += 1) { + const [timeMs, value] = points[index]; + let comparisonPoint = null; + if (rateWindow === "point_to_point") comparisonPoint = points[index - 1]; + else { + const windowMs = getTrendWindowMs(rateWindow); + if (!Number.isFinite(windowMs) || windowMs <= 0) continue; + for (let candidateIndex = index - 1; candidateIndex >= 0; candidateIndex -= 1) { + const candidatePoint = points[candidateIndex]; + if (timeMs - candidatePoint[0] >= windowMs) { + comparisonPoint = candidatePoint; + break; + } + } + if (!comparisonPoint) comparisonPoint = points[0]; + } + if (!Array.isArray(comparisonPoint) || comparisonPoint.length < 2) continue; + const deltaMs = timeMs - comparisonPoint[0]; + if (!Number.isFinite(deltaMs) || deltaMs <= 0) continue; + const deltaHours = deltaMs / (3600 * 1e3); + if (!Number.isFinite(deltaHours) || deltaHours <= 0) continue; + const rateValue = (value - comparisonPoint[1]) / deltaHours; + if (!Number.isFinite(rateValue)) continue; + ratePoints.push([timeMs, rateValue]); + } + return ratePoints; + } + function buildDeltaPoints(sourcePoints, comparisonPoints) { + if (!Array.isArray(sourcePoints) || sourcePoints.length < 2 || !Array.isArray(comparisonPoints) || comparisonPoints.length < 2) return []; + const deltaPoints = []; + for (const [timeMs, value] of sourcePoints) { + const comparisonValue = interpolateSeriesValue(comparisonPoints, timeMs); + if (comparisonValue == null) continue; + deltaPoints.push([timeMs, value - comparisonValue]); + } + return deltaPoints; + } + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/analysis/summary.ts + function buildSummaryStats(points) { + if (!Array.isArray(points) || !points.length) return null; + let min = Infinity; + let max = -Infinity; + let sum = 0; + let count = 0; + for (const point of points) { + const value = Number(point?.[1]); + if (!Number.isFinite(value)) continue; + if (value < min) min = value; + if (value > max) max = value; + sum += value; + count += 1; + } + if (!Number.isFinite(min) || !Number.isFinite(max) || count === 0) return null; + return { + min, + max, + mean: sum / count + }; + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/domain/chart-zoom.ts + /** + * Date parsing and zoom state helpers shared by chart/timeline subsystems. + */ + function parseDateValue(value) { + if (!value) return null; + if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value; + const parsed = new Date(value); + return Number.isNaN(parsed.getTime()) ? null : parsed; + } + function createChartZoomRange(startValue, endValue) { + const startDate = parseDateValue(startValue); + const endDate = parseDateValue(endValue); + const start = startDate?.getTime(); + const end = endDate?.getTime(); + if (typeof start === "number" && Number.isFinite(start) && typeof end === "number" && Number.isFinite(end) && start < end) return { + start, + end + }; + return null; + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/history-page/history-url-state.ts + function makeDateWindowId(label, existingIds = /* @__PURE__ */ new Set()) { + const base = slugifySeriesName(label) || "date-window"; + let candidate = base; + let suffix = 2; + while (existingIds.has(candidate)) { + candidate = `${base}-${suffix}`; + suffix += 1; + } + return candidate; + } + function normalizeDateWindows(windows) { + if (!Array.isArray(windows)) return []; + const seen = /* @__PURE__ */ new Set(); + const normalized = []; + windows.forEach((window, index) => { + const label = String(window?.label || window?.name || "").trim(); + const start = parseDateValue(window?.start_time || window?.start); + const end = parseDateValue(window?.end_time || window?.end); + if (!label || !start || !end || start >= end) return; + const id = String(window?.id || "").trim() || makeDateWindowId(`${label}-${index + 1}`, seen); + if (seen.has(id)) return; + seen.add(id); + normalized.push({ + id, + label, + start_time: start.toISOString(), + end_time: end.toISOString() + }); + }); + return normalized; + } + function parseDateWindowsParam(value) { + if (!value || typeof value !== "string") return []; + return normalizeDateWindows(value.split("|").map((entry) => { + const [rawId, rawLabel, rawStart, rawEnd] = String(entry).split("~"); + return { + id: decodeURIComponent(rawId || ""), + label: decodeURIComponent(rawLabel || ""), + start_time: decodeURIComponent(rawStart || ""), + end_time: decodeURIComponent(rawEnd || "") + }; + })); + } + function serializeDateWindowsParam(windows) { + const normalized = normalizeDateWindows(windows); + if (!normalized.length) return ""; + return normalized.map((window) => [ + encodeURIComponent(window.id), + encodeURIComponent(window.label ?? ""), + encodeURIComponent(window.start_time), + encodeURIComponent(window.end_time) + ].join("~")).join("|"); + } + function parseHistoryPageStateParam(value) { + if (!value || typeof value !== "string") return null; + try { + const parsed = JSON.parse(value); + return parsed && typeof parsed === "object" ? parsed : null; + } catch { + return null; + } + } + function serializeHistoryPageStateParam(state) { + if (!state || typeof state !== "object") return ""; + try { + return JSON.stringify(state); + } catch { + return ""; + } + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/ha/navigation.ts + function buildDataPointsHistoryPath(target = {}, options = {}) { + const normalizedTarget = { + entity_id: [...new Set((target.entity_id || []).filter(Boolean))], + device_id: [...new Set((target.device_id || []).filter(Boolean))], + area_id: [...new Set((target.area_id || []).filter(Boolean))], + label_id: [...new Set((target.label_id || []).filter(Boolean))] + }; + const params = new URLSearchParams(); + if (normalizedTarget.entity_id.length) params.set("entity_id", normalizedTarget.entity_id.join(",")); + if (normalizedTarget.device_id.length) params.set("device_id", normalizedTarget.device_id.join(",")); + if (normalizedTarget.area_id.length) params.set("area_id", normalizedTarget.area_id.join(",")); + if (normalizedTarget.label_id.length) params.set("label_id", normalizedTarget.label_id.join(",")); + if (options.datapoint_scope === "all") params.set("datapoints_scope", "all"); + const start = options.start_time ? new Date(options.start_time) : null; + const end = options.end_time ? new Date(options.end_time) : null; + if (start && end && Number.isFinite(start.getTime()) && Number.isFinite(end.getTime()) && start < end) { + params.set("start_time", start.toISOString()); + params.set("end_time", end.toISOString()); + params.set("hours_to_show", String(Math.max(1, Math.round((end.getTime() - start.getTime()) / 36e5)))); + } + const zoomStart = options.zoom_start_time ? new Date(options.zoom_start_time) : null; + const zoomEnd = options.zoom_end_time ? new Date(options.zoom_end_time) : null; + if (zoomStart && zoomEnd && Number.isFinite(zoomStart.getTime()) && Number.isFinite(zoomEnd.getTime()) && zoomStart < zoomEnd) { + params.set("zoom_start_time", zoomStart.toISOString()); + params.set("zoom_end_time", zoomEnd.toISOString()); + } + const pageStateParam = serializeHistoryPageStateParam(options.page_state); + if (pageStateParam) params.set("page_state", pageStateParam); + return `/${PANEL_URL_PATH}?${params.toString()}`; + } + function navigateToDataPointsHistory(_card, target = {}, options = {}) { + const path = buildDataPointsHistoryPath(target, options); + if (window.history && window.history.pushState) { + window.history.pushState(null, "", path); + window.dispatchEvent(new Event("location-changed")); + return; + } + window.location.assign(path); + } + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.styles.ts + var styles$54 = ` + hass-datapoints-history-chart { + position: relative; + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; + --dp-spacing-xs: calc(var(--spacing, 8px) * 0.5); + --dp-spacing-sm: var(--spacing, 8px); + --dp-spacing-md: calc(var(--spacing, 8px) * 1.5); + --dp-spacing-lg: calc(var(--spacing, 8px) * 2); + --dp-spacing-xl: calc(var(--spacing, 8px) * 2.5); + --ha-tooltip-background-color: color-mix(in srgb, #0f1218 96%, transparent); + --ha-tooltip-text-color: rgba(255, 255, 255, 0.96); + --ha-tooltip-padding: calc(var(--dp-spacing-sm) + 2px) calc(var(--dp-spacing-md) + 2px); + --ha-tooltip-border-radius: 10px; + --ha-tooltip-arrow-size: 10px; + --ha-tooltip-font-size: 0.86rem; + --ha-tooltip-line-height: 1.1; } - function findNearestSeriesPointTime(seriesPoints, timeMs) { - if (!Array.isArray(seriesPoints) || seriesPoints.length === 0) { - return null; - } - let lo = 0; - let hi = seriesPoints.length - 1; - while (lo + 1 < hi) { - const mid = Math.floor((lo + hi) / 2); - if (seriesPoints[mid][0] <= timeMs) { - lo = mid; - } else { - hi = mid; - } - } - const left = seriesPoints[lo]?.[0]; - const right = seriesPoints[hi]?.[0]; - if (!Number.isFinite(left) && !Number.isFinite(right)) { - return null; - } - if (!Number.isFinite(left)) { - return right; - } - if (!Number.isFinite(right)) { - return left; - } - return Math.abs(left - timeMs) <= Math.abs(right - timeMs) ? left : right; + ha-card { padding: 0; overflow: visible; height: 100%; display: flex; flex-direction: column; } + .card-header { + padding: var(--dp-spacing-lg); + font-size: 1.1em; + font-weight: 500; + color: var(--primary-text-color); + flex: 0 0 auto; + line-height: 1.3; } - function resolveLineChartHoverTime(series, timeMs, mode = "follow_series") { - if (mode !== "snap_to_data_points") { - return timeMs; - } - let bestTime = null; - let bestDistance = Infinity; - for (const seriesItem of Array.isArray(series) ? series : []) { - const candidateTime = findNearestSeriesPointTime(seriesItem?.pts, timeMs); - if (candidateTime == null || !Number.isFinite(candidateTime)) { - continue; - } - const distance = Math.abs(candidateTime - timeMs); - if (distance < bestDistance) { - bestDistance = distance; - bestTime = candidateTime; - } - } - return bestTime != null && Number.isFinite(bestTime) ? bestTime : timeMs; - } - function hideLineChartHover(card) { - dispatchLineChartHover(card, null); - hideTooltip(card); - const overlay = getRoot(card).getElementById("chart-crosshair"); - const points = getRoot(card).getElementById("crosshair-points"); - const addButton = getRoot(card).getElementById("chart-add-annotation"); - if (overlay) overlay.hidden = true; - if (points) points.innerHTML = ""; - renderChartAxisHoverDots(card, []); - const horizontal = getRoot(card).getElementById("crosshair-horizontal"); - if (horizontal) horizontal.hidden = true; - if (addButton) addButton.hidden = true; - } - function attachLineChartHover(card, canvas, renderer, series, events, t0, t1, vMin, vMax, axes = null, options = {}) { - const interactionState = getInteractionState(card); - if (!canvas || !renderer) { - return; - } - if (interactionState._chartHoverCleanup) { - interactionState._chartHoverCleanup(); - interactionState._chartHoverCleanup = null; - } - const resolvedSeries = Array.isArray(series) ? series : []; - const eventThresholdMs = renderer.cw ? 14 * ((t1 - t0) / renderer.cw) : 0; - const binaryStates = Array.isArray(options.binaryStates) ? options.binaryStates : []; - const comparisonSeries = Array.isArray(options.comparisonSeries) ? options.comparisonSeries : []; - const trendSeries = Array.isArray(options.trendSeries) ? options.trendSeries : []; - const rateSeries = Array.isArray(options.rateSeries) ? options.rateSeries : []; - const deltaSeries = Array.isArray(options.deltaSeries) ? options.deltaSeries : []; - const summarySeries = Array.isArray(options.summarySeries) ? options.summarySeries : []; - const thresholdSeries = Array.isArray(options.thresholdSeries) ? options.thresholdSeries : []; - const anomalyRegions = Array.isArray(options.anomalyRegions) ? options.anomalyRegions : []; - if (!resolvedSeries.length && !binaryStates.length && !comparisonSeries.length && !trendSeries.length && !rateSeries.length && !deltaSeries.length && !summarySeries.length && !thresholdSeries.length && !anomalyRegions.length) { - return; - } - const hoverSurfaceEl = options.hoverSurfaceEl || null; - const addAnnotationButton = getRoot(card)?.getElementById("chart-add-annotation") || null; - const resolveHoverAxis = (seriesItem) => seriesItem.axis || axes && axes[0] || { min: vMin, max: vMax }; - const buildHoverValueEntry = (seriesItem, value, axis, extra = {}, entryOpts = {}) => { - const hasNumericValue = typeof value === "number" && Number.isFinite(value); - const includePosition = entryOpts.includePosition === true && hasNumericValue; - return { - entityId: seriesItem.entityId || "", - comparisonParentId: seriesItem.comparisonParentId || "", - relatedEntityId: seriesItem.relatedEntityId || "", - label: seriesItem.label || seriesItem.entityId || "", - baseLabel: seriesItem.baseLabel || "", - windowLabel: seriesItem.windowLabel || "", - value: hasNumericValue ? value : value ?? null, - unit: seriesItem.unit || "", - color: seriesItem.color, - opacity: Number.isFinite(seriesItem.hoverOpacity) ? seriesItem.hoverOpacity : 1, - hasValue: hasNumericValue || value != null, - x: includePosition ? entryOpts.x : void 0, - y: includePosition ? renderer.yOf(value, axis.min, axis.max) : void 0, - axisSide: axis.side === "right" ? "right" : "left", - axisSlot: Number.isFinite(axis.slot) ? axis.slot : 0, - rawVisible: seriesItem.rawVisible !== false, - comparisonDerived: seriesItem.comparisonDerived === true, - showCrosshair: seriesItem.showCrosshair === true, - ...extra - }; - }; - const findAnomalyRegions = (clientX, clientY) => { - const rect = canvas.getBoundingClientRect(); - if (!rect.width || !rect.height) { - return []; - } - const localX = clientX - rect.left; - const localY = clientY - rect.top; - const hits = []; - for (const region of anomalyRegions) { - const radiusX = Number(region?.radiusX) || 0; - const radiusY = Number(region?.radiusY) || 0; - if (radiusX <= 0 || radiusY <= 0) { - continue; - } - const dx = (localX - region.centerX) / radiusX; - const dy = (localY - region.centerY) / radiusY; - if (dx * dx + dy * dy <= 1) { - hits.push(region); - } - } - return hits; - }; - const buildHoverState = (clientX, clientY) => { - const rect = canvas.getBoundingClientRect(); - if (!rect.width || !rect.height || !renderer.cw || !renderer.ch) { - return null; - } - const localX = clampChartValue( - clientX - rect.left, - renderer.pad.left, - renderer.pad.left + renderer.cw - ); - const localY = clampChartValue( - clientY - rect.top, - renderer.pad.top, - renderer.pad.top + renderer.ch - ); - const ratio = renderer.cw ? (localX - renderer.pad.left) / renderer.cw : 0; - const rawTimeMs = t0 + ratio * (t1 - t0); - const timeMs = resolveLineChartHoverTime( - resolvedSeries, - rawTimeMs, - options.hoverSnapMode || "follow_series" - ); - const x2 = renderer.xOf(timeMs, t0, t1); - const values = resolvedSeries.map((seriesItem) => { - const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); - const axis = resolveHoverAxis(seriesItem); - return buildHoverValueEntry( - seriesItem, - value, - axis, - {}, - { - includePosition: value != null, - x: x2 - } - ); - }); - const comparisonValues = comparisonSeries.map((seriesItem) => { - const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); - const axis = resolveHoverAxis(seriesItem); - return buildHoverValueEntry( - seriesItem, - value, - axis, - { comparison: true }, - { includePosition: value != null, x: x2 } - ); - }); - const trendValues = trendSeries.map((seriesItem) => { - const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); - const axis = resolveHoverAxis(seriesItem); - return buildHoverValueEntry( - seriesItem, - value, - axis, - { trend: true }, - { includePosition: value != null, x: x2 } - ); - }); - const rateValues = rateSeries.map((seriesItem) => { - const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); - const axis = resolveHoverAxis(seriesItem); - return buildHoverValueEntry( - seriesItem, - value, - axis, - { rate: true }, - { includePosition: value != null, x: x2 } - ); - }); - const deltaValues = deltaSeries.map((seriesItem) => { - const value = renderer._interpolateValue(seriesItem.pts || [], timeMs); - const axis = resolveHoverAxis(seriesItem); - return buildHoverValueEntry( - seriesItem, - value, - axis, - { delta: true }, - { includePosition: value != null, x: x2 } - ); - }); - const summaryValues = summarySeries.map((seriesItem) => { - const axis = resolveHoverAxis(seriesItem); - const value = Number(seriesItem.value); - return buildHoverValueEntry(seriesItem, value, axis, { - summary: true, - summaryType: seriesItem.summaryType || "" - }); - }); - const thresholdValues = thresholdSeries.map((seriesItem) => { - const axis = resolveHoverAxis(seriesItem); - const value = Number(seriesItem.value); - return buildHoverValueEntry(seriesItem, value, axis, { - threshold: true - }); - }); - const plottedValues = [ - ...values.filter((entry) => entry?.hasValue !== false), - ...comparisonValues.filter((entry) => entry?.hasValue !== false), - ...rateValues.filter((entry) => entry?.hasValue !== false), - ...options.showTrendCrosshairs === true ? trendValues.filter( - (entry) => entry?.hasValue !== false && entry.showCrosshair === true - ) : [] - ]; - let rangeStartMs = timeMs; - let rangeEndMs = timeMs; - let primary = plottedValues[0] || null; - if (primary) { - for (const entry of plottedValues) { - if (Number.isFinite(entry.y) && Number.isFinite(primary.y) && Math.abs(entry.y - localY) < Math.abs(primary.y - localY)) { - primary = entry; - } - } - } - const activePrimarySeries = primary ? resolvedSeries.find( - (seriesItem) => seriesItem.entityId === primary.entityId - ) || null : null; - if (activePrimarySeries?.pts?.length) { - const pts = activePrimarySeries.pts; - const pLen = pts.length; - let lo = 0; - let hi = pLen - 1; - let previousIndex = -1; - if (pts[0][0] <= timeMs) { - while (lo + 1 < hi) { - const mid = Math.floor((lo + hi) / 2); - if (pts[mid][0] <= timeMs) { - lo = mid; - } else { - hi = mid; - } - } - previousIndex = pts[hi][0] <= timeMs ? hi : lo; - } - const nextIndex = previousIndex < pLen - 1 ? previousIndex + 1 : -1; - const previous = previousIndex >= 0 ? pts[previousIndex] : null; - let next = null; - if (nextIndex >= 0) { - next = pts[nextIndex]; - } else if (previousIndex < 0) { - next = pts[0]; - } - if (previous && next) { - const prevPrev = pts[Math.max(0, previousIndex - 1)] || previous; - const nextNext = pts[Math.min(pLen - 1, nextIndex + 1)] || next; - rangeStartMs = previous === next ? previous[0] : Math.round((previous[0] + prevPrev[0]) / 2); - rangeEndMs = previous === next ? next[0] : Math.round((next[0] + nextNext[0]) / 2); - } else if (previous) { - rangeStartMs = previous[0]; - rangeEndMs = previous[0]; - } else if (next) { - rangeStartMs = next[0]; - rangeEndMs = next[0]; - } - } - const binaryValues = binaryStates.map((entry) => { - const activeSpan = (entry.spans || []).find( - (span) => timeMs >= span.start && timeMs <= span.end - ); - return { - entityId: entry.entityId || "", - label: entry.label || entry.entityId || "", - value: activeSpan ? entry.onLabel || "on" : entry.offLabel || "off", - unit: "", - color: entry.color, - hasValue: true, - active: !!activeSpan - }; - }).filter((entry) => Boolean(entry.label)); - if (!values.length && !binaryValues.length && !trendValues.length && !rateValues.length && !deltaValues.length && !summaryValues.length && !thresholdValues.length && !comparisonValues.length) { - return null; - } - const fallbackY = renderer.pad.top + 12; - const hoverY = primary ? primary.y : fallbackY; - const hoveredEvents = []; - for (const event of events || []) { - const eventTime = new Date(event.timestamp).getTime(); - if (eventTime < t0 || eventTime > t1) { - continue; - } - const distance = Math.abs(eventTime - timeMs); - if (distance <= eventThresholdMs) { - hoveredEvents.push({ - ...event, - _hoverDistanceMs: distance - }); - } - } - hoveredEvents.sort((left, right) => { - const distanceDelta = (left._hoverDistanceMs || 0) - (right._hoverDistanceMs || 0); - if (distanceDelta !== 0) { - return distanceDelta; - } - return new Date(left.timestamp).getTime() - new Date(right.timestamp).getTime(); - }); - const normalizedHoveredEvents = hoveredEvents.map( - (event) => { - const { _hoverDistanceMs: _2, ...normalizedEvent } = event; - return normalizedEvent; - } - ); - return { - x: x2, - y: hoverY, - timeMs, - rangeStartMs, - rangeEndMs, - values, - trendValues, - rateValues, - deltaValues: options.showDeltaTooltip === true ? deltaValues : [], - summaryValues, - thresholdValues, - comparisonValues, - binaryValues, - primary, - event: normalizedHoveredEvents[0] || null, - events: normalizedHoveredEvents, - emphasizeGuides: options.emphasizeHoverGuides === true, - showTrendCrosshairs: options.showTrendCrosshairs === true, - showRateCrosshairs: options.showRateCrosshairs === true, - hideRawData: options.hideRawData === true - }; - }; - const showFromPointer = (clientX, clientY) => { - if (interactionState._chartZoomDragging) { - return; - } - const anomalyRegionsHit = findAnomalyRegions(clientX, clientY); - const hover = buildHoverState(clientX, clientY); - if (!hover) { - interactionState._chartLastHover = null; - hideLineChartHover(card); - canvas.style.cursor = "default"; - return; - } - hover.anomalyRegions = anomalyRegionsHit; - interactionState._chartLastHover = hover; - showLineChartCrosshair(card, renderer, hover); - if (options.showTooltip !== false || Array.isArray(hover.events) && hover.events.length > 0) { - showLineChartTooltip(card, hover, clientX, clientY); - } else { - hideTooltip(card); - } - dispatchLineChartHover(card, hover); - canvas.style.cursor = anomalyRegionsHit.length > 0 ? "pointer" : "crosshair"; - }; - const hideHover = () => { - interactionState._chartLastHover = null; - hideLineChartHover(card); - canvas.style.cursor = "default"; - }; - let _rafHandle = null; - let _pendingX = 0; - let _pendingY = 0; - const onMouseMove = (ev) => { - _pendingX = ev.clientX; - _pendingY = ev.clientY; - if (_rafHandle !== null) { - return; - } - _rafHandle = requestAnimationFrame(() => { - _rafHandle = null; - showFromPointer(_pendingX, _pendingY); - }); - }; - const onMouseLeave = (ev) => { - const nextTarget = ev.relatedTarget; - if (nextTarget instanceof Node && hoverSurfaceEl && hoverSurfaceEl.contains(nextTarget)) { - return; - } - if (nextTarget instanceof Node && addAnnotationButton && addAnnotationButton.contains(nextTarget)) { - return; - } - hideHover(); - }; - const onOverlayMove = (ev) => { - showFromPointer(ev.clientX, ev.clientY); - }; - const onOverlayLeave = (ev) => { - const nextTarget = ev.relatedTarget; - if (nextTarget instanceof Node && canvas.contains(nextTarget)) { - return; - } - if (nextTarget instanceof Node && addAnnotationButton && addAnnotationButton.contains(nextTarget)) { - return; - } - hideHover(); - }; - const onAddButtonLeave = (ev) => { - const nextTarget = ev.relatedTarget; - if (nextTarget instanceof Node && (canvas.contains(nextTarget) || hoverSurfaceEl && hoverSurfaceEl.contains(nextTarget))) { - return; - } - hideHover(); - }; - const onAddButtonClick = (ev) => { - if (typeof options.onAddAnnotation !== "function" || !interactionState._chartLastHover) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - options.onAddAnnotation(interactionState._chartLastHover, ev); - }; - const onContextMenu = (ev) => { - if (typeof options.onContextMenu !== "function") { - return; - } - const hover = buildHoverState(ev.clientX, ev.clientY); - if (!hover) { - return; - } - ev.preventDefault(); - interactionState._chartLastHover = hover; - showLineChartCrosshair(card, renderer, hover); - showLineChartTooltip(card, hover, ev.clientX, ev.clientY); - dispatchLineChartHover(card, hover); - options.onContextMenu(hover, ev); - }; - const onClick = (ev) => { - if (typeof options.onAnomalyClick !== "function") { - return; - } - const regions = findAnomalyRegions(ev.clientX, ev.clientY); - if (!regions.length) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - options.onAnomalyClick(regions, ev); - }; - let touchTimer = null; - const scheduleTouchHide = () => { - if (touchTimer) { - window.clearTimeout(touchTimer); - } - touchTimer = window.setTimeout(() => hideHover(), 1800); - }; - const onTouchStart = (ev) => { - ev.preventDefault(); - const touch = ev.touches[0]; - if (!touch) { - return; - } - showFromPointer(touch.clientX, touch.clientY); - scheduleTouchHide(); - }; - const onTouchMove = (ev) => { - ev.preventDefault(); - const touch = ev.touches[0]; - if (!touch) { - return; - } - showFromPointer(touch.clientX, touch.clientY); - scheduleTouchHide(); - }; - const onTouchEnd = () => scheduleTouchHide(); - canvas.addEventListener("mousemove", onMouseMove); - canvas.addEventListener("mouseleave", onMouseLeave); - canvas.addEventListener("click", onClick); - canvas.addEventListener("contextmenu", onContextMenu); - canvas.addEventListener("touchstart", onTouchStart, { passive: false }); - canvas.addEventListener("touchmove", onTouchMove, { passive: false }); - canvas.addEventListener("touchend", onTouchEnd); - canvas.addEventListener("touchcancel", onTouchEnd); - hoverSurfaceEl?.addEventListener("mousemove", onOverlayMove); - hoverSurfaceEl?.addEventListener("mouseleave", onOverlayLeave); - addAnnotationButton?.addEventListener("mouseleave", onAddButtonLeave); - addAnnotationButton?.addEventListener("click", onAddButtonClick); - interactionState._chartHoverCleanup = () => { - canvas.removeEventListener("mousemove", onMouseMove); - canvas.removeEventListener("mouseleave", onMouseLeave); - canvas.removeEventListener("click", onClick); - canvas.removeEventListener("contextmenu", onContextMenu); - canvas.removeEventListener("touchstart", onTouchStart); - canvas.removeEventListener("touchmove", onTouchMove); - canvas.removeEventListener("touchend", onTouchEnd); - canvas.removeEventListener("touchcancel", onTouchEnd); - hoverSurfaceEl?.removeEventListener("mousemove", onOverlayMove); - hoverSurfaceEl?.removeEventListener("mouseleave", onOverlayLeave); - addAnnotationButton?.removeEventListener("mouseleave", onAddButtonLeave); - addAnnotationButton?.removeEventListener("click", onAddButtonClick); - if (_rafHandle !== null) { - cancelAnimationFrame(_rafHandle); - _rafHandle = null; - } - if (touchTimer) { - window.clearTimeout(touchTimer); - touchTimer = null; - } - hideHover(); - }; - } - function attachLineChartRangeZoom(card, canvas, renderer, t0, t1, options = {}) { - const interactionState = getInteractionState(card); - if (!canvas || !renderer) { - return; - } - if (interactionState._chartZoomCleanup) { - interactionState._chartZoomCleanup(); - interactionState._chartZoomCleanup = null; - } - const selection = getRoot(card).getElementById( - "chart-zoom-selection" - ); - if (!selection) { - return; - } - let pointerId = null; - let startX = 0; - let currentX = 0; - let dragging = false; - const hideSelection = () => { - selection.hidden = true; - selection.classList.remove("visible"); - }; - const clientXToTime = (clientX) => { - const rect = canvas.getBoundingClientRect(); - const localX = clampChartValue( - clientX - rect.left, - renderer.pad.left, - renderer.pad.left + renderer.cw - ); - const ratio = renderer.cw ? (localX - renderer.pad.left) / renderer.cw : 0; - return t0 + ratio * (t1 - t0); - }; - const inPlotBounds = (clientX, clientY) => { - const rect = canvas.getBoundingClientRect(); - const localX = clientX - rect.left; - const localY = clientY - rect.top; - return localX >= renderer.pad.left && localX <= renderer.pad.left + renderer.cw && localY >= renderer.pad.top && localY <= renderer.pad.top + renderer.ch; - }; - const renderSelection = () => { - const left = Math.min(startX, currentX); - const width = Math.abs(currentX - startX); - selection.style.left = `${left}px`; - selection.style.top = `${renderer.pad.top}px`; - selection.style.width = `${width}px`; - selection.style.height = `${renderer.ch}px`; - selection.hidden = false; - selection.classList.add("visible"); - }; - const emitPreview = () => { - if (!dragging || Math.abs(currentX - startX) < 8) { - options.onPreview?.(null); - return; - } - const rectLeft = canvas.getBoundingClientRect().left; - const startTime = Math.min( - clientXToTime(rectLeft + startX), - clientXToTime(rectLeft + currentX) - ); - const endTime = Math.max( - clientXToTime(rectLeft + startX), - clientXToTime(rectLeft + currentX) - ); - options.onPreview?.({ startTime, endTime }); - }; - const resetDragging = (clearPreview = true) => { - pointerId = null; - dragging = false; - interactionState._chartZoomDragging = false; - hideSelection(); - if (clearPreview) { - options.onPreview?.(null); - } - }; - const onPointerMove = (ev) => { - if (pointerId == null || ev.pointerId !== pointerId) { - return; - } - currentX = clampChartValue( - ev.clientX - canvas.getBoundingClientRect().left, - renderer.pad.left, - renderer.pad.left + renderer.cw - ); - const movedPx = Math.abs(currentX - startX); - if (!dragging && movedPx < 6) { - return; - } - dragging = true; - interactionState._chartZoomDragging = true; - hideLineChartHover(card); - renderSelection(); - emitPreview(); - ev.preventDefault(); - }; - const finish = (ev) => { - if (pointerId == null || ev.pointerId !== pointerId) { - return; - } - const didDrag = dragging; - const endX = currentX; - window.removeEventListener("pointermove", onPointerMove); - window.removeEventListener("pointerup", finish); - window.removeEventListener("pointercancel", finish); - if (!didDrag || Math.abs(endX - startX) < 8) { - resetDragging(true); - return; - } - const rectLeft = canvas.getBoundingClientRect().left; - const startTime = Math.min( - clientXToTime(rectLeft + startX), - clientXToTime(rectLeft + endX) - ); - const endTime = Math.max( - clientXToTime(rectLeft + startX), - clientXToTime(rectLeft + endX) - ); - options.onZoom?.({ startTime, endTime }); - resetDragging(false); - }; - const onPointerDown = (ev) => { - if (ev.button !== 0 || !inPlotBounds(ev.clientX, ev.clientY)) { - return; - } - pointerId = ev.pointerId; - const rect = canvas.getBoundingClientRect(); - startX = clampChartValue( - ev.clientX - rect.left, - renderer.pad.left, - renderer.pad.left + renderer.cw - ); - currentX = startX; - dragging = false; - interactionState._chartZoomDragging = false; - options.onPreview?.(null); - window.addEventListener("pointermove", onPointerMove); - window.addEventListener("pointerup", finish); - window.addEventListener("pointercancel", finish); - }; - const onDoubleClick = (ev) => { - if (!inPlotBounds(ev.clientX, ev.clientY)) { - return; - } - if (!options.onReset) { - return; - } - ev.preventDefault(); - options.onReset(); - }; - canvas.addEventListener("pointerdown", onPointerDown); - canvas.addEventListener("dblclick", onDoubleClick); - interactionState._chartZoomCleanup = () => { - canvas.removeEventListener("pointerdown", onPointerDown); - canvas.removeEventListener("dblclick", onDoubleClick); - window.removeEventListener("pointermove", onPointerMove); - window.removeEventListener("pointerup", finish); - window.removeEventListener("pointercancel", finish); - resetDragging(); - }; - } - const DATA_RANGE_CACHE_TTL_MS = 10 * 60 * 1e3; - const DATA_RANGE_CACHE_LIVE_EDGE_MS = 5 * 60 * 1e3; - const dataRangeCache = /* @__PURE__ */ new Map(); - function normalizeCacheIdList(values) { - return [ - ...new Set( - (Array.isArray(values) ? values : []).filter(Boolean) - ) - ].sort(); + .chart-top-slot[hidden] { + display: none; } - function shouldUseStableRangeCache(endTime) { - const endMs = new Date(endTime || 0).getTime(); - if (!Number.isFinite(endMs)) { - return false; - } - return endMs < Date.now() - DATA_RANGE_CACHE_LIVE_EDGE_MS; + .chart-top-slot { + position: relative; + flex: 0 0 auto; + min-width: 0; + margin-left: calc(var(--dp-spacing-md) * -1); + margin-right: calc(var(--dp-spacing-md) * -1); + margin-top: -5px; + z-index: 1; } - function getCachedRangePromise(key) { - const entry = dataRangeCache.get(key); - if (!entry) { - return null; - } - if (entry.expiresAt <= Date.now()) { - dataRangeCache.delete(key); - return null; - } - return entry.promise; - } - function setCachedRangePromise(key, promise) { - dataRangeCache.set(key, { - promise, - expiresAt: Date.now() + DATA_RANGE_CACHE_TTL_MS - }); - return promise; - } - function withStableRangeCache(key, endTime, loader) { - if (!shouldUseStableRangeCache(endTime)) { - return Promise.resolve().then(loader); - } - const cached = getCachedRangePromise(key); - if (cached) { - return cached; - } - const promise = Promise.resolve().then(loader).catch((err) => { - dataRangeCache.delete(key); - throw err; - }); - return setCachedRangePromise(key, promise); - } - function clearStableRangeCacheMatching(predicate) { - if (typeof predicate !== "function") { - return 0; - } - let deletedCount = 0; - [...dataRangeCache.keys()].forEach((key) => { - if (predicate(key) === true) { - dataRangeCache.delete(key); - deletedCount += 1; - } - }); - return deletedCount; - } - const MAX_DOWNSAMPLED_HISTORY_RANGE_MS = 90 * 24 * 60 * 60 * 1e3; - function parseIsoTimeMs(value) { - const timeMs = Date.parse(value); - if (!Number.isFinite(timeMs)) { - return null; - } - return timeMs; + .chart-wrap { + position: relative; + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + padding: var(--dp-spacing-sm) var(--dp-spacing-md) var(--dp-spacing-md); + box-sizing: border-box; + overflow: visible; + isolation: isolate; + z-index: 3; } - function buildDownsampledHistoryChunks(startTime, endTime) { - const startTimeMs = parseIsoTimeMs(startTime); - const endTimeMs = parseIsoTimeMs(endTime); - if (startTimeMs == null || endTimeMs == null || endTimeMs <= startTimeMs || endTimeMs - startTimeMs <= MAX_DOWNSAMPLED_HISTORY_RANGE_MS) { - return [{ startTime, endTime }]; - } - const chunks = []; - let chunkStartMs = startTimeMs; - while (chunkStartMs < endTimeMs) { - const chunkEndMs = Math.min( - endTimeMs, - chunkStartMs + MAX_DOWNSAMPLED_HISTORY_RANGE_MS - ); - chunks.push({ - startTime: new Date(chunkStartMs).toISOString(), - endTime: new Date(chunkEndMs).toISOString() - }); - if (chunkEndMs >= endTimeMs) { - break; - } - chunkStartMs = chunkEndMs + 1; - } - return chunks; - } - function fetchDownsampledHistory(hass, entityId, startTime, endTime, interval, aggregate) { - const cacheKey = JSON.stringify({ - type: "hass_datapoints/history", - entity_id: entityId, - start_time: startTime, - end_time: endTime, - interval, - aggregate - }); - return withStableRangeCache(cacheKey, endTime, async () => { - const chunks = buildDownsampledHistoryChunks(startTime, endTime); - const responses = await Promise.all( - chunks.map( - async (chunk) => hass.connection.sendMessagePromise({ - type: "hass_datapoints/history", - entity_id: entityId, - start_time: chunk.startTime, - end_time: chunk.endTime, - interval, - aggregate - }) - ) - ); - const mergedPoints = responses.flatMap( - (result) => result.pts || [] - ); - if (!mergedPoints.length) { - return []; - } - const dedupedPoints = /* @__PURE__ */ new Map(); - for (const point of mergedPoints) { - if (Array.isArray(point) && point.length > 0) { - dedupedPoints.set(String(point[0]), point); - } else { - dedupedPoints.set(JSON.stringify(point), point); - } - } - return [...dedupedPoints.values()]; - }); - } - function fetchAnomaliesFromBackend(hass, entityId, startTime, endTime, config) { - return hass.connection.sendMessagePromise({ - type: "hass_datapoints/anomalies", - entity_id: entityId, - start_time: startTime, - end_time: endTime, - anomaly_methods: config.anomaly_methods || [], - anomaly_sensitivity: config.anomaly_sensitivity || "medium", - anomaly_overlap_mode: config.anomaly_overlap_mode || "all", - anomaly_rate_window: config.anomaly_rate_window || "1h", - anomaly_zscore_window: config.anomaly_zscore_window || "24h", - anomaly_persistence_window: config.anomaly_persistence_window || "1h", - trend_method: config.trend_method || "rolling_average", - trend_window: config.trend_window || "24h", - ...config.anomaly_use_sampled_data !== false && config.sample_interval && config.sample_interval !== "raw" ? { - sample_interval: config.sample_interval, - sample_aggregate: config.sample_aggregate || "mean" - } : {}, - ...config.comparison_entity_id ? { - comparison_entity_id: config.comparison_entity_id, - comparison_start_time: config.comparison_start_time, - comparison_end_time: config.comparison_end_time, - comparison_time_offset_ms: config.comparison_time_offset_ms || 0 - } : {} - }).then( - (result) => result.anomaly_clusters || [] - ); + .chart-preview-overlay[hidden] { + display: none; } - async function fetchHistoryDuringPeriod(hass, startTime, endTime, entityIds, options = {}) { - const normalizedEntityIds = normalizeCacheIdList(entityIds); - const cacheKey = JSON.stringify({ - type: "history/history_during_period", - start_time: startTime, - end_time: endTime, - entity_ids: normalizedEntityIds, - include_start_time_state: options.include_start_time_state !== false, - significant_changes_only: !!options.significant_changes_only, - no_attributes: options.no_attributes !== false - }); - return withStableRangeCache( - cacheKey, - endTime, - () => hass.connection.sendMessagePromise({ - type: "history/history_during_period", - start_time: startTime, - end_time: endTime, - entity_ids: normalizedEntityIds, - include_start_time_state: options.include_start_time_state !== false, - significant_changes_only: !!options.significant_changes_only, - no_attributes: options.no_attributes !== false - }) - ); + .chart-preview-overlay { + position: absolute; + top: calc(var(--dp-chart-top-slot-height, 0px) + var(--dp-spacing-sm)); + left: var(--dp-spacing-md); + display: flex; + flex-direction: column; + gap: 2px; + max-width: min(340px, calc(100% - (var(--dp-spacing-lg) * 2))); + padding: 8px 12px; + border-radius: 10px; + background: color-mix(in srgb, var(--card-background-color, #fff) 90%, transparent); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); + backdrop-filter: blur(4px); + pointer-events: none; + z-index: 4; } - const VALID_ANOMALY_METHODS = [ - "trend_residual", - "rate_of_change", - "iqr", - "rolling_zscore", - "persistence", - "comparison_window" - ]; - const VALID_SAMPLE_INTERVALS = [ - "raw", - "1s", - "5s", - "10s", - "15s", - "30s", - "1m", - "2m", - "5m", - "10m", - "15m", - "30m", - "1h", - "2h", - "3h", - "4h", - "6h", - "12h", - "24h" - ]; - const VALID_SAMPLE_AGGREGATES = [ - "mean", - "min", - "max", - "median", - "first", - "last" - ]; - function normalizeHistorySeriesAnalysis(analysis) { - const source = analysis && typeof analysis === "object" ? analysis : {}; - return { - expanded: source.expanded === true, - show_trend_lines: source.show_trend_lines === true, - trend_method: source.trend_method === "linear_trend" ? "linear_trend" : "rolling_average", - trend_window: typeof source.trend_window === "string" && source.trend_window ? source.trend_window : "24h", - show_trend_crosshairs: source.show_trend_crosshairs !== false, - show_summary_stats: source.show_summary_stats === true, - show_summary_stats_shading: source.show_summary_stats_shading === true, - show_rate_of_change: source.show_rate_of_change === true, - show_rate_crosshairs: source.show_rate_crosshairs !== false, - rate_window: typeof source.rate_window === "string" && source.rate_window ? source.rate_window : "1h", - show_threshold_analysis: source.show_threshold_analysis === true, - show_threshold_shading: source.show_threshold_shading === true, - threshold_value: typeof source.threshold_value === "string" || typeof source.threshold_value === "number" ? String(source.threshold_value).trim() : "", - threshold_direction: source.threshold_direction === "below" ? "below" : "above", - show_anomalies: source.show_anomalies === true, - anomaly_methods: (() => { - if (Array.isArray(source.anomaly_methods)) { - return source.anomaly_methods.filter( - (method) => typeof method === "string" && VALID_ANOMALY_METHODS.includes(method) - ); - } - const legacy = typeof source.anomaly_method === "string" && VALID_ANOMALY_METHODS.includes(source.anomaly_method) ? source.anomaly_method : null; - return legacy ? [legacy] : []; - })(), - anomaly_overlap_mode: source.anomaly_overlap_mode === "only" ? "only" : "all", - anomaly_sensitivity: typeof source.anomaly_sensitivity === "string" && source.anomaly_sensitivity ? source.anomaly_sensitivity : "medium", - anomaly_rate_window: typeof source.anomaly_rate_window === "string" && source.anomaly_rate_window ? source.anomaly_rate_window : "1h", - anomaly_zscore_window: typeof source.anomaly_zscore_window === "string" && source.anomaly_zscore_window ? source.anomaly_zscore_window : "24h", - anomaly_persistence_window: typeof source.anomaly_persistence_window === "string" && source.anomaly_persistence_window ? source.anomaly_persistence_window : "1h", - anomaly_comparison_window_id: typeof source.anomaly_comparison_window_id === "string" && source.anomaly_comparison_window_id ? source.anomaly_comparison_window_id : null, - show_delta_analysis: source.show_delta_analysis === true, - show_delta_tooltip: source.show_delta_tooltip !== false, - show_delta_lines: source.show_delta_lines === true, - hide_source_series: source.hide_source_series === true, - sample_interval: typeof source.sample_interval === "string" && VALID_SAMPLE_INTERVALS.includes(source.sample_interval) ? source.sample_interval : "1m", - sample_aggregate: typeof source.sample_aggregate === "string" && VALID_SAMPLE_AGGREGATES.includes(source.sample_aggregate) ? source.sample_aggregate : "mean", - stepped_series: source.stepped_series === true, - anomaly_use_sampled_data: source.anomaly_use_sampled_data !== false - }; - } - function normalizeHistorySeriesRows(rows) { - if (!Array.isArray(rows)) { - return []; - } - const seen = /* @__PURE__ */ new Set(); - const normalized = []; - rows.forEach((row, index) => { - const entityId = typeof row?.entity_id === "string" ? row.entity_id.trim() : ""; - if (!entityId || seen.has(entityId)) { - return; - } - seen.add(entityId); - normalized.push({ - entity_id: entityId, - color: typeof row?.color === "string" && /^#[0-9a-f]{6}$/i.test(row.color) ? row.color : COLORS[index % COLORS.length], - visible: row?.visible !== false, - analysis: normalizeHistorySeriesAnalysis(row?.analysis) - }); - }); - return normalized; - } - function buildHistorySeriesRows(entityIds, previousRows = []) { - const normalizedPrevious = normalizeHistorySeriesRows(previousRows); - const previousMap = new Map( - normalizedPrevious.map((row) => [row.entity_id, row]) - ); - const intervals = normalizedPrevious.map( - (row) => row.analysis.sample_interval - ); - const allSame = intervals.length > 0 && intervals.every((interval) => interval === intervals[0]); - const inheritedSampleSettings = allSame ? { - sample_interval: intervals[0], - sample_aggregate: normalizedPrevious[0].analysis.sample_aggregate - } : null; - return normalizeEntityIds(entityIds).map((entityId, index) => { - const existing = previousMap.get(entityId); - if (existing) { - return existing; - } - return { - entity_id: entityId, - color: COLORS[index % COLORS.length], - visible: true, - analysis: normalizeHistorySeriesAnalysis(inheritedSampleSettings) - }; - }); - } - function slugifySeriesName(value) { - return String(value || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); - } - function parseSeriesColorsParam(value) { - if (!value || typeof value !== "string") { - return {}; - } - return value.split(",").reduce((acc, entry) => { - const [rawKey, rawColor] = entry.split(":"); - const key = decodeURIComponent(rawKey || "").trim(); - const color = String(rawColor || "").trim(); - if (!key || !/^#[0-9a-f]{6}$/i.test(color)) { - return acc; - } - acc[key] = color; - return acc; - }, {}); - } - const jsContent$1 = '(function() {\n "use strict";\n const HOUR_MS = 60 * 60 * 1e3;\n function getTrendWindowMs(value) {\n const windows = {\n "1h": 60 * 60 * 1e3,\n "6h": 6 * 60 * 60 * 1e3,\n "24h": 24 * 60 * 60 * 1e3,\n "7d": 7 * 24 * 60 * 60 * 1e3,\n "14d": 14 * 24 * 60 * 60 * 1e3,\n "21d": 21 * 24 * 60 * 60 * 1e3,\n "28d": 28 * 24 * 60 * 60 * 1e3\n };\n return windows[value] || windows["24h"];\n }\n function buildRollingAverageTrend(points, windowMs) {\n if (!Array.isArray(points) || points.length < 2 || !Number.isFinite(windowMs) || windowMs <= 0) {\n return [];\n }\n const trendPoints = [];\n let windowStartIndex = 0;\n let windowSum = 0;\n for (let index = 0; index < points.length; index += 1) {\n const [time, value] = points[index];\n windowSum += value;\n while (windowStartIndex < index && time - points[windowStartIndex][0] > windowMs) {\n windowSum -= points[windowStartIndex][1];\n windowStartIndex += 1;\n }\n const count = index - windowStartIndex + 1;\n if (count > 0) {\n trendPoints.push([time, windowSum / count]);\n }\n }\n return trendPoints;\n }\n function buildLinearTrend(points) {\n if (!Array.isArray(points) || points.length < 2) {\n return [];\n }\n const origin = points[0][0];\n let sumX = 0;\n let sumY = 0;\n let sumXX = 0;\n let sumXY = 0;\n for (const [time, value] of points) {\n const x = (time - origin) / HOUR_MS;\n sumX += x;\n sumY += value;\n sumXX += x * x;\n sumXY += x * value;\n }\n const count = points.length;\n const denominator = count * sumXX - sumX * sumX;\n if (!Number.isFinite(denominator) || Math.abs(denominator) < 1e-9) {\n return [];\n }\n const slope = (count * sumXY - sumX * sumY) / denominator;\n const intercept = (sumY - slope * sumX) / count;\n const firstTime = points[0][0];\n const lastTime = points[points.length - 1][0];\n const firstX = (firstTime - origin) / HOUR_MS;\n const lastX = (lastTime - origin) / HOUR_MS;\n return [\n [firstTime, intercept + slope * firstX],\n [lastTime, intercept + slope * lastX]\n ];\n }\n function buildTrendPoints(points, method, trendWindow) {\n if (!Array.isArray(points) || points.length < 2) {\n return [];\n }\n if (method === "linear_trend") {\n return buildLinearTrend(points);\n }\n return buildRollingAverageTrend(points, getTrendWindowMs(trendWindow));\n }\n function normalizeSeriesAnalysis(analysis) {\n const source = analysis && typeof analysis === "object" ? analysis : {};\n return {\n show_trend_lines: source.show_trend_lines === true,\n trend_method: source.trend_method === "linear_trend" ? "linear_trend" : "rolling_average",\n trend_window: typeof source.trend_window === "string" && source.trend_window ? source.trend_window : "24h",\n show_summary_stats: source.show_summary_stats === true,\n show_rate_of_change: source.show_rate_of_change === true,\n rate_window: typeof source.rate_window === "string" && source.rate_window ? source.rate_window : "1h",\n show_delta_analysis: source.show_delta_analysis === true\n };\n }\n function interpolateSeriesValue(points, timeMs) {\n if (!Array.isArray(points) || points.length === 0) {\n return null;\n }\n if (timeMs < points[0][0] || timeMs > points[points.length - 1][0]) {\n return null;\n }\n if (timeMs === points[0][0]) {\n return points[0][1];\n }\n if (timeMs === points[points.length - 1][0]) {\n return points[points.length - 1][1];\n }\n for (let index = 0; index < points.length - 1; index += 1) {\n const [startTime, startValue] = points[index];\n const [endTime, endValue] = points[index + 1];\n if (timeMs >= startTime && timeMs <= endTime) {\n const fraction = (timeMs - startTime) / (endTime - startTime);\n return startValue + (endValue - startValue) * fraction;\n }\n }\n return null;\n }\n function buildRateOfChangePoints(points, rateWindow) {\n if (!Array.isArray(points) || points.length < 2) {\n return [];\n }\n const ratePoints = [];\n for (let index = 1; index < points.length; index += 1) {\n const [timeMs, value] = points[index];\n let comparisonPoint = null;\n if (rateWindow === "point_to_point") {\n comparisonPoint = points[index - 1];\n } else {\n const windowMs = getTrendWindowMs(rateWindow);\n if (!Number.isFinite(windowMs) || windowMs <= 0) {\n continue;\n }\n for (let candidateIndex = index - 1; candidateIndex >= 0; candidateIndex -= 1) {\n const candidatePoint = points[candidateIndex];\n if (timeMs - candidatePoint[0] >= windowMs) {\n comparisonPoint = candidatePoint;\n break;\n }\n }\n if (!comparisonPoint) {\n comparisonPoint = points[0];\n }\n }\n if (!Array.isArray(comparisonPoint) || comparisonPoint.length < 2) {\n continue;\n }\n const deltaMs = timeMs - comparisonPoint[0];\n if (!Number.isFinite(deltaMs) || deltaMs <= 0) {\n continue;\n }\n const deltaHours = deltaMs / HOUR_MS;\n if (!Number.isFinite(deltaHours) || deltaHours <= 0) {\n continue;\n }\n const rateValue = (value - comparisonPoint[1]) / deltaHours;\n if (!Number.isFinite(rateValue)) {\n continue;\n }\n ratePoints.push([timeMs, rateValue]);\n }\n return ratePoints;\n }\n function buildDeltaPoints(sourcePoints, comparisonPoints) {\n if (!Array.isArray(sourcePoints) || sourcePoints.length < 2 || !Array.isArray(comparisonPoints) || comparisonPoints.length < 2) {\n return [];\n }\n const deltaPoints = [];\n for (const [timeMs, value] of sourcePoints) {\n const comparisonValue = interpolateSeriesValue(comparisonPoints, timeMs);\n if (comparisonValue == null) {\n continue;\n }\n deltaPoints.push([timeMs, value - comparisonValue]);\n }\n return deltaPoints;\n }\n function buildSummaryStats(points) {\n if (!Array.isArray(points) || points.length === 0) {\n return null;\n }\n let min = Infinity;\n let max = -Infinity;\n let sum = 0;\n let count = 0;\n for (const point of points) {\n const value = Number(point?.[1]);\n if (!Number.isFinite(value)) {\n continue;\n }\n if (value < min) {\n min = value;\n }\n if (value > max) {\n max = value;\n }\n sum += value;\n count += 1;\n }\n if (!Number.isFinite(min) || !Number.isFinite(max) || count === 0) {\n return null;\n }\n return {\n min,\n max,\n mean: sum / count\n };\n }\n function computeHistoryAnalysis(payload) {\n const series = (Array.isArray(payload?.series) ? payload.series : []).map(\n (seriesItem) => ({\n ...seriesItem,\n analysis: normalizeSeriesAnalysis(seriesItem?.analysis)\n })\n );\n const comparisonSeries = new Map(\n (Array.isArray(payload?.comparisonSeries) ? payload.comparisonSeries : []).filter((entry) => entry?.entityId).map((entry) => [entry.entityId, entry])\n );\n const result = {\n trendSeries: [],\n rateSeries: [],\n deltaSeries: [],\n summaryStats: [],\n anomalySeries: [],\n comparisonWindowResults: {}\n };\n for (const seriesItem of series) {\n const points = Array.isArray(seriesItem?.pts) ? seriesItem.pts : [];\n const analysis = normalizeSeriesAnalysis(seriesItem?.analysis);\n if (points.length < 2) {\n continue;\n }\n if (analysis.show_trend_lines === true) {\n const trendPoints = buildTrendPoints(\n points,\n analysis.trend_method,\n analysis.trend_window\n );\n if (trendPoints.length >= 2) {\n result.trendSeries.push({\n entityId: seriesItem.entityId,\n pts: trendPoints\n });\n }\n }\n if (analysis.show_rate_of_change === true) {\n const ratePoints = buildRateOfChangePoints(points, analysis.rate_window);\n if (ratePoints.length >= 2) {\n result.rateSeries.push({\n entityId: seriesItem.entityId,\n pts: ratePoints\n });\n }\n }\n if (analysis.show_summary_stats === true) {\n const summaryStats = buildSummaryStats(points);\n if (summaryStats) {\n result.summaryStats.push({\n entityId: seriesItem.entityId,\n ...summaryStats\n });\n }\n }\n if (analysis.show_delta_analysis === true && payload?.hasSelectedComparisonWindow === true) {\n const comparisonEntry = comparisonSeries.get(seriesItem.entityId);\n const comparisonPoints = comparisonEntry?.pts ?? [];\n if (comparisonPoints.length >= 2) {\n const deltaPoints = buildDeltaPoints(points, comparisonPoints);\n if (deltaPoints.length >= 2) {\n result.deltaSeries.push({\n entityId: seriesItem.entityId,\n pts: deltaPoints\n });\n }\n }\n }\n }\n const seriesAnalysisConfigs = typeof payload?.seriesAnalysisConfigs === "object" && payload.seriesAnalysisConfigs !== null ? payload.seriesAnalysisConfigs : {};\n const allComparisonWindowsData = typeof payload?.allComparisonWindowsData === "object" && payload.allComparisonWindowsData !== null ? payload.allComparisonWindowsData : {};\n for (const [windowId, entityPtsMap] of Object.entries(\n allComparisonWindowsData\n )) {\n result.comparisonWindowResults[windowId] = {};\n for (const [entityId, pts] of Object.entries(entityPtsMap)) {\n const winAnalysis = normalizeSeriesAnalysis(\n seriesAnalysisConfigs[entityId]\n );\n result.comparisonWindowResults[windowId][entityId] = {\n trendPts: winAnalysis.show_trend_lines && pts.length >= 2 ? buildTrendPoints(\n pts,\n winAnalysis.trend_method,\n winAnalysis.trend_window\n ) : [],\n ratePts: winAnalysis.show_rate_of_change && pts.length >= 2 ? buildRateOfChangePoints(pts, winAnalysis.rate_window) : [],\n summaryStats: winAnalysis.show_summary_stats ? buildSummaryStats(pts) : null\n };\n }\n }\n return result;\n }\n const workerScope = globalThis;\n workerScope.onmessage = (event) => {\n const { id, payload } = event.data || {};\n try {\n const result = computeHistoryAnalysis(payload);\n workerScope.postMessage({ id, result });\n } catch (error) {\n workerScope.postMessage({\n id,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n };\n})();\n'; - const blob$1 = typeof self !== "undefined" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", jsContent$1], { type: "text/javascript;charset=utf-8" }); - function WorkerWrapper$1(options) { - let objURL; - try { - objURL = blob$1 && (self.URL || self.webkitURL).createObjectURL(blob$1); - if (!objURL) throw ""; - const worker = new Worker(objURL, { - name: options?.name - }); - worker.addEventListener("error", () => { - (self.URL || self.webkitURL).revokeObjectURL(objURL); - }); - return worker; - } catch (e2) { - return new Worker( - "data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent$1), - { - name: options?.name - } - ); - } - } - let workerInstance$1 = null; - let requestId$1 = 0; - const pending$1 = /* @__PURE__ */ new Map(); - function getHistoryAnalysisWorker() { - if (workerInstance$1) { - return workerInstance$1; - } - workerInstance$1 = new WorkerWrapper$1(); - workerInstance$1.addEventListener( - "message", - (event) => { - const { id, result, error } = event.data || {}; - const handlers = pending$1.get(id || -1); - if (!handlers) { - return; - } - pending$1.delete(id || -1); - if (error) { - handlers.reject(new Error(error)); - return; - } - handlers.resolve(result); - } - ); - workerInstance$1.addEventListener("error", (error) => { - pending$1.forEach((handlers) => { - handlers.reject(error); - }); - pending$1.clear(); - workerInstance$1 = null; - }); - return workerInstance$1; - } - function terminateHistoryAnalysisWorker() { - if (pending$1.size > 0) { - pending$1.forEach(({ reject }) => { - reject(new Error("Aborted: superseded by newer analysis")); - }); - pending$1.clear(); - } - if (workerInstance$1) { - workerInstance$1.terminate(); - workerInstance$1 = null; - } - } - function computeHistoryAnalysisInWorker(payload) { - const worker = getHistoryAnalysisWorker(); - return new Promise((resolve, reject) => { - const id = ++requestId$1; - pending$1.set(id, { resolve, reject }); - worker.postMessage({ id, payload }); - }); - } - const jsContent = '(function() {\n "use strict";\n function downsamplePts(pts, intervalMs, aggregate) {\n if (!pts.length || intervalMs <= 0) {\n return pts;\n }\n const buckets = /* @__PURE__ */ new Map();\n const bucketRepTime = /* @__PURE__ */ new Map();\n for (const [time, value] of pts) {\n const idx = Math.floor(time / intervalMs);\n if (!buckets.has(idx)) {\n buckets.set(idx, []);\n bucketRepTime.set(idx, time);\n }\n buckets.get(idx)?.push(value);\n }\n const result = [];\n for (const idx of [...buckets.keys()].sort((a, b) => a - b)) {\n const values = buckets.get(idx) || [];\n const repTime = bucketRepTime.get(idx) || 0;\n let agg;\n if (aggregate === "min") {\n agg = Math.min(...values);\n } else if (aggregate === "max") {\n agg = Math.max(...values);\n } else if (aggregate === "median") {\n const sorted = [...values].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n agg = sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;\n } else if (aggregate === "first") {\n agg = values[0];\n } else if (aggregate === "last") {\n agg = values[values.length - 1];\n } else {\n agg = values.reduce((sum, current) => sum + current, 0) / values.length;\n }\n result.push([repTime, agg]);\n }\n return result;\n }\n self.onmessage = ({ data }) => {\n const { id, type, payload } = data || {};\n try {\n let result;\n if (type === "downsample") {\n result = downsamplePts(\n payload.pts,\n payload.intervalMs,\n payload.aggregate\n );\n } else {\n throw new Error(`Unknown message type: ${type}`);\n }\n self.postMessage({ id, result });\n } catch (err) {\n self.postMessage({\n id,\n error: String(err)\n });\n }\n };\n})();\n'; - const blob = typeof self !== "undefined" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", jsContent], { type: "text/javascript;charset=utf-8" }); - function WorkerWrapper(options) { - let objURL; - try { - objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob); - if (!objURL) throw ""; - const worker = new Worker(objURL, { - name: options?.name - }); - worker.addEventListener("error", () => { - (self.URL || self.webkitURL).revokeObjectURL(objURL); - }); - return worker; - } catch (e2) { - return new Worker( - "data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent), - { - name: options?.name - } - ); - } - } - let workerInstance; - let requestId = 0; - const pending = /* @__PURE__ */ new Map(); - function getChartDataWorker() { - if (workerInstance !== void 0) { - return workerInstance; - } - try { - workerInstance = new WorkerWrapper(); - workerInstance.addEventListener( - "message", - (event) => { - const { id, result, error } = event.data || {}; - const handlers = pending.get(id || -1); - if (!handlers) { - return; - } - pending.delete(id || -1); - if (error) { - handlers.reject(new Error(error)); - } else { - handlers.resolve(result || []); - } - } - ); - workerInstance.addEventListener("error", (err) => { - pending.forEach(({ reject }) => { - reject(err); - }); - pending.clear(); - workerInstance = null; - }); - } catch { - workerInstance = null; - } - return workerInstance; - } - function downsampleInWorker(pts, intervalMs, aggregate) { - if (pts.length === 0) { - return Promise.resolve([]); - } - const worker = getChartDataWorker(); - if (!worker) { - return Promise.reject(new Error("Worker not available")); - } - return new Promise((resolve, reject) => { - const id = ++requestId; - pending.set(id, { resolve, reject }); - worker.postMessage({ - id, - type: "downsample", - payload: { pts, intervalMs, aggregate } - }); - }); - } - const HOUR_MS$1 = 60 * 60 * 1e3; - function getTrendWindowMs(value) { - const windows = { - "1h": HOUR_MS$1, - "6h": 6 * HOUR_MS$1, - "24h": 24 * HOUR_MS$1, - "7d": 7 * 24 * HOUR_MS$1, - "14d": 14 * 24 * HOUR_MS$1, - "21d": 21 * 24 * HOUR_MS$1, - "28d": 28 * 24 * HOUR_MS$1 - }; - return windows[value] ?? windows["24h"]; - } - function buildRollingAverageTrend(points, windowMs) { - if (!Array.isArray(points) || points.length < 2 || !Number.isFinite(windowMs) || windowMs <= 0) { - return []; - } - const trendPoints = []; - let windowStartIndex = 0; - let windowSum = 0; - for (let index = 0; index < points.length; index += 1) { - const [time, value] = points[index]; - windowSum += value; - while (windowStartIndex < index && time - points[windowStartIndex][0] > windowMs) { - windowSum -= points[windowStartIndex][1]; - windowStartIndex += 1; - } - const count = index - windowStartIndex + 1; - if (count > 0) { - trendPoints.push([time, windowSum / count]); - } - } - return trendPoints; - } - function buildLinearTrend(points) { - if (!Array.isArray(points) || points.length < 2) { - return []; - } - const origin = points[0][0]; - let sumX = 0; - let sumY = 0; - let sumXX = 0; - let sumXY = 0; - for (const [time, value] of points) { - const x2 = (time - origin) / (60 * 60 * 1e3); - sumX += x2; - sumY += value; - sumXX += x2 * x2; - sumXY += x2 * value; - } - const count = points.length; - const denominator = count * sumXX - sumX * sumX; - if (!Number.isFinite(denominator) || Math.abs(denominator) < 1e-9) { - return []; - } - const slope = (count * sumXY - sumX * sumY) / denominator; - const intercept = (sumY - slope * sumX) / count; - const firstTime = points[0][0]; - const lastTime = points[points.length - 1][0]; - const firstX = (firstTime - origin) / (60 * 60 * 1e3); - const lastX = (lastTime - origin) / (60 * 60 * 1e3); - return [ - [firstTime, intercept + slope * firstX], - [lastTime, intercept + slope * lastX] - ]; - } - function interpolateSeriesValue(points, timeMs) { - if (!Array.isArray(points) || !points.length) { - return null; - } - if (timeMs < points[0][0] || timeMs > points[points.length - 1][0]) { - return null; - } - if (timeMs === points[0][0]) { - return points[0][1]; - } - if (timeMs === points[points.length - 1][0]) { - return points[points.length - 1][1]; - } - for (let index = 0; index < points.length - 1; index += 1) { - const [startTime, startValue] = points[index]; - const [endTime, endValue] = points[index + 1]; - if (timeMs >= startTime && timeMs <= endTime) { - const fraction = (timeMs - startTime) / (endTime - startTime); - return startValue + fraction * (endValue - startValue); - } - } - return null; - } - function buildRateOfChangePoints(points, rateWindow = "1h") { - if (!Array.isArray(points) || points.length < 2) { - return []; - } - const ratePoints = []; - for (let index = 1; index < points.length; index += 1) { - const [timeMs, value] = points[index]; - let comparisonPoint = null; - if (rateWindow === "point_to_point") { - comparisonPoint = points[index - 1]; - } else { - const windowMs = getTrendWindowMs(rateWindow); - if (!Number.isFinite(windowMs) || windowMs <= 0) { - continue; - } - for (let candidateIndex = index - 1; candidateIndex >= 0; candidateIndex -= 1) { - const candidatePoint = points[candidateIndex]; - if (timeMs - candidatePoint[0] >= windowMs) { - comparisonPoint = candidatePoint; - break; - } - } - if (!comparisonPoint) { - comparisonPoint = points[0]; - } - } - if (!Array.isArray(comparisonPoint) || comparisonPoint.length < 2) { - continue; - } - const deltaMs = timeMs - comparisonPoint[0]; - if (!Number.isFinite(deltaMs) || deltaMs <= 0) { - continue; - } - const deltaHours = deltaMs / (60 * 60 * 1e3); - if (!Number.isFinite(deltaHours) || deltaHours <= 0) { - continue; - } - const rateValue = (value - comparisonPoint[1]) / deltaHours; - if (!Number.isFinite(rateValue)) { - continue; - } - ratePoints.push([timeMs, rateValue]); - } - return ratePoints; - } - function buildDeltaPoints(sourcePoints, comparisonPoints) { - if (!Array.isArray(sourcePoints) || sourcePoints.length < 2 || !Array.isArray(comparisonPoints) || comparisonPoints.length < 2) { - return []; - } - const deltaPoints = []; - for (const [timeMs, value] of sourcePoints) { - const comparisonValue = interpolateSeriesValue(comparisonPoints, timeMs); - if (comparisonValue == null) { - continue; - } - deltaPoints.push([timeMs, value - comparisonValue]); - } - return deltaPoints; - } - function buildSummaryStats(points) { - if (!Array.isArray(points) || !points.length) { - return null; - } - let min = Infinity; - let max = -Infinity; - let sum = 0; - let count = 0; - for (const point of points) { - const value = Number(point?.[1]); - if (!Number.isFinite(value)) { - continue; - } - if (value < min) { - min = value; - } - if (value > max) { - max = value; - } - sum += value; - count += 1; - } - if (!Number.isFinite(min) || !Number.isFinite(max) || count === 0) { - return null; - } - return { - min, - max, - mean: sum / count - }; - } - function parseDateValue(value) { - if (!value) { - return null; - } - if (value instanceof Date) { - return Number.isNaN(value.getTime()) ? null : value; - } - const parsed = new Date(value); - return Number.isNaN(parsed.getTime()) ? null : parsed; - } - function createChartZoomRange(startValue, endValue) { - const startDate = parseDateValue(startValue); - const endDate = parseDateValue(endValue); - const start = startDate?.getTime(); - const end = endDate?.getTime(); - if (typeof start === "number" && Number.isFinite(start) && typeof end === "number" && Number.isFinite(end) && start < end) { - return { start, end }; - } - return null; - } - function makeDateWindowId(label, existingIds = /* @__PURE__ */ new Set()) { - const base = slugifySeriesName(label) || "date-window"; - let candidate = base; - let suffix = 2; - while (existingIds.has(candidate)) { - candidate = `${base}-${suffix}`; - suffix += 1; - } - return candidate; - } - function normalizeDateWindows(windows) { - if (!Array.isArray(windows)) { - return []; - } - const seen = /* @__PURE__ */ new Set(); - const normalized = []; - windows.forEach((window2, index) => { - const label = String(window2?.label || window2?.name || "").trim(); - const start = parseDateValue( - window2?.start_time || window2?.start - ); - const end = parseDateValue( - window2?.end_time || window2?.end - ); - if (!label || !start || !end || start >= end) { - return; - } - const id = String(window2?.id || "").trim() || makeDateWindowId(`${label}-${index + 1}`, seen); - if (seen.has(id)) { - return; - } - seen.add(id); - normalized.push({ - id, - label, - start_time: start.toISOString(), - end_time: end.toISOString() - }); - }); - return normalized; - } - function parseDateWindowsParam(value) { - if (!value || typeof value !== "string") { - return []; - } - return normalizeDateWindows( - value.split("|").map((entry) => { - const [rawId, rawLabel, rawStart, rawEnd] = String(entry).split("~"); - return { - id: decodeURIComponent(rawId || ""), - label: decodeURIComponent(rawLabel || ""), - start_time: decodeURIComponent(rawStart || ""), - end_time: decodeURIComponent(rawEnd || "") - }; - }) - ); - } - function serializeDateWindowsParam(windows) { - const normalized = normalizeDateWindows(windows); - if (!normalized.length) { - return ""; - } - return normalized.map( - (window2) => [ - encodeURIComponent(window2.id), - encodeURIComponent(window2.label ?? ""), - encodeURIComponent(window2.start_time), - encodeURIComponent(window2.end_time) - ].join("~") - ).join("|"); - } - function parseHistoryPageStateParam(value) { - if (!value || typeof value !== "string") { - return null; - } - try { - const parsed = JSON.parse(value); - return parsed && typeof parsed === "object" ? parsed : null; - } catch { - return null; - } - } - function serializeHistoryPageStateParam(state) { - if (!state || typeof state !== "object") { - return ""; - } - try { - return JSON.stringify(state); - } catch { - return ""; - } - } - function buildDataPointsHistoryPath(target = {}, options = {}) { - const normalizedTarget = { - entity_id: [...new Set((target.entity_id || []).filter(Boolean))], - device_id: [...new Set((target.device_id || []).filter(Boolean))], - area_id: [...new Set((target.area_id || []).filter(Boolean))], - label_id: [...new Set((target.label_id || []).filter(Boolean))] - }; - const params = new URLSearchParams(); - if (normalizedTarget.entity_id.length) { - params.set("entity_id", normalizedTarget.entity_id.join(",")); - } - if (normalizedTarget.device_id.length) { - params.set("device_id", normalizedTarget.device_id.join(",")); - } - if (normalizedTarget.area_id.length) { - params.set("area_id", normalizedTarget.area_id.join(",")); - } - if (normalizedTarget.label_id.length) { - params.set("label_id", normalizedTarget.label_id.join(",")); - } - if (options.datapoint_scope === "all") { - params.set("datapoints_scope", "all"); - } - const start = options.start_time ? new Date(options.start_time) : null; - const end = options.end_time ? new Date(options.end_time) : null; - if (start && end && Number.isFinite(start.getTime()) && Number.isFinite(end.getTime()) && start < end) { - params.set("start_time", start.toISOString()); - params.set("end_time", end.toISOString()); - params.set( - "hours_to_show", - String( - Math.max(1, Math.round((end.getTime() - start.getTime()) / 36e5)) - ) - ); - } - const zoomStart = options.zoom_start_time ? new Date(options.zoom_start_time) : null; - const zoomEnd = options.zoom_end_time ? new Date(options.zoom_end_time) : null; - if (zoomStart && zoomEnd && Number.isFinite(zoomStart.getTime()) && Number.isFinite(zoomEnd.getTime()) && zoomStart < zoomEnd) { - params.set("zoom_start_time", zoomStart.toISOString()); - params.set("zoom_end_time", zoomEnd.toISOString()); - } - const pageStateParam = serializeHistoryPageStateParam(options.page_state); - if (pageStateParam) { - params.set("page_state", pageStateParam); - } - return `/${PANEL_URL_PATH}?${params.toString()}`; - } - function navigateToDataPointsHistory(_card, target = {}, options = {}) { - const path = buildDataPointsHistoryPath(target, options); - if (window.history && window.history.pushState) { - window.history.pushState(null, "", path); - window.dispatchEvent(new Event("location-changed")); - return; - } - window.location.assign(path); - } - const styles$S = ` - hass-datapoints-history-chart { - position: relative; - display: flex; - flex-direction: column; - height: 100%; - min-height: 0; - --dp-spacing-xs: calc(var(--spacing, 8px) * 0.5); - --dp-spacing-sm: var(--spacing, 8px); - --dp-spacing-md: calc(var(--spacing, 8px) * 1.5); - --dp-spacing-lg: calc(var(--spacing, 8px) * 2); - --dp-spacing-xl: calc(var(--spacing, 8px) * 2.5); - --ha-tooltip-background-color: color-mix(in srgb, #0f1218 96%, transparent); - --ha-tooltip-text-color: rgba(255, 255, 255, 0.96); - --ha-tooltip-padding: calc(var(--dp-spacing-sm) + 2px) calc(var(--dp-spacing-md) + 2px); - --ha-tooltip-border-radius: 10px; - --ha-tooltip-arrow-size: 10px; - --ha-tooltip-font-size: 0.86rem; - --ha-tooltip-line-height: 1.1; - } - ha-card { padding: 0; overflow: visible; height: 100%; display: flex; flex-direction: column; } - .card-header { - padding: var(--dp-spacing-lg); - font-size: 1.1em; - font-weight: 500; - color: var(--primary-text-color); - flex: 0 0 auto; - line-height: 1.3; - } - .chart-top-slot[hidden] { - display: none; - } - .chart-top-slot { - position: relative; - flex: 0 0 auto; - min-width: 0; - margin-left: calc(var(--dp-spacing-md) * -1); - margin-right: calc(var(--dp-spacing-md) * -1); - margin-top: -5px; - z-index: 1; - } - .chart-wrap { - position: relative; - display: flex; - flex-direction: column; - flex: 1 1 auto; - min-height: 0; - padding: var(--dp-spacing-sm) var(--dp-spacing-md) var(--dp-spacing-md); - box-sizing: border-box; - overflow: visible; - isolation: isolate; - z-index: 3; - } - .chart-preview-overlay[hidden] { - display: none; - } - .chart-preview-overlay { - position: absolute; - top: calc(var(--dp-chart-top-slot-height, 0px) + var(--dp-spacing-sm)); - left: var(--dp-spacing-md); - display: flex; - flex-direction: column; - gap: 2px; - max-width: min(340px, calc(100% - (var(--dp-spacing-lg) * 2))); - padding: 8px 12px; - border-radius: 10px; - background: color-mix(in srgb, var(--card-background-color, #fff) 90%, transparent); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); - backdrop-filter: blur(4px); - pointer-events: none; - z-index: 4; - } - .chart-preview-kicker { - font-size: 0.68rem; - line-height: 1.15; - color: color-mix(in srgb, var(--warning-color, #f59e0b) 72%, var(--secondary-text-color, #6b7280)); - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.04em; + .chart-preview-kicker { + font-size: 0.68rem; + line-height: 1.15; + color: color-mix(in srgb, var(--warning-color, #f59e0b) 72%, var(--secondary-text-color, #6b7280)); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; } .chart-preview-title { font-size: 0.84rem; @@ -9004,100 +10620,298 @@ ${s2.description}`).join("\n\n"); text-overflow: ellipsis; } `; - var __defProp$V = Object.defineProperty; - var __defNormalProp$V = (obj, key, value) => key in obj ? __defProp$V(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$V = (obj, key, value) => __defNormalProp$V(obj, typeof key !== "symbol" ? key + "" : key, value); - const HISTORY_LEGEND_WRAP_ENABLE_HEIGHT_PX = 500; - const HISTORY_LEGEND_WRAP_DISABLE_HEIGHT_PX = 440; - const HISTORY_CHART_MAX_CANVAS_WIDTH_PX = Math.floor( - 16383 / (window.devicePixelRatio || 1) - ); - const HISTORY_CHART_MAX_ZOOM_MULTIPLIER = 365; - let HistoryChart$1 = class HistoryChart extends HTMLElement { - constructor() { - super(...arguments); - __publicField$V(this, "_hass", null); - __publicField$V(this, "_config", {}); - __publicField$V(this, "_legendWrapRows", false); - __publicField$V(this, "_adjustComparisonAxisScale", false); - __publicField$V(this, "_drawRequestId", 0); - __publicField$V(this, "_analysisCache", null); - __publicField$V(this, "_backendAnomalyByEntity", /* @__PURE__ */ new Map()); - __publicField$V(this, "_backendComparisonAnomalyByKey", /* @__PURE__ */ new Map()); - __publicField$V(this, "_pendingAnomalyEntityIds", /* @__PURE__ */ new Set()); - __publicField$V(this, "_pendingComparisonAnomalyKeys", /* @__PURE__ */ new Set()); - __publicField$V(this, "_chartHoverCleanup", null); - __publicField$V(this, "_chartZoomCleanup", null); - __publicField$V(this, "_chartZoomDragging", false); - __publicField$V(this, "_chartLastHover", null); - __publicField$V(this, "_scrollSyncSuspended", false); - __publicField$V(this, "_lastProgrammaticScrollLeft", null); - __publicField$V(this, "_ignoreNextProgrammaticScrollEvent", false); - __publicField$V(this, "_skipNextScrollViewportSync", false); - __publicField$V(this, "_creatingContextAnnotation", false); - __publicField$V(this, "_lastComparisonResults", null); - __publicField$V(this, "_hiddenSeries", /* @__PURE__ */ new Set()); - __publicField$V(this, "_entityIds", []); - __publicField$V(this, "_previousSeriesEndpoints", /* @__PURE__ */ new Map()); - __publicField$V(this, "_zoomRange", null); - __publicField$V(this, "_chartScrollViewportEl", null); - __publicField$V(this, "_chartStageEl", null); - __publicField$V(this, "_annotationDialog", null); - __publicField$V(this, "_scrollZoomApplyTimer", null); - __publicField$V(this, "_onChartScroll", () => { - if (this._scrollSyncSuspended || this._ignoreNextProgrammaticScrollEvent) { - this._ignoreNextProgrammaticScrollEvent = false; - return; - } - if (!this._chartScrollViewportEl || !this._zoomRange) return; - const viewport = this._chartScrollViewportEl; - const scrollLeft = viewport.scrollLeft; - const maxScrollLeft = Math.max( - 1, - viewport.scrollWidth - viewport.clientWidth - ); - const ratio = scrollLeft / maxScrollLeft; - const totalMs = Math.max(1, this._lastT1 - this._lastT0); - const spanMs = this._zoomRange.end - this._zoomRange.start; - const maxStartOffsetMs = Math.max(0, totalMs - spanMs); - const newStart = this._lastT0 + ratio * maxStartOffsetMs; - this._zoomRange = { start: newStart, end: newStart + spanMs }; - this._dispatchZoomPreview({ - startTime: newStart, - endTime: newStart + spanMs - }); - if (this._scrollZoomApplyTimer !== null) { - clearTimeout(this._scrollZoomApplyTimer); - } - this._scrollZoomApplyTimer = setTimeout(() => { - this._scrollZoomApplyTimer = null; - if (!this._zoomRange) return; - this.dispatchEvent( - new CustomEvent("hass-datapoints-zoom-apply", { - bubbles: true, - composed: true, - detail: { start: this._zoomRange.start, end: this._zoomRange.end } - }) - ); - }, 300); - }); - __publicField$V(this, "_lastAnomalyRegions", []); - __publicField$V(this, "_lastHistResult", null); - __publicField$V(this, "_lastStatsResult", null); - __publicField$V(this, "_lastEvents", null); - __publicField$V(this, "_hiddenEventIds", /* @__PURE__ */ new Set()); - __publicField$V(this, "_lastT0", 0); - __publicField$V(this, "_lastT1", 0); - __publicField$V(this, "_lastDrawArgs", []); - } - // ── No shadow DOM ─────────────────────────────────────────────────────────── - // Renders synchronously into its own children so the canvas and legend are - // accessible from the parent card's shadow root (required by existing tests). - /** Called once when the element is inserted into the DOM. */ - connectedCallback() { - this.style.cssText = "position:relative;display:flex;flex-direction:column;height:100%;min-height:0;padding:var(--dp-spacing-sm,8px) var(--dp-spacing-md,12px) var(--dp-spacing-md,12px);box-sizing:border-box;overflow:visible;isolation:isolate;z-index:3;"; - if (this.querySelector("#chart")) return; - this.innerHTML = ` + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.ts + /** + * history-chart.ts + * + * LitElement sub-component that owns the canvas, the chart DOM shell + * (loading spinner, tooltips, crosshair, legend, axis overlays), and all + * drawing/interaction state for the history chart card. + * + * IMPORTANT: No shadow DOM — renders into its own children so the canvas and + * legend remain accessible from the parent card's shadow root. + */ + var HISTORY_LEGEND_WRAP_ENABLE_HEIGHT_PX = 500; + var HISTORY_LEGEND_WRAP_DISABLE_HEIGHT_PX = 440; + var HISTORY_CHART_MAX_CANVAS_WIDTH_PX = Math.floor(16383 / (window.devicePixelRatio || 1)); + var HISTORY_CHART_MAX_ZOOM_MULTIPLIER = 365; + var HistoryChart$1 = class extends HTMLElement { + constructor(..._args) { + super(..._args); + _defineProperty(this, "_hass", null); + _defineProperty(this, "_config", {}); + _defineProperty( + this, + /** Whether the legend should wrap to multiple rows. */ + "_legendWrapRows", + false + ); + _defineProperty( + this, + /** When true, the comparison axis scale has been manually adjusted by the user. */ + "_adjustComparisonAxisScale", + false + ); + _defineProperty( + this, + /** Monotonically-incrementing request ID — stale draws are discarded. */ + "_drawRequestId", + 0 + ); + _defineProperty( + this, + /** Cached analysis result keyed by a content hash. */ + "_analysisCache", + null + ); + _defineProperty( + this, + /** Backend anomaly data keyed by entity ID. */ + "_backendAnomalyByEntity", + /* @__PURE__ */ new Map() + ); + _defineProperty( + this, + /** Backend anomaly data keyed by comparison window ID + entity ID. */ + "_backendComparisonAnomalyByKey", + /* @__PURE__ */ new Map() + ); + _defineProperty( + this, + /** Entity IDs whose anomaly fetch is currently in-flight. */ + "_pendingAnomalyEntityIds", + /* @__PURE__ */ new Set() + ); + _defineProperty( + this, + /** Comparison window + entity keys whose anomaly fetch is currently in-flight. */ + "_pendingComparisonAnomalyKeys", + /* @__PURE__ */ new Set() + ); + _defineProperty( + this, + /** + * Cleanup function returned by attachLineChartHover. + * Must be called before re-attaching hover or before unmount. + */ + "_chartHoverCleanup", + null + ); + _defineProperty( + this, + /** + * Cleanup function returned by attachLineChartRangeZoom. + * Must be called before re-attaching zoom or before unmount. + */ + "_chartZoomCleanup", + null + ); + _defineProperty( + this, + /** True while the user is drag-zooming on the split chart overlay. */ + "_chartZoomDragging", + false + ); + _defineProperty( + this, + /** Last computed hover object — used by split-chart crosshair/tooltip. */ + "_chartLastHover", + null + ); + _defineProperty( + this, + /** + * When true, scroll sync events from the secondary (comparison) chart + * viewport are temporarily ignored to prevent feedback loops. + */ + "_scrollSyncSuspended", + false + ); + _defineProperty( + this, + /** + * The scrollLeft value of the last programmatic scroll, used to detect + * and ignore the scroll event that the browser fires for that scroll. + */ + "_lastProgrammaticScrollLeft", + null + ); + _defineProperty( + this, + /** + * Set to true immediately before a programmatic scroll so the resulting + * scroll event can be identified and skipped by the scroll handler. + */ + "_ignoreNextProgrammaticScrollEvent", + false + ); + _defineProperty( + this, + /** + * When true, the next call to _syncChartViewportScroll will skip + * repositioning the viewport and clear this flag. Used to prevent + * a snap-back immediately after the user commits a zoom via scroll. + */ + "_skipNextScrollViewportSync", + false + ); + _defineProperty( + this, + /** + * True while the user is in the process of creating a context annotation + * (i.e. after clicking the "+" button but before the dialog closes). + */ + "_creatingContextAnnotation", + false + ); + _defineProperty( + this, + /** Last comparison results fetched by the card. */ + "_lastComparisonResults", + null + ); + _defineProperty( + this, + /** Series entity IDs that are currently hidden from the chart. */ + "_hiddenSeries", + /* @__PURE__ */ new Set() + ); + _defineProperty( + this, + /** Entity IDs tracked by this card instance. */ + "_entityIds", + [] + ); + _defineProperty( + this, + /** Previous drawn endpoints per entity, used for live-update logging. */ + "_previousSeriesEndpoints", + /* @__PURE__ */ new Map() + ); + _defineProperty( + this, + /** The currently active zoom range, or null if not zoomed. */ + "_zoomRange", + null + ); + _defineProperty( + this, + /** The chart scroll viewport element (set during draw). */ + "_chartScrollViewportEl", + null + ); + _defineProperty( + this, + /** The chart stage element (set during draw). */ + "_chartStageEl", + null + ); + _defineProperty( + this, + /** The annotation dialog controller. */ + "_annotationDialog", + null + ); + _defineProperty( + this, + /** Debounce timer for dispatching zoom-apply after a user scroll. */ + "_scrollZoomApplyTimer", + null + ); + _defineProperty( + this, + /** Bound scroll handler — wired to _chartScrollViewportEl. */ + "_onChartScroll", + () => { + if (this._scrollSyncSuspended || this._ignoreNextProgrammaticScrollEvent) { + this._ignoreNextProgrammaticScrollEvent = false; + return; + } + if (!this._chartScrollViewportEl || !this._zoomRange) return; + const viewport = this._chartScrollViewportEl; + const ratio = viewport.scrollLeft / Math.max(1, viewport.scrollWidth - viewport.clientWidth); + const totalMs = Math.max(1, this._lastT1 - this._lastT0); + const spanMs = this._zoomRange.end - this._zoomRange.start; + const maxStartOffsetMs = Math.max(0, totalMs - spanMs); + const newStart = this._lastT0 + ratio * maxStartOffsetMs; + this._zoomRange = { + start: newStart, + end: newStart + spanMs + }; + this._dispatchZoomPreview({ + startTime: newStart, + endTime: newStart + spanMs + }); + if (this._scrollZoomApplyTimer !== null) clearTimeout(this._scrollZoomApplyTimer); + this._scrollZoomApplyTimer = setTimeout(() => { + this._scrollZoomApplyTimer = null; + if (!this._zoomRange) return; + this.dispatchEvent(new CustomEvent("hass-datapoints-zoom-apply", { + bubbles: true, + composed: true, + detail: { + start: this._zoomRange.start, + end: this._zoomRange.end + } + })); + }, 300); + } + ); + _defineProperty( + this, + /** Last drawn anomaly regions (for hover hit-testing). */ + "_lastAnomalyRegions", + [] + ); + _defineProperty( + this, + /** Cache of last drawn history result. */ + "_lastHistResult", + null + ); + _defineProperty( + this, + /** Cache of last drawn statistics result. */ + "_lastStatsResult", + null + ); + _defineProperty( + this, + /** Cache of last drawn events list. */ + "_lastEvents", + null + ); + _defineProperty( + this, + /** IDs of datapoint events currently hidden from the chart. */ + "_hiddenEventIds", + /* @__PURE__ */ new Set() + ); + _defineProperty( + this, + /** Cache of last draw time range start (ms). */ + "_lastT0", + 0 + ); + _defineProperty( + this, + /** Cache of last draw time range end (ms). */ + "_lastT1", + 0 + ); + _defineProperty( + this, + /** Cache of last _drawChart argument list. */ + "_lastDrawArgs", + [] + ); + } + /** Called once when the element is inserted into the DOM. */ + connectedCallback() { + this.style.cssText = "position:relative;display:flex;flex-direction:column;height:100%;min-height:0;padding:var(--dp-spacing-sm,8px) var(--dp-spacing-md,12px) var(--dp-spacing-md,12px);box-sizing:border-box;overflow:visible;isolation:isolate;z-index:3;"; + if (this.querySelector("#chart")) return; + this.innerHTML = `
@@ -9151,8910 +10965,6354 @@ ${s2.description}`).join("\n\n");
`; - } - disconnectedCallback() { - if (this._chartHoverCleanup) { - this._chartHoverCleanup(); - this._chartHoverCleanup = null; - } - if (this._chartZoomCleanup) { - this._chartZoomCleanup(); - this._chartZoomCleanup = null; - } - if (this._chartScrollViewportEl) { - this._chartScrollViewportEl.removeEventListener( - "scroll", - this._onChartScroll - ); - } - if (this._scrollZoomApplyTimer !== null) { - clearTimeout(this._scrollZoomApplyTimer); - this._scrollZoomApplyTimer = null; - } - } - get hass() { - return this._hass; - } - set hass(value) { - this._hass = value; - } - /** True when the current user may create a data point via the chart + button. */ - get _canAddAnnotation() { - return this._config.show_add_annotation_button !== false && this._hass?.user?.is_admin === true; - } - // ── DOM helpers ───────────────────────────────────────────────────────────── - // Use `this.querySelector` / `this.querySelectorAll` instead of - // `this.shadowRoot.querySelector` because there is no shadow root. - _el(id) { - return this.querySelector(`#${id}`); - } - _els(selector) { - return this.querySelectorAll(selector); - } - // ── Public draw-state helpers ─────────────────────────────────────────────── - /** - * Show or hide the loading spinner. - * Mirrors _setChartLoading from card-chart-base-legacy.js. - */ - _setChartLoading(isLoading) { - const loadingEl = this._el("loading"); - if (!loadingEl) { - return; - } - loadingEl.classList.toggle("active", !!isLoading); - } - /** - * Set or clear the chart message (shown when data is unavailable). - * Mirrors _setChartMessage from card-chart-base-legacy.js. - */ - _setChartMessage(message = "") { - const messageEl = this._el("chart-message"); - if (!messageEl) { - return; - } - messageEl.textContent = message || ""; - messageEl.classList.toggle("visible", !!message); - } - // ── Chart frame helpers ───────────────────────────────────────────────────── - /** - * Draw an empty grid frame (shown while data is loading for the first time). - * Ported from _drawEmptyChartFrame in card-history.js. - */ - _drawEmptyChartFrame(t0, t1) { - const canvas = this._el("chart"); - const wrap = this; - const scrollViewport = this._el( - "chart-scroll-viewport" - ); - const chartStage = this._el("chart-stage"); - if (!canvas) { - return; - } - this._syncTopSlotOffset(); - const availableHeight = this._getAvailableChartHeight(280); - const viewportWidth = Math.max( - scrollViewport?.clientWidth || wrap?.clientWidth || 360, - 360 - ); - if (chartStage) { - chartStage.style.width = `${viewportWidth}px`; - chartStage.style.height = `${availableHeight}px`; - } - const { w, h: h2 } = setupCanvas(canvas, chartStage || wrap, availableHeight, viewportWidth); - const renderer = new ChartRenderer(canvas, w, h2); - renderer.labelColor = resolveChartLabelColor(this); - renderer.clear(); - renderer.drawGrid( - t0, - t1, - [ - { - key: "placeholder", - min: 0, - max: 1, - side: "left", - unit: "", - color: null - } - ], - void 0, - 5, - { fixedAxisOverlay: true } - ); - renderChartAxisOverlays( - this, - renderer, - renderer._activeAxes || [] - ); - } - /** - * Compute the chart canvas height from the surrounding card layout. - * Ported from _getAvailableChartHeight in card-history.js. - * When called from within this component (no shadow root) all selectors - * target the host element's light DOM ancestors via `closest`. - */ - _getAvailableChartHeight(minChartHeight = 280) { - const card = this.closest("ha-card"); - const header = card?.querySelector(".card-header"); - const topSlot = this._el("chart-top-slot"); - const legend = this._el("legend"); - const scrollViewport = this._el( - "chart-scroll-viewport" - ); - const wrap = this; - const cardHeight = card?.clientHeight || 0; - const occupiedHeight = (header?.offsetHeight || 0) + (topSlot && !topSlot.hidden ? topSlot.offsetHeight || 0 : 0) + (legend?.offsetHeight || 0); - const cardDerivedHeight = cardHeight ? Math.max(0, cardHeight - occupiedHeight) : 0; - const viewportHeight = scrollViewport?.clientHeight || 0; - const wrapHeight = wrap?.clientHeight || 0; - return Math.max( - minChartHeight, - cardDerivedHeight || viewportHeight || wrapHeight || 0 - ); - } - _syncTopSlotOffset() { - const topSlot = this._el("chart-top-slot"); - const topSlotHeight = topSlot && !topSlot.hidden ? topSlot.offsetHeight || 0 : 0; - this.style.setProperty("--dp-chart-top-slot-height", `${topSlotHeight}px`); - } - /** - * Toggle the legend `wrap-rows` class based on the card height. - * Ported from _updateLegendLayout in card-history.js. - */ - _updateLegendLayout(legendEl) { - if (!legendEl) { - return; - } - const card = this.closest("ha-card"); - const cardHeight = card?.clientHeight || 0; - if (this._legendWrapRows) { - this._legendWrapRows = cardHeight >= HISTORY_LEGEND_WRAP_DISABLE_HEIGHT_PX; - } else { - this._legendWrapRows = cardHeight >= HISTORY_LEGEND_WRAP_ENABLE_HEIGHT_PX; - } - legendEl.classList.toggle("wrap-rows", this._legendWrapRows); - } - /** - * Show or hide the "Adjust Y-Axis" button and wire up its click handler. - * Ported from _setAdjustAxisButtonVisibility in card-history.js. - */ - _setAdjustAxisButtonVisibility(visible, onAdjust) { - const button = this._el("chart-adjust-axis"); - if (!button) { - return; - } - button.hidden = !visible; - if (visible) { - button.onclick = () => { - this._adjustComparisonAxisScale = true; - onAdjust?.(); - this._redrawLastDraw(); - }; - return; - } - button.onclick = null; - } - /** - * Render (or clear) the comparison preview overlay badge. - * Ported from _renderComparisonPreviewOverlay in card-history.js. - */ - _renderComparisonPreviewOverlay(renderer = null) { - const overlayEl = this._el( - "chart-preview-overlay" - ); - if (!overlayEl) { - return; - } - const overlay = this._config?.comparison_preview_overlay || null; - if (!overlay?.window_range_label || !overlay?.actual_range_label) { - overlayEl.hidden = true; - overlayEl.innerHTML = ""; - return; - } - if (renderer?.pad?.left != null) { - overlayEl.style.left = `${Math.max(8, renderer.pad.left + 8)}px`; - } else { - overlayEl.style.left = ""; - } - overlayEl.innerHTML = ` + } + disconnectedCallback() { + if (this._chartHoverCleanup) { + this._chartHoverCleanup(); + this._chartHoverCleanup = null; + } + if (this._chartZoomCleanup) { + this._chartZoomCleanup(); + this._chartZoomCleanup = null; + } + if (this._chartScrollViewportEl) this._chartScrollViewportEl.removeEventListener("scroll", this._onChartScroll); + if (this._scrollZoomApplyTimer !== null) { + clearTimeout(this._scrollZoomApplyTimer); + this._scrollZoomApplyTimer = null; + } + } + get hass() { + return this._hass; + } + set hass(value) { + this._hass = value; + } + /** True when the current user may create a data point via the chart + button. */ + get _canAddAnnotation() { + return this._config.show_add_annotation_button !== false && this._hass?.user?.is_admin === true; + } + _el(id) { + return this.querySelector(`#${id}`); + } + _els(selector) { + return this.querySelectorAll(selector); + } + /** + * Show or hide the loading spinner. + * Mirrors _setChartLoading from card-chart-base-legacy.js. + */ + _setChartLoading(isLoading) { + const loadingEl = this._el("loading"); + if (!loadingEl) return; + loadingEl.classList.toggle("active", !!isLoading); + } + /** + * Set or clear the chart message (shown when data is unavailable). + * Mirrors _setChartMessage from card-chart-base-legacy.js. + */ + _setChartMessage(message = "") { + const messageEl = this._el("chart-message"); + if (!messageEl) return; + messageEl.textContent = message || ""; + messageEl.classList.toggle("visible", !!message); + } + /** + * Draw an empty grid frame (shown while data is loading for the first time). + * Ported from _drawEmptyChartFrame in card-history.js. + */ + _drawEmptyChartFrame(t0, t1) { + const canvas = this._el("chart"); + const wrap = this; + const scrollViewport = this._el("chart-scroll-viewport"); + const chartStage = this._el("chart-stage"); + if (!canvas) return; + this._syncTopSlotOffset(); + const availableHeight = this._getAvailableChartHeight(280); + const viewportWidth = Math.max(scrollViewport?.clientWidth || wrap?.clientWidth || 360, 360); + if (chartStage) { + chartStage.style.width = `${viewportWidth}px`; + chartStage.style.height = `${availableHeight}px`; + } + const { w, h } = setupCanvas(canvas, chartStage || wrap, availableHeight, viewportWidth); + const renderer = new ChartRenderer(canvas, w, h); + renderer.labelColor = resolveChartLabelColor(this); + renderer.clear(); + renderer.drawGrid(t0, t1, [{ + key: "placeholder", + min: 0, + max: 1, + side: "left", + unit: "", + color: null + }], void 0, 5, { fixedAxisOverlay: true }); + renderChartAxisOverlays(this, renderer, renderer._activeAxes || []); + } + /** + * Compute the chart canvas height from the surrounding card layout. + * Ported from _getAvailableChartHeight in card-history.js. + * When called from within this component (no shadow root) all selectors + * target the host element's light DOM ancestors via `closest`. + */ + _getAvailableChartHeight(minChartHeight = 280) { + const card = this.closest("ha-card"); + const header = card?.querySelector(".card-header"); + const topSlot = this._el("chart-top-slot"); + const legend = this._el("legend"); + const scrollViewport = this._el("chart-scroll-viewport"); + const wrap = this; + const cardHeight = card?.clientHeight || 0; + const occupiedHeight = (header?.offsetHeight || 0) + (topSlot && !topSlot.hidden ? topSlot.offsetHeight || 0 : 0) + (legend?.offsetHeight || 0); + const cardDerivedHeight = cardHeight ? Math.max(0, cardHeight - occupiedHeight) : 0; + const viewportHeight = scrollViewport?.clientHeight || 0; + const wrapHeight = wrap?.clientHeight || 0; + return Math.max(minChartHeight, cardDerivedHeight || viewportHeight || wrapHeight || 0); + } + _syncTopSlotOffset() { + const topSlot = this._el("chart-top-slot"); + const topSlotHeight = topSlot && !topSlot.hidden ? topSlot.offsetHeight || 0 : 0; + this.style.setProperty("--dp-chart-top-slot-height", `${topSlotHeight}px`); + } + /** + * Toggle the legend `wrap-rows` class based on the card height. + * Ported from _updateLegendLayout in card-history.js. + */ + _updateLegendLayout(legendEl) { + if (!legendEl) return; + const cardHeight = this.closest("ha-card")?.clientHeight || 0; + if (this._legendWrapRows) this._legendWrapRows = cardHeight >= HISTORY_LEGEND_WRAP_DISABLE_HEIGHT_PX; + else this._legendWrapRows = cardHeight >= HISTORY_LEGEND_WRAP_ENABLE_HEIGHT_PX; + legendEl.classList.toggle("wrap-rows", this._legendWrapRows); + } + /** + * Show or hide the "Adjust Y-Axis" button and wire up its click handler. + * Ported from _setAdjustAxisButtonVisibility in card-history.js. + */ + _setAdjustAxisButtonVisibility(visible, onAdjust) { + const button = this._el("chart-adjust-axis"); + if (!button) return; + button.hidden = !visible; + if (visible) { + button.onclick = () => { + this._adjustComparisonAxisScale = true; + onAdjust?.(); + this._redrawLastDraw(); + }; + return; + } + button.onclick = null; + } + /** + * Render (or clear) the comparison preview overlay badge. + * Ported from _renderComparisonPreviewOverlay in card-history.js. + */ + _renderComparisonPreviewOverlay(renderer = null) { + const overlayEl = this._el("chart-preview-overlay"); + if (!overlayEl) return; + const overlay = this._config?.comparison_preview_overlay || null; + if (!overlay?.window_range_label || !overlay?.actual_range_label) { + overlayEl.hidden = true; + overlayEl.innerHTML = ""; + return; + } + if (renderer?.pad?.left != null) overlayEl.style.left = `${Math.max(8, renderer.pad.left + 8)}px`; + else overlayEl.style.left = ""; + overlayEl.innerHTML = `
${msg("Date window:")} ${esc(overlay.window_range_label)}
${msg("Actual:")} ${esc(overlay.actual_range_label)}
`; - overlayEl.hidden = false; - } - // ── Draw queue ────────────────────────────────────────────────────────────── - /** - * Queue an async draw, discarding any in-flight stale requests. - * Ported from _queueDrawChart in card-history.js. - */ - _queueDrawChart(histResult, statsResult, events, t0, t1, options = {}) { - const drawRequestId = ++this._drawRequestId; - logger$1.log("[hass-datapoints history-card] draw queued", { - drawRequestId, - loading: options.loading ?? false - }); - this._drawChart(histResult, statsResult, events, t0, t1, { - ...options, - drawRequestId - }).catch((error) => { - if (drawRequestId !== this._drawRequestId) { - return; - } - logger$1.error("[hass-datapoints history-card] draw failed", error); - this._setChartLoading(false); - this._setChartMessage("Failed to render chart."); - }); - } - _redrawLastDraw() { - if (this._lastDrawArgs.length !== 5 && this._lastDrawArgs.length !== 6) { - return; - } - const [histResult, statsResult, events, t0, t1, options = {}] = this._lastDrawArgs; - this._queueDrawChart(histResult, statsResult, events, t0, t1, options); - } - _buildBackendAnomalyConfig(analysis) { - const config = { - anomaly_methods: Array.isArray(analysis.anomaly_methods) ? analysis.anomaly_methods : void 0, - anomaly_sensitivity: typeof analysis.anomaly_sensitivity === "string" ? analysis.anomaly_sensitivity : void 0, - anomaly_overlap_mode: typeof analysis.anomaly_overlap_mode === "string" ? analysis.anomaly_overlap_mode : void 0, - anomaly_rate_window: typeof analysis.anomaly_rate_window === "string" ? analysis.anomaly_rate_window : void 0, - anomaly_zscore_window: typeof analysis.anomaly_zscore_window === "string" ? analysis.anomaly_zscore_window : void 0, - anomaly_persistence_window: typeof analysis.anomaly_persistence_window === "string" ? analysis.anomaly_persistence_window : void 0, - trend_method: typeof analysis.trend_method === "string" ? analysis.trend_method : void 0, - trend_window: typeof analysis.trend_window === "string" ? analysis.trend_window : void 0, - anomaly_use_sampled_data: analysis.anomaly_use_sampled_data !== false - }; - if (analysis.anomaly_use_sampled_data !== false) { - config.sample_interval = typeof analysis.sample_interval === "string" ? analysis.sample_interval : null; - config.sample_aggregate = typeof analysis.sample_aggregate === "string" ? analysis.sample_aggregate : null; - } - return config; - } - // ── Instance method stubs — implemented by the parent card / JS layer ──────── - /** Build normalised state list for an entity from histResult/statsResult. Overridden by parent. */ - _buildEntityStateList(entityId, histResult, statsResult) { - const entityIds = this._seriesSettings?.map((seriesSetting) => seriesSetting.entity_id).filter(Boolean) ?? []; - const historyStates = getHistoryStatesForEntity$1( - histResult, - entityId, - entityIds - ); - const rawHistory = normalizeNumericHistory(entityId, historyStates); - const statsHistory = normalizeStatisticsHistory(entityId, statsResult); - return mergeNumericHistoryWithStatistics(rawHistory, statsHistory); - } - /** Build binary state spans from a state list. */ - _buildBinaryStateSpans(stateList, t0, t1) { - const spans = []; - if (!stateList.length) return spans; - let current = stateList[0]; - for (let i2 = 1; i2 < stateList.length; i2++) { - const next = stateList[i2]; - const start = Math.max(current.lu * 1e3, t0); - const end = Math.min(next.lu * 1e3, t1); - if (end > start) { - spans.push({ start, end, state: current.s }); - } - current = next; - } - const lastStart = Math.max(current.lu * 1e3, t0); - if (t1 > lastStart) { - spans.push({ start: lastStart, end: t1, state: current.s }); - } - return spans; - } - /** Return the "on" label for a binary_sensor entity. */ - _binaryOnLabel(entityId) { - const dc = this._hass?.states?.[entityId]?.attributes?.device_class; - return binaryOnLabel(dc ?? ""); - } - /** Return the "off" label for a binary_sensor entity. */ - _binaryOffLabel(entityId) { - const dc = this._hass?.states?.[entityId]?.attributes?.device_class; - return binaryOffLabel(dc ?? ""); - } - /** Normalise the statistics history array for an entity. */ - _normalizeStatisticsHistory(entityId, statsData) { - return normalizeStatisticsHistory(entityId, statsData); - } - /** Render legend items for the given series and binary backgrounds. */ - _renderLegend(series, binaryBackgrounds) { - const legendEl = this.querySelector("#legend"); - if (!legendEl) return; - const allItems = [ - ...series, - ...binaryBackgrounds - ]; - this._updateLegendLayout(legendEl); - legendEl.innerHTML = allItems.map((item) => { - const hidden = this._hiddenSeries?.has(item.entityId); - return `
-
`; - }).join(""); - legendEl.querySelectorAll(".legend-toggle").forEach((btn) => { - btn.addEventListener("click", () => { - const entityId = btn.dataset.entityId; - if (!entityId || !this._hiddenSeries) return; - if (this._hiddenSeries.has(entityId)) { - this._hiddenSeries.delete(entityId); - } else { - this._hiddenSeries.add(entityId); - } - btn.setAttribute( - "aria-pressed", - this._hiddenSeries.has(entityId) ? "false" : "true" - ); - this._redrawLastDraw(); - }); - }); - } - /** Draw a single series line onto the renderer, with optional data-gap rendering. */ - _drawSeriesLine(renderer, pts, color, t0, t1, min, max, opts) { - const r2 = renderer; - const config = this._config; - const showGaps = config?.show_data_gaps !== false; - const gapThresholdKey = config?.data_gap_threshold || "2h"; - const gapThresholdMs = SAMPLE_INTERVAL_MS[gapThresholdKey] ?? 2 * 60 * 6e4; - if (!showGaps || pts.length < 2 || !Number.isFinite(gapThresholdMs)) { - r2.drawLine(pts, color, t0, t1, min, max, opts); - return; - } - const segments = []; - const gapBridges = []; - let current = [pts[0]]; - for (let i2 = 1; i2 < pts.length; i2++) { - const dt = pts[i2][0] - pts[i2 - 1][0]; - if (dt > gapThresholdMs) { - segments.push(current); - gapBridges.push([pts[i2 - 1], pts[i2]]); - current = [pts[i2]]; - } else { - current.push(pts[i2]); - } - } - segments.push(current); - if (segments.length === 1) { - r2.drawLine(pts, color, t0, t1, min, max, opts); - return; - } - for (const seg of segments) { - r2.drawLine(seg, color, t0, t1, min, max, opts); - } - const gapColor = color.startsWith("rgba") ? color.replace(/[\d.]+\)$/, "0.35)") : `${color}59`; - for (const [lastPt, firstPt] of gapBridges) { - r2.drawLine([lastPt, firstPt], gapColor, t0, t1, min, max, { - dashed: true, - lineWidth: 1.2, - lineOpacity: 0.5 - }); - } - const boundaryPoints = gapBridges.flatMap(([a2, b2]) => [ - a2, - b2 - ]); - r2.drawGapMarkers(boundaryPoints, color, t0, t1, min, max); - } - /** Draw event point icons onto the renderer. */ - _drawRecordedEventPoints(_renderer, _visibleSeries, _events2, _t0, _t1, _opts) { - const renderer = _renderer; - const series = _visibleSeries; - const events = _events2; - const opts = _opts; - const overlay = this.querySelector( - "#chart-icon-overlay" - ); - if (overlay && !opts.skipOverlayClear) { - overlay.innerHTML = ""; - } - if (!renderer || !events?.length) { - return []; - } - const yOffset = Number.isFinite(opts.yOffset) ? opts.yOffset : 0; - const hits = []; - const { ctx } = renderer; - const showIcons = opts.showIcons !== false; - const fallbackHighlightedIds = Array.isArray( - this._config?.hovered_event_ids - ) ? this._config.hovered_event_ids : []; - const highlightedEventIds = new Set( - Array.isArray(opts.highlightedEventIds) ? opts.highlightedEventIds : fallbackHighlightedIds - ); - for (const event of events) { - const timestamp = new Date(event.timestamp).getTime(); - if (timestamp < _t0 || timestamp > _t1) continue; - const eventEntityIds = Array.isArray(event.entity_ids) ? event.entity_ids : []; - const x2 = renderer.xOf(timestamp, _t0, _t1); - const findSeriesWithValue = (candidates) => { - for (const candidate of candidates) { - if (!candidate?.pts?.length || !candidate.axis) continue; - const candidateValue = renderer._interpolateValue( - candidate.pts, - timestamp - ); - if (candidateValue == null) continue; - return { series: candidate, value: candidateValue }; - } - return null; - }; - const matchingSeriesCandidates = eventEntityIds.map((entityId) => series.find((entry) => entry.entityId === entityId)).filter((s2) => s2 != null); - const matchingSeriesHit = findSeriesWithValue(matchingSeriesCandidates); - const linkedToOtherTarget = eventEntityIds.length > 0 && matchingSeriesCandidates.length === 0; - const fallbackSeriesHit = matchingSeriesHit || (linkedToOtherTarget ? null : findSeriesWithValue([...series].reverse())); - const targetSeries = fallbackSeriesHit?.series || null; - const hasNumericTarget = !!(targetSeries?.pts?.length && targetSeries.axis); - const value = hasNumericTarget ? fallbackSeriesHit?.value ?? null : null; - if (hasNumericTarget && value == null) continue; - let y2; - if (hasNumericTarget) { - y2 = renderer.yOf( - value, - targetSeries.axis.min, - targetSeries.axis.max - ); - } else if (linkedToOtherTarget) { - y2 = renderer.pad.top + renderer.ch; - } else { - y2 = renderer.pad.top + 12; - } - const color = event.color || targetSeries?.color || "#03a9f4"; - const isHighlighted = highlightedEventIds.has(String(event.id || "")); - const highlightOuterRadius = showIcons ? 18 : 10; - const highlightInnerRadius = showIcons ? 15 : 8; - const outerRadius = showIcons ? 13 : 6; - const innerRadius = showIcons ? 11 : 4; - if (isHighlighted) { - ctx.save(); - ctx.beginPath(); - ctx.arc(x2, y2, highlightOuterRadius, 0, Math.PI * 2); - ctx.fillStyle = hexToRgba(color, 0.18); - ctx.fill(); - ctx.restore(); - ctx.save(); - ctx.beginPath(); - ctx.arc(x2, y2, highlightInnerRadius, 0, Math.PI * 2); - ctx.strokeStyle = color; - ctx.lineWidth = 2; - ctx.stroke(); - ctx.restore(); - } - ctx.save(); - ctx.beginPath(); - ctx.arc(x2, y2, outerRadius, 0, Math.PI * 2); - ctx.fillStyle = "rgba(255,255,255,0.92)"; - ctx.fill(); - ctx.restore(); - ctx.save(); - ctx.beginPath(); - ctx.arc(x2, y2, innerRadius, 0, Math.PI * 2); - ctx.fillStyle = color; - ctx.fill(); - ctx.restore(); - const navigateToHistory = (ev) => { - ev.preventDefault(); - ev.stopPropagation(); - navigateToDataPointsHistory( - this, - { - entity_id: event.entity_ids || [], - device_id: [], - area_id: [], - label_id: [] - }, - { - start_time: this._config?.start_time || null, - end_time: this._config?.end_time || null, - zoom_start_time: this._config?.zoom_start_time || null, - zoom_end_time: this._config?.zoom_end_time || null, - datapoint_scope: this._config?.datapoint_scope - } - ); - }; - if (overlay && showIcons) { - const iconEl = document.createElement("button"); - iconEl.type = "button"; - iconEl.className = "chart-event-icon"; - iconEl.style.left = `${x2}px`; - iconEl.style.top = `${y2 + yOffset}px`; - iconEl.title = event.message || "Open related history"; - iconEl.setAttribute( - "aria-label", - event.message || "Open related history" - ); - iconEl.innerHTML = ``; - iconEl.addEventListener("click", navigateToHistory); - overlay.appendChild(iconEl); - } else if (overlay) { - const hitEl = document.createElement("button"); - hitEl.type = "button"; - hitEl.className = "chart-event-icon"; - hitEl.style.left = `${x2}px`; - hitEl.style.top = `${y2 + yOffset}px`; - hitEl.title = event.message || "Open related history"; - hitEl.setAttribute( - "aria-label", - event.message || "Open related history" - ); - hitEl.addEventListener("click", navigateToHistory); - overlay.appendChild(hitEl); - } - hits.push({ - event, - entityId: targetSeries?.entityId || null, - unit: targetSeries?.unit || "", - value, - x: x2, - y: y2 - }); - } - return hits; - } - /** Get [min, max] extent for an axis value array. */ - _getAxisValueExtent(values) { - return getAxisValueExtent(values); - } - _getComparisonWindowLineStyle(isHovered, isSelected, hoveringDifferentComparison) { - if (isHovered) { - return { - lineOpacity: 1, - dashed: false, - hoverOpacity: 0.85 - }; - } - if (hoveringDifferentComparison && isSelected) { - return { - lineOpacity: 0.25, - lineWidth: 1.25, - dashed: false, - hoverOpacity: 0.25 - }; - } - return { - lineOpacity: 0.85, - dashed: false, - hoverOpacity: 0.85 - }; - } - /** Sync chart viewport scroll to the current zoom range. */ - _syncChartViewportScroll(_t0, _t1, _canvasWidth) { - if (!this._chartScrollViewportEl) { - return; - } - const viewport = this._chartScrollViewportEl; - if (!this._zoomRange) { - if (viewport.scrollLeft !== 0) { - this._scrollSyncSuspended = true; - this._ignoreNextProgrammaticScrollEvent = true; - viewport.scrollLeft = 0; - window.requestAnimationFrame(() => { - this._scrollSyncSuspended = false; - }); - } - return; - } - if (this._skipNextScrollViewportSync) { - this._skipNextScrollViewportSync = false; - return; - } - const viewportWidth = viewport.clientWidth; - const totalMs = Math.max(1, _t1 - _t0); - const visibleSpanMs = totalMs * Math.min(1, viewportWidth / Math.max(_canvasWidth, viewportWidth)); - const maxScrollLeft = Math.max( - 0, - Math.max(_canvasWidth, viewportWidth) - viewportWidth - ); - const maxStartOffsetMs = Math.max(0, totalMs - visibleSpanMs); - const clampedStart = clampChartValue( - this._zoomRange.start, - _t0, - _t1 - visibleSpanMs - ); - const ratio = maxStartOffsetMs > 0 ? (clampedStart - _t0) / maxStartOffsetMs : 0; - const nextLeft = ratio * maxScrollLeft; - const currentLeft = viewport.scrollLeft; - if (Math.abs(currentLeft - nextLeft) < 2) { - return; - } - this._scrollSyncSuspended = true; - this._lastProgrammaticScrollLeft = nextLeft; - this._ignoreNextProgrammaticScrollEvent = true; - viewport.scrollLeft = nextLeft; - window.requestAnimationFrame(() => { - this._scrollSyncSuspended = false; - }); - } - /** Ensure the context annotation dialog element is present. */ - _ensureContextAnnotationDialog() { - const parentCard = this.getRootNode()?.host ?? null; - if (parentCard?._annotationDialog && typeof parentCard._annotationDialog.ensureDialog === "function") { - parentCard._annotationDialog.ensureDialog(); - } - } - /** Open the context annotation dialog with the given hover data. */ - _openContextAnnotationDialog(_hover) { - const parentCard = this.getRootNode()?.host ?? null; - if (parentCard?._annotationDialog && typeof parentCard._annotationDialog.open === "function") { - parentCard._annotationDialog.open( - _hover - ); - } - } - /** Handle context menu on the chart canvas. */ - async _handleChartContextMenu(_hover) { - const parentCard = this.getRootNode()?.host ?? null; - const annotationDialog = parentCard?._annotationDialog; - if (!_hover || !this._hass || annotationDialog?.isOpen?.()) { - return; - } - this._openContextAnnotationDialog(_hover); - } - /** Handle add-annotation click from the chart hover layer. */ - _handleChartAddAnnotation(_hover) { - const parentCard = this.getRootNode()?.host ?? null; - const annotationDialog = parentCard?._annotationDialog; - if (!_hover || !this._hass || !this._hass.user?.is_admin || annotationDialog?.isOpen?.()) { - return; - } - this._openContextAnnotationDialog(_hover); - } - /** Handle anomaly cluster click — opens the annotation dialog prefilled with anomaly details. */ - _handleAnomalyAddAnnotation(_regions) { - const regions = _regions; - if (!regions?.length || !this._hass) return; - const prefill = this._buildAnomalyAnnotationPrefill(regions); - const firstRegion = regions[0]; - const points = firstRegion?.cluster?.points; - const peakPoint = points?.reduce( - (peak, p2) => !peak || Math.abs(p2.residual) > Math.abs(peak.residual) ? p2 : peak, - null - ); - const timeMs = peakPoint?.timeMs ?? this._chartLastHover?.timeMs ?? Date.now(); - this._openContextAnnotationDialog({ timeMs, annotationPrefill: prefill }); - } - /** Build an annotation prefill object from anomaly regions. */ - _buildAnomalyAnnotationPrefill(_regions) { - const regions = _regions; - if (!regions?.length) return {}; - const content = buildAnomalyTooltipContent(regions); - const firstRegion = regions[0]; - const entityId = firstRegion?.relatedEntityId ?? null; - return { - message: content ? `${content.description} -${content.alert}` : "", - icon: "mdi:alert-circle", - linkedTarget: entityId ? { entity_id: [entityId] } : null - }; - } - /** Fire backend anomaly detection requests. */ - _fireBackendAnomalyRequests(_anomalyEntityIds, _analysisMap, _startIso, _endIso) { - if (!_anomalyEntityIds || !_anomalyEntityIds.length || !this._hass) { - return; - } - const hass = this._hass; - _anomalyEntityIds.forEach((entityId) => { - const analysis = _analysisMap.get(entityId); - if (!analysis) { - return; - } - const config = this._buildBackendAnomalyConfig(analysis); - const configKey = JSON.stringify({ - ...config, - anomaly_methods: [ - ...config.anomaly_methods || [] - ].sort() - }); - const cached = this._backendAnomalyByEntity.get(entityId); - if (cached && cached.configKey === configKey) return; - this._pendingAnomalyEntityIds.add(entityId); - this._setChartLoading(true); - fetchAnomaliesFromBackend(hass, entityId, _startIso, _endIso, config).then((clusters) => { - this._pendingAnomalyEntityIds.delete(entityId); - if (_startIso !== new Date(this._lastT0).toISOString() || _endIso !== new Date(this._lastT1).toISOString()) { - if (this._pendingAnomalyEntityIds.size === 0) { - this._setChartLoading(false); - } - return; - } - this._backendAnomalyByEntity.set(entityId, { configKey, clusters }); - if (this._analysisCache?.result) { - const existing = this._analysisCache.result.anomalySeries || []; - const idx = existing.findIndex( - (entry2) => entry2.entityId === entityId - ); - const entry = { entityId, anomalyClusters: clusters }; - if (idx >= 0) { - existing[idx] = entry; - } else { - existing.push(entry); - } - } - if (this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._filterEvents(this._lastEvents), - this._lastT0, - this._lastT1, - { loading: this._pendingAnomalyEntityIds.size > 0 } - ); - } else if (this._pendingAnomalyEntityIds.size === 0) { - this._setChartLoading(false); - } - }).catch((err) => { - this._pendingAnomalyEntityIds.delete(entityId); - if (this._pendingAnomalyEntityIds.size === 0) { - this._setChartLoading(false); - } - logger$1.warn( - "[hass-datapoints history-card] backend anomaly fetch failed", - { entityId, err } - ); - }); - }); - } - /** Dispatch a zoom preview event. */ - _dispatchZoomPreview(_range) { - const range = _range; - this.dispatchEvent( - new CustomEvent("hass-datapoints-chart-zoom", { - bubbles: true, - composed: true, - detail: range ? { - startTime: range.startTime, - endTime: range.endTime, - preview: true - } : { startTime: null, endTime: null, preview: true } - }) - ); - } - /** Apply a zoom range to the chart. */ - _applyZoomRange(_startTime2, _endTime2) { - const start = Math.min(_startTime2, _endTime2); - const end = Math.max(_startTime2, _endTime2); - if (!(start < end)) { - return; - } - this._zoomRange = { start, end }; - this.dispatchEvent( - new CustomEvent("hass-datapoints-zoom-apply", { - bubbles: true, - composed: true, - detail: { start, end } - }) - ); - this._redrawLastDraw(); - } - /** Clear the active zoom range. */ - _clearZoomRange() { - if (!this._zoomRange) { - return; - } - this._zoomRange = null; - this.dispatchEvent( - new CustomEvent("hass-datapoints-zoom-apply", { - bubbles: true, - composed: true, - detail: null - }) - ); - this._redrawLastDraw(); - } - /** Filter events list by hidden IDs and message filter. */ - _filterEvents(_events2) { - const events = _events2; - const query = String( - this._config?.message_filter || "" - ).trim().toLowerCase(); - const visibleEvents = events.filter( - (event) => !this._hiddenEventIds.has(event?.id ?? "") - ); - if (!query) { - return visibleEvents; - } - return visibleEvents.filter((event) => { - const haystack = [ - event?.message || "", - event?.annotation || "", - ...(event?.entity_ids || []).filter(Boolean) - ].join("\n").toLowerCase(); - return haystack.includes(query); - }); - } - /** Build correlated anomaly spans across series. */ - _buildCorrelatedAnomalySpans(_visibleSeries, _anomalyClustersMap, _analysisMap) { - const visibleSeries = _visibleSeries; - const anomalyClustersMap = _anomalyClustersMap; - const seriesIntervals = []; - for (const seriesItem of visibleSeries) { - const analysis = _analysisMap.get(seriesItem.entityId); - if (analysis?.show_anomalies !== true) continue; - const clusters = anomalyClustersMap.get(seriesItem.entityId) || []; - if (!clusters.length) continue; - const pts = seriesItem.pts; - let tolerance = 6e4; - if (Array.isArray(pts) && pts.length >= 2) { - const intervals = []; - for (let i2 = 1; i2 < pts.length; i2++) { - const diff = pts[i2][0] - pts[i2 - 1][0]; - if (diff > 0) intervals.push(diff); - } - if (intervals.length) { - intervals.sort((a2, b2) => a2 - b2); - const mid = Math.floor(intervals.length / 2); - tolerance = intervals.length % 2 === 0 ? (intervals[mid - 1] + intervals[mid]) / 2 : intervals[mid]; - tolerance = Math.max(tolerance, 1e3); - } - } - const entityIntervals = []; - for (const cluster of clusters) { - if (!Array.isArray(cluster.points) || cluster.points.length === 0) - continue; - const startTime = cluster.points[0]?.timeMs; - const endTime = cluster.points[cluster.points.length - 1]?.timeMs; - if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) continue; - entityIntervals.push({ - start: Math.min(startTime, endTime) - tolerance, - end: Math.max(startTime, endTime) + tolerance - }); - } - if (entityIntervals.length) { - seriesIntervals.push({ - entityId: seriesItem.entityId, - intervals: entityIntervals - }); - } - } - if (seriesIntervals.length < 2) return []; - const events = []; - for (const { entityId, intervals } of seriesIntervals) { - for (const { start, end } of intervals) { - events.push({ time: start, delta: 1, entityId }); - events.push({ time: end, delta: -1, entityId }); - } - } - events.sort((a2, b2) => a2.time - b2.time || a2.delta - b2.delta); - const activeCounts = /* @__PURE__ */ new Map(); - const spans = []; - let spanStart = null; - for (const event of events) { - const prev = activeCounts.get(event.entityId) || 0; - const next = prev + event.delta; - if (next <= 0) { - activeCounts.delete(event.entityId); - } else { - activeCounts.set(event.entityId, next); - } - const activeCount = activeCounts.size; - if (spanStart === null && activeCount >= 2) { - spanStart = event.time; - } else if (spanStart !== null && activeCount < 2) { - spans.push({ start: spanStart, end: event.time }); - spanStart = null; - } - } - if (spanStart !== null && events.length > 0) { - spans.push({ start: spanStart, end: events[events.length - 1].time }); - } - return spans; - } - /** Filter out annotated anomaly clusters. */ - _filterAnnotatedAnomalyClusters(_seriesItem, _events2) { - const seriesItem = _seriesItem; - if (!Array.isArray(seriesItem?.anomalyClusters) || seriesItem.anomalyClusters.length === 0) { - return []; - } - const visibleEvents = Array.isArray(_events2) ? _events2 : []; - if (visibleEvents.length === 0) { - return seriesItem.anomalyClusters; - } - const getClusterRange = (cluster) => { - if (!Array.isArray(cluster.points) || cluster.points.length === 0) - return null; - const startTime = cluster.points[0]?.timeMs; - const endTime = cluster.points[cluster.points.length - 1]?.timeMs; - if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) return null; - return { - startTime: Math.min(startTime, endTime), - endTime: Math.max(startTime, endTime) - }; - }; - return seriesItem.anomalyClusters.filter((cluster) => { - const clusterRange = getClusterRange(cluster); - if (!clusterRange) return true; - return !visibleEvents.some((event) => { - const eventEntityIds = Array.isArray(event.entity_ids) ? event.entity_ids.filter(Boolean) : []; - if (!eventEntityIds.includes(seriesItem.entityId)) return false; - const eventTime = new Date(event.timestamp).getTime(); - if (!Number.isFinite(eventTime)) return false; - return eventTime >= clusterRange.startTime && eventTime <= clusterRange.endTime; - }); - }); - } - _fireComparisonBackendAnomalyRequests(drawableComparisonResults, analysisMap, renderT0, renderT1) { - if (!drawableComparisonResults.length || !this._hass) { - return; - } - const startIso = new Date(renderT0).toISOString(); - const endIso = new Date(renderT1).toISOString(); - drawableComparisonResults.forEach((comparisonWindow) => { - const comparisonStartIso = new Date( - renderT0 + comparisonWindow.time_offset_ms - ).toISOString(); - const comparisonEndIso = new Date( - renderT1 + comparisonWindow.time_offset_ms - ).toISOString(); - this._seriesSettings.forEach((seriesSetting) => { - const entityId = String(seriesSetting.entity_id || ""); - if (!entityId || this._hiddenSeries.has(entityId)) { - return; - } - const analysis = analysisMap.get(entityId); - if (!analysis || analysis.show_anomalies !== true) { - return; - } - const config = this._buildBackendAnomalyConfig(analysis); - const configKey = JSON.stringify({ - ...config, - windowId: comparisonWindow.id, - startIso, - endIso, - anomaly_methods: [ - ...config.anomaly_methods || [] - ].sort() - }); - const cacheKey = this._getComparisonAnomalyCacheKey( - comparisonWindow.id, - entityId - ); - const cached = this._backendComparisonAnomalyByKey.get(cacheKey); - if (cached && cached.configKey === configKey) { - return; - } - if (this._pendingComparisonAnomalyKeys.has(cacheKey)) { - return; - } - this._pendingComparisonAnomalyKeys.add(cacheKey); - const hass = this._hass; - if (!hass) { - this._pendingComparisonAnomalyKeys.delete(cacheKey); - return; - } - fetchAnomaliesFromBackend( - hass, - entityId, - comparisonStartIso, - comparisonEndIso, - config - ).then((clusters) => { - this._pendingComparisonAnomalyKeys.delete(cacheKey); - if (startIso !== new Date(this._lastT0).toISOString() || endIso !== new Date(this._lastT1).toISOString()) { - return; - } - const shiftedClusters = this._shiftComparisonAnomalyClusters( - Array.isArray(clusters) ? clusters : [], - comparisonWindow.time_offset_ms - ); - this._backendComparisonAnomalyByKey.set(cacheKey, { - configKey, - clusters: shiftedClusters - }); - if (this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._filterEvents(this._lastEvents), - this._lastT0, - this._lastT1, - { loading: false } - ); - } - }).catch(() => { - this._pendingComparisonAnomalyKeys.delete(cacheKey); - }); - }); - }); - } - /** Render per-row axis overlays for split-view chart. */ - _renderSplitAxisOverlays(_tracks) { - const tracks = _tracks; - const leftEl = this.querySelector( - "#chart-axis-left" - ); - const rightEl = this.querySelector( - "#chart-axis-right" - ); - if (!leftEl || !rightEl || !tracks.length) { - return; - } - const primaryRenderer = tracks[0].renderer; - const leftWidth = Math.max(0, primaryRenderer.pad.left); - const bottomHeight = Math.max(0, primaryRenderer.pad.bottom); - leftEl.style.width = `${leftWidth}px`; - rightEl.style.width = "0px"; - this.style.setProperty("--dp-chart-axis-left-width", `${leftWidth}px`); - this.style.setProperty("--dp-chart-axis-right-width", "0px"); - this.style.setProperty( - "--dp-chart-axis-bottom-height", - `${bottomHeight}px` - ); - const labelRight = 10; - let labelsHtml = ""; - for (const { renderer, axis, rowOffset } of tracks) { - if (!axis?.ticks?.length) continue; - for (const tick of axis.ticks) { - const y2 = rowOffset + renderer.yOf(tick, axis.min, axis.max); - const formatted = renderer._formatAxisTick(tick, axis.unit); - labelsHtml += `
${esc(formatted)}
`; - } - if (axis.unit) { - const unitY = rowOffset + Math.max(0, primaryRenderer.pad.top - 18); - labelsHtml += `
${esc(axis.unit)}
`; - } - } - leftEl.innerHTML = `
${labelsHtml}`; - leftEl.classList.add("visible"); - rightEl.innerHTML = ""; - rightEl.classList.remove("visible"); - } - // ── State stubs — owned by the parent card / JS layer ───────────────────────── - /** Ordered series settings from the card config. */ - get _seriesSettings() { - return Array.isArray( - this._config?.series_settings - ) ? this._config.series_settings : []; - } - /** Active comparison windows from the card config. */ - get _comparisonWindows() { - return Array.isArray( - this._config?.comparison_windows - ) ? this._config.comparison_windows : []; - } - _getDrawableComparisonResults(comparisonResults) { - const drawableComparisonWindowIds = new Set( - this._comparisonWindows.map( - (window2) => String(window2?.id || "") - ).filter((id) => id.length > 0) - ); - const selectedComparisonWindowId = String( - this._config?.selected_comparison_window_id || "" - ); - const hoveredComparisonWindowId = String( - this._config?.hovered_comparison_window_id || "" - ); - if (selectedComparisonWindowId) { - drawableComparisonWindowIds.add(selectedComparisonWindowId); - } - if (hoveredComparisonWindowId) { - drawableComparisonWindowIds.add(hoveredComparisonWindowId); - } - if (drawableComparisonWindowIds.size === 0) { - return []; - } - return comparisonResults.filter( - (window2) => drawableComparisonWindowIds.has(String(window2.id || "")) - ); - } - _resolveAnomalyClusterDisplay(anomalyClusters, overlapMode, correlatedSpans = []) { - const normalClusters = anomalyClusters.filter( - (c2) => !c2.isOverlap - ); - const overlapClusters = anomalyClusters.filter( - (c2) => c2.isOverlap === true - ); - if (overlapMode === "only") { - const overlapOnlyClusters = this._filterClustersByCorrelatedSpans( - anomalyClusters, - correlatedSpans - ); - return { - baseClusters: overlapOnlyClusters, - regionClusters: overlapOnlyClusters, - showCorrelatedSpans: true - }; - } - return { - baseClusters: [...normalClusters, ...overlapClusters], - regionClusters: [...normalClusters, ...overlapClusters], - showCorrelatedSpans: false - }; - } - _filterClustersByCorrelatedSpans(anomalyClusters, correlatedSpans) { - if (!Array.isArray(anomalyClusters) || anomalyClusters.length === 0) { - return []; - } - if (!Array.isArray(correlatedSpans) || correlatedSpans.length === 0) { - return []; - } - return anomalyClusters.filter((cluster) => { - const points = cluster.points; - if (!Array.isArray(points) || points.length === 0) { - return false; - } - const startTime = Number(points[0]?.timeMs); - const endTime = Number(points[points.length - 1]?.timeMs); - if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) { - return false; - } - const clusterStart = Math.min(startTime, endTime); - const clusterEnd = Math.max(startTime, endTime); - return correlatedSpans.some((span) => { - const spanStart = Number(span.start); - const spanEnd = Number(span.end); - if (!Number.isFinite(spanStart) || !Number.isFinite(spanEnd)) { - return false; - } - return clusterEnd >= spanStart && clusterStart <= spanEnd; - }); - }); - } - _getComparisonAnomalyCacheKey(windowId, entityId) { - return `${windowId}:${entityId}`; - } - _shiftComparisonAnomalyClusters(clusters, timeOffsetMs) { - return (Array.isArray(clusters) ? clusters : []).map((cluster) => ({ - ...cluster, - points: Array.isArray(cluster.points) ? cluster.points.map((point) => ({ - ...point, - timeMs: Number(point.timeMs) - timeOffsetMs - })) : [] - })); - } - async _resolveComparisonWindowPoints(entityId, comparisonWindow, analysis, renderT0, renderT1) { - const stateList = this._buildEntityStateList( - entityId, - comparisonWindow.histResult, - comparisonWindow.statsResult || {} - ); - const rawPoints = []; - for (const state of stateList) { - const value = parseFloat(state.s); - if (!Number.isNaN(value)) { - rawPoints.push([ - Math.round(state.lu * 1e3) - comparisonWindow.time_offset_ms, - value - ]); - } - } - const interval = analysis.sample_interval || "raw"; - if (interval === "raw" || !this._hass) { - return rawPoints; - } - const winStartIso = new Date( - renderT0 + comparisonWindow.time_offset_ms - ).toISOString(); - const winEndIso = new Date( - renderT1 + comparisonWindow.time_offset_ms - ).toISOString(); - const sampledPts = await fetchDownsampledHistory( - this._hass, - entityId, - winStartIso, - winEndIso, - interval, - analysis.sample_aggregate || "mean" - ); - if (!Array.isArray(sampledPts) || sampledPts.length === 0) { - const targetIntervalMs = SAMPLE_INTERVAL_MS[interval] ?? 0; - if (targetIntervalMs <= 0 || rawPoints.length === 0) { - return rawPoints; - } - const sampleAggregate = analysis.sample_aggregate || "mean"; - return downsampleInWorker(rawPoints, targetIntervalMs, sampleAggregate); - } - let finalPts = sampledPts.map( - ([timestamp, value]) => [timestamp - comparisonWindow.time_offset_ms, value] - ); - const statsForEntity = this._normalizeStatisticsHistory( - entityId, - comparisonWindow.statsResult || {} - ); - const statsPts = statsForEntity.map( - (state) => [ - Math.round(state.lu * 1e3) - comparisonWindow.time_offset_ms, - parseFloat(state.s) - ] - ).filter(([, value]) => !Number.isNaN(value)); - if (statsPts.length > 0) { - const firstSampledMs = finalPts[0][0]; - const lastSampledMs = finalPts[finalPts.length - 1][0]; - const rawOutsidePts = statsPts.filter( - ([timestamp]) => timestamp < firstSampledMs || timestamp > lastSampledMs - ); - if (rawOutsidePts.length > 0) { - const targetIntervalMs = SAMPLE_INTERVAL_MS[interval] ?? 0; - const sampleAggregate = analysis.sample_aggregate || "mean"; - const outsideStatsPts = targetIntervalMs > 0 ? await downsampleInWorker( - rawOutsidePts, - targetIntervalMs, - sampleAggregate - ) : rawOutsidePts; - finalPts = [...outsideStatsPts, ...finalPts]; - finalPts.sort((a2, b2) => a2[0] - b2[0]); - } - } - return finalPts; - } - _drawComparisonAnalysisOverlays({ - renderer, - entityId, - seriesColor, - comparisonPts, - analysis, - renderT0, - renderT1, - axis, - rateAxis = null, - events = [], - comparisonWindowId - }) { - const precomputed = this._analysisCache?.result?.comparisonWindowResults?.[comparisonWindowId]?.[entityId]; - if (analysis.show_threshold_analysis === true) { - const thresholdValue = Number(analysis.threshold_value); - if (Number.isFinite(thresholdValue)) { - if (analysis.show_threshold_shading === true && comparisonPts.length) { - renderer.drawThresholdArea( - comparisonPts, - thresholdValue, - seriesColor, - renderT0, - renderT1, - axis.min, - axis.max, - { - mode: analysis.threshold_direction === "below" ? "below" : "above", - fillAlpha: 0.08 - } - ); - } - renderer.drawLine( - [ - [renderT0, thresholdValue], - [renderT1, thresholdValue] - ], - hexToRgba(seriesColor, 0.28), - renderT0, - renderT1, - axis.min, - axis.max, - { lineOpacity: 0.34, lineWidth: 1.05, dashed: true } - ); - } - } - if (analysis.show_summary_stats === true) { - const summaryStats = precomputed?.summaryStats ?? this._buildSummaryStats(comparisonPts); - if (Number.isFinite(summaryStats.min) && Number.isFinite(summaryStats.max) && Number.isFinite(summaryStats.mean)) { - if (analysis.show_summary_stats_shading === true) { - renderer.drawGradientBand( - summaryStats.min, - summaryStats.mean, - seriesColor, - renderT0, - renderT1, - axis.min, - axis.max, - { fillAlpha: 0.04 } - ); - renderer.drawGradientBand( - summaryStats.max, - summaryStats.mean, - seriesColor, - renderT0, - renderT1, - axis.min, - axis.max, - { fillAlpha: 0.04 } - ); - } - const summaryEntries = [ - { value: summaryStats.min, alpha: 0.24, width: 1, dotted: true }, - { value: summaryStats.mean, alpha: 0.44, width: 1.45, dotted: false }, - { value: summaryStats.max, alpha: 0.24, width: 1, dotted: true } - ]; - for (const entry of summaryEntries) { - renderer.drawLine( - [ - [renderT0, entry.value], - [renderT1, entry.value] - ], - hexToRgba(seriesColor, entry.alpha), - renderT0, - renderT1, - axis.min, - axis.max, - { - lineOpacity: entry.alpha + 0.08, - lineWidth: entry.width, - dotted: entry.dotted - } - ); - } - } - } - if (analysis.show_trend_lines === true && comparisonPts.length >= 2) { - const trendPts = precomputed?.trendPts ?? this._buildTrendPoints( - comparisonPts, - analysis.trend_method, - analysis.trend_window - ); - if (trendPts.length >= 2) { - const trendOptions = this._getTrendRenderOptions( - analysis.trend_method, - false - ); - renderer.drawLine( - trendPts, - hexToRgba(seriesColor, Math.max(0.3, trendOptions.colorAlpha - 0.18)), - renderT0, - renderT1, - axis.min, - axis.max, - { - lineOpacity: Math.max(0.3, trendOptions.lineOpacity - 0.18), - lineWidth: Math.max(1.35, trendOptions.lineWidth - 0.35), - dashed: trendOptions.dashed, - dotted: trendOptions.dotted - } - ); - } - } - if (analysis.show_rate_of_change === true && rateAxis && comparisonPts.length >= 2) { - const ratePts = precomputed?.ratePts ?? this._buildRateOfChangePoints(comparisonPts, analysis.rate_window); - if (ratePts.length >= 2) { - renderer.drawLine( - ratePts, - hexToRgba(seriesColor, 0.52), - renderT0, - renderT1, - rateAxis.min, - rateAxis.max, - { - lineOpacity: 0.46, - lineWidth: 1.35, - dashPattern: [6, 3, 1.5, 3] - } - ); - } - } - if (analysis.show_anomalies === true) { - const cacheKey = this._getComparisonAnomalyCacheKey( - comparisonWindowId, - entityId - ); - const cached = this._backendComparisonAnomalyByKey.get(cacheKey); - const clusters = Array.isArray(cached?.clusters) ? cached.clusters : []; - if (clusters.length > 0) { - const regionOptions = { - strokeAlpha: 0.72, - lineWidth: 1.6, - haloWidth: 3.4, - haloColor: "rgba(255,255,255,0.72)", - haloAlpha: 0.64, - fillColor: hexToRgba(seriesColor, 0.06), - fillAlpha: 1, - pointPadding: 8, - minRadiusX: 8, - minRadiusY: 8 - }; - const filteredClusters = this._filterAnnotatedAnomalyClusters( - { - entityId, - anomalyClusters: clusters - }, - events - ); - if (filteredClusters.length > 0) { - renderer.drawAnomalyClusters( - filteredClusters, - hexToRgba(seriesColor, 0.74), - renderT0, - renderT1, - axis.min, - axis.max, - regionOptions - ); - } - } - } - } - /** Backend anomaly cache keyed by entity ID. Already declared above. */ - // _backendAnomalyByEntity — declared above - /** - * Full chart draw implementation. - * Ported from _drawChart in card-history.js. - */ - async _drawChart(histResult, statsResult, events, t0, t1, options = {}) { - const chartEvents = Array.isArray(events) ? events : []; - hideTooltip(this); - this._syncTopSlotOffset(); - const canvas = this.querySelector("#chart"); - const zoomOutButton = this.querySelector( - "#chart-zoom-out" - ); - const wrap = this; - const scrollViewport = this.querySelector( - "#chart-scroll-viewport" - ); - const chartStage = this.querySelector( - "#chart-stage" - ); - this._chartScrollViewportEl = scrollViewport; - this._chartStageEl = chartStage; - const series = []; - const axes = []; - const axisMap = /* @__PURE__ */ new Map(); - const binaryBackgrounds = []; - const seriesSettings = this._seriesSettings; - const analysisMap = this._getSeriesAnalysisMap(); - const comparisonResults = Array.isArray(this._lastComparisonResults) ? this._lastComparisonResults : []; - const drawableComparisonResults = this._getDrawableComparisonResults(comparisonResults); - const selectedComparisonWindowId = this._config?.selected_comparison_window_id || null; - const hoveredComparisonWindowId = this._config?.hovered_comparison_window_id || null; - const comparisonPreviewActive = this._comparisonWindows.length > 0; - const delinkYAxis = this._config?.delink_y_axis === true; - const autoAdjustHoveredComparisonAxis = comparisonPreviewActive && !!hoveredComparisonWindowId && !this._zoomRange && !this._chartZoomDragging; - const hoveringDifferentComparison = !!hoveredComparisonWindowId && !!selectedComparisonWindowId && hoveredComparisonWindowId !== selectedComparisonWindowId; - const hasSelectedComparisonWindow = !!selectedComparisonWindowId; - seriesSettings.forEach((seriesSetting, i2) => { - const entityId = seriesSetting.entity_id; - const domain = entityId.split(".")[0]; - if (domain === "binary_sensor") { - const stateList2 = this._buildEntityStateList( - entityId, - histResult, - statsResult - ); - const spans = this._buildBinaryStateSpans(stateList2, t0, t1); - if (spans.length) { - binaryBackgrounds.push({ - entityId, - label: entityName(this._hass, entityId) || entityId, - color: seriesSetting.color || COLORS[i2 % COLORS.length], - onLabel: this._binaryOnLabel(entityId), - offLabel: this._binaryOffLabel(entityId), - spans - }); - } - return; - } - const stateList = this._buildEntityStateList( - entityId, - histResult, - statsResult - ); - const pts = []; - const unit = this._hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; - const axisKey = delinkYAxis ? `${unit || "__unitless__"}::${entityId}` : unit || "__unitless__"; - let axis = axisMap.get(axisKey); - if (!axis) { - axis = { - key: axisKey, - unit, - color: seriesSetting.color || COLORS[i2 % COLORS.length], - side: axisMap.size === 0 ? "left" : "right", - values: [] - }; - axisMap.set(axisKey, axis); - axes.push(axis); - } - for (const s2 of stateList) { - const v2 = parseFloat(s2.s); - if (!Number.isNaN(v2)) { - pts.push([Math.round(s2.lu * 1e3), v2]); - axis.values.push(v2); - } - } - if (pts.length) { - series.push({ - entityId, - legendEntityId: entityId, - label: entityName(this._hass, entityId) || entityId, - unit, - pts, - color: seriesSetting.color || COLORS[i2 % COLORS.length], - axisKey - }); - } - }); - const _startIso = new Date(t0).toISOString(); - const _endIso = new Date(t1).toISOString(); - if (series.length && this._hass) { - if (t0 !== this._lastT0 || t1 !== this._lastT1) { - this._backendAnomalyByEntity.clear(); - this._backendComparisonAnomalyByKey.clear(); - } - await Promise.all( - series.map(async (seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - const interval = analysis.sample_interval || "raw"; - if (interval === "raw") return; - const hass = this._hass; - if (!hass) { - return; - } - try { - const targetIntervalMs = SAMPLE_INTERVAL_MS[interval] ?? 0; - const sampleAggregate = analysis.sample_aggregate || "mean"; - const sampledPts = await fetchDownsampledHistory( - hass, - seriesItem.entityId, - _startIso, - _endIso, - interval, - sampleAggregate - ); - if (Array.isArray(sampledPts) && sampledPts.length > 0) { - const statsForEntity = this._normalizeStatisticsHistory( - seriesItem.entityId, - statsResult - ); - const statsPts = statsForEntity.map( - (s2) => [Math.round(s2.lu * 1e3), parseFloat(s2.s)] - ).filter(([, v2]) => !Number.isNaN(v2)); - let finalPts = sampledPts; - if (statsPts.length > 0) { - const firstSampledMs = sampledPts[0][0]; - const lastSampledMs = sampledPts[sampledPts.length - 1][0]; - const rawOutsidePts = statsPts.filter( - ([t2]) => t2 < firstSampledMs || t2 > lastSampledMs - ); - if (rawOutsidePts.length > 0) { - const outsideStatsPts = targetIntervalMs > 0 ? await downsampleInWorker( - rawOutsidePts, - targetIntervalMs, - sampleAggregate - ) : rawOutsidePts; - finalPts = [...outsideStatsPts, ...sampledPts]; - finalPts.sort((a2, b2) => a2[0] - b2[0]); - } - } - seriesItem.pts = finalPts; - const axisEntry = axes.find( - (ax) => ax.key === seriesItem.axisKey - ); - if (axisEntry) { - axisEntry.values = finalPts.map(([, v2]) => v2).filter(Number.isFinite); - } - } else if (Array.isArray(seriesItem.pts) && seriesItem.pts.length > 0 && targetIntervalMs > 0) { - const fallbackPts = await downsampleInWorker( - seriesItem.pts, - targetIntervalMs, - sampleAggregate - ); - if (fallbackPts.length > 0) { - seriesItem.pts = fallbackPts; - const axisEntry = axes.find( - (ax) => ax.key === seriesItem.axisKey - ); - if (axisEntry) { - axisEntry.values = fallbackPts.map(([, v2]) => v2).filter(Number.isFinite); - } - } - } - } catch (err) { - logger$1.warn( - "[hass-datapoints history-card] downsampled history fetch failed", - { entityId: seriesItem.entityId, err } - ); - } - }) - ); - } - for (const seriesItem of series) { - if (!seriesItem.pts.length) { - continue; - } - const lastPt = seriesItem.pts[seriesItem.pts.length - 1]; - const prev = this._previousSeriesEndpoints.get(seriesItem.entityId); - if (!prev) { - logger$1.log("[hass-datapoints history-card] series initial draw", { - entityId: seriesItem.entityId, - pointCount: seriesItem.pts.length, - lastPt - }); - } else if (lastPt[0] !== prev.t || lastPt[1] !== prev.v) { - logger$1.log( - "[hass-datapoints history-card] series updated — live update detected", - { - entityId: seriesItem.entityId, - pointCount: seriesItem.pts.length, - prev, - lastPt - } - ); - } else { - logger$1.log( - "[hass-datapoints history-card] series unchanged — no new data", - { - entityId: seriesItem.entityId, - pointCount: seriesItem.pts.length, - lastPt - } - ); - } - this._previousSeriesEndpoints.set(seriesItem.entityId, { - t: lastPt[0], - v: lastPt[1] - }); - } - if (!series.length && !binaryBackgrounds.length) { - this._setAdjustAxisButtonVisibility(false); - this._renderComparisonPreviewOverlay(); - const sameRangeAsLastDraw = Number.isFinite(this._lastT0) && Number.isFinite(this._lastT1) && this._lastT0 === t0 && this._lastT1 === t1 && Array.isArray(this._lastDrawArgs) && this._lastDrawArgs.length > 0; - this._setChartLoading(!!options.loading); - this._setChartMessage( - options.loading ? "" : "No numeric data in the selected time range." - ); - if (sameRangeAsLastDraw) { - return; - } - this._lastHistResult = histResult; - this._lastStatsResult = statsResult; - this._lastEvents = chartEvents; - this._lastT0 = t0; - this._lastT1 = t1; - this._lastDrawArgs = [ - histResult, - statsResult, - chartEvents, - t0, - t1, - options - ]; - return; - } - this._lastHistResult = histResult; - this._lastStatsResult = statsResult; - this._lastEvents = chartEvents; - this._lastT0 = t0; - this._lastT1 = t1; - this._lastDrawArgs = [ - histResult, - statsResult, - chartEvents, - t0, - t1, - options - ]; - const visibleSeries = series.filter( - (entry) => !this._hiddenSeries.has(entry.legendEntityId || entry.entityId) - ); - const selectedComparisonResult = drawableComparisonResults.find( - (window2) => window2.id === selectedComparisonWindowId - ) || null; - const selectedComparisonSeriesMap = /* @__PURE__ */ new Map(); - if (selectedComparisonResult) { - for (let index = 0; index < seriesSettings.length; index += 1) { - const seriesSetting = seriesSettings[index]; - const entityId = seriesSetting.entity_id; - if (entityId.split(".")[0] === "binary_sensor") { - continue; - } - if (this._hiddenSeries.has(entityId)) { - continue; - } - const unit = this._hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; - const analysis = analysisMap.get(entityId) || normalizeHistorySeriesAnalysis(null); - const points = await this._resolveComparisonWindowPoints( - entityId, - selectedComparisonResult, - analysis, - t0, - t1 - ); - if (!points.length) { - continue; - } - selectedComparisonSeriesMap.set(entityId, { - entityId, - label: entityName(this._hass, entityId) || entityId, - unit, - color: seriesSetting.color || COLORS[index % COLORS.length], - pts: points - }); - } - } - const allComparisonWindowsData = {}; - for (const seriesItem of visibleSeries) { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (analysis.show_anomalies !== true || !analysis.anomaly_methods?.includes( - "comparison_window" - ) || !analysis.anomaly_comparison_window_id) { - continue; - } - const windowId = analysis.anomaly_comparison_window_id; - if (!allComparisonWindowsData[windowId]) { - allComparisonWindowsData[windowId] = {}; - } - if (!allComparisonWindowsData[windowId][seriesItem.entityId]) { - const compResult = comparisonResults.find((win) => win.id === windowId); - if (compResult) { - const pts = await this._resolveComparisonWindowPoints( - seriesItem.entityId, - compResult, - analysis, - t0, - t1 - ); - if (pts.length) { - allComparisonWindowsData[windowId][seriesItem.entityId] = pts; - } - } - } - } - const analysisEntityIds = visibleSeries.filter((s2) => { - const a2 = analysisMap.get(s2.entityId) || {}; - return a2.show_trend_lines || a2.show_summary_stats || a2.show_rate_of_change; - }).map((s2) => s2.entityId); - const onAnalysisProgress = analysisEntityIds.length ? (progress) => { - this.dispatchEvent( - new CustomEvent("hass-datapoints-analysis-computing", { - bubbles: true, - composed: true, - detail: { - computing: true, - entityIds: analysisEntityIds, - progress - } - }) - ); - } : null; - const analysisResult = await this._computeHistoryAnalysis( - visibleSeries, - selectedComparisonSeriesMap, - analysisMap, - hasSelectedComparisonWindow, - allComparisonWindowsData, - t0, - t1, - onAnalysisProgress - ); - if (analysisEntityIds.length) { - this.dispatchEvent( - new CustomEvent("hass-datapoints-analysis-computing", { - bubbles: true, - composed: true, - detail: { - computing: false, - entityIds: analysisEntityIds, - progress: 100 - } - }) - ); - } - const _anomalyEntityIds = visibleSeries.filter((s2) => { - const a2 = analysisMap.get(s2.entityId); - return a2 && a2.show_anomalies === true && Array.isArray(a2.anomaly_methods) && a2.anomaly_methods.length > 0; - }).map((s2) => s2.entityId); - if (_anomalyEntityIds.length > 0) { - const merged2 = _anomalyEntityIds.map((entityId) => { - const cached = this._backendAnomalyByEntity.get(entityId); - if (!cached) return null; - return { entityId, anomalyClusters: cached.clusters }; - }).filter(Boolean); - analysisResult.anomalySeries = merged2; - } - if (options.drawRequestId && options.drawRequestId !== this._drawRequestId) { - return; - } - if (canvas) { - canvas.style.display = ""; - } - chartStage?.querySelectorAll(".split-series-row").forEach((el) => el.remove()); - chartStage?.querySelector("#chart-split-overlay")?.remove(); - const axisLeftEl = this.querySelector( - "#chart-axis-left" - ); - const axisRightEl = this.querySelector( - "#chart-axis-right" - ); - if (axisLeftEl) { - axisLeftEl.style.display = ""; - } - if (axisRightEl) { - axisRightEl.style.display = ""; - } - let minChartHeight; - if (series.length) { - minChartHeight = 280; - } else if (binaryBackgrounds.length) { - minChartHeight = 100; - } else { - minChartHeight = 280; - } - const availableHeight = this._getAvailableChartHeight(minChartHeight); - const viewportWidth = Math.max( - scrollViewport?.clientWidth || wrap?.clientWidth || 360, - 360 - ); - const totalSpanMs = Math.max(1, t1 - t0); - const zoomSpanMs = this._zoomRange ? Math.max(1, this._zoomRange.end - this._zoomRange.start) : null; - const rawZoomMultiplier = zoomSpanMs ? totalSpanMs / zoomSpanMs : 1; - const zoomMultiplier = clampChartValue( - rawZoomMultiplier, - 1, - HISTORY_CHART_MAX_ZOOM_MULTIPLIER - ); - const canvasWidth = Math.min( - HISTORY_CHART_MAX_CANVAS_WIDTH_PX, - zoomSpanMs ? Math.max(viewportWidth, Math.round(viewportWidth * zoomMultiplier)) : viewportWidth - ); - if (this._config?.split_view === true && visibleSeries.length >= 2) { - if (zoomOutButton) { - zoomOutButton.hidden = !this._zoomRange; - zoomOutButton.onclick = () => this._clearZoomRange(); - } - this._renderLegend(series, binaryBackgrounds); - await this._drawSplitChart({ - visibleSeries, - binaryBackgrounds, - events, - renderT0: t0, - renderT1: t1, - canvasWidth, - availableHeight, - chartStage, - canvas, - wrap, - options, - drawableComparisonResults, - selectedComparisonWindowId, - hoveredComparisonWindowId, - comparisonPreviewActive, - hoveringDifferentComparison, - analysisResult, - analysisMap, - hasSelectedComparisonWindow - }); - if (this._chartScrollViewportEl) { - this._chartScrollViewportEl.removeEventListener( - "scroll", - this._onChartScroll - ); - this._chartScrollViewportEl.addEventListener( - "scroll", - this._onChartScroll, - { passive: true } - ); - this._syncChartViewportScroll(t0, t1, canvasWidth); - } - this._fireBackendAnomalyRequests( - _anomalyEntityIds, - analysisMap, - _startIso, - _endIso - ); - return; - } - if (chartStage) { - chartStage.style.width = `${canvasWidth}px`; - chartStage.style.height = `${availableHeight}px`; - } - if (scrollViewport) { - scrollViewport.style.overflowY = ""; - } - const { w, h: h2 } = setupCanvas(canvas, chartStage || wrap, availableHeight, canvasWidth); - const renderer = new ChartRenderer(canvas, w, h2); - renderer.labelColor = resolveChartLabelColor(this); - renderer.clear(); - const renderT0 = t0; - const renderT1 = t1; - const trendPointsMap = new Map( - (analysisResult?.trendSeries || []).map((entry) => [ - entry.entityId, - entry.pts - ]) - ); - const ratePointsMap = new Map( - (analysisResult?.rateSeries || []).map((entry) => [ - entry.entityId, - entry.pts - ]) - ); - const deltaPointsMap = new Map( - (analysisResult?.deltaSeries || []).map((entry) => [ - entry.entityId, - entry.pts - ]) - ); - const summaryStatsMap = new Map( - (analysisResult?.summaryStats || []).map((entry) => [ - entry.entityId, - entry - ]) - ); - const anomalyClustersMap = new Map( - (analysisResult?.anomalySeries || []).map((entry) => [ - entry.entityId, - entry.anomalyClusters - ]) - ); - const hiddenSourceEntityIds = /* @__PURE__ */ new Set(); - const hiddenComparisonEntityIds = /* @__PURE__ */ new Set(); - visibleSeries.forEach((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (this._seriesShouldHideSource( - analysis, - hasSelectedComparisonWindow - )) { - hiddenSourceEntityIds.add(seriesItem.entityId); - } - if (analysis.hide_source_series === true && analysis.show_delta_analysis === true && hasSelectedComparisonWindow) { - hiddenComparisonEntityIds.add(seriesItem.entityId); - } - }); - const anyTrendCrosshairs = visibleSeries.some((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - return analysis.show_trend_lines === true && analysis.show_trend_crosshairs === true; - }); - const anyRateCrosshairs = visibleSeries.some((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - return analysis.show_rate_of_change === true && analysis.show_rate_crosshairs === true; - }); - this._setChartLoading(!!options.loading); - this._setChartMessage(""); - if (zoomOutButton) { - zoomOutButton.hidden = !this._zoomRange; - zoomOutButton.onclick = () => this._clearZoomRange(); - } - const comparisonAxisValues = /* @__PURE__ */ new Map(); - if (this._adjustComparisonAxisScale || autoAdjustHoveredComparisonAxis) { - for (const win of drawableComparisonResults) { - if (autoAdjustHoveredComparisonAxis && hoveredComparisonWindowId && win.id !== hoveredComparisonWindowId) { - continue; - } - for (const seriesSetting of seriesSettings) { - const entityId = seriesSetting.entity_id; - if (entityId.split(".")[0] === "binary_sensor") { - continue; - } - if (this._hiddenSeries.has(entityId)) { - continue; - } - const unit = this._hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; - const axisKey = delinkYAxis ? `${unit || "__unitless__"}::${entityId}` : unit || "__unitless__"; - const analysis = analysisMap.get(entityId) || normalizeHistorySeriesAnalysis(null); - const points = await this._resolveComparisonWindowPoints( - entityId, - win, - analysis, - renderT0, - renderT1 - ); - for (const [, numericValue] of points) { - if (!comparisonAxisValues.has(axisKey)) { - comparisonAxisValues.set(axisKey, []); - } - comparisonAxisValues.get(axisKey).push(numericValue); - } - } - } - } - const deltaAxisMap = /* @__PURE__ */ new Map(); - const rateAxisMap = /* @__PURE__ */ new Map(); - visibleSeries.forEach((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (analysis.show_delta_analysis === true && hasSelectedComparisonWindow && analysis.show_delta_lines === true) { - const deltaPoints = deltaPointsMap.get(seriesItem.entityId) || []; - if (deltaPoints.length) { - const axisKey = `delta:${seriesItem.axisKey}`; - const unitLabel = seriesItem.unit ? `Δ ${seriesItem.unit}` : "Δ"; - let axis = deltaAxisMap.get(axisKey); - if (!axis) { - axis = { - key: axisKey, - unit: unitLabel, - color: seriesItem.color, - side: "right", - values: [] - }; - deltaAxisMap.set(axisKey, axis); - } - deltaPoints.forEach((point) => { - axis.values.push(point[1]); - }); - } - } - if (analysis.show_rate_of_change === true) { - const ratePoints = ratePointsMap.get(seriesItem.entityId) || []; - if (ratePoints.length) { - const axisKey = `rate:${seriesItem.axisKey}`; - const unitLabel = seriesItem.unit ? `${seriesItem.unit}/h` : "Rate/h"; - let axis = rateAxisMap.get(axisKey); - if (!axis) { - axis = { - key: axisKey, - unit: unitLabel, - color: seriesItem.color, - side: "right", - values: [] - }; - rateAxisMap.set(axisKey, axis); - } - ratePoints.forEach((point) => { - axis.values.push(point[1]); - }); - } - } - }); - const resolvedAxes = axes.filter((axis) => axis.values.length).map((axis) => { - const axisValues = series.filter((entry) => entry.axisKey === axis.key).flatMap((entry) => entry.pts.map((point) => point[1])); - if ((this._adjustComparisonAxisScale || autoAdjustHoveredComparisonAxis) && comparisonAxisValues.has(axis.key)) { - axisValues.push(...comparisonAxisValues.get(axis.key)); - } - const extent = this._getAxisValueExtent(axisValues); - if (!extent) { - return null; - } - const { min, max } = extent; - const pad = (max - min) * 0.1 || 1; - return { - key: axis.key, - unit: axis.unit, - color: axis.color, - side: axis.side === "right" ? "right" : "left", - min: min - pad, - max: max + pad - }; - }).filter((ax) => ax !== null); - const deltaResolvedAxes = Array.from(deltaAxisMap.values()).filter((axis) => axis.values.length).map((axis) => { - const extent = this._getAxisValueExtent(axis.values); - if (!extent) { - return null; - } - const { min, max } = extent; - const pad = (max - min) * 0.1 || 1; - return { - key: axis.key, - unit: axis.unit, - color: axis.color, - side: axis.side === "right" ? "right" : "left", - min: min - pad, - max: max + pad - }; - }).filter((ax) => ax !== null); - const rateResolvedAxes = Array.from(rateAxisMap.values()).filter((axis) => axis.values.length).map((axis) => { - const extent = this._getAxisValueExtent(axis.values); - if (!extent) { - return null; - } - const { min, max } = extent; - const pad = (max - min) * 0.1 || 1; - return { - key: axis.key, - unit: axis.unit, - color: axis.color, - side: axis.side === "right" ? "right" : "left", - min: min - pad, - max: max + pad - }; - }).filter((ax) => ax !== null); - const gridAxes = resolvedAxes.length || deltaResolvedAxes.length ? [...resolvedAxes, ...deltaResolvedAxes, ...rateResolvedAxes] : [ - { - key: "binary", - min: 0, - max: 1, - side: "left", - unit: "", - color: null - } - ]; - renderer.drawGrid(renderT0, renderT1, gridAxes, void 0, 5, { - fixedAxisOverlay: true - }); - this._renderComparisonPreviewOverlay( - renderer - ); - const activeAxes = resolvedAxes.length ? renderer._activeAxes || [] : []; - const axisLookup = new Map( - activeAxes.map((axis) => [axis.key, axis]) - ); - series.forEach((s2) => { - s2.axis = axisLookup.get(s2.axisKey) || activeAxes[0] || resolvedAxes[0]; - }); - renderChartAxisOverlays(this, renderer, activeAxes); - this.style.setProperty( - "--dp-chart-axis-bottom-height", - `${Math.max(0, renderer.pad.bottom)}px` - ); - binaryBackgrounds.forEach((binaryBackground) => { - if (binaryBackground?.spans?.length && !this._hiddenSeries.has(binaryBackground.entityId)) { - renderer.drawStateBands( - binaryBackground.spans, - renderT0, - renderT1, - binaryBackground.color, - 0.12 - ); - } - }); - const shouldUseCorrelatedSpans = this._config?.show_correlated_anomalies === true || this._config?.anomaly_overlap_mode === "only"; - const shouldDrawCorrelatedSpans = this._config?.show_correlated_anomalies === true; - const correlatedSpans = shouldUseCorrelatedSpans ? this._buildCorrelatedAnomalySpans( - visibleSeries, - anomalyClustersMap, - analysisMap - ) : []; - if (shouldDrawCorrelatedSpans) { - if (correlatedSpans.length) { - renderer.drawStateBands( - correlatedSpans, - renderT0, - renderT1, - "#ef4444", - 0.1 - ); - } - } - let comparisonOutOfBounds = false; - let mainSeriesHoverOpacity; - if (!comparisonPreviewActive) { - mainSeriesHoverOpacity = 1; - } else if (hoveringDifferentComparison) { - mainSeriesHoverOpacity = 0.15; - } else { - mainSeriesHoverOpacity = 0.25; - } - const anyHiddenSourceSeries = hiddenSourceEntityIds.size > 0; - const hoverSeries = visibleSeries.filter((seriesItem) => !hiddenSourceEntityIds.has(seriesItem.entityId)).map((seriesItem) => ({ - ...seriesItem, - hoverOpacity: mainSeriesHoverOpacity - })); - const summaryHoverSeries = visibleSeries.flatMap((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (analysis.show_summary_stats !== true) { - return []; - } - const stats = summaryStatsMap.get(seriesItem.entityId) || null; - if (!stats) { - return []; - } - const seriesWithAxis = seriesItem; - return [ - { - entityId: `summary:min:${seriesItem.entityId}`, - relatedEntityId: seriesItem.entityId, - label: seriesItem.label, - baseLabel: seriesItem.label, - unit: seriesItem.unit || "", - value: stats.min, - color: hexToRgba( - seriesItem.color, - anyHiddenSourceSeries ? 0.94 : 0.78 - ), - baseColor: seriesItem.color, - axis: seriesWithAxis.axis, - hoverOpacity: anyHiddenSourceSeries ? 0.94 : 0.72, - summaryType: "min", - summary: true - }, - { - entityId: `summary:mean:${seriesItem.entityId}`, - relatedEntityId: seriesItem.entityId, - label: seriesItem.label, - baseLabel: seriesItem.label, - unit: seriesItem.unit || "", - value: stats.mean, - color: hexToRgba( - seriesItem.color, - anyHiddenSourceSeries ? 0.94 : 0.78 - ), - baseColor: seriesItem.color, - axis: seriesWithAxis.axis, - hoverOpacity: anyHiddenSourceSeries ? 0.94 : 0.72, - summaryType: "mean", - summary: true - }, - { - entityId: `summary:max:${seriesItem.entityId}`, - relatedEntityId: seriesItem.entityId, - label: seriesItem.label, - baseLabel: seriesItem.label, - unit: seriesItem.unit || "", - value: stats.max, - color: hexToRgba( - seriesItem.color, - anyHiddenSourceSeries ? 0.94 : 0.78 - ), - baseColor: seriesItem.color, - axis: seriesWithAxis.axis, - hoverOpacity: anyHiddenSourceSeries ? 0.94 : 0.72, - summaryType: "max", - summary: true - } - ]; - }); - const thresholdHoverSeries = visibleSeries.flatMap((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (analysis.show_threshold_analysis !== true) { - return []; - } - const rawThreshold = analysis.threshold_value; - const thresholdValue = Number(rawThreshold); - if (!Number.isFinite(thresholdValue)) { - return []; - } - const seriesWithAxis = seriesItem; - return [ - { - entityId: `threshold:${seriesItem.entityId}`, - relatedEntityId: seriesItem.entityId, - label: seriesItem.label, - baseLabel: seriesItem.label, - unit: seriesItem.unit || "", - value: thresholdValue, - baseColor: seriesItem.color, - color: hexToRgba( - seriesItem.color, - anyHiddenSourceSeries ? 0.82 : 0.46 - ), - axis: seriesWithAxis.axis, - hoverOpacity: anyHiddenSourceSeries ? 0.84 : 0.48, - direction: analysis.threshold_direction === "below" ? "below" : "above", - threshold: true - } - ]; - }); - const trendSeries = visibleSeries.map((seriesItem) => ({ - ...seriesItem, - trendPts: trendPointsMap.get(seriesItem.entityId) || [] - })).filter( - (seriesItem) => Array.isArray(seriesItem.trendPts) && seriesItem.trendPts.length >= 2 - ); - const rateSeries = visibleSeries.map((seriesItem) => { - const ratePoints = ratePointsMap.get(seriesItem.entityId) || []; - if (!Array.isArray(ratePoints) || ratePoints.length < 2) { - return null; - } - const axis = axisLookup.get(`rate:${seriesItem.axisKey}`); - if (!axis) { - return null; - } - return { - ...seriesItem, - ratePts: ratePoints, - rateAxis: axis - }; - }).filter((s2) => s2 !== null); - const anomalySeries = visibleSeries.map((seriesItem) => { - const anomalyClusters = anomalyClustersMap.get(seriesItem.entityId) || []; - if (!Array.isArray(anomalyClusters) || anomalyClusters.length === 0) { - return null; - } - return { - ...seriesItem, - anomalyClusters - }; - }).filter((s2) => s2 !== null); - const comparisonHoverSeries = []; - const comparisonTrendHoverSeries = []; - const comparisonRateHoverSeries = []; - const comparisonSummaryHoverSeries = []; - const comparisonThresholdHoverSeries = []; - const deltaHoverSeries = []; - for (const win of drawableComparisonResults) { - for (const seriesSetting of seriesSettings) { - const entityId = seriesSetting.entity_id; - if (entityId.split(".")[0] === "binary_sensor") { - continue; - } - if (this._hiddenSeries.has(entityId)) { - continue; - } - const analysis = analysisMap.get(entityId) || normalizeHistorySeriesAnalysis(null); - const winPts = await this._resolveComparisonWindowPoints( - entityId, - win, - analysis, - renderT0, - renderT1 - ); - if (!winPts.length) { - continue; - } - const unit = this._hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; - const axisKey = delinkYAxis ? `${unit || "__unitless__"}::${entityId}` : unit || "__unitless__"; - const axis = axisLookup.get(axisKey); - if (!axis) continue; - if (!this._adjustComparisonAxisScale && !autoAdjustHoveredComparisonAxis) { - const hasOutOfBoundsPoint = winPts.some( - (point) => point[1] < axis.min || point[1] > axis.max - ); - if (hasOutOfBoundsPoint) { - comparisonOutOfBounds = true; - } - } - const baseColor = seriesSetting.color || COLORS[seriesSettings.indexOf(seriesSetting) % COLORS.length]; - const isHoveredComparison = !!hoveredComparisonWindowId && win.id === hoveredComparisonWindowId; - const isSelectedComparison = !!selectedComparisonWindowId && win.id === selectedComparisonWindowId; - const comparisonLineStyle = this._getComparisonWindowLineStyle( - isHoveredComparison, - isSelectedComparison, - hoveringDifferentComparison - ); - comparisonHoverSeries.push({ - entityId: `${win.id}:${entityId}`, - relatedEntityId: entityId, - label: seriesSetting.label || entityName(this._hass, entityId) || entityId, - windowLabel: win.label || "Date window", - unit, - pts: winPts, - color: baseColor, - axis, - hoverOpacity: comparisonLineStyle.hoverOpacity - }); - if (analysis.show_trend_lines === true && winPts.length >= 2) { - const winPrecomputed = this._analysisCache?.result?.comparisonWindowResults?.[win.id]?.[entityId]; - const trendPts = winPrecomputed?.trendPts ?? this._buildTrendPoints( - winPts, - analysis.trend_method, - analysis.trend_window - ); - if (trendPts.length >= 2) { - const trendOptions = this._getTrendRenderOptions( - analysis.trend_method, - false - ); - comparisonTrendHoverSeries.push({ - entityId: `trend:${win.id}:${entityId}`, - relatedEntityId: entityId, - comparisonParentId: `${win.id}:${entityId}`, - label: seriesSetting.label || entityName(this._hass, entityId) || entityId, - baseLabel: seriesSetting.label || entityName(this._hass, entityId) || entityId, - windowLabel: win.label || "Date window", - unit, - pts: trendPts, - color: hexToRgba( - baseColor, - Math.max(0.3, trendOptions.colorAlpha - 0.18) - ), - axis, - rawVisible: true, - hoverOpacity: Math.max(0.3, trendOptions.lineOpacity - 0.18), - trend: true - }); - } - } - if (analysis.show_rate_of_change === true && winPts.length >= 2) { - const winPrecomputed = this._analysisCache?.result?.comparisonWindowResults?.[win.id]?.[entityId]; - const ratePts = winPrecomputed?.ratePts ?? this._buildRateOfChangePoints(winPts, analysis.rate_window); - const rateAxis2 = axisLookup.get(`rate:${axisKey}`) || axis; - if (ratePts.length >= 2) { - comparisonRateHoverSeries.push({ - entityId: `rate:${win.id}:${entityId}`, - relatedEntityId: entityId, - comparisonParentId: `${win.id}:${entityId}`, - label: seriesSetting.label || entityName(this._hass, entityId) || entityId, - baseLabel: seriesSetting.label || entityName(this._hass, entityId) || entityId, - windowLabel: win.label || "Date window", - unit: unit ? `${unit}/h` : "/h", - pts: ratePts, - color: hexToRgba(baseColor, 0.52), - axis: rateAxis2, - rawVisible: true, - hoverOpacity: 0.46, - rate: true - }); - } - } - if (analysis.show_summary_stats === true) { - const winPrecomputed = this._analysisCache?.result?.comparisonWindowResults?.[win.id]?.[entityId]; - const summaryStats = winPrecomputed?.summaryStats ?? this._buildSummaryStats(winPts); - [ - { type: "min", value: summaryStats.min }, - { type: "mean", value: summaryStats.mean }, - { type: "max", value: summaryStats.max } - ].forEach((entry) => { - if (!Number.isFinite(entry.value)) { - return; - } - comparisonSummaryHoverSeries.push({ - entityId: `summary:${entry.type}:${win.id}:${entityId}`, - relatedEntityId: entityId, - comparisonParentId: `${win.id}:${entityId}`, - label: seriesSetting.label || entityName(this._hass, entityId) || entityId, - baseLabel: seriesSetting.label || entityName(this._hass, entityId) || entityId, - windowLabel: win.label || "Date window", - unit, - value: entry.value, - color: hexToRgba(baseColor, entry.type === "mean" ? 0.44 : 0.24), - axis, - rawVisible: true, - hoverOpacity: entry.type === "mean" ? 0.52 : 0.3, - summaryType: entry.type, - summary: true - }); - }); - } - if (analysis.show_threshold_analysis === true) { - const thresholdValue = Number(analysis.threshold_value); - if (Number.isFinite(thresholdValue)) { - comparisonThresholdHoverSeries.push({ - entityId: `threshold:${win.id}:${entityId}`, - relatedEntityId: entityId, - comparisonParentId: `${win.id}:${entityId}`, - label: seriesSetting.label || entityName(this._hass, entityId) || entityId, - baseLabel: seriesSetting.label || entityName(this._hass, entityId) || entityId, - windowLabel: win.label || "Date window", - unit, - value: thresholdValue, - color: hexToRgba(baseColor, 0.28), - axis, - rawVisible: true, - hoverOpacity: 0.34, - threshold: true - }); - } - } - if (!hiddenComparisonEntityIds.has(entityId)) { - renderer.drawLine( - winPts, - baseColor, - renderT0, - renderT1, - axis.min, - axis.max, - { - lineOpacity: comparisonLineStyle.lineOpacity, - lineWidth: comparisonLineStyle.lineWidth, - dashed: comparisonLineStyle.dashed, - dashPattern: comparisonLineStyle.dashPattern, - stepped: analysis.stepped_series === true - } - ); - } - const rateAxis = axisLookup.get(`rate:${axisKey}`) || null; - this._drawComparisonAnalysisOverlays({ - renderer, - entityId, - seriesColor: baseColor, - comparisonPts: winPts, - analysis, - renderT0, - renderT1, - axis, - rateAxis, - events, - comparisonWindowId: win.id - }); + }).join(""); + legendEl.querySelectorAll(".legend-toggle").forEach((btn) => { + btn.addEventListener("click", () => { + const entityId = btn.dataset.entityId; + if (!entityId || !this._hiddenSeries) return; + if (this._hiddenSeries.has(entityId)) this._hiddenSeries.delete(entityId); + else this._hiddenSeries.add(entityId); + btn.setAttribute("aria-pressed", this._hiddenSeries.has(entityId) ? "false" : "true"); + this._redrawLastDraw(); + }); + }); + } + /** Draw a single series line onto the renderer, with optional data-gap rendering. */ + _drawSeriesLine(renderer, pts, color, t0, t1, min, max, opts) { + const r = renderer; + const config = this._config; + const showGaps = config?.show_data_gaps !== false; + const gapThresholdMs = SAMPLE_INTERVAL_MS[config?.data_gap_threshold || "2h"] ?? 120 * 6e4; + if (!showGaps || pts.length < 2 || !Number.isFinite(gapThresholdMs)) { + r.drawLine(pts, color, t0, t1, min, max, opts); + return; + } + const segments = []; + const gapBridges = []; + let current = [pts[0]]; + for (let i = 1; i < pts.length; i++) if (pts[i][0] - pts[i - 1][0] > gapThresholdMs) { + segments.push(current); + gapBridges.push([pts[i - 1], pts[i]]); + current = [pts[i]]; + } else current.push(pts[i]); + segments.push(current); + if (segments.length === 1) { + r.drawLine(pts, color, t0, t1, min, max, opts); + return; + } + for (const seg of segments) r.drawLine(seg, color, t0, t1, min, max, opts); + const gapColor = color.startsWith("rgba") ? color.replace(/[\d.]+\)$/, "0.35)") : `${color}59`; + for (const [lastPt, firstPt] of gapBridges) r.drawLine([lastPt, firstPt], gapColor, t0, t1, min, max, { + dashed: true, + lineWidth: 1.2, + lineOpacity: .5 + }); + const boundaryPoints = gapBridges.flatMap(([a, b]) => [a, b]); + r.drawGapMarkers(boundaryPoints, color, t0, t1, min, max); + } + /** Draw event point icons onto the renderer. */ + _drawRecordedEventPoints(_renderer, _visibleSeries, _events, _t0, _t1, _opts) { + const renderer = _renderer; + const series = _visibleSeries; + const events = _events; + const opts = _opts; + const overlay = this.querySelector("#chart-icon-overlay"); + if (overlay && !opts.skipOverlayClear) overlay.innerHTML = ""; + if (!renderer || !events?.length) return []; + const yOffset = Number.isFinite(opts.yOffset) ? opts.yOffset : 0; + const hits = []; + const { ctx } = renderer; + const showIcons = opts.showIcons !== false; + const fallbackHighlightedIds = Array.isArray(this._config?.hovered_event_ids) ? this._config.hovered_event_ids : []; + const highlightedEventIds = new Set(Array.isArray(opts.highlightedEventIds) ? opts.highlightedEventIds : fallbackHighlightedIds); + for (const event of events) { + const timestamp = new Date(event.timestamp).getTime(); + if (timestamp < _t0 || timestamp > _t1) continue; + const eventEntityIds = Array.isArray(event.entity_ids) ? event.entity_ids : []; + const x = renderer.xOf(timestamp, _t0, _t1); + const findSeriesWithValue = (candidates) => { + for (const candidate of candidates) { + if (!candidate?.pts?.length || !candidate.axis) continue; + const candidateValue = renderer._interpolateValue(candidate.pts, timestamp); + if (candidateValue == null) continue; + return { + series: candidate, + value: candidateValue + }; + } + return null; + }; + const matchingSeriesCandidates = eventEntityIds.map((entityId) => series.find((entry) => entry.entityId === entityId)).filter((s) => s != null); + const matchingSeriesHit = findSeriesWithValue(matchingSeriesCandidates); + const linkedToOtherTarget = eventEntityIds.length > 0 && matchingSeriesCandidates.length === 0; + const fallbackSeriesHit = matchingSeriesHit || (linkedToOtherTarget ? null : findSeriesWithValue([...series].reverse())); + const targetSeries = fallbackSeriesHit?.series || null; + const hasNumericTarget = !!(targetSeries?.pts?.length && targetSeries.axis); + const value = hasNumericTarget ? fallbackSeriesHit?.value ?? null : null; + if (hasNumericTarget && value == null) continue; + let y; + if (hasNumericTarget) y = renderer.yOf(value, targetSeries.axis.min, targetSeries.axis.max); + else if (linkedToOtherTarget) y = renderer.pad.top + renderer.ch; + else y = renderer.pad.top + 12; + const color = event.color || targetSeries?.color || "#03a9f4"; + const isHighlighted = highlightedEventIds.has(String(event.id || "")); + const highlightOuterRadius = showIcons ? 18 : 10; + const highlightInnerRadius = showIcons ? 15 : 8; + const outerRadius = showIcons ? 13 : 6; + const innerRadius = showIcons ? 11 : 4; + if (isHighlighted) { + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, highlightOuterRadius, 0, Math.PI * 2); + ctx.fillStyle = hexToRgba(color, .18); + ctx.fill(); + ctx.restore(); + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, highlightInnerRadius, 0, Math.PI * 2); + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.stroke(); + ctx.restore(); + } + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, outerRadius, 0, Math.PI * 2); + ctx.fillStyle = "rgba(255,255,255,0.92)"; + ctx.fill(); + ctx.restore(); + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, innerRadius, 0, Math.PI * 2); + ctx.fillStyle = color; + ctx.fill(); + ctx.restore(); + const navigateToHistory = (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + navigateToDataPointsHistory(this, { + entity_id: event.entity_ids || [], + device_id: [], + area_id: [], + label_id: [] + }, { + start_time: this._config?.start_time || null, + end_time: this._config?.end_time || null, + zoom_start_time: this._config?.zoom_start_time || null, + zoom_end_time: this._config?.zoom_end_time || null, + datapoint_scope: this._config?.datapoint_scope + }); + }; + if (overlay && showIcons) { + const iconEl = document.createElement("button"); + iconEl.type = "button"; + iconEl.className = "chart-event-icon"; + iconEl.style.left = `${x}px`; + iconEl.style.top = `${y + yOffset}px`; + iconEl.title = event.message || "Open related history"; + iconEl.setAttribute("aria-label", event.message || "Open related history"); + iconEl.innerHTML = ``; + iconEl.addEventListener("click", navigateToHistory); + overlay.appendChild(iconEl); + } else if (overlay) { + const hitEl = document.createElement("button"); + hitEl.type = "button"; + hitEl.className = "chart-event-icon"; + hitEl.style.left = `${x}px`; + hitEl.style.top = `${y + yOffset}px`; + hitEl.title = event.message || "Open related history"; + hitEl.setAttribute("aria-label", event.message || "Open related history"); + hitEl.addEventListener("click", navigateToHistory); + overlay.appendChild(hitEl); + } + hits.push({ + event, + entityId: targetSeries?.entityId || null, + unit: targetSeries?.unit || "", + value, + x, + y + }); + } + return hits; + } + /** Get [min, max] extent for an axis value array. */ + _getAxisValueExtent(values) { + return getAxisValueExtent(values); + } + _getComparisonWindowLineStyle(isHovered, isSelected, hoveringDifferentComparison) { + if (isHovered) return { + lineOpacity: 1, + dashed: false, + hoverOpacity: .85 + }; + if (hoveringDifferentComparison && isSelected) return { + lineOpacity: .25, + lineWidth: 1.25, + dashed: false, + hoverOpacity: .25 + }; + return { + lineOpacity: .85, + dashed: false, + hoverOpacity: .85 + }; + } + /** Sync chart viewport scroll to the current zoom range. */ + _syncChartViewportScroll(_t0, _t1, _canvasWidth) { + if (!this._chartScrollViewportEl) return; + const viewport = this._chartScrollViewportEl; + if (!this._zoomRange) { + if (viewport.scrollLeft !== 0) { + this._scrollSyncSuspended = true; + this._ignoreNextProgrammaticScrollEvent = true; + viewport.scrollLeft = 0; + window.requestAnimationFrame(() => { + this._scrollSyncSuspended = false; + }); + } + return; + } + if (this._skipNextScrollViewportSync) { + this._skipNextScrollViewportSync = false; + return; + } + const viewportWidth = viewport.clientWidth; + const totalMs = Math.max(1, _t1 - _t0); + const visibleSpanMs = totalMs * Math.min(1, viewportWidth / Math.max(_canvasWidth, viewportWidth)); + const maxScrollLeft = Math.max(0, Math.max(_canvasWidth, viewportWidth) - viewportWidth); + const maxStartOffsetMs = Math.max(0, totalMs - visibleSpanMs); + const clampedStart = clampChartValue(this._zoomRange.start, _t0, _t1 - visibleSpanMs); + const nextLeft = (maxStartOffsetMs > 0 ? (clampedStart - _t0) / maxStartOffsetMs : 0) * maxScrollLeft; + const currentLeft = viewport.scrollLeft; + if (Math.abs(currentLeft - nextLeft) < 2) return; + this._scrollSyncSuspended = true; + this._lastProgrammaticScrollLeft = nextLeft; + this._ignoreNextProgrammaticScrollEvent = true; + viewport.scrollLeft = nextLeft; + window.requestAnimationFrame(() => { + this._scrollSyncSuspended = false; + }); + } + /** Ensure the context annotation dialog element is present. */ + _ensureContextAnnotationDialog() { + const parentCard = this.getRootNode()?.host ?? null; + if (parentCard?._annotationDialog && typeof parentCard._annotationDialog.ensureDialog === "function") parentCard._annotationDialog.ensureDialog(); + } + /** Open the context annotation dialog with the given hover data. */ + _openContextAnnotationDialog(_hover) { + const parentCard = this.getRootNode()?.host ?? null; + if (parentCard?._annotationDialog && typeof parentCard._annotationDialog.open === "function") parentCard._annotationDialog.open(_hover); + } + /** Handle context menu on the chart canvas. */ + async _handleChartContextMenu(_hover) { + const annotationDialog = (this.getRootNode()?.host ?? null)?._annotationDialog; + if (!_hover || !this._hass || annotationDialog?.isOpen?.()) return; + this._openContextAnnotationDialog(_hover); + } + /** Handle add-annotation click from the chart hover layer. */ + _handleChartAddAnnotation(_hover) { + const annotationDialog = (this.getRootNode()?.host ?? null)?._annotationDialog; + if (!_hover || !this._hass || !this._hass.user?.is_admin || annotationDialog?.isOpen?.()) return; + this._openContextAnnotationDialog(_hover); + } + /** Handle anomaly cluster click — opens the annotation dialog prefilled with anomaly details. */ + _handleAnomalyAddAnnotation(_regions) { + const regions = _regions; + if (!regions?.length || !this._hass) return; + const prefill = this._buildAnomalyAnnotationPrefill(regions); + const timeMs = ((regions[0]?.cluster?.points)?.reduce((peak, p) => !peak || Math.abs(p.residual) > Math.abs(peak.residual) ? p : peak, null))?.timeMs ?? this._chartLastHover?.timeMs ?? Date.now(); + this._openContextAnnotationDialog({ + timeMs, + annotationPrefill: prefill + }); + } + /** Build an annotation prefill object from anomaly regions. */ + _buildAnomalyAnnotationPrefill(_regions) { + const regions = _regions; + if (!regions?.length) return {}; + const content = buildAnomalyTooltipContent(regions); + const entityId = regions[0]?.relatedEntityId ?? null; + return { + message: content ? `${content.description}\n${content.alert}` : "", + icon: "mdi:alert-circle", + linkedTarget: entityId ? { entity_id: [entityId] } : null + }; + } + /** Fire backend anomaly detection requests. */ + _fireBackendAnomalyRequests(_anomalyEntityIds, _analysisMap, _startIso, _endIso) { + if (!_anomalyEntityIds || !_anomalyEntityIds.length || !this._hass) return; + const hass = this._hass; + _anomalyEntityIds.forEach((entityId) => { + const analysis = _analysisMap.get(entityId); + if (!analysis) return; + const config = this._buildBackendAnomalyConfig(analysis); + const configKey = JSON.stringify({ + ...config, + anomaly_methods: [...config.anomaly_methods || []].sort() + }); + const cached = this._backendAnomalyByEntity.get(entityId); + if (cached && cached.configKey === configKey) return; + this._pendingAnomalyEntityIds.add(entityId); + this._setChartLoading(true); + fetchAnomaliesFromBackend(hass, entityId, _startIso, _endIso, config).then((clusters) => { + this._pendingAnomalyEntityIds.delete(entityId); + if (_startIso !== new Date(this._lastT0).toISOString() || _endIso !== new Date(this._lastT1).toISOString()) { + if (this._pendingAnomalyEntityIds.size === 0) this._setChartLoading(false); + return; + } + this._backendAnomalyByEntity.set(entityId, { + configKey, + clusters + }); + if (this._analysisCache?.result) { + const existing = this._analysisCache.result.anomalySeries || []; + const idx = existing.findIndex((entry) => entry.entityId === entityId); + const entry = { + entityId, + anomalyClusters: clusters + }; + if (idx >= 0) existing[idx] = entry; + else existing.push(entry); + } + if (this._lastHistResult && this._lastEvents) this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._filterEvents(this._lastEvents), this._lastT0, this._lastT1, { loading: this._pendingAnomalyEntityIds.size > 0 }); + else if (this._pendingAnomalyEntityIds.size === 0) this._setChartLoading(false); + }).catch((err) => { + this._pendingAnomalyEntityIds.delete(entityId); + if (this._pendingAnomalyEntityIds.size === 0) this._setChartLoading(false); + logger$1.warn("[hass-datapoints history-card] backend anomaly fetch failed", { + entityId, + err + }); + }); + }); + } + /** Dispatch a zoom preview event. */ + _dispatchZoomPreview(_range) { + const range = _range; + this.dispatchEvent(new CustomEvent("hass-datapoints-chart-zoom", { + bubbles: true, + composed: true, + detail: range ? { + startTime: range.startTime, + endTime: range.endTime, + preview: true + } : { + startTime: null, + endTime: null, + preview: true + } + })); + } + /** Apply a zoom range to the chart. */ + _applyZoomRange(_startTime, _endTime) { + const start = Math.min(_startTime, _endTime); + const end = Math.max(_startTime, _endTime); + if (!(start < end)) return; + this._zoomRange = { + start, + end + }; + this.dispatchEvent(new CustomEvent("hass-datapoints-zoom-apply", { + bubbles: true, + composed: true, + detail: { + start, + end + } + })); + this._redrawLastDraw(); + } + /** Clear the active zoom range. */ + _clearZoomRange() { + if (!this._zoomRange) return; + this._zoomRange = null; + this.dispatchEvent(new CustomEvent("hass-datapoints-zoom-apply", { + bubbles: true, + composed: true, + detail: null + })); + this._redrawLastDraw(); + } + /** Filter events list by hidden IDs and message filter. */ + _filterEvents(_events) { + const events = _events; + const query = String(this._config?.message_filter || "").trim().toLowerCase(); + const visibleEvents = events.filter((event) => !this._hiddenEventIds.has(event?.id ?? "")); + if (!query) return visibleEvents; + return visibleEvents.filter((event) => { + return [ + event?.message || "", + event?.annotation || "", + ...(event?.entity_ids || []).filter(Boolean) + ].join("\n").toLowerCase().includes(query); + }); + } + /** Build correlated anomaly spans across series. */ + _buildCorrelatedAnomalySpans(_visibleSeries, _anomalyClustersMap, _analysisMap) { + const visibleSeries = _visibleSeries; + const anomalyClustersMap = _anomalyClustersMap; + const seriesIntervals = []; + for (const seriesItem of visibleSeries) { + if (_analysisMap.get(seriesItem.entityId)?.show_anomalies !== true) continue; + const clusters = anomalyClustersMap.get(seriesItem.entityId) || []; + if (!clusters.length) continue; + const pts = seriesItem.pts; + let tolerance = 6e4; + if (Array.isArray(pts) && pts.length >= 2) { + const intervals = []; + for (let i = 1; i < pts.length; i++) { + const diff = pts[i][0] - pts[i - 1][0]; + if (diff > 0) intervals.push(diff); + } + if (intervals.length) { + intervals.sort((a, b) => a - b); + const mid = Math.floor(intervals.length / 2); + tolerance = intervals.length % 2 === 0 ? (intervals[mid - 1] + intervals[mid]) / 2 : intervals[mid]; + tolerance = Math.max(tolerance, 1e3); + } + } + const entityIntervals = []; + for (const cluster of clusters) { + if (!Array.isArray(cluster.points) || cluster.points.length === 0) continue; + const startTime = cluster.points[0]?.timeMs; + const endTime = cluster.points[cluster.points.length - 1]?.timeMs; + if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) continue; + entityIntervals.push({ + start: Math.min(startTime, endTime) - tolerance, + end: Math.max(startTime, endTime) + tolerance + }); + } + if (entityIntervals.length) seriesIntervals.push({ + entityId: seriesItem.entityId, + intervals: entityIntervals + }); + } + if (seriesIntervals.length < 2) return []; + const events = []; + for (const { entityId, intervals } of seriesIntervals) for (const { start, end } of intervals) { + events.push({ + time: start, + delta: 1, + entityId + }); + events.push({ + time: end, + delta: -1, + entityId + }); + } + events.sort((a, b) => a.time - b.time || a.delta - b.delta); + const activeCounts = /* @__PURE__ */ new Map(); + const spans = []; + let spanStart = null; + for (const event of events) { + const next = (activeCounts.get(event.entityId) || 0) + event.delta; + if (next <= 0) activeCounts.delete(event.entityId); + else activeCounts.set(event.entityId, next); + const activeCount = activeCounts.size; + if (spanStart === null && activeCount >= 2) spanStart = event.time; + else if (spanStart !== null && activeCount < 2) { + spans.push({ + start: spanStart, + end: event.time + }); + spanStart = null; + } + } + if (spanStart !== null && events.length > 0) spans.push({ + start: spanStart, + end: events[events.length - 1].time + }); + return spans; + } + /** Filter out annotated anomaly clusters. */ + _filterAnnotatedAnomalyClusters(_seriesItem, _events) { + const seriesItem = _seriesItem; + if (!Array.isArray(seriesItem?.anomalyClusters) || seriesItem.anomalyClusters.length === 0) return []; + const visibleEvents = Array.isArray(_events) ? _events : []; + if (visibleEvents.length === 0) return seriesItem.anomalyClusters; + const getClusterRange = (cluster) => { + if (!Array.isArray(cluster.points) || cluster.points.length === 0) return null; + const startTime = cluster.points[0]?.timeMs; + const endTime = cluster.points[cluster.points.length - 1]?.timeMs; + if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) return null; + return { + startTime: Math.min(startTime, endTime), + endTime: Math.max(startTime, endTime) + }; + }; + return seriesItem.anomalyClusters.filter((cluster) => { + const clusterRange = getClusterRange(cluster); + if (!clusterRange) return true; + return !visibleEvents.some((event) => { + if (!(Array.isArray(event.entity_ids) ? event.entity_ids.filter(Boolean) : []).includes(seriesItem.entityId)) return false; + const eventTime = new Date(event.timestamp).getTime(); + if (!Number.isFinite(eventTime)) return false; + return eventTime >= clusterRange.startTime && eventTime <= clusterRange.endTime; + }); + }); + } + _fireComparisonBackendAnomalyRequests(drawableComparisonResults, analysisMap, renderT0, renderT1) { + if (!drawableComparisonResults.length || !this._hass) return; + const startIso = new Date(renderT0).toISOString(); + const endIso = new Date(renderT1).toISOString(); + drawableComparisonResults.forEach((comparisonWindow) => { + const comparisonStartIso = new Date(renderT0 + comparisonWindow.time_offset_ms).toISOString(); + const comparisonEndIso = new Date(renderT1 + comparisonWindow.time_offset_ms).toISOString(); + this._seriesSettings.forEach((seriesSetting) => { + const entityId = String(seriesSetting.entity_id || ""); + if (!entityId || this._hiddenSeries.has(entityId)) return; + const analysis = analysisMap.get(entityId); + if (!analysis || analysis.show_anomalies !== true) return; + const config = this._buildBackendAnomalyConfig(analysis); + const configKey = JSON.stringify({ + ...config, + windowId: comparisonWindow.id, + startIso, + endIso, + anomaly_methods: [...config.anomaly_methods || []].sort() + }); + const cacheKey = this._getComparisonAnomalyCacheKey(comparisonWindow.id, entityId); + const cached = this._backendComparisonAnomalyByKey.get(cacheKey); + if (cached && cached.configKey === configKey) return; + if (this._pendingComparisonAnomalyKeys.has(cacheKey)) return; + this._pendingComparisonAnomalyKeys.add(cacheKey); + const hass = this._hass; + if (!hass) { + this._pendingComparisonAnomalyKeys.delete(cacheKey); + return; + } + fetchAnomaliesFromBackend(hass, entityId, comparisonStartIso, comparisonEndIso, config).then((clusters) => { + this._pendingComparisonAnomalyKeys.delete(cacheKey); + if (startIso !== new Date(this._lastT0).toISOString() || endIso !== new Date(this._lastT1).toISOString()) return; + const shiftedClusters = this._shiftComparisonAnomalyClusters(Array.isArray(clusters) ? clusters : [], comparisonWindow.time_offset_ms); + this._backendComparisonAnomalyByKey.set(cacheKey, { + configKey, + clusters: shiftedClusters + }); + if (this._lastHistResult && this._lastEvents) this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._filterEvents(this._lastEvents), this._lastT0, this._lastT1, { loading: false }); + }).catch(() => { + this._pendingComparisonAnomalyKeys.delete(cacheKey); + }); + }); + }); + } + /** Render per-row axis overlays for split-view chart. */ + _renderSplitAxisOverlays(_tracks) { + const tracks = _tracks; + const leftEl = this.querySelector("#chart-axis-left"); + const rightEl = this.querySelector("#chart-axis-right"); + if (!leftEl || !rightEl || !tracks.length) return; + const primaryRenderer = tracks[0].renderer; + const leftWidth = Math.max(0, primaryRenderer.pad.left); + const bottomHeight = Math.max(0, primaryRenderer.pad.bottom); + leftEl.style.width = `${leftWidth}px`; + rightEl.style.width = "0px"; + this.style.setProperty("--dp-chart-axis-left-width", `${leftWidth}px`); + this.style.setProperty("--dp-chart-axis-right-width", "0px"); + this.style.setProperty("--dp-chart-axis-bottom-height", `${bottomHeight}px`); + const labelRight = 10; + let labelsHtml = ""; + for (const { renderer, axis, rowOffset } of tracks) { + if (!axis?.ticks?.length) continue; + for (const tick of axis.ticks) { + const y = rowOffset + renderer.yOf(tick, axis.min, axis.max); + const formatted = renderer._formatAxisTick(tick, axis.unit); + labelsHtml += `
${esc(formatted)}
`; + } + if (axis.unit) { + const unitY = rowOffset + Math.max(0, primaryRenderer.pad.top - 18); + labelsHtml += `
${esc(axis.unit)}
`; + } + } + leftEl.innerHTML = `
${labelsHtml}`; + leftEl.classList.add("visible"); + rightEl.innerHTML = ""; + rightEl.classList.remove("visible"); + } + /** Ordered series settings from the card config. */ + get _seriesSettings() { + return Array.isArray(this._config?.series_settings) ? this._config.series_settings : []; + } + /** Active comparison windows from the card config. */ + get _comparisonWindows() { + return Array.isArray(this._config?.comparison_windows) ? this._config.comparison_windows : []; + } + _getDrawableComparisonResults(comparisonResults) { + const drawableComparisonWindowIds = new Set(this._comparisonWindows.map((window) => String(window?.id || "")).filter((id) => id.length > 0)); + const selectedComparisonWindowId = String(this._config?.selected_comparison_window_id || ""); + const hoveredComparisonWindowId = String(this._config?.hovered_comparison_window_id || ""); + if (selectedComparisonWindowId) drawableComparisonWindowIds.add(selectedComparisonWindowId); + if (hoveredComparisonWindowId) drawableComparisonWindowIds.add(hoveredComparisonWindowId); + if (drawableComparisonWindowIds.size === 0) return []; + return comparisonResults.filter((window) => drawableComparisonWindowIds.has(String(window.id || ""))); + } + _resolveAnomalyClusterDisplay(anomalyClusters, overlapMode, correlatedSpans = []) { + const normalClusters = anomalyClusters.filter((c) => !c.isOverlap); + const overlapClusters = anomalyClusters.filter((c) => c.isOverlap === true); + if (overlapMode === "only") { + const overlapOnlyClusters = this._filterClustersByCorrelatedSpans(anomalyClusters, correlatedSpans); + return { + baseClusters: overlapOnlyClusters, + regionClusters: overlapOnlyClusters, + showCorrelatedSpans: true + }; + } + return { + baseClusters: [...normalClusters, ...overlapClusters], + regionClusters: [...normalClusters, ...overlapClusters], + showCorrelatedSpans: false + }; + } + _filterClustersByCorrelatedSpans(anomalyClusters, correlatedSpans) { + if (!Array.isArray(anomalyClusters) || anomalyClusters.length === 0) return []; + if (!Array.isArray(correlatedSpans) || correlatedSpans.length === 0) return []; + return anomalyClusters.filter((cluster) => { + const points = cluster.points; + if (!Array.isArray(points) || points.length === 0) return false; + const startTime = Number(points[0]?.timeMs); + const endTime = Number(points[points.length - 1]?.timeMs); + if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) return false; + const clusterStart = Math.min(startTime, endTime); + const clusterEnd = Math.max(startTime, endTime); + return correlatedSpans.some((span) => { + const spanStart = Number(span.start); + const spanEnd = Number(span.end); + if (!Number.isFinite(spanStart) || !Number.isFinite(spanEnd)) return false; + return clusterEnd >= spanStart && clusterStart <= spanEnd; + }); + }); + } + _getComparisonAnomalyCacheKey(windowId, entityId) { + return `${windowId}:${entityId}`; + } + _shiftComparisonAnomalyClusters(clusters, timeOffsetMs) { + return (Array.isArray(clusters) ? clusters : []).map((cluster) => ({ + ...cluster, + points: Array.isArray(cluster.points) ? cluster.points.map((point) => ({ + ...point, + timeMs: Number(point.timeMs) - timeOffsetMs + })) : [] + })); + } + async _resolveComparisonWindowPoints(entityId, comparisonWindow, analysis, renderT0, renderT1) { + const stateList = this._buildEntityStateList(entityId, comparisonWindow.histResult, comparisonWindow.statsResult || {}); + const rawPoints = []; + for (const state of stateList) { + const value = parseFloat(state.s); + if (!Number.isNaN(value)) rawPoints.push([Math.round(state.lu * 1e3) - comparisonWindow.time_offset_ms, value]); + } + const interval = analysis.sample_interval || "raw"; + if (interval === "raw" || !this._hass) return rawPoints; + const winStartIso = new Date(renderT0 + comparisonWindow.time_offset_ms).toISOString(); + const winEndIso = new Date(renderT1 + comparisonWindow.time_offset_ms).toISOString(); + const sampledPts = await fetchDownsampledHistory(this._hass, entityId, winStartIso, winEndIso, interval, analysis.sample_aggregate || "mean"); + if (!Array.isArray(sampledPts) || sampledPts.length === 0) { + const targetIntervalMs = SAMPLE_INTERVAL_MS[interval] ?? 0; + if (targetIntervalMs <= 0 || rawPoints.length === 0) return rawPoints; + return downsampleInWorker(rawPoints, targetIntervalMs, analysis.sample_aggregate || "mean"); + } + let finalPts = sampledPts.map(([timestamp, value]) => [timestamp - comparisonWindow.time_offset_ms, value]); + const statsPts = this._normalizeStatisticsHistory(entityId, comparisonWindow.statsResult || {}).map((state) => [Math.round(state.lu * 1e3) - comparisonWindow.time_offset_ms, parseFloat(state.s)]).filter(([, value]) => !Number.isNaN(value)); + if (statsPts.length > 0) { + const firstSampledMs = finalPts[0][0]; + const lastSampledMs = finalPts[finalPts.length - 1][0]; + const rawOutsidePts = statsPts.filter(([timestamp]) => timestamp < firstSampledMs || timestamp > lastSampledMs); + if (rawOutsidePts.length > 0) { + const targetIntervalMs = SAMPLE_INTERVAL_MS[interval] ?? 0; + const sampleAggregate = analysis.sample_aggregate || "mean"; + finalPts = [...targetIntervalMs > 0 ? await downsampleInWorker(rawOutsidePts, targetIntervalMs, sampleAggregate) : rawOutsidePts, ...finalPts]; + finalPts.sort((a, b) => a[0] - b[0]); + } + } + return finalPts; + } + _drawComparisonAnalysisOverlays({ renderer, entityId, seriesColor, comparisonPts, analysis, renderT0, renderT1, axis, rateAxis = null, events = [], comparisonWindowId }) { + const precomputed = this._analysisCache?.result?.comparisonWindowResults?.[comparisonWindowId]?.[entityId]; + if (analysis.show_threshold_analysis === true) { + const thresholdValue = Number(analysis.threshold_value); + if (Number.isFinite(thresholdValue)) { + if (analysis.show_threshold_shading === true && comparisonPts.length) renderer.drawThresholdArea(comparisonPts, thresholdValue, seriesColor, renderT0, renderT1, axis.min, axis.max, { + mode: analysis.threshold_direction === "below" ? "below" : "above", + fillAlpha: .08 + }); + renderer.drawLine([[renderT0, thresholdValue], [renderT1, thresholdValue]], hexToRgba(seriesColor, .28), renderT0, renderT1, axis.min, axis.max, { + lineOpacity: .34, + lineWidth: 1.05, + dashed: true + }); + } + } + if (analysis.show_summary_stats === true) { + const summaryStats = precomputed?.summaryStats ?? this._buildSummaryStats(comparisonPts); + if (Number.isFinite(summaryStats.min) && Number.isFinite(summaryStats.max) && Number.isFinite(summaryStats.mean)) { + if (analysis.show_summary_stats_shading === true) { + renderer.drawGradientBand(summaryStats.min, summaryStats.mean, seriesColor, renderT0, renderT1, axis.min, axis.max, { fillAlpha: .04 }); + renderer.drawGradientBand(summaryStats.max, summaryStats.mean, seriesColor, renderT0, renderT1, axis.min, axis.max, { fillAlpha: .04 }); + } + const summaryEntries = [ + { + value: summaryStats.min, + alpha: .24, + width: 1, + dotted: true + }, + { + value: summaryStats.mean, + alpha: .44, + width: 1.45, + dotted: false + }, + { + value: summaryStats.max, + alpha: .24, + width: 1, + dotted: true + } + ]; + for (const entry of summaryEntries) renderer.drawLine([[renderT0, entry.value], [renderT1, entry.value]], hexToRgba(seriesColor, entry.alpha), renderT0, renderT1, axis.min, axis.max, { + lineOpacity: entry.alpha + .08, + lineWidth: entry.width, + dotted: entry.dotted + }); + } + } + if (analysis.show_trend_lines === true && comparisonPts.length >= 2) { + const trendPts = precomputed?.trendPts ?? this._buildTrendPoints(comparisonPts, analysis.trend_method, analysis.trend_window); + if (trendPts.length >= 2) { + const trendOptions = this._getTrendRenderOptions(analysis.trend_method, false); + renderer.drawLine(trendPts, hexToRgba(seriesColor, Math.max(.3, trendOptions.colorAlpha - .18)), renderT0, renderT1, axis.min, axis.max, { + lineOpacity: Math.max(.3, trendOptions.lineOpacity - .18), + lineWidth: Math.max(1.35, trendOptions.lineWidth - .35), + dashed: trendOptions.dashed, + dotted: trendOptions.dotted + }); + } + } + if (analysis.show_rate_of_change === true && rateAxis && comparisonPts.length >= 2) { + const ratePts = precomputed?.ratePts ?? this._buildRateOfChangePoints(comparisonPts, analysis.rate_window); + if (ratePts.length >= 2) renderer.drawLine(ratePts, hexToRgba(seriesColor, .52), renderT0, renderT1, rateAxis.min, rateAxis.max, { + lineOpacity: .46, + lineWidth: 1.35, + dashPattern: [ + 6, + 3, + 1.5, + 3 + ] + }); + } + if (analysis.show_anomalies === true) { + const cacheKey = this._getComparisonAnomalyCacheKey(comparisonWindowId, entityId); + const cached = this._backendComparisonAnomalyByKey.get(cacheKey); + const clusters = Array.isArray(cached?.clusters) ? cached.clusters : []; + if (clusters.length > 0) { + const regionOptions = { + strokeAlpha: .72, + lineWidth: 1.6, + haloWidth: 3.4, + haloColor: "rgba(255,255,255,0.72)", + haloAlpha: .64, + fillColor: hexToRgba(seriesColor, .06), + fillAlpha: 1, + pointPadding: 8, + minRadiusX: 8, + minRadiusY: 8 + }; + const filteredClusters = this._filterAnnotatedAnomalyClusters({ + entityId, + anomalyClusters: clusters + }, events); + if (filteredClusters.length > 0) renderer.drawAnomalyClusters(filteredClusters, hexToRgba(seriesColor, .74), renderT0, renderT1, axis.min, axis.max, regionOptions); + } + } + } + /** Backend anomaly cache keyed by entity ID. Already declared above. */ + /** + * Full chart draw implementation. + * Ported from _drawChart in card-history.js. + */ + async _drawChart(histResult, statsResult, events, t0, t1, options = {}) { + const chartEvents = Array.isArray(events) ? events : []; + hideTooltip(this); + this._syncTopSlotOffset(); + const canvas = this.querySelector("#chart"); + const zoomOutButton = this.querySelector("#chart-zoom-out"); + const wrap = this; + const scrollViewport = this.querySelector("#chart-scroll-viewport"); + const chartStage = this.querySelector("#chart-stage"); + this._chartScrollViewportEl = scrollViewport; + this._chartStageEl = chartStage; + const series = []; + const axes = []; + const axisMap = /* @__PURE__ */ new Map(); + const binaryBackgrounds = []; + const seriesSettings = this._seriesSettings; + const analysisMap = this._getSeriesAnalysisMap(); + const comparisonResults = Array.isArray(this._lastComparisonResults) ? this._lastComparisonResults : []; + const drawableComparisonResults = this._getDrawableComparisonResults(comparisonResults); + const selectedComparisonWindowId = this._config?.selected_comparison_window_id || null; + const hoveredComparisonWindowId = this._config?.hovered_comparison_window_id || null; + const comparisonPreviewActive = this._comparisonWindows.length > 0; + const delinkYAxis = this._config?.delink_y_axis === true; + const autoAdjustHoveredComparisonAxis = comparisonPreviewActive && !!hoveredComparisonWindowId && !this._zoomRange && !this._chartZoomDragging; + const hoveringDifferentComparison = !!hoveredComparisonWindowId && !!selectedComparisonWindowId && hoveredComparisonWindowId !== selectedComparisonWindowId; + const hasSelectedComparisonWindow = !!selectedComparisonWindowId; + seriesSettings.forEach((seriesSetting, i) => { + const entityId = seriesSetting.entity_id; + if (entityId.split(".")[0] === "binary_sensor") { + const stateList = this._buildEntityStateList(entityId, histResult, statsResult); + const spans = this._buildBinaryStateSpans(stateList, t0, t1); + if (spans.length) binaryBackgrounds.push({ + entityId, + label: entityName(this._hass, entityId) || entityId, + color: seriesSetting.color || COLORS[i % COLORS.length], + onLabel: this._binaryOnLabel(entityId), + offLabel: this._binaryOffLabel(entityId), + spans + }); + return; + } + const stateList = this._buildEntityStateList(entityId, histResult, statsResult); + const pts = []; + const unit = this._hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; + const axisKey = delinkYAxis ? `${unit || "__unitless__"}::${entityId}` : unit || "__unitless__"; + let axis = axisMap.get(axisKey); + if (!axis) { + axis = { + key: axisKey, + unit, + color: seriesSetting.color || COLORS[i % COLORS.length], + side: axisMap.size === 0 ? "left" : "right", + values: [] + }; + axisMap.set(axisKey, axis); + axes.push(axis); + } + for (const s of stateList) { + const v = parseFloat(s.s); + if (!Number.isNaN(v)) { + pts.push([Math.round(s.lu * 1e3), v]); + axis.values.push(v); + } + } + if (pts.length) series.push({ + entityId, + legendEntityId: entityId, + label: entityName(this._hass, entityId) || entityId, + unit, + pts, + color: seriesSetting.color || COLORS[i % COLORS.length], + axisKey + }); + }); + const _startIso = new Date(t0).toISOString(); + const _endIso = new Date(t1).toISOString(); + if (series.length && this._hass) { + if (t0 !== this._lastT0 || t1 !== this._lastT1) { + this._backendAnomalyByEntity.clear(); + this._backendComparisonAnomalyByKey.clear(); + } + await Promise.all(series.map(async (seriesItem) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + const interval = analysis.sample_interval || "raw"; + if (interval === "raw") return; + const hass = this._hass; + if (!hass) return; + try { + const targetIntervalMs = SAMPLE_INTERVAL_MS[interval] ?? 0; + const sampleAggregate = analysis.sample_aggregate || "mean"; + const sampledPts = await fetchDownsampledHistory(hass, seriesItem.entityId, _startIso, _endIso, interval, sampleAggregate); + if (Array.isArray(sampledPts) && sampledPts.length > 0) { + const statsPts = this._normalizeStatisticsHistory(seriesItem.entityId, statsResult).map((s) => [Math.round(s.lu * 1e3), parseFloat(s.s)]).filter(([, v]) => !Number.isNaN(v)); + let finalPts = sampledPts; + if (statsPts.length > 0) { + const firstSampledMs = sampledPts[0][0]; + const lastSampledMs = sampledPts[sampledPts.length - 1][0]; + const rawOutsidePts = statsPts.filter(([t]) => t < firstSampledMs || t > lastSampledMs); + if (rawOutsidePts.length > 0) { + finalPts = [...targetIntervalMs > 0 ? await downsampleInWorker(rawOutsidePts, targetIntervalMs, sampleAggregate) : rawOutsidePts, ...sampledPts]; + finalPts.sort((a, b) => a[0] - b[0]); + } + } + seriesItem.pts = finalPts; + const axisEntry = axes.find((ax) => ax.key === seriesItem.axisKey); + if (axisEntry) axisEntry.values = finalPts.map(([, v]) => v).filter(Number.isFinite); + } else if (Array.isArray(seriesItem.pts) && seriesItem.pts.length > 0 && targetIntervalMs > 0) { + const fallbackPts = await downsampleInWorker(seriesItem.pts, targetIntervalMs, sampleAggregate); + if (fallbackPts.length > 0) { + seriesItem.pts = fallbackPts; + const axisEntry = axes.find((ax) => ax.key === seriesItem.axisKey); + if (axisEntry) axisEntry.values = fallbackPts.map(([, v]) => v).filter(Number.isFinite); + } + } + } catch (err) { + logger$1.warn("[hass-datapoints history-card] downsampled history fetch failed", { + entityId: seriesItem.entityId, + err + }); + } + })); + } + for (const seriesItem of series) { + if (!seriesItem.pts.length) continue; + const lastPt = seriesItem.pts[seriesItem.pts.length - 1]; + const prev = this._previousSeriesEndpoints.get(seriesItem.entityId); + if (!prev) logger$1.log("[hass-datapoints history-card] series initial draw", { + entityId: seriesItem.entityId, + pointCount: seriesItem.pts.length, + lastPt + }); + else if (lastPt[0] !== prev.t || lastPt[1] !== prev.v) logger$1.log("[hass-datapoints history-card] series updated — live update detected", { + entityId: seriesItem.entityId, + pointCount: seriesItem.pts.length, + prev, + lastPt + }); + else logger$1.log("[hass-datapoints history-card] series unchanged — no new data", { + entityId: seriesItem.entityId, + pointCount: seriesItem.pts.length, + lastPt + }); + this._previousSeriesEndpoints.set(seriesItem.entityId, { + t: lastPt[0], + v: lastPt[1] + }); + } + if (!series.length && !binaryBackgrounds.length) { + this._setAdjustAxisButtonVisibility(false); + this._renderComparisonPreviewOverlay(); + const sameRangeAsLastDraw = Number.isFinite(this._lastT0) && Number.isFinite(this._lastT1) && this._lastT0 === t0 && this._lastT1 === t1 && Array.isArray(this._lastDrawArgs) && this._lastDrawArgs.length > 0; + this._setChartLoading(!!options.loading); + this._setChartMessage(options.loading ? "" : "No numeric data in the selected time range."); + if (sameRangeAsLastDraw) return; + this._lastHistResult = histResult; + this._lastStatsResult = statsResult; + this._lastEvents = chartEvents; + this._lastT0 = t0; + this._lastT1 = t1; + this._lastDrawArgs = [ + histResult, + statsResult, + chartEvents, + t0, + t1, + options + ]; + return; + } + this._lastHistResult = histResult; + this._lastStatsResult = statsResult; + this._lastEvents = chartEvents; + this._lastT0 = t0; + this._lastT1 = t1; + this._lastDrawArgs = [ + histResult, + statsResult, + chartEvents, + t0, + t1, + options + ]; + const visibleSeries = series.filter((entry) => !this._hiddenSeries.has(entry.legendEntityId || entry.entityId)); + const selectedComparisonResult = drawableComparisonResults.find((window) => window.id === selectedComparisonWindowId) || null; + const selectedComparisonSeriesMap = /* @__PURE__ */ new Map(); + if (selectedComparisonResult) for (let index = 0; index < seriesSettings.length; index += 1) { + const seriesSetting = seriesSettings[index]; + const entityId = seriesSetting.entity_id; + if (entityId.split(".")[0] === "binary_sensor") continue; + if (this._hiddenSeries.has(entityId)) continue; + const unit = this._hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; + const analysis = analysisMap.get(entityId) || normalizeHistorySeriesAnalysis(null); + const points = await this._resolveComparisonWindowPoints(entityId, selectedComparisonResult, analysis, t0, t1); + if (!points.length) continue; + selectedComparisonSeriesMap.set(entityId, { + entityId, + label: entityName(this._hass, entityId) || entityId, + unit, + color: seriesSetting.color || COLORS[index % COLORS.length], + pts: points + }); + } + const allComparisonWindowsData = {}; + for (const seriesItem of visibleSeries) { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + if (analysis.show_anomalies !== true || !analysis.anomaly_methods?.includes("comparison_window") || !analysis.anomaly_comparison_window_id) continue; + const windowId = analysis.anomaly_comparison_window_id; + if (!allComparisonWindowsData[windowId]) allComparisonWindowsData[windowId] = {}; + if (!allComparisonWindowsData[windowId][seriesItem.entityId]) { + const compResult = comparisonResults.find((win) => win.id === windowId); + if (compResult) { + const pts = await this._resolveComparisonWindowPoints(seriesItem.entityId, compResult, analysis, t0, t1); + if (pts.length) allComparisonWindowsData[windowId][seriesItem.entityId] = pts; + } + } + } + const analysisEntityIds = visibleSeries.filter((s) => { + const a = analysisMap.get(s.entityId) || {}; + return a.show_trend_lines || a.show_summary_stats || a.show_rate_of_change; + }).map((s) => s.entityId); + const onAnalysisProgress = analysisEntityIds.length ? (progress) => { + this.dispatchEvent(new CustomEvent("hass-datapoints-analysis-computing", { + bubbles: true, + composed: true, + detail: { + computing: true, + entityIds: analysisEntityIds, + progress + } + })); + } : null; + const analysisResult = await this._computeHistoryAnalysis(visibleSeries, selectedComparisonSeriesMap, analysisMap, hasSelectedComparisonWindow, allComparisonWindowsData, t0, t1, onAnalysisProgress); + if (analysisEntityIds.length) this.dispatchEvent(new CustomEvent("hass-datapoints-analysis-computing", { + bubbles: true, + composed: true, + detail: { + computing: false, + entityIds: analysisEntityIds, + progress: 100 + } + })); + const _anomalyEntityIds = visibleSeries.filter((s) => { + const a = analysisMap.get(s.entityId); + return a && a.show_anomalies === true && Array.isArray(a.anomaly_methods) && a.anomaly_methods.length > 0; + }).map((s) => s.entityId); + if (_anomalyEntityIds.length > 0) analysisResult.anomalySeries = _anomalyEntityIds.map((entityId) => { + const cached = this._backendAnomalyByEntity.get(entityId); + if (!cached) return null; + return { + entityId, + anomalyClusters: cached.clusters + }; + }).filter(Boolean); + if (options.drawRequestId && options.drawRequestId !== this._drawRequestId) return; + if (canvas) canvas.style.display = ""; + chartStage?.querySelectorAll(".split-series-row").forEach((el) => el.remove()); + chartStage?.querySelector("#chart-split-overlay")?.remove(); + const axisLeftEl = this.querySelector("#chart-axis-left"); + const axisRightEl = this.querySelector("#chart-axis-right"); + if (axisLeftEl) axisLeftEl.style.display = ""; + if (axisRightEl) axisRightEl.style.display = ""; + let minChartHeight; + if (series.length) minChartHeight = 280; + else if (binaryBackgrounds.length) minChartHeight = 100; + else minChartHeight = 280; + const availableHeight = this._getAvailableChartHeight(minChartHeight); + const viewportWidth = Math.max(scrollViewport?.clientWidth || wrap?.clientWidth || 360, 360); + const totalSpanMs = Math.max(1, t1 - t0); + const zoomSpanMs = this._zoomRange ? Math.max(1, this._zoomRange.end - this._zoomRange.start) : null; + const zoomMultiplier = clampChartValue(zoomSpanMs ? totalSpanMs / zoomSpanMs : 1, 1, HISTORY_CHART_MAX_ZOOM_MULTIPLIER); + const canvasWidth = Math.min(HISTORY_CHART_MAX_CANVAS_WIDTH_PX, zoomSpanMs ? Math.max(viewportWidth, Math.round(viewportWidth * zoomMultiplier)) : viewportWidth); + if (this._config?.split_view === true && visibleSeries.length >= 2) { + if (zoomOutButton) { + zoomOutButton.hidden = !this._zoomRange; + zoomOutButton.onclick = () => this._clearZoomRange(); + } + this._renderLegend(series, binaryBackgrounds); + await this._drawSplitChart({ + visibleSeries, + binaryBackgrounds, + events, + renderT0: t0, + renderT1: t1, + canvasWidth, + availableHeight, + chartStage, + canvas, + wrap, + options, + drawableComparisonResults, + selectedComparisonWindowId, + hoveredComparisonWindowId, + comparisonPreviewActive, + hoveringDifferentComparison, + analysisResult, + analysisMap, + hasSelectedComparisonWindow + }); + if (this._chartScrollViewportEl) { + this._chartScrollViewportEl.removeEventListener("scroll", this._onChartScroll); + this._chartScrollViewportEl.addEventListener("scroll", this._onChartScroll, { passive: true }); + this._syncChartViewportScroll(t0, t1, canvasWidth); + } + this._fireBackendAnomalyRequests(_anomalyEntityIds, analysisMap, _startIso, _endIso); + return; + } + if (chartStage) { + chartStage.style.width = `${canvasWidth}px`; + chartStage.style.height = `${availableHeight}px`; + } + if (scrollViewport) scrollViewport.style.overflowY = ""; + const { w, h } = setupCanvas(canvas, chartStage || wrap, availableHeight, canvasWidth); + const renderer = new ChartRenderer(canvas, w, h); + renderer.labelColor = resolveChartLabelColor(this); + renderer.clear(); + const renderT0 = t0; + const renderT1 = t1; + const trendPointsMap = new Map((analysisResult?.trendSeries || []).map((entry) => [entry.entityId, entry.pts])); + const ratePointsMap = new Map((analysisResult?.rateSeries || []).map((entry) => [entry.entityId, entry.pts])); + const deltaPointsMap = new Map((analysisResult?.deltaSeries || []).map((entry) => [entry.entityId, entry.pts])); + const summaryStatsMap = new Map((analysisResult?.summaryStats || []).map((entry) => [entry.entityId, entry])); + const anomalyClustersMap = new Map((analysisResult?.anomalySeries || []).map((entry) => [entry.entityId, entry.anomalyClusters])); + const hiddenSourceEntityIds = /* @__PURE__ */ new Set(); + const hiddenComparisonEntityIds = /* @__PURE__ */ new Set(); + visibleSeries.forEach((seriesItem) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + if (this._seriesShouldHideSource(analysis, hasSelectedComparisonWindow)) hiddenSourceEntityIds.add(seriesItem.entityId); + if (analysis.hide_source_series === true && analysis.show_delta_analysis === true && hasSelectedComparisonWindow) hiddenComparisonEntityIds.add(seriesItem.entityId); + }); + const anyTrendCrosshairs = visibleSeries.some((seriesItem) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + return analysis.show_trend_lines === true && analysis.show_trend_crosshairs === true; + }); + const anyRateCrosshairs = visibleSeries.some((seriesItem) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + return analysis.show_rate_of_change === true && analysis.show_rate_crosshairs === true; + }); + this._setChartLoading(!!options.loading); + this._setChartMessage(""); + if (zoomOutButton) { + zoomOutButton.hidden = !this._zoomRange; + zoomOutButton.onclick = () => this._clearZoomRange(); + } + const comparisonAxisValues = /* @__PURE__ */ new Map(); + if (this._adjustComparisonAxisScale || autoAdjustHoveredComparisonAxis) for (const win of drawableComparisonResults) { + if (autoAdjustHoveredComparisonAxis && hoveredComparisonWindowId && win.id !== hoveredComparisonWindowId) continue; + for (const seriesSetting of seriesSettings) { + const entityId = seriesSetting.entity_id; + if (entityId.split(".")[0] === "binary_sensor") continue; + if (this._hiddenSeries.has(entityId)) continue; + const unit = this._hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; + const axisKey = delinkYAxis ? `${unit || "__unitless__"}::${entityId}` : unit || "__unitless__"; + const analysis = analysisMap.get(entityId) || normalizeHistorySeriesAnalysis(null); + const points = await this._resolveComparisonWindowPoints(entityId, win, analysis, renderT0, renderT1); + for (const [, numericValue] of points) { + if (!comparisonAxisValues.has(axisKey)) comparisonAxisValues.set(axisKey, []); + comparisonAxisValues.get(axisKey).push(numericValue); + } + } + } + const deltaAxisMap = /* @__PURE__ */ new Map(); + const rateAxisMap = /* @__PURE__ */ new Map(); + visibleSeries.forEach((seriesItem) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + if (analysis.show_delta_analysis === true && hasSelectedComparisonWindow && analysis.show_delta_lines === true) { + const deltaPoints = deltaPointsMap.get(seriesItem.entityId) || []; + if (deltaPoints.length) { + const axisKey = `delta:${seriesItem.axisKey}`; + const unitLabel = seriesItem.unit ? `Δ ${seriesItem.unit}` : "Δ"; + let axis = deltaAxisMap.get(axisKey); + if (!axis) { + axis = { + key: axisKey, + unit: unitLabel, + color: seriesItem.color, + side: "right", + values: [] + }; + deltaAxisMap.set(axisKey, axis); + } + deltaPoints.forEach((point) => { + axis.values.push(point[1]); + }); + } + } + if (analysis.show_rate_of_change === true) { + const ratePoints = ratePointsMap.get(seriesItem.entityId) || []; + if (ratePoints.length) { + const axisKey = `rate:${seriesItem.axisKey}`; + const unitLabel = seriesItem.unit ? `${seriesItem.unit}/h` : "Rate/h"; + let axis = rateAxisMap.get(axisKey); + if (!axis) { + axis = { + key: axisKey, + unit: unitLabel, + color: seriesItem.color, + side: "right", + values: [] + }; + rateAxisMap.set(axisKey, axis); + } + ratePoints.forEach((point) => { + axis.values.push(point[1]); + }); + } + } + }); + const resolvedAxes = axes.filter((axis) => axis.values.length).map((axis) => { + const axisValues = series.filter((entry) => entry.axisKey === axis.key).flatMap((entry) => entry.pts.map((point) => point[1])); + if ((this._adjustComparisonAxisScale || autoAdjustHoveredComparisonAxis) && comparisonAxisValues.has(axis.key)) axisValues.push(...comparisonAxisValues.get(axis.key)); + const extent = this._getAxisValueExtent(axisValues); + if (!extent) return null; + const { min, max } = extent; + const pad = (max - min) * .1 || 1; + return { + key: axis.key, + unit: axis.unit, + color: axis.color, + side: axis.side === "right" ? "right" : "left", + min: min - pad, + max: max + pad + }; + }).filter((ax) => ax !== null); + const deltaResolvedAxes = Array.from(deltaAxisMap.values()).filter((axis) => axis.values.length).map((axis) => { + const extent = this._getAxisValueExtent(axis.values); + if (!extent) return null; + const { min, max } = extent; + const pad = (max - min) * .1 || 1; + return { + key: axis.key, + unit: axis.unit, + color: axis.color, + side: axis.side === "right" ? "right" : "left", + min: min - pad, + max: max + pad + }; + }).filter((ax) => ax !== null); + const rateResolvedAxes = Array.from(rateAxisMap.values()).filter((axis) => axis.values.length).map((axis) => { + const extent = this._getAxisValueExtent(axis.values); + if (!extent) return null; + const { min, max } = extent; + const pad = (max - min) * .1 || 1; + return { + key: axis.key, + unit: axis.unit, + color: axis.color, + side: axis.side === "right" ? "right" : "left", + min: min - pad, + max: max + pad + }; + }).filter((ax) => ax !== null); + const gridAxes = resolvedAxes.length || deltaResolvedAxes.length ? [ + ...resolvedAxes, + ...deltaResolvedAxes, + ...rateResolvedAxes + ] : [{ + key: "binary", + min: 0, + max: 1, + side: "left", + unit: "", + color: null + }]; + renderer.drawGrid(renderT0, renderT1, gridAxes, void 0, 5, { fixedAxisOverlay: true }); + this._renderComparisonPreviewOverlay(renderer); + const activeAxes = resolvedAxes.length ? renderer._activeAxes || [] : []; + const axisLookup = new Map(activeAxes.map((axis) => [axis.key, axis])); + series.forEach((s) => { + s.axis = axisLookup.get(s.axisKey) || activeAxes[0] || resolvedAxes[0]; + }); + renderChartAxisOverlays(this, renderer, activeAxes); + this.style.setProperty("--dp-chart-axis-bottom-height", `${Math.max(0, renderer.pad.bottom)}px`); + binaryBackgrounds.forEach((binaryBackground) => { + if (binaryBackground?.spans?.length && !this._hiddenSeries.has(binaryBackground.entityId)) renderer.drawStateBands(binaryBackground.spans, renderT0, renderT1, binaryBackground.color, .12); + }); + const shouldUseCorrelatedSpans = this._config?.show_correlated_anomalies === true || this._config?.anomaly_overlap_mode === "only"; + const shouldDrawCorrelatedSpans = this._config?.show_correlated_anomalies === true; + const correlatedSpans = shouldUseCorrelatedSpans ? this._buildCorrelatedAnomalySpans(visibleSeries, anomalyClustersMap, analysisMap) : []; + if (shouldDrawCorrelatedSpans) { + if (correlatedSpans.length) renderer.drawStateBands(correlatedSpans, renderT0, renderT1, "#ef4444", .1); + } + let comparisonOutOfBounds = false; + let mainSeriesHoverOpacity; + if (!comparisonPreviewActive) mainSeriesHoverOpacity = 1; + else if (hoveringDifferentComparison) mainSeriesHoverOpacity = .15; + else mainSeriesHoverOpacity = .25; + const anyHiddenSourceSeries = hiddenSourceEntityIds.size > 0; + const hoverSeries = visibleSeries.filter((seriesItem) => !hiddenSourceEntityIds.has(seriesItem.entityId)).map((seriesItem) => ({ + ...seriesItem, + hoverOpacity: mainSeriesHoverOpacity + })); + const summaryHoverSeries = visibleSeries.flatMap((seriesItem) => { + if ((analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null)).show_summary_stats !== true) return []; + const stats = summaryStatsMap.get(seriesItem.entityId) || null; + if (!stats) return []; + const seriesWithAxis = seriesItem; + return [ + { + entityId: `summary:min:${seriesItem.entityId}`, + relatedEntityId: seriesItem.entityId, + label: seriesItem.label, + baseLabel: seriesItem.label, + unit: seriesItem.unit || "", + value: stats.min, + color: hexToRgba(seriesItem.color, anyHiddenSourceSeries ? .94 : .78), + baseColor: seriesItem.color, + axis: seriesWithAxis.axis, + hoverOpacity: anyHiddenSourceSeries ? .94 : .72, + summaryType: "min", + summary: true + }, + { + entityId: `summary:mean:${seriesItem.entityId}`, + relatedEntityId: seriesItem.entityId, + label: seriesItem.label, + baseLabel: seriesItem.label, + unit: seriesItem.unit || "", + value: stats.mean, + color: hexToRgba(seriesItem.color, anyHiddenSourceSeries ? .94 : .78), + baseColor: seriesItem.color, + axis: seriesWithAxis.axis, + hoverOpacity: anyHiddenSourceSeries ? .94 : .72, + summaryType: "mean", + summary: true + }, + { + entityId: `summary:max:${seriesItem.entityId}`, + relatedEntityId: seriesItem.entityId, + label: seriesItem.label, + baseLabel: seriesItem.label, + unit: seriesItem.unit || "", + value: stats.max, + color: hexToRgba(seriesItem.color, anyHiddenSourceSeries ? .94 : .78), + baseColor: seriesItem.color, + axis: seriesWithAxis.axis, + hoverOpacity: anyHiddenSourceSeries ? .94 : .72, + summaryType: "max", + summary: true + } + ]; + }); + const thresholdHoverSeries = visibleSeries.flatMap((seriesItem) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + if (analysis.show_threshold_analysis !== true) return []; + const rawThreshold = analysis.threshold_value; + const thresholdValue = Number(rawThreshold); + if (!Number.isFinite(thresholdValue)) return []; + const seriesWithAxis = seriesItem; + return [{ + entityId: `threshold:${seriesItem.entityId}`, + relatedEntityId: seriesItem.entityId, + label: seriesItem.label, + baseLabel: seriesItem.label, + unit: seriesItem.unit || "", + value: thresholdValue, + baseColor: seriesItem.color, + color: hexToRgba(seriesItem.color, anyHiddenSourceSeries ? .82 : .46), + axis: seriesWithAxis.axis, + hoverOpacity: anyHiddenSourceSeries ? .84 : .48, + direction: analysis.threshold_direction === "below" ? "below" : "above", + threshold: true + }]; + }); + const trendSeries = visibleSeries.map((seriesItem) => ({ + ...seriesItem, + trendPts: trendPointsMap.get(seriesItem.entityId) || [] + })).filter((seriesItem) => Array.isArray(seriesItem.trendPts) && seriesItem.trendPts.length >= 2); + const rateSeries = visibleSeries.map((seriesItem) => { + const ratePoints = ratePointsMap.get(seriesItem.entityId) || []; + if (!Array.isArray(ratePoints) || ratePoints.length < 2) return null; + const axis = axisLookup.get(`rate:${seriesItem.axisKey}`); + if (!axis) return null; + return { + ...seriesItem, + ratePts: ratePoints, + rateAxis: axis + }; + }).filter((s) => s !== null); + const anomalySeries = visibleSeries.map((seriesItem) => { + const anomalyClusters = anomalyClustersMap.get(seriesItem.entityId) || []; + if (!Array.isArray(anomalyClusters) || anomalyClusters.length === 0) return null; + return { + ...seriesItem, + anomalyClusters + }; + }).filter((s) => s !== null); + const comparisonHoverSeries = []; + const comparisonTrendHoverSeries = []; + const comparisonRateHoverSeries = []; + const comparisonSummaryHoverSeries = []; + const comparisonThresholdHoverSeries = []; + const deltaHoverSeries = []; + for (const win of drawableComparisonResults) for (const seriesSetting of seriesSettings) { + const entityId = seriesSetting.entity_id; + if (entityId.split(".")[0] === "binary_sensor") continue; + if (this._hiddenSeries.has(entityId)) continue; + const analysis = analysisMap.get(entityId) || normalizeHistorySeriesAnalysis(null); + const winPts = await this._resolveComparisonWindowPoints(entityId, win, analysis, renderT0, renderT1); + if (!winPts.length) continue; + const unit = this._hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; + const axisKey = delinkYAxis ? `${unit || "__unitless__"}::${entityId}` : unit || "__unitless__"; + const axis = axisLookup.get(axisKey); + if (!axis) continue; + if (!this._adjustComparisonAxisScale && !autoAdjustHoveredComparisonAxis) { + if (winPts.some((point) => point[1] < axis.min || point[1] > axis.max)) comparisonOutOfBounds = true; + } + const baseColor = seriesSetting.color || COLORS[seriesSettings.indexOf(seriesSetting) % COLORS.length]; + const isHoveredComparison = !!hoveredComparisonWindowId && win.id === hoveredComparisonWindowId; + const isSelectedComparison = !!selectedComparisonWindowId && win.id === selectedComparisonWindowId; + const comparisonLineStyle = this._getComparisonWindowLineStyle(isHoveredComparison, isSelectedComparison, hoveringDifferentComparison); + comparisonHoverSeries.push({ + entityId: `${win.id}:${entityId}`, + relatedEntityId: entityId, + label: seriesSetting.label || entityName(this._hass, entityId) || entityId, + windowLabel: win.label || "Date window", + unit, + pts: winPts, + color: baseColor, + axis, + hoverOpacity: comparisonLineStyle.hoverOpacity + }); + if (analysis.show_trend_lines === true && winPts.length >= 2) { + const trendPts = (this._analysisCache?.result?.comparisonWindowResults?.[win.id]?.[entityId])?.trendPts ?? this._buildTrendPoints(winPts, analysis.trend_method, analysis.trend_window); + if (trendPts.length >= 2) { + const trendOptions = this._getTrendRenderOptions(analysis.trend_method, false); + comparisonTrendHoverSeries.push({ + entityId: `trend:${win.id}:${entityId}`, + relatedEntityId: entityId, + comparisonParentId: `${win.id}:${entityId}`, + label: seriesSetting.label || entityName(this._hass, entityId) || entityId, + baseLabel: seriesSetting.label || entityName(this._hass, entityId) || entityId, + windowLabel: win.label || "Date window", + unit, + pts: trendPts, + color: hexToRgba(baseColor, Math.max(.3, trendOptions.colorAlpha - .18)), + axis, + rawVisible: true, + hoverOpacity: Math.max(.3, trendOptions.lineOpacity - .18), + trend: true + }); + } + } + if (analysis.show_rate_of_change === true && winPts.length >= 2) { + const ratePts = (this._analysisCache?.result?.comparisonWindowResults?.[win.id]?.[entityId])?.ratePts ?? this._buildRateOfChangePoints(winPts, analysis.rate_window); + const rateAxis = axisLookup.get(`rate:${axisKey}`) || axis; + if (ratePts.length >= 2) comparisonRateHoverSeries.push({ + entityId: `rate:${win.id}:${entityId}`, + relatedEntityId: entityId, + comparisonParentId: `${win.id}:${entityId}`, + label: seriesSetting.label || entityName(this._hass, entityId) || entityId, + baseLabel: seriesSetting.label || entityName(this._hass, entityId) || entityId, + windowLabel: win.label || "Date window", + unit: unit ? `${unit}/h` : "/h", + pts: ratePts, + color: hexToRgba(baseColor, .52), + axis: rateAxis, + rawVisible: true, + hoverOpacity: .46, + rate: true + }); + } + if (analysis.show_summary_stats === true) { + const summaryStats = (this._analysisCache?.result?.comparisonWindowResults?.[win.id]?.[entityId])?.summaryStats ?? this._buildSummaryStats(winPts); + [ + { + type: "min", + value: summaryStats.min + }, + { + type: "mean", + value: summaryStats.mean + }, + { + type: "max", + value: summaryStats.max + } + ].forEach((entry) => { + if (!Number.isFinite(entry.value)) return; + comparisonSummaryHoverSeries.push({ + entityId: `summary:${entry.type}:${win.id}:${entityId}`, + relatedEntityId: entityId, + comparisonParentId: `${win.id}:${entityId}`, + label: seriesSetting.label || entityName(this._hass, entityId) || entityId, + baseLabel: seriesSetting.label || entityName(this._hass, entityId) || entityId, + windowLabel: win.label || "Date window", + unit, + value: entry.value, + color: hexToRgba(baseColor, entry.type === "mean" ? .44 : .24), + axis, + rawVisible: true, + hoverOpacity: entry.type === "mean" ? .52 : .3, + summaryType: entry.type, + summary: true + }); + }); + } + if (analysis.show_threshold_analysis === true) { + const thresholdValue = Number(analysis.threshold_value); + if (Number.isFinite(thresholdValue)) comparisonThresholdHoverSeries.push({ + entityId: `threshold:${win.id}:${entityId}`, + relatedEntityId: entityId, + comparisonParentId: `${win.id}:${entityId}`, + label: seriesSetting.label || entityName(this._hass, entityId) || entityId, + baseLabel: seriesSetting.label || entityName(this._hass, entityId) || entityId, + windowLabel: win.label || "Date window", + unit, + value: thresholdValue, + color: hexToRgba(baseColor, .28), + axis, + rawVisible: true, + hoverOpacity: .34, + threshold: true + }); + } + if (!hiddenComparisonEntityIds.has(entityId)) renderer.drawLine(winPts, baseColor, renderT0, renderT1, axis.min, axis.max, { + lineOpacity: comparisonLineStyle.lineOpacity, + lineWidth: comparisonLineStyle.lineWidth, + dashed: comparisonLineStyle.dashed, + dashPattern: comparisonLineStyle.dashPattern, + stepped: analysis.stepped_series === true + }); + const rateAxis = axisLookup.get(`rate:${axisKey}`) || null; + this._drawComparisonAnalysisOverlays({ + renderer, + entityId, + seriesColor: baseColor, + comparisonPts: winPts, + analysis, + renderT0, + renderT1, + axis, + rateAxis, + events, + comparisonWindowId: win.id + }); + } + this._setAdjustAxisButtonVisibility(comparisonPreviewActive && comparisonOutOfBounds && !this._adjustComparisonAxisScale && !autoAdjustHoveredComparisonAxis); + for (const s of visibleSeries) { + if (hiddenSourceEntityIds.has(s.entityId)) continue; + const sWithAxis = s; + this._drawSeriesLine(renderer, s.pts, s.color, renderT0, renderT1, sWithAxis.axis.min, sWithAxis.axis.max, { + lineOpacity: mainSeriesHoverOpacity, + lineWidth: this._config?.comparison_hover_active === true ? 1.25 : void 0, + stepped: (analysisMap.get(s.entityId) || normalizeHistorySeriesAnalysis(null)).stepped_series === true + }); + } + const trendHoverSeries = trendSeries.map((seriesItem) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + const hiddenSource = hiddenSourceEntityIds.has(seriesItem.entityId); + const trendRenderOptions = this._getTrendRenderOptions(analysis.trend_method, hiddenSource); + const seriesWithAxis = seriesItem; + return { + entityId: `trend:${seriesItem.entityId}`, + relatedEntityId: seriesItem.entityId, + label: seriesItem.label, + baseLabel: seriesItem.label, + unit: seriesItem.unit || "", + pts: seriesItem.trendPts, + color: hexToRgba(seriesItem.color, trendRenderOptions.colorAlpha), + axis: seriesWithAxis.axis, + rawVisible: !hiddenSource, + showCrosshair: analysis.show_trend_crosshairs === true, + hoverOpacity: comparisonPreviewActive ? Math.max(.25, Math.min(.9, mainSeriesHoverOpacity + .12)) : trendRenderOptions.lineOpacity, + trend: true + }; + }); + const rateHoverSeries = rateSeries.map((seriesItem) => ({ + entityId: `rate:${seriesItem.entityId}`, + relatedEntityId: seriesItem.entityId, + label: seriesItem.label, + baseLabel: seriesItem.label, + unit: seriesItem.unit ? `${seriesItem.unit}/h` : "/h", + pts: seriesItem.ratePts, + color: hexToRgba(seriesItem.color, anyHiddenSourceSeries ? .9 : .72), + axis: seriesItem.rateAxis, + rawVisible: !hiddenSourceEntityIds.has(seriesItem.entityId), + showCrosshair: (analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null)).show_rate_crosshairs === true, + hoverOpacity: anyHiddenSourceSeries ? .88 : .66, + rate: true + })); + for (const trend of trendSeries) { + const analysis = analysisMap.get(trend.entityId) || normalizeHistorySeriesAnalysis(null); + const trendRenderOptions = this._getTrendRenderOptions(analysis.trend_method, hiddenSourceEntityIds.has(trend.entityId)); + const trendWithAxis = trend; + renderer.drawLine(trend.trendPts, hexToRgba(trend.color, trendRenderOptions.colorAlpha), renderT0, renderT1, trendWithAxis.axis.min, trendWithAxis.axis.max, { + lineOpacity: comparisonPreviewActive ? Math.max(.25, Math.min(.9, mainSeriesHoverOpacity + .12)) : trendRenderOptions.lineOpacity, + lineWidth: trendRenderOptions.lineWidth, + dashed: trendRenderOptions.dashed, + dotted: trendRenderOptions.dotted + }); + } + for (const rateSeriesItem of rateSeries) renderer.drawLine(rateSeriesItem.ratePts, hexToRgba(rateSeriesItem.color, anyHiddenSourceSeries ? .96 : .82), renderT0, renderT1, rateSeriesItem.rateAxis.min, rateSeriesItem.rateAxis.max, { + lineOpacity: anyHiddenSourceSeries ? .88 : .66, + lineWidth: 1.55, + dashed: false, + dotted: false, + dashPattern: [ + 7, + 3, + 1.5, + 3 + ] + }); + for (const seriesItem of visibleSeries) { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + if (!(analysis.show_delta_analysis === true && hasSelectedComparisonWindow === true)) continue; + const deltaAxis = axisLookup.get(`delta:${seriesItem.axisKey}`); + if (!deltaAxis) continue; + const deltaPoints = deltaPointsMap.get(seriesItem.entityId) || []; + if (!deltaPoints.length) continue; + if (analysis.show_delta_tooltip === true) deltaHoverSeries.push({ + entityId: `delta:${seriesItem.entityId}`, + relatedEntityId: seriesItem.entityId, + label: seriesItem.label, + baseLabel: seriesItem.label, + unit: seriesItem.unit || "", + pts: deltaPoints, + color: hexToRgba(seriesItem.color, .92), + axis: deltaAxis, + rawVisible: !hiddenSourceEntityIds.has(seriesItem.entityId), + hoverOpacity: .82, + delta: true + }); + if (analysis.show_delta_lines === true) renderer.drawLine(deltaPoints, hexToRgba(seriesItem.color, .92), renderT0, renderT1, deltaAxis.min, deltaAxis.max, { + lineOpacity: .82, + lineWidth: 1.9, + dashed: true + }); + } + visibleSeries.forEach((seriesItem) => { + const shadingAnalysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + if (shadingAnalysis.show_summary_stats !== true || shadingAnalysis.show_summary_stats_shading !== true) return; + const stats = summaryStatsMap.get(seriesItem.entityId); + const axis = seriesItem.axis; + if (!stats || !axis) return; + if (!Number.isFinite(stats.min) || !Number.isFinite(stats.max) || !Number.isFinite(stats.mean)) return; + const fillAlpha = anyHiddenSourceSeries ? .1 : .06; + renderer.drawGradientBand(stats.min, stats.mean, seriesItem.color, renderT0, renderT1, axis.min, axis.max, { fillAlpha }); + renderer.drawGradientBand(stats.max, stats.mean, seriesItem.color, renderT0, renderT1, axis.min, axis.max, { fillAlpha }); + }); + summaryHoverSeries.forEach((summarySeries) => { + const axis = summarySeries.axis; + if (!axis) return; + renderer.drawLine([[renderT0, summarySeries.value], [renderT1, summarySeries.value]], summarySeries.color, renderT0, renderT1, axis.min, axis.max, { + lineOpacity: summarySeries.hoverOpacity, + lineWidth: 1.8, + dashed: false, + dotted: false + }); + }); + thresholdHoverSeries.forEach((thresholdSeries) => { + const axis = thresholdSeries.axis; + if (!axis) return; + if ((analysisMap.get(thresholdSeries.relatedEntityId) || normalizeHistorySeriesAnalysis(null)).show_threshold_shading === true) { + const relatedSeries = visibleSeries.find((seriesItem) => seriesItem.entityId === thresholdSeries.relatedEntityId); + if (relatedSeries?.pts?.length) renderer.drawThresholdArea(relatedSeries.pts, thresholdSeries.value, thresholdSeries.baseColor || relatedSeries.color, renderT0, renderT1, axis.min, axis.max, { + mode: thresholdSeries.direction === "below" ? "below" : "above", + fillAlpha: anyHiddenSourceSeries ? .24 : .14 + }); + } + renderer.drawLine([[renderT0, thresholdSeries.value], [renderT1, thresholdSeries.value]], thresholdSeries.color, renderT0, renderT1, axis.min, axis.max, { + lineOpacity: thresholdSeries.hoverOpacity, + lineWidth: 1.15 + }); + }); + if (anomalySeries.length) { + const anomalyRegions = []; + anomalySeries.forEach((seriesItem) => { + const axis = seriesItem.axis; + if (!axis) return; + const visibleAnomalyClusters = this._filterAnnotatedAnomalyClusters(seriesItem, events); + if (visibleAnomalyClusters.length === 0) return; + const overlapMode = (analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null)).anomaly_overlap_mode; + const { baseClusters, regionClusters } = this._resolveAnomalyClusterDisplay(visibleAnomalyClusters, overlapMode, correlatedSpans); + const baseColor = hexToRgba(seriesItem.color, anyHiddenSourceSeries ? .96 : .86); + const regionOptions = { + strokeAlpha: anyHiddenSourceSeries ? .98 : .9, + lineWidth: anyHiddenSourceSeries ? 2.5 : 2.1, + haloWidth: anyHiddenSourceSeries ? 5.5 : 4.8, + haloColor: "rgba(255,255,255,0.88)", + haloAlpha: anyHiddenSourceSeries ? .92 : .82, + fillColor: hexToRgba(seriesItem.color, anyHiddenSourceSeries ? .14 : .1), + fillAlpha: 1, + pointPadding: anyHiddenSourceSeries ? 12 : 10, + minRadiusX: 10, + minRadiusY: 10 + }; + if (baseClusters.length > 0) renderer.drawAnomalyClusters(baseClusters, baseColor, renderT0, renderT1, axis.min, axis.max, regionOptions); + renderer.getAnomalyClusterRegions(regionClusters, renderT0, renderT1, axis.min, axis.max, regionOptions).forEach((region) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + anomalyRegions.push({ + ...region, + relatedEntityId: seriesItem.entityId, + label: String(seriesItem.label), + unit: String(seriesItem.unit || ""), + color: seriesItem.color || null, + sensitivity: analysis.anomaly_sensitivity + }); + }); + }); + this._lastAnomalyRegions = anomalyRegions; + } else this._lastAnomalyRegions = []; + const effectiveComparisonHoverSeries = comparisonHoverSeries.filter((entry) => !hiddenComparisonEntityIds.has(entry.relatedEntityId || entry.entityId)); + renderer.drawAnnotations(chartEvents, renderT0, renderT1, { + showLines: this._config.show_event_lines !== false, + showMarkers: this._config.show_event_lines !== false + }); + const eventHits = this._drawRecordedEventPoints(renderer, visibleSeries, chartEvents, renderT0, renderT1, { showIcons: this._config.show_event_markers !== false }); + this._renderLegend(series, binaryBackgrounds); + const eventValueMap = new Map(eventHits.map((hit) => [hit.event.id, hit])); + const enrichedEvents = chartEvents.map((event) => { + const hit = eventValueMap.get(event.id || ""); + return hit ? { + ...event, + chart_value: hit.value, + chart_unit: hit.unit + } : event; + }); + const addButton = this.querySelector("#chart-add-annotation"); + if (addButton) { + addButton.dataset.allowAddAnnotation = this._canAddAnnotation ? "true" : "false"; + if (addButton.dataset.allowAddAnnotation === "false") addButton.hidden = true; + } + if (visibleSeries.length) { + this._ensureContextAnnotationDialog(); + attachLineChartHover(this, canvas, renderer, hoverSeries, enrichedEvents, renderT0, renderT1, 0, 0, activeAxes, { + onContextMenu: (hover) => this._handleChartContextMenu(hover), + onAddAnnotation: this._canAddAnnotation ? (hover) => this._handleChartAddAnnotation(hover) : void 0, + binaryStates: binaryBackgrounds.filter((entry) => !this._hiddenSeries.has(entry.entityId)), + comparisonSeries: effectiveComparisonHoverSeries, + trendSeries: [...trendHoverSeries, ...comparisonTrendHoverSeries], + rateSeries: [...rateHoverSeries, ...comparisonRateHoverSeries], + deltaSeries: deltaHoverSeries, + summarySeries: [...summaryHoverSeries, ...comparisonSummaryHoverSeries], + thresholdSeries: [...thresholdHoverSeries, ...comparisonThresholdHoverSeries], + anomalyRegions: Array.isArray(this._lastAnomalyRegions) ? this._lastAnomalyRegions : [], + hoverSurfaceEl: this.querySelector("#chart-icon-overlay"), + showTooltip: this._config.show_tooltips !== false, + emphasizeHoverGuides: this._config.emphasize_hover_guides === true, + hoverSnapMode: this._config.hover_snap_mode === "snap_to_data_points" ? "snap_to_data_points" : "follow_series", + showTrendCrosshairs: anyTrendCrosshairs, + showRateCrosshairs: anyRateCrosshairs, + hideRawData: hiddenSourceEntityIds.size === visibleSeries.length && visibleSeries.length > 0, + showDeltaTooltip: deltaHoverSeries.length > 0, + onAnomalyClick: (regions) => this._handleAnomalyAddAnnotation(regions) + }); + attachLineChartRangeZoom(this, canvas, renderer, renderT0, renderT1, { + onPreview: (range) => this._dispatchZoomPreview(range), + onZoom: ({ startTime, endTime }) => this._applyZoomRange(startTime, endTime), + onReset: () => this._clearZoomRange() + }); + } else { + if (this._chartHoverCleanup) { + this._chartHoverCleanup(); + this._chartHoverCleanup = null; + } + if (this._chartZoomCleanup) { + this._chartZoomCleanup(); + this._chartZoomCleanup = null; + } + } + if (this._chartScrollViewportEl) { + this._chartScrollViewportEl.removeEventListener("scroll", this._onChartScroll); + this._chartScrollViewportEl.addEventListener("scroll", this._onChartScroll, { passive: true }); + this._syncChartViewportScroll(t0, t1, w); + } + this._fireBackendAnomalyRequests(_anomalyEntityIds, analysisMap, _startIso, _endIso); + this._fireComparisonBackendAnomalyRequests(drawableComparisonResults, analysisMap, renderT0, renderT1); + } + /** + * Build a lightweight string key that captures all inputs that affect the + * analysis result. Used to skip the worker when data and settings haven't + * changed (e.g. on zoom/pan). + * Ported from _buildAnalysisCacheKey in card-history.js. + */ + _buildAnalysisCacheKey(visibleSeries, selectedComparisonSeriesMap, analysisMap, allComparisonWindowsData, t0, t1) { + const ANALYSIS_FIELDS = [ + "show_trend_lines", + "trend_method", + "trend_window", + "show_rate_of_change", + "rate_window", + "show_delta_analysis", + "show_summary_stats", + "show_anomalies", + "anomaly_methods", + "anomaly_sensitivity", + "anomaly_overlap_mode", + "anomaly_rate_window", + "anomaly_zscore_window", + "anomaly_persistence_window", + "anomaly_comparison_window_id", + "anomaly_use_sampled_data" + ]; + return `${t0}:${t1}|${visibleSeries.map((s) => { + const a = analysisMap.get(s.entityId) || normalizeHistorySeriesAnalysis(null); + const first = s.pts[0]?.[0] ?? 0; + const last = s.pts[s.pts.length - 1]?.[0] ?? 0; + const aKey = ANALYSIS_FIELDS.map((f) => JSON.stringify(a[f])).join(","); + return `${s.entityId}:${s.pts.length}:${first}:${last}:${aKey}`; + }).join("|")}|${Array.from(selectedComparisonSeriesMap.values()).map((s) => { + const first = s.pts[0]?.[0] ?? 0; + const last = s.pts[s.pts.length - 1]?.[0] ?? 0; + return `${s.entityId}:${s.pts.length}:${first}:${last}`; + }).sort().join("|")}|${Object.entries(allComparisonWindowsData).flatMap(([windowId, entities]) => Object.entries(entities).map(([entityId, pts]) => { + const first = pts[0]?.[0] ?? 0; + const last = pts[pts.length - 1]?.[0] ?? 0; + return `${windowId}:${entityId}:${pts.length}:${first}:${last}`; + })).sort().join("|")}`; + } + /** + * Build the serialisable payload sent to the history-analysis worker. + * Ported from _buildHistoryAnalysisPayload in card-history.js. + */ + _buildHistoryAnalysisPayload(visibleSeries, selectedComparisonSeriesMap, analysisMap, hasSelectedComparisonWindow, allComparisonWindowsData = {}) { + const defaultAnalysis = normalizeHistorySeriesAnalysis(null); + const series = visibleSeries.map((seriesItem) => ({ + entityId: seriesItem.entityId, + pts: seriesItem.pts, + analysis: analysisMap.get(seriesItem.entityId) || defaultAnalysis + })); + const comparisonSeries = Array.from(selectedComparisonSeriesMap.values()).map((seriesItem) => ({ + entityId: seriesItem.entityId, + pts: seriesItem.pts + })); + const seriesAnalysisConfigs = {}; + for (const seriesItem of visibleSeries) seriesAnalysisConfigs[seriesItem.entityId] = analysisMap.get(seriesItem.entityId) || defaultAnalysis; + return { + series, + comparisonSeries, + hasSelectedComparisonWindow: hasSelectedComparisonWindow === true, + allComparisonWindowsData, + seriesAnalysisConfigs + }; + } + /** + * Run or cache the history analysis computation. + * Terminates any in-flight worker before starting a new one. + * Falls back to a synchronous main-thread path when the worker fails. + * Ported from _computeHistoryAnalysis in card-history.js. + */ + async _computeHistoryAnalysis(visibleSeries, selectedComparisonSeriesMap, analysisMap, hasSelectedComparisonWindow, allComparisonWindowsData = {}, t0 = 0, t1 = 0, onProgress = null) { + terminateHistoryAnalysisWorker(); + const cacheKey = this._buildAnalysisCacheKey(visibleSeries, selectedComparisonSeriesMap, analysisMap, allComparisonWindowsData, t0, t1); + if (this._analysisCache?.key === cacheKey) return this._analysisCache.result; + onProgress?.(0); + const payload = this._buildHistoryAnalysisPayload(visibleSeries, selectedComparisonSeriesMap, analysisMap, hasSelectedComparisonWindow, allComparisonWindowsData); + try { + const result = await computeHistoryAnalysisInWorker(payload); + this._analysisCache = { + key: cacheKey, + result + }; + return result; + } catch (error) { + const errorMessage = error?.message ?? ""; + if (errorMessage.startsWith("Aborted")) return { + trendSeries: [], + rateSeries: [], + deltaSeries: [], + summaryStats: [], + anomalySeries: [] + }; + logger$1.warn("[hass-datapoints history-card] analysis worker fallback", { message: errorMessage || String(error) }); + try { + return { + trendSeries: visibleSeries.map((seriesItem) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + if (analysis.show_trend_lines !== true) return null; + return { + entityId: seriesItem.entityId, + pts: this._buildTrendPoints(seriesItem.pts, analysis.trend_method, analysis.trend_window) + }; + }).filter(Boolean).filter((s) => Array.isArray(s.pts) && s.pts.length >= 2), + rateSeries: visibleSeries.map((seriesItem) => { + const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + if (analysis.show_rate_of_change !== true) return null; + return { + entityId: seriesItem.entityId, + pts: this._buildRateOfChangePoints(seriesItem.pts, analysis.rate_window) + }; + }).filter(Boolean).filter((s) => Array.isArray(s.pts) && s.pts.length >= 2), + deltaSeries: visibleSeries.map((seriesItem) => { + if (!((analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null)).show_delta_analysis === true && hasSelectedComparisonWindow === true)) return null; + const comparisonSeries = selectedComparisonSeriesMap.get(seriesItem.entityId); + return { + entityId: seriesItem.entityId, + pts: comparisonSeries ? this._buildDeltaPoints(seriesItem.pts, comparisonSeries.pts) : [] + }; + }).filter(Boolean).filter((s) => Array.isArray(s.pts) && s.pts.length >= 2), + summaryStats: visibleSeries.map((seriesItem) => { + if ((analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null)).show_summary_stats !== true) return null; + return { + entityId: seriesItem.entityId, + ...this._buildSummaryStats(seriesItem.pts) + }; + }).filter((entry) => entry && Number.isFinite(entry.min) && Number.isFinite(entry.max) && Number.isFinite(entry.mean)), + anomalySeries: [] + }; + } catch (fallbackError) { + logger$1.error("[hass-datapoints history-card] analysis fallback failed", fallbackError); + return { + trendSeries: [], + rateSeries: [], + deltaSeries: [], + summaryStats: [], + anomalySeries: [] + }; + } + } + } + /** Build trend points from raw series data. */ + _buildTrendPoints(_pts, _method, _window) { + if (!Array.isArray(_pts) || _pts.length < 2) return []; + if ((_method || "rolling_average") === "linear_trend") return buildLinearTrend(_pts); + return buildRollingAverageTrend(_pts, getTrendWindowMs(_window || "24h")); + } + /** Build rate-of-change points from raw series data. */ + _buildRateOfChangePoints(_pts, _window) { + if (!Array.isArray(_pts) || _pts.length < 2) return []; + return buildRateOfChangePoints(_pts, _window || "1h"); + } + /** Build delta points (main vs comparison series). */ + _buildDeltaPoints(_pts, _comparisonPts) { + return buildDeltaPoints(_pts, _comparisonPts); + } + /** Build summary statistics from raw series data. */ + _buildSummaryStats(_pts) { + return buildSummaryStats(_pts) ?? { + min: 0, + max: 0, + mean: 0 + }; + } + /** + * Build a Map of entityId → normalised analysis settings from config. + * Ported from _getSeriesAnalysisMap in card-history.js. + */ + _getSeriesAnalysisMap() { + const seriesSettings = Array.isArray(this._config?.series_settings) ? this._config.series_settings : []; + return new Map(seriesSettings.filter((entry) => entry?.entity_id != null).map((entry) => [entry.entity_id, normalizeHistorySeriesAnalysis(entry?.analysis)])); + } + /** + * Get the normalised analysis settings for a single entity. + * Ported from _getSeriesAnalysis in card-history.js. + */ + _getSeriesAnalysis(entityId, analysisMap = null) { + return normalizeHistorySeriesAnalysis((analysisMap || this._getSeriesAnalysisMap()).get(entityId)); + } + /** + * Returns true if any analysis feature is active for the given series. + * Ported from _seriesHasActiveAnalysis in card-history.js. + */ + _seriesHasActiveAnalysis(analysis, hasSelectedComparisonWindow = false) { + return !!(analysis.show_trend_lines || analysis.show_summary_stats || analysis.show_rate_of_change || analysis.show_threshold_analysis || analysis.show_anomalies || analysis.show_delta_analysis && hasSelectedComparisonWindow); + } + /** + * Returns true if the source series should be hidden (replaced by its + * analysis overlay series). + * Ported from _seriesShouldHideSource in card-history.js. + */ + _seriesShouldHideSource(analysis, hasSelectedComparisonWindow = false) { + return analysis.hide_source_series === true && this._seriesHasActiveAnalysis(analysis, hasSelectedComparisonWindow); + } + /** + * Return ChartRenderer render options for the active trend method. + * Ported from _getTrendRenderOptions in card-history.js. + */ + _getTrendRenderOptions(method = "rolling_average", hideRawData = false) { + if (method === "linear_trend") return { + colorAlpha: hideRawData ? .94 : .88, + lineOpacity: hideRawData ? .86 : .74, + lineWidth: 2.1, + dashed: true, + dotted: false + }; + return { + colorAlpha: hideRawData ? .9 : .82, + lineOpacity: hideRawData ? .84 : .62, + lineWidth: 2.2, + dashed: false, + dotted: true + }; + } + async _drawSplitChart({ visibleSeries, binaryBackgrounds, events, renderT0, renderT1, canvasWidth, availableHeight, chartStage, canvas, wrap, options, drawableComparisonResults, selectedComparisonWindowId, hoveredComparisonWindowId, comparisonPreviewActive, hoveringDifferentComparison, analysisResult, analysisMap, hasSelectedComparisonWindow }) { + if (canvas) canvas.style.display = "none"; + const N = visibleSeries.length; + const rowHeight = Math.max(140, Math.floor(availableHeight / N)); + const totalHeight = rowHeight * N; + if (chartStage) { + chartStage.style.width = `${canvasWidth}px`; + chartStage.style.height = `${totalHeight}px`; + } + const splitScrollViewport = this.querySelector("#chart-scroll-viewport"); + if (splitScrollViewport) splitScrollViewport.style.overflowY = totalHeight > availableHeight ? "auto" : "hidden"; + this._setChartLoading(!!options.loading); + this._setChartMessage(""); + const iconOverlay = this.querySelector("#chart-icon-overlay"); + if (iconOverlay) iconOverlay.innerHTML = ""; + const trendPointsMap = new Map((analysisResult?.trendSeries || []).map((entry) => [entry.entityId, entry.pts])); + const ratePointsMap = new Map((analysisResult?.rateSeries || []).map((entry) => [entry.entityId, entry.pts])); + const deltaPointsMap = new Map((analysisResult?.deltaSeries || []).map((entry) => [entry.entityId, entry.pts])); + const summaryStatsMap = new Map((analysisResult?.summaryStats || []).map((entry) => [entry.entityId, entry])); + const anomalyClustersMap = new Map((analysisResult?.anomalySeries || []).map((entry) => [entry.entityId, entry.anomalyClusters])); + const effectiveAnalysisMap = analysisMap || /* @__PURE__ */ new Map(); + const shouldUseCorrelatedAnomalySpans = this._config?.show_correlated_anomalies === true || this._config?.anomaly_overlap_mode === "only"; + const shouldDrawCorrelatedAnomalySpans = this._config?.show_correlated_anomalies === true; + const correlatedAnomalySpans = shouldUseCorrelatedAnomalySpans ? this._buildCorrelatedAnomalySpans(visibleSeries, anomalyClustersMap, effectiveAnalysisMap) : []; + const tracks = []; + for (let i = 0; i < N; i += 1) { + const isLastRow = i === N - 1; + const seriesItem = visibleSeries[i]; + const rowOffset = i * rowHeight; + const rowDiv = document.createElement("div"); + rowDiv.className = "split-series-row"; + rowDiv.style.cssText = `position:absolute;left:0;top:${rowOffset}px;width:${canvasWidth}px;height:${rowHeight}px;pointer-events:none;overflow:hidden;`; + const rowCanvas = document.createElement("canvas"); + rowCanvas.className = "split-series-canvas"; + rowDiv.appendChild(rowCanvas); + chartStage?.appendChild(rowDiv); + const { w, h } = setupCanvas(rowCanvas, chartStage || wrap, rowHeight, canvasWidth); + const renderer = new ChartRenderer(rowCanvas, w, h); + renderer.labelColor = resolveChartLabelColor(this); + renderer.basePad = { + top: 24, + right: 12, + bottom: isLastRow ? 48 : 10, + left: 12 + }; + renderer.clear(); + const rowAnalysis = effectiveAnalysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); + const rowTrendPts = rowAnalysis.show_trend_lines === true ? trendPointsMap.get(seriesItem.entityId) || [] : []; + const rowRatePts = rowAnalysis.show_rate_of_change === true ? ratePointsMap.get(seriesItem.entityId) || [] : []; + const rowDeltaPts = rowAnalysis.show_delta_analysis === true && hasSelectedComparisonWindow ? deltaPointsMap.get(seriesItem.entityId) || [] : []; + const rowSummaryStats = rowAnalysis.show_summary_stats === true ? summaryStatsMap.get(seriesItem.entityId) || null : null; + const rowAnomalyClusters = rowAnalysis.show_anomalies === true ? anomalyClustersMap.get(seriesItem.entityId) || [] : []; + const rowHideSource = this._seriesShouldHideSource(rowAnalysis, hasSelectedComparisonWindow); + const axisValues = seriesItem.pts.map(([, v]) => v); + const extent = this._getAxisValueExtent(axisValues); + let axisMin = 0; + let axisMax = 1; + if (extent) { + const pad = (extent.max - extent.min) * .1 || 1; + axisMin = extent.min - pad; + axisMax = extent.max + pad; + } + const primaryAxisKey = seriesItem.axisKey || seriesItem.unit || "__unitless__"; + const axis = { + key: primaryAxisKey, + unit: String(seriesItem.unit || ""), + color: seriesItem.color || null, + side: "left", + min: axisMin, + max: axisMax, + values: axisValues + }; + const rowAxes = [axis]; + let rowRateAxisKey = null; + if (rowRatePts.length >= 2) { + const rateVals = rowRatePts.map(([, v]) => v); + const rateExt = this._getAxisValueExtent(rateVals); + if (rateExt) { + const pad = (rateExt.max - rateExt.min) * .1 || 1; + rowRateAxisKey = `rate:${primaryAxisKey}`; + rowAxes.push({ + key: rowRateAxisKey, + unit: seriesItem.unit ? `${String(seriesItem.unit)}/h` : "Rate/h", + color: seriesItem.color || null, + side: "right", + min: rateExt.min - pad, + max: rateExt.max + pad, + values: rateVals + }); + } + } + let rowDeltaAxisKey = null; + if (rowDeltaPts.length >= 2) { + const deltaVals = rowDeltaPts.map(([, v]) => v); + const deltaExt = this._getAxisValueExtent(deltaVals); + if (deltaExt) { + const pad = (deltaExt.max - deltaExt.min) * .1 || 1; + rowDeltaAxisKey = `delta:${primaryAxisKey}`; + rowAxes.push({ + key: rowDeltaAxisKey, + unit: seriesItem.unit ? `Δ ${String(seriesItem.unit)}` : "Δ", + color: seriesItem.color || null, + side: "right", + min: deltaExt.min - pad, + max: deltaExt.max + pad, + values: deltaVals + }); + } + } + renderer.drawGrid(renderT0, renderT1, rowAxes, void 0, 4, { + fixedAxisOverlay: true, + hideTimeLabels: !isLastRow + }); + const activeAxes = renderer._activeAxes; + const resolvedAxis = activeAxes?.[0] || axis; + const resolvedRateAxis = rowRateAxisKey ? activeAxes?.find((a) => a.key === rowRateAxisKey) || null : null; + const resolvedDeltaAxis = rowDeltaAxisKey ? activeAxes?.find((a) => a.key === rowDeltaAxisKey) || null : null; + seriesItem.axis = resolvedAxis; + let mainSeriesOpacity; + if (!comparisonPreviewActive) mainSeriesOpacity = 1; + else if (hoveringDifferentComparison) mainSeriesOpacity = .15; + else mainSeriesOpacity = .25; + if (!rowHideSource) this._drawSeriesLine(renderer, seriesItem.pts, seriesItem.color, renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, { + lineWidth: comparisonPreviewActive ? 1.25 : 1.75, + lineOpacity: mainSeriesOpacity, + stepped: rowAnalysis.stepped_series === true + }); + for (const win of drawableComparisonResults || []) { + const winPts = await this._resolveComparisonWindowPoints(seriesItem.entityId, win, rowAnalysis, renderT0, renderT1); + if (!winPts.length) continue; + const isHovered = !!hoveredComparisonWindowId && win.id === hoveredComparisonWindowId; + const isSelected = !!selectedComparisonWindowId && win.id === selectedComparisonWindowId; + const comparisonLineStyle = this._getComparisonWindowLineStyle(isHovered, isSelected, hoveringDifferentComparison); + renderer.drawLine(winPts, seriesItem.color, renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, { + lineOpacity: comparisonLineStyle.lineOpacity, + lineWidth: comparisonLineStyle.lineWidth, + dashed: comparisonLineStyle.dashed, + dashPattern: comparisonLineStyle.dashPattern, + stepped: rowAnalysis.stepped_series === true + }); + this._drawComparisonAnalysisOverlays({ + renderer, + entityId: seriesItem.entityId, + seriesColor: seriesItem.color, + comparisonPts: winPts, + analysis: rowAnalysis, + renderT0, + renderT1, + axis: resolvedAxis, + rateAxis: resolvedRateAxis, + events, + comparisonWindowId: String(win.id || "") + }); + } + binaryBackgrounds.forEach((bg) => { + const bgItem = bg; + if (!this._hiddenSeries.has(bgItem.entityId) && bgItem.spans?.length) renderer.drawStateBands(bgItem.spans, renderT0, renderT1, bgItem.color, .1); + }); + if (shouldDrawCorrelatedAnomalySpans && correlatedAnomalySpans.length) renderer.drawStateBands(correlatedAnomalySpans, renderT0, renderT1, "#ef4444", .1); + renderer.drawAnnotations(events, renderT0, renderT1, { + showLines: this._config.show_event_lines !== false, + showMarkers: this._config.show_event_lines !== false + }); + this._drawRecordedEventPoints(renderer, [seriesItem], events, renderT0, renderT1, { + showIcons: this._config.show_event_markers !== false, + yOffset: rowOffset, + skipOverlayClear: true + }); + if (rowAnalysis.show_threshold_analysis === true) { + const thresholdValue = Number(rowAnalysis.threshold_value); + if (Number.isFinite(thresholdValue)) { + if (rowAnalysis.show_threshold_shading === true && seriesItem.pts.length) renderer.drawThresholdArea(seriesItem.pts, thresholdValue, seriesItem.color, renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, { + mode: rowAnalysis.threshold_direction === "below" ? "below" : "above", + fillAlpha: rowHideSource ? .24 : .14 + }); + renderer.drawLine([[renderT0, thresholdValue], [renderT1, thresholdValue]], hexToRgba(seriesItem.color, rowHideSource ? .82 : .46), renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, { + lineOpacity: rowHideSource ? .84 : .48, + lineWidth: 1.15 + }); + } + } + if (rowSummaryStats) { + if (rowAnalysis.show_summary_stats_shading === true) { + const fillAlpha = rowHideSource ? .1 : .06; + renderer.drawGradientBand(rowSummaryStats.min, rowSummaryStats.mean, seriesItem.color, renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, { fillAlpha }); + renderer.drawGradientBand(rowSummaryStats.max, rowSummaryStats.mean, seriesItem.color, renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, { fillAlpha }); + } + const summaryEntries = [ + { + type: "min", + value: rowSummaryStats.min, + alpha: rowHideSource ? .78 : .42, + width: 1.1, + dotted: true + }, + { + type: "mean", + value: rowSummaryStats.mean, + alpha: rowHideSource ? .94 : .78, + width: 1.8, + dotted: false + }, + { + type: "max", + value: rowSummaryStats.max, + alpha: rowHideSource ? .78 : .42, + width: 1.1, + dotted: true + } + ]; + for (const entry of summaryEntries) { + if (!Number.isFinite(entry.value)) continue; + renderer.drawLine([[renderT0, entry.value], [renderT1, entry.value]], hexToRgba(seriesItem.color, entry.alpha), renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, { + lineOpacity: rowHideSource ? .82 : .34, + lineWidth: entry.width, + dotted: entry.dotted + }); + } + } + if (rowTrendPts.length >= 2) { + const trendOpts = this._getTrendRenderOptions(rowAnalysis.trend_method, rowHideSource); + renderer.drawLine(rowTrendPts, hexToRgba(seriesItem.color, trendOpts.colorAlpha), renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, { + lineOpacity: trendOpts.lineOpacity, + lineWidth: trendOpts.lineWidth, + dashed: trendOpts.dashed, + dotted: trendOpts.dotted + }); + } + if (rowRatePts.length >= 2 && resolvedRateAxis) renderer.drawLine(rowRatePts, hexToRgba(seriesItem.color, rowHideSource ? .96 : .82), renderT0, renderT1, resolvedRateAxis.min, resolvedRateAxis.max, { + lineOpacity: rowHideSource ? .88 : .66, + lineWidth: 1.55, + dashPattern: [ + 7, + 3, + 1.5, + 3 + ] + }); + if (rowDeltaPts.length >= 2 && resolvedDeltaAxis && rowAnalysis.show_delta_lines === true) renderer.drawLine(rowDeltaPts, hexToRgba(seriesItem.color, .92), renderT0, renderT1, resolvedDeltaAxis.min, resolvedDeltaAxis.max, { + lineOpacity: .82, + lineWidth: 1.9, + dashed: true + }); + let rowAnomalyRegions = []; + if (rowAnomalyClusters.length) { + const filteredClusters = this._filterAnnotatedAnomalyClusters({ + entityId: seriesItem.entityId, + anomalyClusters: rowAnomalyClusters + }, events); + if (filteredClusters.length > 0) { + const { baseClusters, regionClusters } = this._resolveAnomalyClusterDisplay(filteredClusters, rowAnalysis.anomaly_overlap_mode, correlatedAnomalySpans); + const baseColor = hexToRgba(seriesItem.color, rowHideSource ? .96 : .86); + const regionOpts = { + strokeAlpha: rowHideSource ? .98 : .9, + lineWidth: rowHideSource ? 2.5 : 2.1, + haloWidth: rowHideSource ? 5.5 : 4.8, + haloColor: "rgba(255,255,255,0.88)", + haloAlpha: rowHideSource ? .92 : .82, + fillColor: hexToRgba(seriesItem.color, rowHideSource ? .14 : .1), + fillAlpha: 1, + pointPadding: rowHideSource ? 12 : 10, + minRadiusX: 10, + minRadiusY: 10 + }; + if (baseClusters.length > 0) renderer.drawAnomalyClusters(baseClusters, baseColor, renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, regionOpts); + rowAnomalyRegions = renderer.getAnomalyClusterRegions(regionClusters, renderT0, renderT1, resolvedAxis.min, resolvedAxis.max, regionOpts).map((region) => ({ + ...region, + relatedEntityId: String(seriesItem.entityId), + label: String(seriesItem.label), + unit: String(seriesItem.unit || ""), + color: seriesItem.color || null, + sensitivity: String(rowAnalysis.anomaly_sensitivity || "") + })); + } + } + tracks.push({ + canvas: rowCanvas, + renderer, + series: seriesItem, + axis: resolvedAxis, + rowOffset, + analysis: rowAnalysis, + summaryStats: rowSummaryStats, + trendPts: rowTrendPts, + ratePts: rowRatePts, + rateAxis: resolvedRateAxis, + deltaPts: rowDeltaPts, + deltaAxis: resolvedDeltaAxis, + anomalyRegions: rowAnomalyRegions + }); + } + this._renderSplitAxisOverlays(tracks); + this._renderComparisonPreviewOverlay(tracks[0] ? tracks[0].renderer : null); + const comparisonHoverSeries = []; + for (const track of tracks) { + const trackSeries = track.series; + const trackAnalysis = track.analysis || normalizeHistorySeriesAnalysis(null); + for (const win of drawableComparisonResults || []) { + const winPts = await this._resolveComparisonWindowPoints(trackSeries.entityId, win, trackAnalysis, renderT0, renderT1); + if (!winPts.length) continue; + const isHovered = !!hoveredComparisonWindowId && win.id === hoveredComparisonWindowId; + const isSelected = !!selectedComparisonWindowId && win.id === selectedComparisonWindowId; + const comparisonLineStyle = this._getComparisonWindowLineStyle(isHovered, isSelected, hoveringDifferentComparison); + const winId = String(win.id || ""); + const splitPrecomputed = this._analysisCache?.result?.comparisonWindowResults?.[winId]?.[trackSeries.entityId]; + comparisonHoverSeries.push({ + entityId: `${winId}:${trackSeries.entityId}`, + relatedEntityId: trackSeries.entityId, + comparisonParentId: `${winId}:${trackSeries.entityId}`, + label: trackSeries.label, + windowLabel: win.label || "Date window", + unit: trackSeries.unit, + pts: winPts, + trendPts: trackAnalysis.show_trend_lines === true && winPts.length >= 2 ? splitPrecomputed?.trendPts ?? this._buildTrendPoints(winPts, trackAnalysis.trend_method, trackAnalysis.trend_window) : [], + ratePts: trackAnalysis.show_rate_of_change === true && winPts.length >= 2 ? splitPrecomputed?.ratePts ?? this._buildRateOfChangePoints(winPts, trackAnalysis.rate_window) : [], + summaryStats: trackAnalysis.show_summary_stats === true ? splitPrecomputed?.summaryStats ?? this._buildSummaryStats(winPts) : null, + thresholdValue: trackAnalysis.show_threshold_analysis === true ? Number(trackAnalysis.threshold_value) : null, + color: trackSeries.color, + hoverOpacity: comparisonLineStyle.hoverOpacity, + track + }); + } + } + this._attachSplitHover(tracks, comparisonHoverSeries, events, renderT0, renderT1, chartStage, options, effectiveAnalysisMap, hasSelectedComparisonWindow); + this._fireComparisonBackendAnomalyRequests(drawableComparisonResults, effectiveAnalysisMap, renderT0, renderT1); + } + _attachSplitHover(tracks, comparisonHoverSeries, events, t0, t1, chartStage, options, analysisMap, hasSelectedComparisonWindow) { + if (this._chartHoverCleanup) { + this._chartHoverCleanup(); + this._chartHoverCleanup = null; + } + if (!tracks.length || !chartStage) return; + const primaryRenderer = tracks[0].renderer; + const lastTrack = tracks[tracks.length - 1]; + const eventThresholdMs = primaryRenderer.cw ? 14 * ((t1 - t0) / primaryRenderer.cw) : 0; + const splitSelTop = tracks[0].rowOffset + primaryRenderer.pad.top; + const splitSelHeight = lastTrack.rowOffset + lastTrack.renderer.pad.top + lastTrack.renderer.ch - splitSelTop; + const overlayEl = document.createElement("div"); + overlayEl.id = "chart-split-overlay"; + overlayEl.style.cssText = "position:absolute;inset:0;pointer-events:auto;z-index:2;cursor:crosshair;"; + chartStage.appendChild(overlayEl); + const overlayRelX = (clientX) => { + return clampChartValue(clientX - overlayEl.getBoundingClientRect().left, primaryRenderer.pad.left, primaryRenderer.pad.left + primaryRenderer.cw); + }; + const stageXToTime = (stageX) => { + return t0 + (primaryRenderer.cw ? (stageX - primaryRenderer.pad.left) / primaryRenderer.cw : 0) * (t1 - t0); + }; + const inPlotBoundsX = (clientX) => { + const localX = clientX - overlayEl.getBoundingClientRect().left; + return localX >= primaryRenderer.pad.left && localX <= primaryRenderer.pad.left + primaryRenderer.cw; + }; + const buildSplitHover = (clientX) => { + const baseRect = tracks[0].canvas.getBoundingClientRect(); + if (!baseRect.width || !primaryRenderer.cw) return null; + const rawTimeMs = t0 + (clampChartValue(clientX - baseRect.left, primaryRenderer.pad.left, primaryRenderer.pad.left + primaryRenderer.cw) - primaryRenderer.pad.left) / primaryRenderer.cw * (t1 - t0); + const hoverSnapMode = this._config.hover_snap_mode === "snap_to_data_points" ? "snap_to_data_points" : "follow_series"; + const timeMs = resolveLineChartHoverTime(tracks.map((track) => ({ pts: track.series?.pts || [] })), rawTimeMs, hoverSnapMode); + const x = primaryRenderer.xOf(timeMs, t0, t1); + const values = tracks.map((trackItem) => { + const { renderer: trackRenderer, series, axis, rowOffset } = trackItem; + const value = trackRenderer._interpolateValue(series.pts, timeMs); + if (value == null) return { + entityId: String(series.entityId || ""), + label: String(series.label || ""), + value: null, + unit: String(series.unit || ""), + color: String(series.color || ""), + opacity: 1, + hasValue: false, + axisSide: "left", + axisSlot: 0 + }; + return { + entityId: String(series.entityId || ""), + label: String(series.label || ""), + value, + unit: String(series.unit || ""), + color: String(series.color || ""), + opacity: 1, + hasValue: true, + x, + y: rowOffset + trackRenderer.yOf(value, axis.min, axis.max), + axisSide: "left", + axisSlot: 0 + }; + }); + const hoveredEvents = []; + for (const event of events || []) { + const eventTime = new Date(event.timestamp).getTime(); + if (eventTime < t0 || eventTime > t1) continue; + const distance = Math.abs(eventTime - timeMs); + if (distance <= eventThresholdMs) hoveredEvents.push({ + ...event, + _hoverDistanceMs: distance + }); + } + hoveredEvents.sort((a, b) => (a._hoverDistanceMs || 0) - (b._hoverDistanceMs || 0)); + const comparisonValues = (comparisonHoverSeries || []).map((chs) => { + const { pts, entityId, relatedEntityId, comparisonParentId, label, windowLabel, unit, color, hoverOpacity, track: cTrack } = chs; + const value = cTrack.renderer._interpolateValue(pts, timeMs); + if (value == null) return { + entityId: String(entityId || ""), + label: String(label || ""), + value: null, + unit: String(unit || ""), + color, + opacity: Number(hoverOpacity) || 1, + hasValue: false, + axisSide: "left", + axisSlot: 0 + }; + return { + entityId: String(entityId || ""), + relatedEntityId: String(relatedEntityId || ""), + comparisonParentId: String(comparisonParentId || ""), + label: String(label || ""), + windowLabel: String(windowLabel || ""), + value, + unit: String(unit || ""), + color, + opacity: Number(hoverOpacity) || 1, + hasValue: true, + x, + y: cTrack.rowOffset + cTrack.renderer.yOf(value, cTrack.axis.min, cTrack.axis.max), + axisSide: "left", + axisSlot: 0 + }; + }); + const trendValues = []; + const rateValues = []; + const deltaValues = []; + const summaryValues = []; + const thresholdValues = []; + const anomalyRegions = []; + let showTrendCrosshairs = false; + let showRateCrosshairs = false; + for (const track of tracks) { + const { renderer: trackRenderer, series: trackSeries, axis: trackAxis, rowOffset: trackRowOffset, analysis: trackAnalysis, summaryStats: trackSummaryStats, trendPts: trackTrendPts, ratePts: trackRatePts, rateAxis: trackRateAxis, deltaPts: trackDeltaPts, deltaAxis: trackDeltaAxis, anomalyRegions: trackAnomalyRegions } = track; + const effectiveAnalysis = trackAnalysis || (analysisMap || /* @__PURE__ */ new Map()).get(trackSeries.entityId) || normalizeHistorySeriesAnalysis(null); + const trackHideSource = this._seriesShouldHideSource(effectiveAnalysis, hasSelectedComparisonWindow); + if (effectiveAnalysis.show_trend_lines === true && Array.isArray(trackTrendPts) && trackTrendPts.length >= 2) { + if (effectiveAnalysis.show_trend_crosshairs === true) showTrendCrosshairs = true; + const trendOpts = this._getTrendRenderOptions(effectiveAnalysis.trend_method, trackHideSource); + const trendVal = trackRenderer._interpolateValue(trackTrendPts, timeMs); + trendValues.push({ + entityId: `trend:${trackSeries.entityId}`, + relatedEntityId: String(trackSeries.entityId || ""), + label: String(trackSeries.label || ""), + baseLabel: String(trackSeries.label || ""), + unit: String(trackSeries.unit || ""), + color: hexToRgba(trackSeries.color, trendOpts.colorAlpha), + opacity: trendOpts.lineOpacity, + hasValue: trendVal != null, + value: trendVal ?? null, + ...trendVal != null ? { + x, + y: trackRowOffset + trackRenderer.yOf(trendVal, trackAxis.min, trackAxis.max) + } : {}, + axisSide: "left", + axisSlot: 0, + trend: true, + rawVisible: !trackHideSource, + showCrosshair: effectiveAnalysis.show_trend_crosshairs === true + }); + } + if (effectiveAnalysis.show_rate_of_change === true && Array.isArray(trackRatePts) && trackRatePts.length >= 2 && trackRateAxis) { + if (effectiveAnalysis.show_rate_crosshairs === true) showRateCrosshairs = true; + const rateVal = trackRenderer._interpolateValue(trackRatePts, timeMs); + rateValues.push({ + entityId: `rate:${trackSeries.entityId}`, + relatedEntityId: String(trackSeries.entityId || ""), + label: String(trackSeries.label || ""), + baseLabel: String(trackSeries.label || ""), + unit: trackSeries.unit ? `${String(trackSeries.unit)}/h` : "/h", + color: hexToRgba(trackSeries.color, trackHideSource ? .96 : .82), + opacity: trackHideSource ? .88 : .66, + hasValue: rateVal != null, + value: rateVal ?? null, + ...rateVal != null ? { + x, + y: trackRowOffset + trackRenderer.yOf(rateVal, trackRateAxis.min, trackRateAxis.max) + } : {}, + axisSide: "right", + axisSlot: 0, + rate: true, + rawVisible: !trackHideSource, + showCrosshair: effectiveAnalysis.show_rate_crosshairs === true + }); + } + if (effectiveAnalysis.show_delta_analysis === true && effectiveAnalysis.show_delta_tooltip === true && Array.isArray(trackDeltaPts) && trackDeltaPts.length >= 2 && trackDeltaAxis) { + const deltaVal = trackRenderer._interpolateValue(trackDeltaPts, timeMs); + deltaValues.push({ + entityId: `delta:${trackSeries.entityId}`, + relatedEntityId: String(trackSeries.entityId || ""), + label: String(trackSeries.label || ""), + baseLabel: String(trackSeries.label || ""), + unit: String(trackSeries.unit || ""), + color: hexToRgba(trackSeries.color, .92), + opacity: .82, + hasValue: deltaVal != null, + value: deltaVal ?? null, + ...deltaVal != null ? { + x, + y: trackRowOffset + trackRenderer.yOf(deltaVal, trackDeltaAxis.min, trackDeltaAxis.max) + } : {}, + axisSide: "right", + axisSlot: 0, + delta: true, + rawVisible: !trackHideSource + }); + } + if (effectiveAnalysis.show_summary_stats === true && trackSummaryStats) { + const summaryEntries = [ + { + type: "min", + value: trackSummaryStats.min, + alphaV: trackHideSource ? .94 : .78, + opac: trackHideSource ? .94 : .72 + }, + { + type: "mean", + value: trackSummaryStats.mean, + alphaV: trackHideSource ? .94 : .78, + opac: trackHideSource ? .94 : .72 + }, + { + type: "max", + value: trackSummaryStats.max, + alphaV: trackHideSource ? .94 : .78, + opac: trackHideSource ? .94 : .72 + } + ]; + for (const entry of summaryEntries) { + if (!Number.isFinite(entry.value)) continue; + summaryValues.push({ + entityId: `summary:${entry.type}:${trackSeries.entityId}`, + relatedEntityId: String(trackSeries.entityId || ""), + label: String(trackSeries.label || ""), + baseLabel: String(trackSeries.label || ""), + unit: String(trackSeries.unit || ""), + color: hexToRgba(trackSeries.color, entry.alphaV), + opacity: entry.opac, + hasValue: true, + value: entry.value, + axisSide: "left", + axisSlot: 0, + summaryType: entry.type, + summary: true, + rawVisible: !trackHideSource + }); + } + } + if (effectiveAnalysis.show_threshold_analysis === true) { + const thresholdValue = Number(effectiveAnalysis.threshold_value); + if (Number.isFinite(thresholdValue)) thresholdValues.push({ + entityId: `threshold:${trackSeries.entityId}`, + relatedEntityId: String(trackSeries.entityId || ""), + label: String(trackSeries.label || ""), + baseLabel: String(trackSeries.label || ""), + unit: String(trackSeries.unit || ""), + color: hexToRgba(trackSeries.color, trackHideSource ? .82 : .46), + opacity: trackHideSource ? .84 : .48, + hasValue: true, + value: thresholdValue, + axisSide: "left", + axisSlot: 0, + threshold: true, + rawVisible: !trackHideSource + }); + } + if (Array.isArray(trackAnomalyRegions)) for (const region of trackAnomalyRegions) { + const clusterPoints = region?.cluster ? region.cluster?.points : void 0; + const regionStartMs = clusterPoints?.[0] ? clusterPoints[0]?.timeMs ?? region.startTime : region.startTime; + const regionEndMs = clusterPoints?.length ? clusterPoints[clusterPoints.length - 1]?.timeMs ?? region.endTime : region.endTime; + if (Number.isFinite(regionStartMs) && Number.isFinite(regionEndMs) && timeMs >= regionStartMs && timeMs <= regionEndMs) anomalyRegions.push(region); + } + } + for (const comparisonSeries of comparisonHoverSeries) { + const seriesEntry = comparisonSeries; + const track = seriesEntry.track; + const trackRenderer = track.renderer; + const trackAxis = track.axis; + const trackRateAxis = track.rateAxis || null; + const trackRowOffset = track.rowOffset; + const trendPts = Array.isArray(seriesEntry.trendPts) ? seriesEntry.trendPts : []; + if (trendPts.length >= 2) { + const trendVal = trackRenderer._interpolateValue(trendPts, timeMs); + trendValues.push({ + entityId: `trend:${seriesEntry.entityId}`, + relatedEntityId: String(seriesEntry.relatedEntityId || ""), + comparisonParentId: String(seriesEntry.comparisonParentId || ""), + label: String(seriesEntry.label || ""), + baseLabel: String(seriesEntry.label || ""), + windowLabel: String(seriesEntry.windowLabel || "Date window"), + unit: String(seriesEntry.unit || ""), + color: hexToRgba(seriesEntry.color, .34), + opacity: .34, + hasValue: trendVal != null, + value: trendVal ?? null, + ...trendVal != null ? { + x, + y: trackRowOffset + trackRenderer.yOf(trendVal, trackAxis.min, trackAxis.max) + } : {}, + axisSide: "left", + axisSlot: 0, + trend: true, + rawVisible: true, + comparisonDerived: true + }); + } + const ratePts = Array.isArray(seriesEntry.ratePts) ? seriesEntry.ratePts : []; + if (ratePts.length >= 2 && trackRateAxis) { + const rateVal = trackRenderer._interpolateValue(ratePts, timeMs); + rateValues.push({ + entityId: `rate:${seriesEntry.entityId}`, + relatedEntityId: String(seriesEntry.relatedEntityId || ""), + comparisonParentId: String(seriesEntry.comparisonParentId || ""), + label: String(seriesEntry.label || ""), + baseLabel: String(seriesEntry.label || ""), + windowLabel: String(seriesEntry.windowLabel || "Date window"), + unit: seriesEntry.unit ? `${String(seriesEntry.unit)}/h` : "/h", + color: hexToRgba(seriesEntry.color, .46), + opacity: .46, + hasValue: rateVal != null, + value: rateVal ?? null, + ...rateVal != null ? { + x, + y: trackRowOffset + trackRenderer.yOf(rateVal, trackRateAxis.min, trackRateAxis.max) + } : {}, + axisSide: "right", + axisSlot: 0, + rate: true, + rawVisible: true, + comparisonDerived: true, + showCrosshair: analysisMap.get(String(seriesEntry.relatedEntityId || ""))?.show_rate_crosshairs === true + }); + } + const summaryStats = seriesEntry.summaryStats; + if (summaryStats) [ + { + type: "min", + value: summaryStats.min + }, + { + type: "mean", + value: summaryStats.mean + }, + { + type: "max", + value: summaryStats.max + } + ].forEach((entry) => { + if (!Number.isFinite(entry.value)) return; + summaryValues.push({ + entityId: `summary:${entry.type}:${seriesEntry.entityId}`, + relatedEntityId: String(seriesEntry.relatedEntityId || ""), + comparisonParentId: String(seriesEntry.comparisonParentId || ""), + label: String(seriesEntry.label || ""), + baseLabel: String(seriesEntry.label || ""), + windowLabel: String(seriesEntry.windowLabel || "Date window"), + unit: String(seriesEntry.unit || ""), + color: hexToRgba(seriesEntry.color, entry.type === "mean" ? .44 : .24), + opacity: entry.type === "mean" ? .52 : .3, + hasValue: true, + value: entry.value, + axisSide: "left", + axisSlot: 0, + summaryType: entry.type, + summary: true, + rawVisible: true, + comparisonDerived: true + }); + }); + const thresholdValue = Number(seriesEntry.thresholdValue); + if (Number.isFinite(thresholdValue)) thresholdValues.push({ + entityId: `threshold:${seriesEntry.entityId}`, + relatedEntityId: String(seriesEntry.relatedEntityId || ""), + comparisonParentId: String(seriesEntry.comparisonParentId || ""), + label: String(seriesEntry.label || ""), + baseLabel: String(seriesEntry.label || ""), + windowLabel: String(seriesEntry.windowLabel || "Date window"), + unit: String(seriesEntry.unit || ""), + color: hexToRgba(seriesEntry.color, .28), + opacity: .34, + hasValue: true, + value: thresholdValue, + axisSide: "left", + axisSlot: 0, + threshold: true, + rawVisible: true, + comparisonDerived: true + }); + } + const hideRawData = tracks.every((track) => { + const eff = track.analysis || (analysisMap || /* @__PURE__ */ new Map()).get(track.series.entityId) || normalizeHistorySeriesAnalysis(null); + return this._seriesShouldHideSource(eff, hasSelectedComparisonWindow); + }); + const firstWithValue = values.find((value) => value.hasValue === true); + return { + x, + y: firstWithValue?.y ?? splitSelTop + 12, + timeMs, + rangeStartMs: timeMs, + rangeEndMs: timeMs, + values: values.filter((value) => value.hasValue === true), + trendValues, + rateValues, + deltaValues, + summaryValues, + thresholdValues, + comparisonValues: comparisonValues.filter((value) => value.hasValue === true), + binaryValues: [], + primary: firstWithValue ?? null, + event: hoveredEvents.length > 0 ? (({ _hoverDistanceMs: _, ...event }) => event)(hoveredEvents[0]) : null, + events: hoveredEvents.map(({ _hoverDistanceMs: _d, ...event }) => event), + anomalyRegions, + emphasizeGuides: options.emphasizeHoverGuides === true, + showTrendCrosshairs, + showRateCrosshairs, + hideRawData, + splitVertical: { + top: splitSelTop, + height: splitSelHeight + } + }; + }; + const showFromPointer = (clientX, clientY) => { + if (this._chartZoomDragging) return; + const hover = buildSplitHover(clientX); + if (!hover) { + this._chartLastHover = null; + hideLineChartHover(this); + overlayEl.style.cursor = "default"; + return; + } + this._chartLastHover = hover; + showLineChartCrosshair(this, primaryRenderer, hover); + if (this._config.show_tooltips !== false) showLineChartTooltip(this, hover, clientX, clientY); + else hideTooltip(this); + dispatchLineChartHover(this, hover); + overlayEl.style.cursor = "crosshair"; + }; + const hideHover = () => { + this._chartLastHover = null; + hideLineChartHover(this); + }; + const onMouseMove = (ev) => showFromPointer(ev.clientX, ev.clientY); + const onMouseLeave = (ev) => { + const nextTarget = ev.relatedTarget; + const addButton = this.querySelector("#chart-add-annotation"); + if (nextTarget && addButton instanceof HTMLElement && addButton.contains(nextTarget)) return; + hideHover(); + }; + const onTouchMove = (ev) => { + if (ev.touches.length === 1) showFromPointer(ev.touches[0].clientX, ev.touches[0].clientY); + }; + const onTouchEnd = () => hideHover(); + const onOverlayClick = (ev) => { + if (this._chartZoomDragging) return; + const regions = this._chartLastHover?.anomalyRegions; + if (!regions?.length) return; + ev.preventDefault(); + ev.stopPropagation(); + this._handleAnomalyAddAnnotation(regions); + }; + const addAnnotationBtn = this.querySelector("#chart-add-annotation"); + const onAddBtnClick = (ev) => { + if (!this._canAddAnnotation || !this._chartLastHover) return; + ev.preventDefault(); + ev.stopPropagation(); + this._handleChartAddAnnotation(this._chartLastHover); + }; + const onAddBtnLeave = (ev) => { + const nextTarget = ev.relatedTarget; + if (nextTarget instanceof Node && overlayEl.contains(nextTarget)) return; + hideHover(); + }; + overlayEl.addEventListener("mousemove", onMouseMove); + overlayEl.addEventListener("mouseleave", onMouseLeave); + overlayEl.addEventListener("touchmove", onTouchMove, { passive: true }); + overlayEl.addEventListener("touchend", onTouchEnd); + overlayEl.addEventListener("click", onOverlayClick); + addAnnotationBtn?.addEventListener("click", onAddBtnClick); + addAnnotationBtn?.addEventListener("mouseleave", onAddBtnLeave); + this._chartHoverCleanup = () => { + overlayEl.removeEventListener("mousemove", onMouseMove); + overlayEl.removeEventListener("mouseleave", onMouseLeave); + overlayEl.removeEventListener("touchmove", onTouchMove); + overlayEl.removeEventListener("touchend", onTouchEnd); + overlayEl.removeEventListener("click", onOverlayClick); + addAnnotationBtn?.removeEventListener("click", onAddBtnClick); + addAnnotationBtn?.removeEventListener("mouseleave", onAddBtnLeave); + }; + const selection = this.querySelector("#chart-zoom-selection"); + const hideZoomSelection = () => { + if (!selection) return; + selection.hidden = true; + selection.classList.remove("visible"); + }; + const renderZoomSelection = (startX, currentX) => { + if (!selection) return; + const left = Math.min(startX, currentX); + const width = Math.abs(currentX - startX); + selection.style.left = `${left}px`; + selection.style.top = `${splitSelTop}px`; + selection.style.width = `${width}px`; + selection.style.height = `${splitSelHeight}px`; + selection.hidden = false; + selection.classList.add("visible"); + }; + let zoomPointerId = null; + let zoomStartX = 0; + let zoomCurrentX = 0; + let zoomDragging = false; + const onZoomPointerMove = (ev) => { + if (zoomPointerId == null || ev.pointerId !== zoomPointerId) return; + zoomCurrentX = overlayRelX(ev.clientX); + if (!zoomDragging && Math.abs(zoomCurrentX - zoomStartX) < 6) return; + zoomDragging = true; + this._chartZoomDragging = true; + hideLineChartHover(this); + renderZoomSelection(zoomStartX, zoomCurrentX); + ev.preventDefault(); + }; + const finishZoom = (ev) => { + if (zoomPointerId == null || ev.pointerId !== zoomPointerId) return; + const didDrag = zoomDragging; + const endX = zoomCurrentX; + window.removeEventListener("pointermove", onZoomPointerMove); + window.removeEventListener("pointerup", finishZoom); + window.removeEventListener("pointercancel", finishZoom); + zoomPointerId = null; + zoomDragging = false; + this._chartZoomDragging = false; + hideZoomSelection(); + if (!didDrag || Math.abs(endX - zoomStartX) < 8) return; + const startTime = Math.min(stageXToTime(zoomStartX), stageXToTime(endX)); + const endTime = Math.max(stageXToTime(zoomStartX), stageXToTime(endX)); + this._applyZoomRange(startTime, endTime); + }; + const onZoomPointerDown = (ev) => { + if (ev.button !== 0 || !inPlotBoundsX(ev.clientX)) return; + zoomPointerId = ev.pointerId; + zoomStartX = overlayRelX(ev.clientX); + zoomCurrentX = zoomStartX; + zoomDragging = false; + this._chartZoomDragging = false; + window.addEventListener("pointermove", onZoomPointerMove); + window.addEventListener("pointerup", finishZoom); + window.addEventListener("pointercancel", finishZoom); + }; + const onZoomDoubleClick = (ev) => { + if (!inPlotBoundsX(ev.clientX)) return; + ev.preventDefault(); + this._clearZoomRange(); + }; + overlayEl.addEventListener("pointerdown", onZoomPointerDown); + overlayEl.addEventListener("dblclick", onZoomDoubleClick); + if (this._chartZoomCleanup) this._chartZoomCleanup(); + this._chartZoomCleanup = () => { + overlayEl.removeEventListener("pointerdown", onZoomPointerDown); + overlayEl.removeEventListener("dblclick", onZoomDoubleClick); + window.removeEventListener("pointermove", onZoomPointerMove); + window.removeEventListener("pointerup", finishZoom); + window.removeEventListener("pointercancel", finishZoom); + zoomPointerId = null; + zoomDragging = false; + this._chartZoomDragging = false; + hideZoomSelection(); + }; + } + }; + if (!customElements.get("hass-datapoints-history-chart")) customElements.define("hass-datapoints-history-chart", HistoryChart$1); + //#endregion + //#region custom_components/hass_datapoints/src/lib/chart/chart-state.ts + function createHiddenSeriesSet(seriesSettings = []) { + return new Set((Array.isArray(seriesSettings) ? seriesSettings : []).filter((entry) => entry?.visible === false).map((entry) => entry.entity_id || entry.entity || entry.entityId).filter((value) => Boolean(value))); + } + function createHiddenEventIdSet(hiddenEventIds = []) { + return new Set((Array.isArray(hiddenEventIds) ? hiddenEventIds : []).filter(Boolean)); + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/data/events-api.ts + async function fetchEvents(hass, startTime, endTime, entityIds) { + try { + const normalizedEntityIds = normalizeCacheIdList(entityIds); + return await withStableRangeCache(JSON.stringify({ + type: `${DOMAIN}/events`, + start_time: startTime, + end_time: endTime, + entity_ids: normalizedEntityIds + }), endTime, async () => { + const msg = { + type: `${DOMAIN}/events`, + start_time: startTime, + end_time: endTime + }; + if (normalizedEntityIds.length) msg.entity_ids = normalizedEntityIds; + return (await hass.connection.sendMessagePromise(msg)).events || []; + }); + } catch (err) { + logger.warn("[hass-datapoints] fetchEvents failed:", err); + return []; + } + } + function invalidateEventsCache() { + return clearStableRangeCacheMatching((key) => { + if (typeof key !== "string") return false; + return key.includes(`"type":"${DOMAIN}/events"`); + }); + } + async function fetchEventBounds(hass) { + try { + const result = await hass.connection.sendMessagePromise({ type: `${DOMAIN}/events_bounds` }); + return { + start: result?.start_time || null, + end: result?.end_time || null + }; + } catch (err) { + logger.warn("[hass-datapoints] fetchEventBounds failed:", err); + return { + start: null, + end: null + }; + } + } + async function deleteEvent(hass, eventId) { + const result = await hass.connection.sendMessagePromise({ + type: `${DOMAIN}/events/delete`, + event_id: eventId + }); + invalidateEventsCache(); + window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); + return result; + } + async function updateEvent(hass, eventId, fields) { + const result = await hass.connection.sendMessagePromise({ + type: `${DOMAIN}/events/update`, + event_id: eventId, + ...fields + }); + invalidateEventsCache(); + window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); + return result; + } + //#endregion + //#region node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directive.js + /** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + var t$1 = { + ATTRIBUTE: 1, + CHILD: 2, + PROPERTY: 3, + BOOLEAN_ATTRIBUTE: 4, + EVENT: 5, + ELEMENT: 6 + }, e$2 = (t) => (...e) => ({ + _$litDirective$: t, + values: e + }); + var i$1 = class { + constructor(t) {} + get _$AU() { + return this._$AM._$AU; + } + _$AT(t, e, i) { + this._$Ct = t, this._$AM = e, this._$Ci = i; + } + _$AS(t, e) { + return this.update(t, e); + } + update(t, e) { + return this.render(...e); + } + }, { I: t } = j$1, i = (o) => o, s = () => document.createComment(""), v = (o, n, e) => { + const l = o._$AA.parentNode, d = void 0 === n ? o._$AB : n._$AA; + if (void 0 === e) e = new t(l.insertBefore(s(), d), l.insertBefore(s(), d), o, o.options); + else { + const t = e._$AB.nextSibling, n = e._$AM, c = n !== o; + if (c) { + let t; + e._$AQ?.(o), e._$AM = o, void 0 !== e._$AP && (t = o._$AU) !== n._$AU && e._$AP(t); + } + if (t !== d || c) { + let o = e._$AA; + for (; o !== t;) { + const t = i(o).nextSibling; + i(l).insertBefore(o, d), o = t; + } + } + } + return e; + }, u$1 = (o, t, i = o) => (o._$AI(t, i), o), m = {}, p = (o, t = m) => o._$AH = t, M = (o) => o._$AH, h = (o) => { + o._$AR(), o._$AA.remove(); + }; + //#endregion + //#region node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/repeat.js + /** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + var u = (e, s, t) => { + const r = /* @__PURE__ */ new Map(); + for (let l = s; l <= t; l++) r.set(e[l], l); + return r; + }, c = e$2(class extends i$1 { + constructor(e) { + if (super(e), e.type !== t$1.CHILD) throw Error("repeat() can only be used in text expressions"); + } + dt(e, s, t) { + let r; + void 0 === t ? t = s : void 0 !== s && (r = s); + const l = [], o = []; + let i = 0; + for (const s of e) l[i] = r ? r(s, i) : i, o[i] = t(s, i), i++; + return { + values: o, + keys: l + }; + } + render(e, s, t) { + return this.dt(e, s, t).values; + } + update(s, [t, r, c]) { + const d = M(s), { values: p$3, keys: a } = this.dt(t, r, c); + if (!Array.isArray(d)) return this.ut = a, p$3; + const h$3 = this.ut ??= [], v$2 = []; + let m, y, x = 0, j = d.length - 1, k = 0, w = p$3.length - 1; + for (; x <= j && k <= w;) if (null === d[x]) x++; + else if (null === d[j]) j--; + else if (h$3[x] === a[k]) v$2[k] = u$1(d[x], p$3[k]), x++, k++; + else if (h$3[j] === a[w]) v$2[w] = u$1(d[j], p$3[w]), j--, w--; + else if (h$3[x] === a[w]) v$2[w] = u$1(d[x], p$3[w]), v(s, v$2[w + 1], d[x]), x++, w--; + else if (h$3[j] === a[k]) v$2[k] = u$1(d[j], p$3[k]), v(s, d[x], d[j]), j--, k++; + else if (void 0 === m && (m = u(a, k, w), y = u(h$3, x, j)), m.has(h$3[x])) if (m.has(h$3[j])) { + const e = y.get(a[k]), t = void 0 !== e ? d[e] : null; + if (null === t) { + const e = v(s, d[x]); + u$1(e, p$3[k]), v$2[k] = e; + } else v$2[k] = u$1(t, p$3[k]), v(s, d[x], t), d[e] = null; + k++; + } else h(d[j]), j--; + else h(d[x]), x++; + for (; k <= w;) { + const e = v(s, v$2[w + 1]); + u$1(e, p$3[k]), v$2[k++] = e; + } + for (; x <= j;) { + const e = d[x++]; + null !== e && h(e); + } + return this.ut = a, p(s, v$2), E; + } + }); + //#endregion + //#region custom_components/hass_datapoints/src/molecules/annotation-chip-row/annotation-chip-row.styles.ts + var styles$53 = i$5` + :host { + display: block; + } + + .context-form-field { + display: grid; + gap: 6px; + min-width: 0; + } + + .context-form-label { + font-size: 0.9rem; + font-weight: 600; + color: var(--primary-text-color); + } + + .context-form-help { + font-size: 0.8rem; + color: var(--secondary-text-color); + line-height: 1.45; + } + + .context-chip-row { + display: flex; + flex-wrap: wrap; + gap: 6px; + } +`; + //#endregion + //#region custom_components/hass_datapoints/src/atoms/interactive/annotation-chip/annotation-chip.styles.ts + var styles$52 = i$5` + :host { + display: inline-flex; + } + .context-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 999px; + background: color-mix(in srgb, var(--primary-color) 12%, transparent); + color: var(--primary-color); + font-size: 0.82rem; + font-family: inherit; + } + .context-chip ha-icon, + .context-chip ha-state-icon { + --mdc-icon-size: 14px; + flex: 0 0 auto; + } + .context-chip-text { + display: inline-flex; + flex-direction: column; + min-width: 0; + line-height: 1.15; + } + .context-chip-primary, + .context-chip-secondary { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .context-chip-primary { + font-weight: 600; + } + .context-chip-secondary { + font-size: 0.74rem; + opacity: 0.8; + } + .context-chip-remove { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + padding: 0; + border: none; + border-radius: 50%; + background: transparent; + color: currentColor; + cursor: pointer; + flex: 0 0 auto; + } + .context-chip-remove:hover { + background: color-mix(in srgb, currentColor 12%, transparent); + } + .context-chip-remove ha-icon { + --mdc-icon-size: 12px; + pointer-events: none; + } +`; + //#endregion + //#region custom_components/hass_datapoints/src/atoms/interactive/annotation-chip/annotation-chip.ts + var AnnotationChip = class extends i$2 { + @n$1({ type: String }) accessor type = ""; + @n$1({ + type: String, + attribute: "item-id" + }) accessor itemId = ""; + @n$1({ type: String }) accessor icon = ""; + @n$1({ type: String }) accessor name = ""; + @n$1({ + type: String, + attribute: "secondary-text" + }) accessor secondaryText = ""; + @n$1({ + type: Object, + attribute: false + }) accessor stateObj = null; + @n$1({ + type: Object, + attribute: false + }) accessor hass = null; + _onRemove() { + this.dispatchEvent(new CustomEvent("dp-chip-remove", { + detail: { + type: this.type, + itemId: this.itemId + }, + bubbles: true, + composed: true + })); + } + render() { + return b` + + ${this.stateObj ? b`` : b``} + + ${this.name} + ${this.secondaryText ? b`${this.secondaryText}` : b``} + + + + `; + } + }; + _defineProperty(AnnotationChip, "styles", styles$52); + customElements.define("annotation-chip", AnnotationChip); + //#endregion + //#region custom_components/hass_datapoints/src/molecules/annotation-chip-row/annotation-chip-row.ts + /** + * `annotation-chip-row` renders a labelled group of removable annotation + * target chips. When the list is empty it shows an empty-state help text instead. + * + * @fires dp-target-remove - `{ type: string, id: string }` fired when a chip's + * remove button is clicked. + */ + var AnnotationChipRow = class extends i$2 { + /** Array of pre-resolved chip items to display. */ + @n$1({ type: Array }) accessor chips = []; + /** HA hass object forwarded to annotation-chip when rendering entity icons. */ + @n$1({ + type: Object, + attribute: false + }) accessor hass = null; + /** Section label shown above the chips. */ + @n$1({ type: String }) accessor label = "Linked targets"; + /** Help text shown below the label when chips are present. */ + @n$1({ + type: String, + attribute: "help-text" + }) accessor helpText = "These targets will be associated with the new data point by default. Remove any that should not be linked."; + /** Help text shown below the label when there are no chips. */ + @n$1({ + type: String, + attribute: "empty-text" + }) accessor emptyText = "No linked targets will be associated with this data point."; + _onChipRemove(ev) { + const detail = ev.detail; + ev.stopPropagation(); + this.dispatchEvent(new CustomEvent("dp-target-remove", { + detail: { + type: detail.type, + id: detail.itemId + }, + bubbles: true, + composed: true + })); + } + render() { + return b` +
+ +
+ ${this.chips.length > 0 ? this.helpText : this.emptyText} +
+ ${this.chips.length > 0 ? b` +
+ ${c(this.chips, (chip) => `${chip.type}:${chip.itemId}`, (chip) => b` + + `)} +
+ ` : A} +
+ `; + } + }; + _defineProperty(AnnotationChipRow, "styles", styles$53); + customElements.define("annotation-chip-row", AnnotationChipRow); + //#endregion + //#region custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts + var HistoryAnnotationDialogController = class { + constructor(host) { + _defineProperty(this, "_host", void 0); + _defineProperty(this, "_dialogEl", void 0); + _defineProperty(this, "_panelEl", void 0); + _defineProperty(this, "_chipRowEl", void 0); + _defineProperty(this, "_linkedTarget", void 0); + _defineProperty(this, "_target", void 0); + this._host = host; + this._dialogEl = null; + this._panelEl = null; + this._chipRowEl = null; + this._linkedTarget = normalizeTargetSelection({}); + this._target = normalizeTargetSelection({}); + } + isOpen() { + return !!this._dialogEl?.open; + } + ensureDialog() { + if (this._dialogEl || !this._host.shadowRoot) return; + const dialog = document.createElement("ha-dialog"); + dialog.id = "chart-context-dialog"; + dialog.scrimClickAction = true; + dialog.escapeKeyAction = true; + dialog.open = false; + dialog.headerTitle = "Create data point"; + dialog.style.setProperty("--dialog-content-padding", "0 var(--dp-spacing-lg, 24px) var(--dp-spacing-lg, 24px)"); + dialog.style.setProperty("--mdc-dialog-min-width", "min(920px, 96vw)"); + dialog.style.setProperty("--mdc-dialog-max-width", "96vw"); + if (this._host._hass) dialog.hass = this._host._hass; + dialog.innerHTML = ` +
+ `; + dialog.addEventListener("closed", () => this.finalizeClose()); + this._host.shadowRoot.appendChild(dialog); + this._dialogEl = dialog; + this._panelEl = dialog.querySelector("#chart-context-dialog-panel"); + } + teardown() { + this.resetFormState(); + this._dialogEl?.remove(); + this._dialogEl = null; + this._panelEl = null; + this._chipRowEl = null; + } + resetFormState() { + this._linkedTarget = normalizeTargetSelection({}); + this._target = normalizeTargetSelection({}); + } + finalizeClose() { + this.teardown(); + this._host._creatingContextAnnotation = false; + } + _shake() { + if (!this._dialogEl) return; + const shadowRoot = this._host.shadowRoot; + if (!shadowRoot) return; + if (!shadowRoot.getElementById("dp-dialog-shake-style")) { + const style = document.createElement("style"); + style.id = "dp-dialog-shake-style"; + style.textContent = ` + @keyframes dp-dialog-shake { + 10%, 90% { transform: translate3d(-2px, 0, 0); } + 20%, 80% { transform: translate3d(4px, 0, 0); } + 30%, 50%, 70% { transform: translate3d(-6px, 0, 0); } + 40%, 60% { transform: translate3d(6px, 0, 0); } } - } - this._setAdjustAxisButtonVisibility( - comparisonPreviewActive && comparisonOutOfBounds && !this._adjustComparisonAxisScale && !autoAdjustHoveredComparisonAxis - ); - for (const s2 of visibleSeries) { - if (hiddenSourceEntityIds.has(s2.entityId)) { - continue; + .dp-shaking { + animation: dp-dialog-shake 0.5s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; } - const sWithAxis = s2; - this._drawSeriesLine( - renderer, - s2.pts, - s2.color, - renderT0, - renderT1, - sWithAxis.axis.min, - sWithAxis.axis.max, - { - lineOpacity: mainSeriesHoverOpacity, - lineWidth: this._config?.comparison_hover_active === true ? 1.25 : void 0, - stepped: (analysisMap.get(s2.entityId) || normalizeHistorySeriesAnalysis(null)).stepped_series === true - } - ); - } - const trendHoverSeries = trendSeries.map((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - const hiddenSource = hiddenSourceEntityIds.has(seriesItem.entityId); - const trendRenderOptions = this._getTrendRenderOptions( - analysis.trend_method, - hiddenSource - ); - const seriesWithAxis = seriesItem; - return { - entityId: `trend:${seriesItem.entityId}`, - relatedEntityId: seriesItem.entityId, - label: seriesItem.label, - baseLabel: seriesItem.label, - unit: seriesItem.unit || "", - pts: seriesItem.trendPts, - color: hexToRgba(seriesItem.color, trendRenderOptions.colorAlpha), - axis: seriesWithAxis.axis, - rawVisible: !hiddenSource, - showCrosshair: analysis.show_trend_crosshairs === true, - hoverOpacity: comparisonPreviewActive ? Math.max(0.25, Math.min(0.9, mainSeriesHoverOpacity + 0.12)) : trendRenderOptions.lineOpacity, - trend: true - }; - }); - const rateHoverSeries = rateSeries.map((seriesItem) => ({ - entityId: `rate:${seriesItem.entityId}`, - relatedEntityId: seriesItem.entityId, - label: seriesItem.label, - baseLabel: seriesItem.label, - unit: seriesItem.unit ? `${seriesItem.unit}/h` : "/h", - pts: seriesItem.ratePts, - color: hexToRgba(seriesItem.color, anyHiddenSourceSeries ? 0.9 : 0.72), - axis: seriesItem.rateAxis, - rawVisible: !hiddenSourceEntityIds.has(seriesItem.entityId), - showCrosshair: (analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null)).show_rate_crosshairs === true, - hoverOpacity: anyHiddenSourceSeries ? 0.88 : 0.66, - rate: true - })); - for (const trend of trendSeries) { - const analysis = analysisMap.get(trend.entityId) || normalizeHistorySeriesAnalysis(null); - const trendRenderOptions = this._getTrendRenderOptions( - analysis.trend_method, - hiddenSourceEntityIds.has(trend.entityId) - ); - const trendWithAxis = trend; - renderer.drawLine( - trend.trendPts, - hexToRgba(trend.color, trendRenderOptions.colorAlpha), - renderT0, - renderT1, - trendWithAxis.axis.min, - trendWithAxis.axis.max, - { - lineOpacity: comparisonPreviewActive ? Math.max(0.25, Math.min(0.9, mainSeriesHoverOpacity + 0.12)) : trendRenderOptions.lineOpacity, - lineWidth: trendRenderOptions.lineWidth, - dashed: trendRenderOptions.dashed, - dotted: trendRenderOptions.dotted - } - ); - } - for (const rateSeriesItem of rateSeries) { - renderer.drawLine( - rateSeriesItem.ratePts, - hexToRgba(rateSeriesItem.color, anyHiddenSourceSeries ? 0.96 : 0.82), - renderT0, - renderT1, - rateSeriesItem.rateAxis.min, - rateSeriesItem.rateAxis.max, - { - lineOpacity: anyHiddenSourceSeries ? 0.88 : 0.66, - lineWidth: 1.55, - dashed: false, - dotted: false, - dashPattern: [7, 3, 1.5, 3] - } - ); - } - for (const seriesItem of visibleSeries) { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (!(analysis.show_delta_analysis === true && hasSelectedComparisonWindow === true)) { - continue; - } - const deltaAxis = axisLookup.get(`delta:${seriesItem.axisKey}`); - if (!deltaAxis) { - continue; - } - const deltaPoints = deltaPointsMap.get(seriesItem.entityId) || []; - if (!deltaPoints.length) { - continue; - } - if (analysis.show_delta_tooltip === true) { - deltaHoverSeries.push({ - entityId: `delta:${seriesItem.entityId}`, - relatedEntityId: seriesItem.entityId, - label: seriesItem.label, - baseLabel: seriesItem.label, - unit: seriesItem.unit || "", - pts: deltaPoints, - color: hexToRgba(seriesItem.color, 0.92), - axis: deltaAxis, - rawVisible: !hiddenSourceEntityIds.has(seriesItem.entityId), - hoverOpacity: 0.82, - delta: true - }); - } - if (analysis.show_delta_lines === true) { - renderer.drawLine( - deltaPoints, - hexToRgba(seriesItem.color, 0.92), - renderT0, - renderT1, - deltaAxis.min, - deltaAxis.max, - { - lineOpacity: 0.82, - lineWidth: 1.9, - dashed: true - } - ); - } - } - visibleSeries.forEach((seriesItem) => { - const shadingAnalysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (shadingAnalysis.show_summary_stats !== true || shadingAnalysis.show_summary_stats_shading !== true) { - return; - } - const stats = summaryStatsMap.get(seriesItem.entityId); - const seriesWithAxis = seriesItem; - const axis = seriesWithAxis.axis; - if (!stats || !axis) { - return; - } - if (!Number.isFinite(stats.min) || !Number.isFinite(stats.max) || !Number.isFinite(stats.mean)) { - return; - } - const fillAlpha = anyHiddenSourceSeries ? 0.1 : 0.06; - renderer.drawGradientBand( - stats.min, - stats.mean, - seriesItem.color, - renderT0, - renderT1, - axis.min, - axis.max, - { fillAlpha } - ); - renderer.drawGradientBand( - stats.max, - stats.mean, - seriesItem.color, - renderT0, - renderT1, - axis.min, - axis.max, - { fillAlpha } - ); - }); - summaryHoverSeries.forEach((summarySeries) => { - const axis = summarySeries.axis; - if (!axis) { - return; - } - renderer.drawLine( - [ - [renderT0, summarySeries.value], - [renderT1, summarySeries.value] - ], - summarySeries.color, - renderT0, - renderT1, - axis.min, - axis.max, - { - lineOpacity: summarySeries.hoverOpacity, - lineWidth: 1.8, - dashed: false, - dotted: false - } - ); - }); - thresholdHoverSeries.forEach((thresholdSeries) => { - const axis = thresholdSeries.axis; - if (!axis) { - return; - } - const thresholdAnalysis = analysisMap.get(thresholdSeries.relatedEntityId) || normalizeHistorySeriesAnalysis(null); - if (thresholdAnalysis.show_threshold_shading === true) { - const relatedSeries = visibleSeries.find( - (seriesItem) => seriesItem.entityId === thresholdSeries.relatedEntityId - ); - if (relatedSeries?.pts?.length) { - renderer.drawThresholdArea( - relatedSeries.pts, - thresholdSeries.value, - thresholdSeries.baseColor || relatedSeries.color, - renderT0, - renderT1, - axis.min, - axis.max, - { - mode: thresholdSeries.direction === "below" ? "below" : "above", - fillAlpha: anyHiddenSourceSeries ? 0.24 : 0.14 - } - ); - } - } - renderer.drawLine( - [ - [renderT0, thresholdSeries.value], - [renderT1, thresholdSeries.value] - ], - thresholdSeries.color, - renderT0, - renderT1, - axis.min, - axis.max, - { - lineOpacity: thresholdSeries.hoverOpacity, - lineWidth: 1.15 - } - ); - }); - if (anomalySeries.length) { - const anomalyRegions = []; - anomalySeries.forEach((seriesItem) => { - const seriesWithAxis = seriesItem; - const axis = seriesWithAxis.axis; - if (!axis) { - return; - } - const visibleAnomalyClusters = this._filterAnnotatedAnomalyClusters( - seriesItem, - events - ); - if (visibleAnomalyClusters.length === 0) { - return; - } - const overlapMode = (analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null)).anomaly_overlap_mode; - const { baseClusters, regionClusters } = this._resolveAnomalyClusterDisplay( - visibleAnomalyClusters, - overlapMode, - correlatedSpans - ); - const baseColor = hexToRgba( - seriesItem.color, - anyHiddenSourceSeries ? 0.96 : 0.86 - ); - const regionOptions = { - strokeAlpha: anyHiddenSourceSeries ? 0.98 : 0.9, - lineWidth: anyHiddenSourceSeries ? 2.5 : 2.1, - haloWidth: anyHiddenSourceSeries ? 5.5 : 4.8, - haloColor: "rgba(255,255,255,0.88)", - haloAlpha: anyHiddenSourceSeries ? 0.92 : 0.82, - fillColor: hexToRgba( - seriesItem.color, - anyHiddenSourceSeries ? 0.14 : 0.1 - ), - fillAlpha: 1, - pointPadding: anyHiddenSourceSeries ? 12 : 10, - minRadiusX: 10, - minRadiusY: 10 - }; - if (baseClusters.length > 0) { - renderer.drawAnomalyClusters( - baseClusters, - baseColor, - renderT0, - renderT1, - axis.min, - axis.max, - regionOptions - ); - } - const clusterRegions = renderer.getAnomalyClusterRegions( - regionClusters, - renderT0, - renderT1, - axis.min, - axis.max, - regionOptions - ); - clusterRegions.forEach((region) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - anomalyRegions.push({ - ...region, - relatedEntityId: seriesItem.entityId, - label: String(seriesItem.label), - unit: String(seriesItem.unit || ""), - color: seriesItem.color || null, - sensitivity: analysis.anomaly_sensitivity - }); - }); - }); - this._lastAnomalyRegions = anomalyRegions; - } else { - this._lastAnomalyRegions = []; - } - const effectiveComparisonHoverSeries = comparisonHoverSeries.filter( - (entry) => !hiddenComparisonEntityIds.has( - entry.relatedEntityId || entry.entityId - ) - ); - renderer.drawAnnotations(chartEvents, renderT0, renderT1, { - showLines: this._config.show_event_lines !== false, - showMarkers: this._config.show_event_lines !== false - }); - const eventHits = this._drawRecordedEventPoints( - renderer, - visibleSeries, - chartEvents, - renderT0, - renderT1, - { - showIcons: this._config.show_event_markers !== false - } - ); - this._renderLegend(series, binaryBackgrounds); - const eventValueMap = new Map(eventHits.map((hit) => [hit.event.id, hit])); - const enrichedEvents = chartEvents.map( - (event) => { - const hit = eventValueMap.get(event.id || ""); - return hit ? { - ...event, - chart_value: hit.value, - chart_unit: hit.unit - } : event; - } - ); - const addButton = this.querySelector("#chart-add-annotation"); - if (addButton) { - addButton.dataset.allowAddAnnotation = this._canAddAnnotation ? "true" : "false"; - if (addButton.dataset.allowAddAnnotation === "false") { - addButton.hidden = true; - } - } - if (visibleSeries.length) { - this._ensureContextAnnotationDialog(); - attachLineChartHover( - this, - canvas, - renderer, - hoverSeries, - enrichedEvents, - renderT0, - renderT1, - 0, - 0, - activeAxes, - { - onContextMenu: (hover) => this._handleChartContextMenu(hover), - onAddAnnotation: this._canAddAnnotation ? (hover) => this._handleChartAddAnnotation(hover) : void 0, - binaryStates: binaryBackgrounds.filter( - (entry) => !this._hiddenSeries.has(entry.entityId) - ), - comparisonSeries: effectiveComparisonHoverSeries, - trendSeries: [ - ...trendHoverSeries, - ...comparisonTrendHoverSeries - ], - rateSeries: [ - ...rateHoverSeries, - ...comparisonRateHoverSeries - ], - deltaSeries: deltaHoverSeries, - summarySeries: [ - ...summaryHoverSeries, - ...comparisonSummaryHoverSeries - ], - thresholdSeries: [ - ...thresholdHoverSeries, - ...comparisonThresholdHoverSeries - ], - anomalyRegions: Array.isArray(this._lastAnomalyRegions) ? this._lastAnomalyRegions : [], - hoverSurfaceEl: this.querySelector( - "#chart-icon-overlay" - ), - showTooltip: this._config.show_tooltips !== false, - emphasizeHoverGuides: this._config.emphasize_hover_guides === true, - hoverSnapMode: this._config.hover_snap_mode === "snap_to_data_points" ? "snap_to_data_points" : "follow_series", - showTrendCrosshairs: anyTrendCrosshairs, - showRateCrosshairs: anyRateCrosshairs, - hideRawData: hiddenSourceEntityIds.size === visibleSeries.length && visibleSeries.length > 0, - showDeltaTooltip: deltaHoverSeries.length > 0, - onAnomalyClick: (regions) => this._handleAnomalyAddAnnotation(regions) - } - ); - attachLineChartRangeZoom(this, canvas, renderer, renderT0, renderT1, { - onPreview: (range) => this._dispatchZoomPreview(range), - onZoom: ({ - startTime, - endTime - }) => this._applyZoomRange(startTime, endTime), - onReset: () => this._clearZoomRange() - }); - } else { - if (this._chartHoverCleanup) { - this._chartHoverCleanup(); - this._chartHoverCleanup = null; - } - if (this._chartZoomCleanup) { - this._chartZoomCleanup(); - this._chartZoomCleanup = null; - } - } - if (this._chartScrollViewportEl) { - this._chartScrollViewportEl.removeEventListener( - "scroll", - this._onChartScroll - ); - this._chartScrollViewportEl.addEventListener( - "scroll", - this._onChartScroll, - { passive: true } - ); - this._syncChartViewportScroll(t0, t1, w); - } - this._fireBackendAnomalyRequests( - _anomalyEntityIds, - analysisMap, - _startIso, - _endIso - ); - this._fireComparisonBackendAnomalyRequests( - drawableComparisonResults, - analysisMap, - renderT0, - renderT1 - ); - } - // ── Analysis cache helpers ────────────────────────────────────────────────── - /** - * Build a lightweight string key that captures all inputs that affect the - * analysis result. Used to skip the worker when data and settings haven't - * changed (e.g. on zoom/pan). - * Ported from _buildAnalysisCacheKey in card-history.js. - */ - _buildAnalysisCacheKey(visibleSeries, selectedComparisonSeriesMap, analysisMap, allComparisonWindowsData, t0, t1) { - const ANALYSIS_FIELDS = [ - "show_trend_lines", - "trend_method", - "trend_window", - "show_rate_of_change", - "rate_window", - "show_delta_analysis", - "show_summary_stats", - "show_anomalies", - "anomaly_methods", - "anomaly_sensitivity", - "anomaly_overlap_mode", - "anomaly_rate_window", - "anomaly_zscore_window", - "anomaly_persistence_window", - "anomaly_comparison_window_id", - "anomaly_use_sampled_data" - ]; - const seriesPart = visibleSeries.map((s2) => { - const a2 = analysisMap.get(s2.entityId) || normalizeHistorySeriesAnalysis(null); - const first = s2.pts[0]?.[0] ?? 0; - const last = s2.pts[s2.pts.length - 1]?.[0] ?? 0; - const aKey = ANALYSIS_FIELDS.map((f2) => JSON.stringify(a2[f2])).join(","); - return `${s2.entityId}:${s2.pts.length}:${first}:${last}:${aKey}`; - }).join("|"); - const cmpPart = Array.from(selectedComparisonSeriesMap.values()).map((s2) => { - const first = s2.pts[0]?.[0] ?? 0; - const last = s2.pts[s2.pts.length - 1]?.[0] ?? 0; - return `${s2.entityId}:${s2.pts.length}:${first}:${last}`; - }).sort().join("|"); - const allCmpPart = Object.entries(allComparisonWindowsData).flatMap( - ([windowId, entities]) => Object.entries(entities).map(([entityId, pts]) => { - const first = pts[0]?.[0] ?? 0; - const last = pts[pts.length - 1]?.[0] ?? 0; - return `${windowId}:${entityId}:${pts.length}:${first}:${last}`; - }) - ).sort().join("|"); - return `${t0}:${t1}|${seriesPart}|${cmpPart}|${allCmpPart}`; - } - /** - * Build the serialisable payload sent to the history-analysis worker. - * Ported from _buildHistoryAnalysisPayload in card-history.js. - */ - _buildHistoryAnalysisPayload(visibleSeries, selectedComparisonSeriesMap, analysisMap, hasSelectedComparisonWindow, allComparisonWindowsData = {}) { - const defaultAnalysis = normalizeHistorySeriesAnalysis(null); - const series = visibleSeries.map( - (seriesItem) => ({ - entityId: seriesItem.entityId, - pts: seriesItem.pts, - analysis: analysisMap.get(seriesItem.entityId) || defaultAnalysis - }) - ); - const comparisonSeries = Array.from(selectedComparisonSeriesMap.values()).map((seriesItem) => ({ - entityId: seriesItem.entityId, - pts: seriesItem.pts - })); - const seriesAnalysisConfigs = {}; - for (const seriesItem of visibleSeries) { - seriesAnalysisConfigs[seriesItem.entityId] = analysisMap.get(seriesItem.entityId) || defaultAnalysis; - } - return { - series, - comparisonSeries, - hasSelectedComparisonWindow: hasSelectedComparisonWindow === true, - allComparisonWindowsData, - seriesAnalysisConfigs - }; - } - /** - * Run or cache the history analysis computation. - * Terminates any in-flight worker before starting a new one. - * Falls back to a synchronous main-thread path when the worker fails. - * Ported from _computeHistoryAnalysis in card-history.js. - */ - async _computeHistoryAnalysis(visibleSeries, selectedComparisonSeriesMap, analysisMap, hasSelectedComparisonWindow, allComparisonWindowsData = {}, t0 = 0, t1 = 0, onProgress = null) { - terminateHistoryAnalysisWorker(); - const cacheKey = this._buildAnalysisCacheKey( - visibleSeries, - selectedComparisonSeriesMap, - analysisMap, - allComparisonWindowsData, - t0, - t1 - ); - if (this._analysisCache?.key === cacheKey) { - return this._analysisCache.result; - } - onProgress?.(0); - const payload = this._buildHistoryAnalysisPayload( - visibleSeries, - selectedComparisonSeriesMap, - analysisMap, - hasSelectedComparisonWindow, - allComparisonWindowsData - ); - try { - const result = await computeHistoryAnalysisInWorker( - payload - ); - this._analysisCache = { key: cacheKey, result }; - return result; - } catch (error) { - const errorMessage = error?.message ?? ""; - if (errorMessage.startsWith("Aborted")) { - return { - trendSeries: [], - rateSeries: [], - deltaSeries: [], - summaryStats: [], - anomalySeries: [] - }; - } - logger$1.warn("[hass-datapoints history-card] analysis worker fallback", { - message: errorMessage || String(error) - }); - try { - return { - trendSeries: visibleSeries.map((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (analysis.show_trend_lines !== true) { - return null; - } - return { - entityId: seriesItem.entityId, - pts: this._buildTrendPoints( - seriesItem.pts, - analysis.trend_method, - analysis.trend_window - ) - }; - }).filter(Boolean).filter( - (s2) => Array.isArray(s2.pts) && s2.pts.length >= 2 - ), - rateSeries: visibleSeries.map((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (analysis.show_rate_of_change !== true) { - return null; - } - return { - entityId: seriesItem.entityId, - pts: this._buildRateOfChangePoints( - seriesItem.pts, - analysis.rate_window - ) - }; - }).filter(Boolean).filter( - (s2) => Array.isArray(s2.pts) && s2.pts.length >= 2 - ), - deltaSeries: visibleSeries.map((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (!(analysis.show_delta_analysis === true && hasSelectedComparisonWindow === true)) { - return null; - } - const comparisonSeries = selectedComparisonSeriesMap.get( - seriesItem.entityId - ); - return { - entityId: seriesItem.entityId, - pts: comparisonSeries ? this._buildDeltaPoints(seriesItem.pts, comparisonSeries.pts) : [] - }; - }).filter(Boolean).filter( - (s2) => Array.isArray(s2.pts) && s2.pts.length >= 2 - ), - summaryStats: visibleSeries.map((seriesItem) => { - const analysis = analysisMap.get(seriesItem.entityId) || normalizeHistorySeriesAnalysis(null); - if (analysis.show_summary_stats !== true) { - return null; - } - return { - entityId: seriesItem.entityId, - ...this._buildSummaryStats(seriesItem.pts) - }; - }).filter( - (entry) => entry && Number.isFinite( - entry.min - ) && Number.isFinite( - entry.max - ) && Number.isFinite( - entry.mean - ) - ), - // Anomaly detection intentionally omitted in the fallback path. - anomalySeries: [] - }; - } catch (fallbackError) { - logger$1.error( - "[hass-datapoints history-card] analysis fallback failed", - fallbackError - ); - return { - trendSeries: [], - rateSeries: [], - deltaSeries: [], - summaryStats: [], - anomalySeries: [] - }; - } - } - } - // ── Analysis helper stubs ─────────────────────────────────────────────────── - // These are called by _computeHistoryAnalysis fallback path. - // Implementations live in history/analysis/index.ts / the parent card. - /** Build trend points from raw series data. */ - _buildTrendPoints(_pts, _method, _window) { - if (!Array.isArray(_pts) || _pts.length < 2) { - return []; - } - const method = _method || "rolling_average"; - if (method === "linear_trend") { - return buildLinearTrend(_pts); - } - return buildRollingAverageTrend(_pts, getTrendWindowMs(_window || "24h")); - } - /** Build rate-of-change points from raw series data. */ - _buildRateOfChangePoints(_pts, _window) { - if (!Array.isArray(_pts) || _pts.length < 2) { - return []; - } - return buildRateOfChangePoints(_pts, _window || "1h"); - } - /** Build delta points (main vs comparison series). */ - _buildDeltaPoints(_pts, _comparisonPts) { - return buildDeltaPoints(_pts, _comparisonPts); - } - /** Build summary statistics from raw series data. */ - _buildSummaryStats(_pts) { - return buildSummaryStats(_pts) ?? { min: 0, max: 0, mean: 0 }; - } - // ── Series analysis helpers ───────────────────────────────────────────────── - /** - * Build a Map of entityId → normalised analysis settings from config. - * Ported from _getSeriesAnalysisMap in card-history.js. - */ - _getSeriesAnalysisMap() { - const seriesSettings = Array.isArray(this._config?.series_settings) ? this._config.series_settings : []; - return new Map( - seriesSettings.filter((entry) => entry?.entity_id != null).map((entry) => [ - entry.entity_id, - normalizeHistorySeriesAnalysis(entry?.analysis) - ]) - ); - } - /** - * Get the normalised analysis settings for a single entity. - * Ported from _getSeriesAnalysis in card-history.js. - */ - _getSeriesAnalysis(entityId, analysisMap = null) { - const map = analysisMap || this._getSeriesAnalysisMap(); - return normalizeHistorySeriesAnalysis(map.get(entityId)); - } - /** - * Returns true if any analysis feature is active for the given series. - * Ported from _seriesHasActiveAnalysis in card-history.js. - */ - _seriesHasActiveAnalysis(analysis, hasSelectedComparisonWindow = false) { - return !!(analysis.show_trend_lines || analysis.show_summary_stats || analysis.show_rate_of_change || analysis.show_threshold_analysis || analysis.show_anomalies || analysis.show_delta_analysis && hasSelectedComparisonWindow); - } - /** - * Returns true if the source series should be hidden (replaced by its - * analysis overlay series). - * Ported from _seriesShouldHideSource in card-history.js. - */ - _seriesShouldHideSource(analysis, hasSelectedComparisonWindow = false) { - return analysis.hide_source_series === true && this._seriesHasActiveAnalysis(analysis, hasSelectedComparisonWindow); - } - /** - * Return ChartRenderer render options for the active trend method. - * Ported from _getTrendRenderOptions in card-history.js. - */ - _getTrendRenderOptions(method = "rolling_average", hideRawData = false) { - if (method === "linear_trend") { - return { - colorAlpha: hideRawData ? 0.94 : 0.88, - lineOpacity: hideRawData ? 0.86 : 0.74, - lineWidth: 2.1, - dashed: true, - dotted: false - }; - } - return { - colorAlpha: hideRawData ? 0.9 : 0.82, - lineOpacity: hideRawData ? 0.84 : 0.62, - lineWidth: 2.2, - dashed: false, - dotted: true - }; - } - // ── Split-view chart rendering ─────────────────────────────────────────────── - async _drawSplitChart({ - visibleSeries, - binaryBackgrounds, - events, - renderT0, - renderT1, - canvasWidth, - availableHeight, - chartStage, - canvas, - wrap, - options, - drawableComparisonResults, - selectedComparisonWindowId, - hoveredComparisonWindowId, - comparisonPreviewActive, - hoveringDifferentComparison, - analysisResult, - analysisMap, - hasSelectedComparisonWindow - }) { - if (canvas) { - canvas.style.display = "none"; - } - const N2 = visibleSeries.length; - const MIN_ROW_HEIGHT = 140; - const rowHeight = Math.max(MIN_ROW_HEIGHT, Math.floor(availableHeight / N2)); - const totalHeight = rowHeight * N2; - if (chartStage) { - chartStage.style.width = `${canvasWidth}px`; - chartStage.style.height = `${totalHeight}px`; - } - const splitScrollViewport = this.querySelector("#chart-scroll-viewport"); - if (splitScrollViewport) { - splitScrollViewport.style.overflowY = totalHeight > availableHeight ? "auto" : "hidden"; - } - this._setChartLoading(!!options.loading); - this._setChartMessage(""); - const iconOverlay = this.querySelector("#chart-icon-overlay"); - if (iconOverlay) { - iconOverlay.innerHTML = ""; - } - const trendPointsMap = new Map( - (analysisResult?.trendSeries || []).map((entry) => [ - entry.entityId, - entry.pts - ]) - ); - const ratePointsMap = new Map( - (analysisResult?.rateSeries || []).map((entry) => [ - entry.entityId, - entry.pts - ]) - ); - const deltaPointsMap = new Map( - (analysisResult?.deltaSeries || []).map((entry) => [ - entry.entityId, - entry.pts - ]) - ); - const summaryStatsMap = new Map( - (analysisResult?.summaryStats || []).map((entry) => [ - entry.entityId, - entry - ]) - ); - const anomalyClustersMap = new Map( - (analysisResult?.anomalySeries || []).map( - (entry) => [ - entry.entityId, - entry.anomalyClusters - ] - ) - ); - const effectiveAnalysisMap = analysisMap || /* @__PURE__ */ new Map(); - const shouldUseCorrelatedAnomalySpans = this._config?.show_correlated_anomalies === true || this._config?.anomaly_overlap_mode === "only"; - const shouldDrawCorrelatedAnomalySpans = this._config?.show_correlated_anomalies === true; - const correlatedAnomalySpans = shouldUseCorrelatedAnomalySpans ? this._buildCorrelatedAnomalySpans( - visibleSeries, - anomalyClustersMap, - effectiveAnalysisMap - ) : []; - const tracks = []; - for (let i2 = 0; i2 < N2; i2 += 1) { - const isLastRow = i2 === N2 - 1; - const seriesItem = visibleSeries[i2]; - const rowOffset = i2 * rowHeight; - const rowDiv = document.createElement("div"); - rowDiv.className = "split-series-row"; - rowDiv.style.cssText = `position:absolute;left:0;top:${rowOffset}px;width:${canvasWidth}px;height:${rowHeight}px;pointer-events:none;overflow:hidden;`; - const rowCanvas = document.createElement("canvas"); - rowCanvas.className = "split-series-canvas"; - rowDiv.appendChild(rowCanvas); - chartStage?.appendChild(rowDiv); - const { w, h: h2 } = setupCanvas(rowCanvas, chartStage || wrap, rowHeight, canvasWidth); - const renderer = new ChartRenderer(rowCanvas, w, h2); - renderer.labelColor = resolveChartLabelColor(this); - renderer.basePad = { - top: 24, - right: 12, - bottom: isLastRow ? 48 : 10, - left: 12 - }; - renderer.clear(); - const rowAnalysis = effectiveAnalysisMap.get( - seriesItem.entityId - ) || normalizeHistorySeriesAnalysis(null); - const rowTrendPts = rowAnalysis.show_trend_lines === true ? trendPointsMap.get( - seriesItem.entityId - ) || [] : []; - const rowRatePts = rowAnalysis.show_rate_of_change === true ? ratePointsMap.get( - seriesItem.entityId - ) || [] : []; - const rowDeltaPts = rowAnalysis.show_delta_analysis === true && hasSelectedComparisonWindow ? deltaPointsMap.get( - seriesItem.entityId - ) || [] : []; - const rowSummaryStats = rowAnalysis.show_summary_stats === true ? summaryStatsMap.get(seriesItem.entityId) || null : null; - const rowAnomalyClusters = rowAnalysis.show_anomalies === true ? anomalyClustersMap.get( - seriesItem.entityId - ) || [] : []; - const rowHideSource = this._seriesShouldHideSource( - rowAnalysis, - hasSelectedComparisonWindow - ); - const axisValues = seriesItem.pts.map( - ([, v2]) => v2 - ); - const extent = this._getAxisValueExtent(axisValues); - let axisMin = 0; - let axisMax = 1; - if (extent) { - const pad = (extent.max - extent.min) * 0.1 || 1; - axisMin = extent.min - pad; - axisMax = extent.max + pad; - } - const primaryAxisKey = seriesItem.axisKey || seriesItem.unit || "__unitless__"; - const axis = { - key: primaryAxisKey, - unit: String(seriesItem.unit || ""), - color: seriesItem.color || null, - side: "left", - min: axisMin, - max: axisMax, - values: axisValues - }; - const rowAxes = [axis]; - let rowRateAxisKey = null; - if (rowRatePts.length >= 2) { - const rateVals = rowRatePts.map(([, v2]) => v2); - const rateExt = this._getAxisValueExtent(rateVals); - if (rateExt) { - const pad = (rateExt.max - rateExt.min) * 0.1 || 1; - rowRateAxisKey = `rate:${primaryAxisKey}`; - rowAxes.push({ - key: rowRateAxisKey, - unit: seriesItem.unit ? `${String(seriesItem.unit)}/h` : "Rate/h", - color: seriesItem.color || null, - side: "right", - min: rateExt.min - pad, - max: rateExt.max + pad, - values: rateVals - }); - } - } - let rowDeltaAxisKey = null; - if (rowDeltaPts.length >= 2) { - const deltaVals = rowDeltaPts.map(([, v2]) => v2); - const deltaExt = this._getAxisValueExtent(deltaVals); - if (deltaExt) { - const pad = (deltaExt.max - deltaExt.min) * 0.1 || 1; - rowDeltaAxisKey = `delta:${primaryAxisKey}`; - rowAxes.push({ - key: rowDeltaAxisKey, - unit: seriesItem.unit ? `Δ ${String(seriesItem.unit)}` : "Δ", - color: seriesItem.color || null, - side: "right", - min: deltaExt.min - pad, - max: deltaExt.max + pad, - values: deltaVals - }); - } - } - renderer.drawGrid(renderT0, renderT1, rowAxes, void 0, 4, { - fixedAxisOverlay: true, - hideTimeLabels: !isLastRow - }); - const activeAxes = renderer._activeAxes; - const resolvedAxis = activeAxes?.[0] || axis; - const resolvedRateAxis = rowRateAxisKey ? activeAxes?.find((a2) => a2.key === rowRateAxisKey) || null : null; - const resolvedDeltaAxis = rowDeltaAxisKey ? activeAxes?.find((a2) => a2.key === rowDeltaAxisKey) || null : null; - seriesItem.axis = resolvedAxis; - let mainSeriesOpacity; - if (!comparisonPreviewActive) { - mainSeriesOpacity = 1; - } else if (hoveringDifferentComparison) { - mainSeriesOpacity = 0.15; - } else { - mainSeriesOpacity = 0.25; - } - if (!rowHideSource) { - this._drawSeriesLine( - renderer, - seriesItem.pts, - seriesItem.color, - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - { - lineWidth: comparisonPreviewActive ? 1.25 : 1.75, - lineOpacity: mainSeriesOpacity, - stepped: rowAnalysis.stepped_series === true - } - ); - } - for (const win of drawableComparisonResults || []) { - const winPts = await this._resolveComparisonWindowPoints( - seriesItem.entityId, - win, - rowAnalysis, - renderT0, - renderT1 - ); - if (!winPts.length) { - continue; - } - const isHovered = !!hoveredComparisonWindowId && win.id === hoveredComparisonWindowId; - const isSelected = !!selectedComparisonWindowId && win.id === selectedComparisonWindowId; - const comparisonLineStyle = this._getComparisonWindowLineStyle( - isHovered, - isSelected, - hoveringDifferentComparison - ); - renderer.drawLine( - winPts, - seriesItem.color, - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - { - lineOpacity: comparisonLineStyle.lineOpacity, - lineWidth: comparisonLineStyle.lineWidth, - dashed: comparisonLineStyle.dashed, - dashPattern: comparisonLineStyle.dashPattern, - stepped: rowAnalysis.stepped_series === true - } - ); - this._drawComparisonAnalysisOverlays({ - renderer, - entityId: seriesItem.entityId, - seriesColor: seriesItem.color, - comparisonPts: winPts, - analysis: rowAnalysis, - renderT0, - renderT1, - axis: resolvedAxis, - rateAxis: resolvedRateAxis, - events, - comparisonWindowId: String(win.id || "") - }); - } - binaryBackgrounds.forEach((bg) => { - const bgItem = bg; - if (!this._hiddenSeries.has(bgItem.entityId) && bgItem.spans?.length) { - renderer.drawStateBands( - bgItem.spans, - renderT0, - renderT1, - bgItem.color, - 0.1 - ); - } - }); - if (shouldDrawCorrelatedAnomalySpans && correlatedAnomalySpans.length) { - renderer.drawStateBands( - correlatedAnomalySpans, - renderT0, - renderT1, - "#ef4444", - 0.1 - ); - } - renderer.drawAnnotations( - events, - renderT0, - renderT1, - { - showLines: this._config.show_event_lines !== false, - showMarkers: this._config.show_event_lines !== false - } - ); - this._drawRecordedEventPoints( - renderer, - [seriesItem], - events, - renderT0, - renderT1, - { - showIcons: this._config.show_event_markers !== false, - yOffset: rowOffset, - skipOverlayClear: true - } - ); - if (rowAnalysis.show_threshold_analysis === true) { - const thresholdValue = Number(rowAnalysis.threshold_value); - if (Number.isFinite(thresholdValue)) { - if (rowAnalysis.show_threshold_shading === true && seriesItem.pts.length) { - renderer.drawThresholdArea( - seriesItem.pts, - thresholdValue, - seriesItem.color, - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - { - mode: rowAnalysis.threshold_direction === "below" ? "below" : "above", - fillAlpha: rowHideSource ? 0.24 : 0.14 - } - ); - } - renderer.drawLine( - [ - [renderT0, thresholdValue], - [renderT1, thresholdValue] - ], - hexToRgba(seriesItem.color, rowHideSource ? 0.82 : 0.46), - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - { lineOpacity: rowHideSource ? 0.84 : 0.48, lineWidth: 1.15 } - ); - } - } - if (rowSummaryStats) { - if (rowAnalysis.show_summary_stats_shading === true) { - const fillAlpha = rowHideSource ? 0.1 : 0.06; - renderer.drawGradientBand( - rowSummaryStats.min, - rowSummaryStats.mean, - seriesItem.color, - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - { fillAlpha } - ); - renderer.drawGradientBand( - rowSummaryStats.max, - rowSummaryStats.mean, - seriesItem.color, - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - { fillAlpha } - ); - } - const summaryEntries = [ - { - type: "min", - value: rowSummaryStats.min, - alpha: rowHideSource ? 0.78 : 0.42, - width: 1.1, - dotted: true - }, - { - type: "mean", - value: rowSummaryStats.mean, - alpha: rowHideSource ? 0.94 : 0.78, - width: 1.8, - dotted: false - }, - { - type: "max", - value: rowSummaryStats.max, - alpha: rowHideSource ? 0.78 : 0.42, - width: 1.1, - dotted: true - } - ]; - for (const entry of summaryEntries) { - if (!Number.isFinite(entry.value)) continue; - renderer.drawLine( - [ - [renderT0, entry.value], - [renderT1, entry.value] - ], - hexToRgba(seriesItem.color, entry.alpha), - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - { - lineOpacity: rowHideSource ? 0.82 : 0.34, - lineWidth: entry.width, - dotted: entry.dotted - } - ); - } - } - if (rowTrendPts.length >= 2) { - const trendOpts = this._getTrendRenderOptions( - rowAnalysis.trend_method, - rowHideSource - ); - renderer.drawLine( - rowTrendPts, - hexToRgba(seriesItem.color, trendOpts.colorAlpha), - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - { - lineOpacity: trendOpts.lineOpacity, - lineWidth: trendOpts.lineWidth, - dashed: trendOpts.dashed, - dotted: trendOpts.dotted - } - ); - } - if (rowRatePts.length >= 2 && resolvedRateAxis) { - renderer.drawLine( - rowRatePts, - hexToRgba(seriesItem.color, rowHideSource ? 0.96 : 0.82), - renderT0, - renderT1, - resolvedRateAxis.min, - resolvedRateAxis.max, - { - lineOpacity: rowHideSource ? 0.88 : 0.66, - lineWidth: 1.55, - dashPattern: [7, 3, 1.5, 3] - } - ); - } - if (rowDeltaPts.length >= 2 && resolvedDeltaAxis && rowAnalysis.show_delta_lines === true) { - renderer.drawLine( - rowDeltaPts, - hexToRgba(seriesItem.color, 0.92), - renderT0, - renderT1, - resolvedDeltaAxis.min, - resolvedDeltaAxis.max, - { lineOpacity: 0.82, lineWidth: 1.9, dashed: true } - ); - } - let rowAnomalyRegions = []; - if (rowAnomalyClusters.length) { - const filteredClusters = this._filterAnnotatedAnomalyClusters( - { - entityId: seriesItem.entityId, - anomalyClusters: rowAnomalyClusters - }, - events - ); - if (filteredClusters.length > 0) { - const { baseClusters, regionClusters } = this._resolveAnomalyClusterDisplay( - filteredClusters, - rowAnalysis.anomaly_overlap_mode, - correlatedAnomalySpans - ); - const baseColor = hexToRgba( - seriesItem.color, - rowHideSource ? 0.96 : 0.86 - ); - const regionOpts = { - strokeAlpha: rowHideSource ? 0.98 : 0.9, - lineWidth: rowHideSource ? 2.5 : 2.1, - haloWidth: rowHideSource ? 5.5 : 4.8, - haloColor: "rgba(255,255,255,0.88)", - haloAlpha: rowHideSource ? 0.92 : 0.82, - fillColor: hexToRgba( - seriesItem.color, - rowHideSource ? 0.14 : 0.1 - ), - fillAlpha: 1, - pointPadding: rowHideSource ? 12 : 10, - minRadiusX: 10, - minRadiusY: 10 - }; - if (baseClusters.length > 0) { - renderer.drawAnomalyClusters( - baseClusters, - baseColor, - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - regionOpts - ); - } - rowAnomalyRegions = renderer.getAnomalyClusterRegions( - regionClusters, - renderT0, - renderT1, - resolvedAxis.min, - resolvedAxis.max, - regionOpts - ).map( - (region) => ({ - ...region, - relatedEntityId: String(seriesItem.entityId), - label: String(seriesItem.label), - unit: String(seriesItem.unit || ""), - color: seriesItem.color || null, - sensitivity: String(rowAnalysis.anomaly_sensitivity || "") - }) - ); - } - } - tracks.push({ - canvas: rowCanvas, - renderer, - series: seriesItem, - axis: resolvedAxis, - rowOffset, - analysis: rowAnalysis, - summaryStats: rowSummaryStats, - trendPts: rowTrendPts, - ratePts: rowRatePts, - rateAxis: resolvedRateAxis, - deltaPts: rowDeltaPts, - deltaAxis: resolvedDeltaAxis, - anomalyRegions: rowAnomalyRegions - }); - } - this._renderSplitAxisOverlays(tracks); - this._renderComparisonPreviewOverlay( - tracks[0] ? tracks[0].renderer : null - ); - const comparisonHoverSeries = []; - for (const track of tracks) { - const trackSeries = track.series; - const trackAnalysis = track.analysis || normalizeHistorySeriesAnalysis(null); - for (const win of drawableComparisonResults || []) { - const winPts = await this._resolveComparisonWindowPoints( - trackSeries.entityId, - win, - trackAnalysis, - renderT0, - renderT1 - ); - if (!winPts.length) { - continue; - } - const isHovered = !!hoveredComparisonWindowId && win.id === hoveredComparisonWindowId; - const isSelected = !!selectedComparisonWindowId && win.id === selectedComparisonWindowId; - const comparisonLineStyle = this._getComparisonWindowLineStyle( - isHovered, - isSelected, - hoveringDifferentComparison - ); - const winId = String(win.id || ""); - const splitPrecomputed = this._analysisCache?.result?.comparisonWindowResults?.[winId]?.[trackSeries.entityId]; - comparisonHoverSeries.push({ - entityId: `${winId}:${trackSeries.entityId}`, - relatedEntityId: trackSeries.entityId, - comparisonParentId: `${winId}:${trackSeries.entityId}`, - label: trackSeries.label, - windowLabel: win.label || "Date window", - unit: trackSeries.unit, - pts: winPts, - trendPts: trackAnalysis.show_trend_lines === true && winPts.length >= 2 ? splitPrecomputed?.trendPts ?? this._buildTrendPoints( - winPts, - trackAnalysis.trend_method, - trackAnalysis.trend_window - ) : [], - ratePts: trackAnalysis.show_rate_of_change === true && winPts.length >= 2 ? splitPrecomputed?.ratePts ?? this._buildRateOfChangePoints( - winPts, - trackAnalysis.rate_window - ) : [], - summaryStats: trackAnalysis.show_summary_stats === true ? splitPrecomputed?.summaryStats ?? this._buildSummaryStats(winPts) : null, - thresholdValue: trackAnalysis.show_threshold_analysis === true ? Number(trackAnalysis.threshold_value) : null, - color: trackSeries.color, - hoverOpacity: comparisonLineStyle.hoverOpacity, - track - }); - } - } - this._attachSplitHover( - tracks, - comparisonHoverSeries, - events, - renderT0, - renderT1, - chartStage, - options, - effectiveAnalysisMap, - hasSelectedComparisonWindow - ); - this._fireComparisonBackendAnomalyRequests( - drawableComparisonResults, - effectiveAnalysisMap, - renderT0, - renderT1 - ); - } - // ── Split-view hover / zoom / scroll interaction ───────────────────────────── - _attachSplitHover(tracks, comparisonHoverSeries, events, t0, t1, chartStage, options, analysisMap, hasSelectedComparisonWindow) { - if (this._chartHoverCleanup) { - this._chartHoverCleanup(); - this._chartHoverCleanup = null; - } - if (!tracks.length || !chartStage) { - return; - } - const primaryRenderer = tracks[0].renderer; - const lastTrack = tracks[tracks.length - 1]; - const eventThresholdMs = primaryRenderer.cw ? 14 * ((t1 - t0) / primaryRenderer.cw) : 0; - const splitSelTop = tracks[0].rowOffset + primaryRenderer.pad.top; - const splitSelBottom = lastTrack.rowOffset + lastTrack.renderer.pad.top + lastTrack.renderer.ch; - const splitSelHeight = splitSelBottom - splitSelTop; - const overlayEl = document.createElement("div"); - overlayEl.id = "chart-split-overlay"; - overlayEl.style.cssText = "position:absolute;inset:0;pointer-events:auto;z-index:2;cursor:crosshair;"; - chartStage.appendChild(overlayEl); - const overlayRelX = (clientX) => { - const rect = overlayEl.getBoundingClientRect(); - return clampChartValue( - clientX - rect.left, - primaryRenderer.pad.left, - primaryRenderer.pad.left + primaryRenderer.cw - ); - }; - const stageXToTime = (stageX) => { - const ratio = primaryRenderer.cw ? (stageX - primaryRenderer.pad.left) / primaryRenderer.cw : 0; - return t0 + ratio * (t1 - t0); - }; - const inPlotBoundsX = (clientX) => { - const rect = overlayEl.getBoundingClientRect(); - const localX = clientX - rect.left; - return localX >= primaryRenderer.pad.left && localX <= primaryRenderer.pad.left + primaryRenderer.cw; - }; - const buildSplitHover = (clientX) => { - const baseRect = tracks[0].canvas.getBoundingClientRect(); - if (!baseRect.width || !primaryRenderer.cw) { - return null; - } - const localX = clampChartValue( - clientX - baseRect.left, - primaryRenderer.pad.left, - primaryRenderer.pad.left + primaryRenderer.cw - ); - const ratio = (localX - primaryRenderer.pad.left) / primaryRenderer.cw; - const rawTimeMs = t0 + ratio * (t1 - t0); - const hoverSnapMode = this._config.hover_snap_mode === "snap_to_data_points" ? "snap_to_data_points" : "follow_series"; - const splitHoverSeries = tracks.map( - (track) => ({ - pts: track.series?.pts || [] - }) - ); - const timeMs = resolveLineChartHoverTime( - splitHoverSeries, - rawTimeMs, - hoverSnapMode - ); - const x2 = primaryRenderer.xOf(timeMs, t0, t1); - const values = tracks.map( - (trackItem) => { - const { - renderer: trackRenderer, - series, - axis, - rowOffset - } = trackItem; - const value = trackRenderer._interpolateValue( - series.pts, - timeMs - ); - if (value == null) { - return { - entityId: String( - series.entityId || "" - ), - label: String(series.label || ""), - value: null, - unit: String(series.unit || ""), - color: String(series.color || ""), - opacity: 1, - hasValue: false, - axisSide: "left", - axisSlot: 0 - }; - } - return { - entityId: String( - series.entityId || "" - ), - label: String(series.label || ""), - value, - unit: String(series.unit || ""), - color: String(series.color || ""), - opacity: 1, - hasValue: true, - x: x2, - y: rowOffset + trackRenderer.yOf( - value, - axis.min, - axis.max - ), - axisSide: "left", - axisSlot: 0 - }; - } - ); - const hoveredEvents = []; - for (const event of events || []) { - const eventTime = new Date( - event.timestamp - ).getTime(); - if (eventTime < t0 || eventTime > t1) { - continue; - } - const distance = Math.abs(eventTime - timeMs); - if (distance <= eventThresholdMs) { - hoveredEvents.push({ - ...event, - _hoverDistanceMs: distance - }); - } - } - hoveredEvents.sort( - (a2, b2) => (a2._hoverDistanceMs || 0) - (b2._hoverDistanceMs || 0) - ); - const comparisonValues = (comparisonHoverSeries || []).map((chs) => { - const { - pts, - entityId, - relatedEntityId, - comparisonParentId, - label, - windowLabel, - unit, - color, - hoverOpacity, - track: cTrack - } = chs; - const value = cTrack.renderer._interpolateValue(pts, timeMs); - if (value == null) { - return { - entityId: String(entityId || ""), - label: String(label || ""), - value: null, - unit: String(unit || ""), - color, - opacity: Number(hoverOpacity) || 1, - hasValue: false, - axisSide: "left", - axisSlot: 0 - }; - } - return { - entityId: String(entityId || ""), - relatedEntityId: String(relatedEntityId || ""), - comparisonParentId: String(comparisonParentId || ""), - label: String(label || ""), - windowLabel: String(windowLabel || ""), - value, - unit: String(unit || ""), - color, - opacity: Number(hoverOpacity) || 1, - hasValue: true, - x: x2, - y: cTrack.rowOffset + cTrack.renderer.yOf( - value, - cTrack.axis.min, - cTrack.axis.max - ), - axisSide: "left", - axisSlot: 0 - }; - }); - const trendValues = []; - const rateValues = []; - const deltaValues = []; - const summaryValues = []; - const thresholdValues = []; - const anomalyRegions = []; - let showTrendCrosshairs = false; - let showRateCrosshairs = false; - for (const track of tracks) { - const { - renderer: trackRenderer, - series: trackSeries, - axis: trackAxis, - rowOffset: trackRowOffset, - analysis: trackAnalysis, - summaryStats: trackSummaryStats, - trendPts: trackTrendPts, - ratePts: trackRatePts, - rateAxis: trackRateAxis, - deltaPts: trackDeltaPts, - deltaAxis: trackDeltaAxis, - anomalyRegions: trackAnomalyRegions - } = track; - const effectiveAnalysis = trackAnalysis || (analysisMap || /* @__PURE__ */ new Map()).get( - trackSeries.entityId - ) || normalizeHistorySeriesAnalysis(null); - const trackHideSource = this._seriesShouldHideSource( - effectiveAnalysis, - hasSelectedComparisonWindow - ); - if (effectiveAnalysis.show_trend_lines === true && Array.isArray(trackTrendPts) && trackTrendPts.length >= 2) { - if (effectiveAnalysis.show_trend_crosshairs === true) - showTrendCrosshairs = true; - const trendOpts = this._getTrendRenderOptions( - effectiveAnalysis.trend_method, - trackHideSource - ); - const trendVal = trackRenderer._interpolateValue(trackTrendPts, timeMs); - trendValues.push({ - entityId: `trend:${trackSeries.entityId}`, - relatedEntityId: String( - trackSeries.entityId || "" - ), - label: String(trackSeries.label || ""), - baseLabel: String( - trackSeries.label || "" - ), - unit: String(trackSeries.unit || ""), - color: hexToRgba( - trackSeries.color, - trendOpts.colorAlpha - ), - opacity: trendOpts.lineOpacity, - hasValue: trendVal != null, - value: trendVal ?? null, - ...trendVal != null ? { - x: x2, - y: trackRowOffset + trackRenderer.yOf( - trendVal, - trackAxis.min, - trackAxis.max - ) - } : {}, - axisSide: "left", - axisSlot: 0, - trend: true, - rawVisible: !trackHideSource, - showCrosshair: effectiveAnalysis.show_trend_crosshairs === true - }); - } - if (effectiveAnalysis.show_rate_of_change === true && Array.isArray(trackRatePts) && trackRatePts.length >= 2 && trackRateAxis) { - if (effectiveAnalysis.show_rate_crosshairs === true) { - showRateCrosshairs = true; - } - const rateVal = trackRenderer._interpolateValue(trackRatePts, timeMs); - rateValues.push({ - entityId: `rate:${trackSeries.entityId}`, - relatedEntityId: String( - trackSeries.entityId || "" - ), - label: String(trackSeries.label || ""), - baseLabel: String( - trackSeries.label || "" - ), - unit: trackSeries.unit ? `${String(trackSeries.unit)}/h` : "/h", - color: hexToRgba( - trackSeries.color, - trackHideSource ? 0.96 : 0.82 - ), - opacity: trackHideSource ? 0.88 : 0.66, - hasValue: rateVal != null, - value: rateVal ?? null, - ...rateVal != null ? { - x: x2, - y: trackRowOffset + trackRenderer.yOf( - rateVal, - trackRateAxis.min, - trackRateAxis.max - ) - } : {}, - axisSide: "right", - axisSlot: 0, - rate: true, - rawVisible: !trackHideSource, - showCrosshair: effectiveAnalysis.show_rate_crosshairs === true - }); - } - if (effectiveAnalysis.show_delta_analysis === true && effectiveAnalysis.show_delta_tooltip === true && Array.isArray(trackDeltaPts) && trackDeltaPts.length >= 2 && trackDeltaAxis) { - const deltaVal = trackRenderer._interpolateValue(trackDeltaPts, timeMs); - deltaValues.push({ - entityId: `delta:${trackSeries.entityId}`, - relatedEntityId: String( - trackSeries.entityId || "" - ), - label: String(trackSeries.label || ""), - baseLabel: String( - trackSeries.label || "" - ), - unit: String(trackSeries.unit || ""), - color: hexToRgba( - trackSeries.color, - 0.92 - ), - opacity: 0.82, - hasValue: deltaVal != null, - value: deltaVal ?? null, - ...deltaVal != null ? { - x: x2, - y: trackRowOffset + trackRenderer.yOf( - deltaVal, - trackDeltaAxis.min, - trackDeltaAxis.max - ) - } : {}, - axisSide: "right", - axisSlot: 0, - delta: true, - rawVisible: !trackHideSource - }); - } - if (effectiveAnalysis.show_summary_stats === true && trackSummaryStats) { - const summaryEntries = [ - { - type: "min", - value: trackSummaryStats.min, - alphaV: trackHideSource ? 0.94 : 0.78, - opac: trackHideSource ? 0.94 : 0.72 - }, - { - type: "mean", - value: trackSummaryStats.mean, - alphaV: trackHideSource ? 0.94 : 0.78, - opac: trackHideSource ? 0.94 : 0.72 - }, - { - type: "max", - value: trackSummaryStats.max, - alphaV: trackHideSource ? 0.94 : 0.78, - opac: trackHideSource ? 0.94 : 0.72 - } - ]; - for (const entry of summaryEntries) { - if (!Number.isFinite(entry.value)) continue; - summaryValues.push({ - entityId: `summary:${entry.type}:${trackSeries.entityId}`, - relatedEntityId: String( - trackSeries.entityId || "" - ), - label: String( - trackSeries.label || "" - ), - baseLabel: String( - trackSeries.label || "" - ), - unit: String(trackSeries.unit || ""), - color: hexToRgba( - trackSeries.color, - entry.alphaV - ), - opacity: entry.opac, - hasValue: true, - value: entry.value, - axisSide: "left", - axisSlot: 0, - summaryType: entry.type, - summary: true, - rawVisible: !trackHideSource - }); - } - } - if (effectiveAnalysis.show_threshold_analysis === true) { - const thresholdValue = Number(effectiveAnalysis.threshold_value); - if (Number.isFinite(thresholdValue)) { - thresholdValues.push({ - entityId: `threshold:${trackSeries.entityId}`, - relatedEntityId: String( - trackSeries.entityId || "" - ), - label: String( - trackSeries.label || "" - ), - baseLabel: String( - trackSeries.label || "" - ), - unit: String(trackSeries.unit || ""), - color: hexToRgba( - trackSeries.color, - trackHideSource ? 0.82 : 0.46 - ), - opacity: trackHideSource ? 0.84 : 0.48, - hasValue: true, - value: thresholdValue, - axisSide: "left", - axisSlot: 0, - threshold: true, - rawVisible: !trackHideSource - }); - } - } - if (Array.isArray(trackAnomalyRegions)) { - for (const region of trackAnomalyRegions) { - const clusterPoints = region?.cluster ? region.cluster?.points : void 0; - const regionStartMs = clusterPoints?.[0] ? clusterPoints[0]?.timeMs ?? region.startTime : region.startTime; - const regionEndMs = clusterPoints?.length ? clusterPoints[clusterPoints.length - 1]?.timeMs ?? region.endTime : region.endTime; - if (Number.isFinite(regionStartMs) && Number.isFinite(regionEndMs) && timeMs >= regionStartMs && timeMs <= regionEndMs) { - anomalyRegions.push(region); - } - } - } - } - for (const comparisonSeries of comparisonHoverSeries) { - const seriesEntry = comparisonSeries; - const track = seriesEntry.track; - const trackRenderer = track.renderer; - const trackAxis = track.axis; - const trackRateAxis = track.rateAxis || null; - const trackRowOffset = track.rowOffset; - const trendPts = Array.isArray(seriesEntry.trendPts) ? seriesEntry.trendPts : []; - if (trendPts.length >= 2) { - const trendVal = trackRenderer._interpolateValue(trendPts, timeMs); - trendValues.push({ - entityId: `trend:${seriesEntry.entityId}`, - relatedEntityId: String(seriesEntry.relatedEntityId || ""), - comparisonParentId: String(seriesEntry.comparisonParentId || ""), - label: String(seriesEntry.label || ""), - baseLabel: String(seriesEntry.label || ""), - windowLabel: String(seriesEntry.windowLabel || "Date window"), - unit: String(seriesEntry.unit || ""), - color: hexToRgba(seriesEntry.color, 0.34), - opacity: 0.34, - hasValue: trendVal != null, - value: trendVal ?? null, - ...trendVal != null ? { - x: x2, - y: trackRowOffset + trackRenderer.yOf(trendVal, trackAxis.min, trackAxis.max) - } : {}, - axisSide: "left", - axisSlot: 0, - trend: true, - rawVisible: true, - comparisonDerived: true - }); - } - const ratePts = Array.isArray(seriesEntry.ratePts) ? seriesEntry.ratePts : []; - if (ratePts.length >= 2 && trackRateAxis) { - const rateVal = trackRenderer._interpolateValue(ratePts, timeMs); - rateValues.push({ - entityId: `rate:${seriesEntry.entityId}`, - relatedEntityId: String(seriesEntry.relatedEntityId || ""), - comparisonParentId: String(seriesEntry.comparisonParentId || ""), - label: String(seriesEntry.label || ""), - baseLabel: String(seriesEntry.label || ""), - windowLabel: String(seriesEntry.windowLabel || "Date window"), - unit: seriesEntry.unit ? `${String(seriesEntry.unit)}/h` : "/h", - color: hexToRgba(seriesEntry.color, 0.46), - opacity: 0.46, - hasValue: rateVal != null, - value: rateVal ?? null, - ...rateVal != null ? { - x: x2, - y: trackRowOffset + trackRenderer.yOf( - rateVal, - trackRateAxis.min, - trackRateAxis.max - ) - } : {}, - axisSide: "right", - axisSlot: 0, - rate: true, - rawVisible: true, - comparisonDerived: true, - showCrosshair: analysisMap.get(String(seriesEntry.relatedEntityId || ""))?.show_rate_crosshairs === true - }); - } - const summaryStats = seriesEntry.summaryStats; - if (summaryStats) { - [ - { type: "min", value: summaryStats.min }, - { type: "mean", value: summaryStats.mean }, - { type: "max", value: summaryStats.max } - ].forEach((entry) => { - if (!Number.isFinite(entry.value)) { - return; - } - summaryValues.push({ - entityId: `summary:${entry.type}:${seriesEntry.entityId}`, - relatedEntityId: String(seriesEntry.relatedEntityId || ""), - comparisonParentId: String(seriesEntry.comparisonParentId || ""), - label: String(seriesEntry.label || ""), - baseLabel: String(seriesEntry.label || ""), - windowLabel: String(seriesEntry.windowLabel || "Date window"), - unit: String(seriesEntry.unit || ""), - color: hexToRgba( - seriesEntry.color, - entry.type === "mean" ? 0.44 : 0.24 - ), - opacity: entry.type === "mean" ? 0.52 : 0.3, - hasValue: true, - value: entry.value, - axisSide: "left", - axisSlot: 0, - summaryType: entry.type, - summary: true, - rawVisible: true, - comparisonDerived: true - }); - }); - } - const thresholdValue = Number(seriesEntry.thresholdValue); - if (Number.isFinite(thresholdValue)) { - thresholdValues.push({ - entityId: `threshold:${seriesEntry.entityId}`, - relatedEntityId: String(seriesEntry.relatedEntityId || ""), - comparisonParentId: String(seriesEntry.comparisonParentId || ""), - label: String(seriesEntry.label || ""), - baseLabel: String(seriesEntry.label || ""), - windowLabel: String(seriesEntry.windowLabel || "Date window"), - unit: String(seriesEntry.unit || ""), - color: hexToRgba(seriesEntry.color, 0.28), - opacity: 0.34, - hasValue: true, - value: thresholdValue, - axisSide: "left", - axisSlot: 0, - threshold: true, - rawVisible: true, - comparisonDerived: true - }); - } - } - const hideRawData = tracks.every((track) => { - const eff = track.analysis || (analysisMap || /* @__PURE__ */ new Map()).get( - track.series.entityId - ) || normalizeHistorySeriesAnalysis(null); - return this._seriesShouldHideSource( - eff, - hasSelectedComparisonWindow - ); - }); - const firstWithValue = values.find((value) => value.hasValue === true); - return { - x: x2, - y: firstWithValue?.y ?? splitSelTop + 12, - timeMs, - rangeStartMs: timeMs, - rangeEndMs: timeMs, - values: values.filter((value) => value.hasValue === true), - trendValues, - rateValues, - deltaValues, - summaryValues, - thresholdValues, - comparisonValues: comparisonValues.filter( - (value) => value.hasValue === true - ), - binaryValues: [], - primary: firstWithValue ?? null, - event: hoveredEvents.length > 0 ? (({ _hoverDistanceMs: _2, ...event }) => event)(hoveredEvents[0]) : null, - events: hoveredEvents.map( - ({ _hoverDistanceMs: _d, ...event }) => event - ), - anomalyRegions, - emphasizeGuides: options.emphasizeHoverGuides === true, - showTrendCrosshairs, - showRateCrosshairs, - hideRawData, - splitVertical: { top: splitSelTop, height: splitSelHeight } - }; - }; - const showFromPointer = (clientX, clientY) => { - if (this._chartZoomDragging) { - return; - } - const hover = buildSplitHover(clientX); - if (!hover) { - this._chartLastHover = null; - hideLineChartHover(this); - overlayEl.style.cursor = "default"; - return; - } - this._chartLastHover = hover; - showLineChartCrosshair(this, primaryRenderer, hover); - if (this._config.show_tooltips !== false) { - showLineChartTooltip(this, hover, clientX, clientY); - } else { - hideTooltip(this); - } - dispatchLineChartHover(this, hover); - overlayEl.style.cursor = "crosshair"; - }; - const hideHover = () => { - this._chartLastHover = null; - hideLineChartHover(this); - }; - const onMouseMove = (ev) => showFromPointer(ev.clientX, ev.clientY); - const onMouseLeave = (ev) => { - const nextTarget = ev.relatedTarget; - const addButton = this.querySelector("#chart-add-annotation"); - if (nextTarget && addButton instanceof HTMLElement && addButton.contains(nextTarget)) { - return; - } - hideHover(); - }; - const onTouchMove = (ev) => { - if (ev.touches.length === 1) { - showFromPointer(ev.touches[0].clientX, ev.touches[0].clientY); - } - }; - const onTouchEnd = () => hideHover(); - const onOverlayClick = (ev) => { - if (this._chartZoomDragging) return; - const hover = this._chartLastHover; - const regions = hover?.anomalyRegions; - if (!regions?.length) return; - ev.preventDefault(); - ev.stopPropagation(); - this._handleAnomalyAddAnnotation(regions); - }; - const addAnnotationBtn = this.querySelector( - "#chart-add-annotation" - ); - const onAddBtnClick = (ev) => { - if (!this._canAddAnnotation || !this._chartLastHover) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - this._handleChartAddAnnotation(this._chartLastHover); - }; - const onAddBtnLeave = (ev) => { - const nextTarget = ev.relatedTarget; - if (nextTarget instanceof Node && overlayEl.contains(nextTarget)) { - return; - } - hideHover(); - }; - overlayEl.addEventListener("mousemove", onMouseMove); - overlayEl.addEventListener("mouseleave", onMouseLeave); - overlayEl.addEventListener("touchmove", onTouchMove, { passive: true }); - overlayEl.addEventListener("touchend", onTouchEnd); - overlayEl.addEventListener("click", onOverlayClick); - addAnnotationBtn?.addEventListener("click", onAddBtnClick); - addAnnotationBtn?.addEventListener("mouseleave", onAddBtnLeave); - this._chartHoverCleanup = () => { - overlayEl.removeEventListener("mousemove", onMouseMove); - overlayEl.removeEventListener("mouseleave", onMouseLeave); - overlayEl.removeEventListener("touchmove", onTouchMove); - overlayEl.removeEventListener("touchend", onTouchEnd); - overlayEl.removeEventListener("click", onOverlayClick); - addAnnotationBtn?.removeEventListener("click", onAddBtnClick); - addAnnotationBtn?.removeEventListener("mouseleave", onAddBtnLeave); - }; - const selection = this.querySelector("#chart-zoom-selection"); - const hideZoomSelection = () => { - if (!selection) { - return; - } - selection.hidden = true; - selection.classList.remove("visible"); - }; - const renderZoomSelection = (startX, currentX) => { - if (!selection) { - return; - } - const left = Math.min(startX, currentX); - const width = Math.abs(currentX - startX); - selection.style.left = `${left}px`; - selection.style.top = `${splitSelTop}px`; - selection.style.width = `${width}px`; - selection.style.height = `${splitSelHeight}px`; - selection.hidden = false; - selection.classList.add("visible"); - }; - let zoomPointerId = null; - let zoomStartX = 0; - let zoomCurrentX = 0; - let zoomDragging = false; - const onZoomPointerMove = (ev) => { - if (zoomPointerId == null || ev.pointerId !== zoomPointerId) { - return; - } - zoomCurrentX = overlayRelX(ev.clientX); - if (!zoomDragging && Math.abs(zoomCurrentX - zoomStartX) < 6) { - return; - } - zoomDragging = true; - this._chartZoomDragging = true; - hideLineChartHover(this); - renderZoomSelection(zoomStartX, zoomCurrentX); - ev.preventDefault(); - }; - const finishZoom = (ev) => { - if (zoomPointerId == null || ev.pointerId !== zoomPointerId) { - return; - } - const didDrag = zoomDragging; - const endX = zoomCurrentX; - window.removeEventListener("pointermove", onZoomPointerMove); - window.removeEventListener("pointerup", finishZoom); - window.removeEventListener("pointercancel", finishZoom); - zoomPointerId = null; - zoomDragging = false; - this._chartZoomDragging = false; - hideZoomSelection(); - if (!didDrag || Math.abs(endX - zoomStartX) < 8) { - return; - } - const startTime = Math.min(stageXToTime(zoomStartX), stageXToTime(endX)); - const endTime = Math.max(stageXToTime(zoomStartX), stageXToTime(endX)); - this._applyZoomRange(startTime, endTime); - }; - const onZoomPointerDown = (ev) => { - if (ev.button !== 0 || !inPlotBoundsX(ev.clientX)) { - return; - } - zoomPointerId = ev.pointerId; - zoomStartX = overlayRelX(ev.clientX); - zoomCurrentX = zoomStartX; - zoomDragging = false; - this._chartZoomDragging = false; - window.addEventListener("pointermove", onZoomPointerMove); - window.addEventListener("pointerup", finishZoom); - window.addEventListener("pointercancel", finishZoom); - }; - const onZoomDoubleClick = (ev) => { - if (!inPlotBoundsX(ev.clientX)) { - return; - } - ev.preventDefault(); - this._clearZoomRange(); - }; - overlayEl.addEventListener("pointerdown", onZoomPointerDown); - overlayEl.addEventListener("dblclick", onZoomDoubleClick); - if (this._chartZoomCleanup) { - this._chartZoomCleanup(); - } - this._chartZoomCleanup = () => { - overlayEl.removeEventListener("pointerdown", onZoomPointerDown); - overlayEl.removeEventListener("dblclick", onZoomDoubleClick); - window.removeEventListener("pointermove", onZoomPointerMove); - window.removeEventListener("pointerup", finishZoom); - window.removeEventListener("pointercancel", finishZoom); - zoomPointerId = null; - zoomDragging = false; - this._chartZoomDragging = false; - hideZoomSelection(); - }; - } - }; - if (!customElements.get("hass-datapoints-history-chart")) { - customElements.define("hass-datapoints-history-chart", HistoryChart$1); - } - function createHiddenSeriesSet(seriesSettings = []) { - return new Set( - (Array.isArray(seriesSettings) ? seriesSettings : []).filter((entry) => entry?.visible === false).map((entry) => entry.entity_id || entry.entity || entry.entityId).filter((value) => Boolean(value)) - ); - } - function createHiddenEventIdSet(hiddenEventIds = []) { - return new Set( - (Array.isArray(hiddenEventIds) ? hiddenEventIds : []).filter(Boolean) - ); - } - async function fetchEvents(hass, startTime, endTime, entityIds) { - try { - const normalizedEntityIds = normalizeCacheIdList(entityIds); - const cacheKey = JSON.stringify({ - type: `${DOMAIN}/events`, - start_time: startTime, - end_time: endTime, - entity_ids: normalizedEntityIds - }); - return await withStableRangeCache(cacheKey, endTime, async () => { - const msg2 = { - type: `${DOMAIN}/events`, - start_time: startTime, - end_time: endTime - }; - if (normalizedEntityIds.length) { - msg2.entity_ids = normalizedEntityIds; - } - const result = await hass.connection.sendMessagePromise( - msg2 - ); - return result.events || []; - }); - } catch (err) { - logger.warn("[hass-datapoints] fetchEvents failed:", err); - return []; - } - } - function invalidateEventsCache() { - return clearStableRangeCacheMatching((key) => { - if (typeof key !== "string") { - return false; - } - return key.includes(`"type":"${DOMAIN}/events"`); - }); - } - async function fetchEventBounds(hass) { - try { - const result = await hass.connection.sendMessagePromise({ - type: `${DOMAIN}/events_bounds` - }); - return { - start: result?.start_time || null, - end: result?.end_time || null - }; - } catch (err) { - logger.warn("[hass-datapoints] fetchEventBounds failed:", err); - return { start: null, end: null }; - } - } - async function deleteEvent(hass, eventId) { - const result = await hass.connection.sendMessagePromise({ - type: `${DOMAIN}/events/delete`, - event_id: eventId - }); - invalidateEventsCache(); - window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); - return result; - } - async function updateEvent(hass, eventId, fields) { - const result = await hass.connection.sendMessagePromise({ - type: `${DOMAIN}/events/update`, - event_id: eventId, - ...fields - }); - invalidateEventsCache(); - window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); - return result; - } - const t$1 = { ATTRIBUTE: 1, CHILD: 2 }, e$1 = (t2) => (...e2) => ({ _$litDirective$: t2, values: e2 }); - let i$1 = class i { - constructor(t2) { - } - get _$AU() { - return this._$AM._$AU; - } - _$AT(t2, e2, i2) { - this._$Ct = t2, this._$AM = e2, this._$Ci = i2; - } - _$AS(t2, e2) { - return this.update(t2, e2); - } - update(t2, e2) { - return this.render(...e2); - } - }; - const { I: t } = j, i = (o2) => o2, s = () => document.createComment(""), v = (o2, n2, e2) => { - const l2 = o2._$AA.parentNode, d2 = void 0 === n2 ? o2._$AB : n2._$AA; - if (void 0 === e2) { - const i2 = l2.insertBefore(s(), d2), n3 = l2.insertBefore(s(), d2); - e2 = new t(i2, n3, o2, o2.options); - } else { - const t2 = e2._$AB.nextSibling, n3 = e2._$AM, c2 = n3 !== o2; - if (c2) { - let t3; - e2._$AQ?.(o2), e2._$AM = o2, void 0 !== e2._$AP && (t3 = o2._$AU) !== n3._$AU && e2._$AP(t3); - } - if (t2 !== d2 || c2) { - let o3 = e2._$AA; - for (; o3 !== t2; ) { - const t3 = i(o3).nextSibling; - i(l2).insertBefore(o3, d2), o3 = t3; - } - } - } - return e2; - }, u$1 = (o2, t2, i2 = o2) => (o2._$AI(t2, i2), o2), m = {}, p = (o2, t2 = m) => o2._$AH = t2, M = (o2) => o2._$AH, h = (o2) => { - o2._$AR(), o2._$AA.remove(); - }; - const u = (e2, s2, t2) => { - const r2 = /* @__PURE__ */ new Map(); - for (let l2 = s2; l2 <= t2; l2++) r2.set(e2[l2], l2); - return r2; - }, c = e$1(class extends i$1 { - constructor(e2) { - if (super(e2), e2.type !== t$1.CHILD) throw Error("repeat() can only be used in text expressions"); - } - dt(e2, s2, t2) { - let r2; - void 0 === t2 ? t2 = s2 : void 0 !== s2 && (r2 = s2); - const l2 = [], o2 = []; - let i2 = 0; - for (const s3 of e2) l2[i2] = r2 ? r2(s3, i2) : i2, o2[i2] = t2(s3, i2), i2++; - return { values: o2, keys: l2 }; - } - render(e2, s2, t2) { - return this.dt(e2, s2, t2).values; - } - update(s2, [t2, r2, c2]) { - const d2 = M(s2), { values: p$12, keys: a2 } = this.dt(t2, r2, c2); - if (!Array.isArray(d2)) return this.ut = a2, p$12; - const h$12 = this.ut ??= [], v$12 = []; - let m2, y2, x2 = 0, j2 = d2.length - 1, k2 = 0, w = p$12.length - 1; - for (; x2 <= j2 && k2 <= w; ) if (null === d2[x2]) x2++; - else if (null === d2[j2]) j2--; - else if (h$12[x2] === a2[k2]) v$12[k2] = u$1(d2[x2], p$12[k2]), x2++, k2++; - else if (h$12[j2] === a2[w]) v$12[w] = u$1(d2[j2], p$12[w]), j2--, w--; - else if (h$12[x2] === a2[w]) v$12[w] = u$1(d2[x2], p$12[w]), v(s2, v$12[w + 1], d2[x2]), x2++, w--; - else if (h$12[j2] === a2[k2]) v$12[k2] = u$1(d2[j2], p$12[k2]), v(s2, d2[x2], d2[j2]), j2--, k2++; - else if (void 0 === m2 && (m2 = u(a2, k2, w), y2 = u(h$12, x2, j2)), m2.has(h$12[x2])) if (m2.has(h$12[j2])) { - const e2 = y2.get(a2[k2]), t3 = void 0 !== e2 ? d2[e2] : null; - if (null === t3) { - const e3 = v(s2, d2[x2]); - u$1(e3, p$12[k2]), v$12[k2] = e3; - } else v$12[k2] = u$1(t3, p$12[k2]), v(s2, d2[x2], t3), d2[e2] = null; - k2++; - } else h(d2[j2]), j2--; - else h(d2[x2]), x2++; - for (; k2 <= w; ) { - const e2 = v(s2, v$12[w + 1]); - u$1(e2, p$12[k2]), v$12[k2++] = e2; - } - for (; x2 <= j2; ) { - const e2 = d2[x2++]; - null !== e2 && h(e2); - } - return this.ut = a2, p(s2, v$12), E; - } - }); - const styles$R = i$5` - :host { - display: block; + `; + shadowRoot.appendChild(style); + } + this._dialogEl.classList.remove("dp-shaking"); + this._dialogEl.offsetWidth; + this._dialogEl.classList.add("dp-shaking"); + this._dialogEl.addEventListener("animationend", () => this._dialogEl?.classList.remove("dp-shaking"), { once: true }); + } + formatDate(timeMs) { + const value = new Date(timeMs); + return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}T${String(value.getHours()).padStart(2, "0")}:${String(value.getMinutes()).padStart(2, "0")}`; + } + _buildChips(target) { + return [ + ...(target.entity_id || []).map((id) => { + const name = entityName(this._host._hass, id); + return { + type: "entity_id", + itemId: id, + icon: entityIcon(this._host._hass, id), + name, + secondaryText: name !== id ? id : "", + stateObj: (this._host?._hass?.states)?.[id] ?? null + }; + }), + ...(target.device_id || []).map((id) => ({ + type: "device_id", + itemId: id, + icon: deviceIcon(this._host._hass, id), + name: deviceName(this._host._hass, id) + })), + ...(target.area_id || []).map((id) => ({ + type: "area_id", + itemId: id, + icon: areaIcon(this._host._hass, id), + name: areaName(this._host._hass, id) + })), + ...(target.label_id || []).map((id) => ({ + type: "label_id", + itemId: id, + icon: labelIcon(this._host._hass, id), + name: labelName(this._host._hass, id) + })) + ]; + } + removeLinkedTarget(type, id) { + if (!this._linkedTarget || !type || !id) return; + const current = normalizeTargetSelection(this._linkedTarget); + if (!current[type]) return; + current[type] = current[type].filter((value) => value !== id); + this._linkedTarget = current; + if (this._chipRowEl) { + this._chipRowEl.hass = this._host._hass ?? null; + this._chipRowEl.chips = this._buildChips(this._linkedTarget); + } + } + bindTargetChipActions() {} + _getDefaultLinkedTarget() { + const config = this._host?._config || {}; + const hiddenSeries = this._host?._hiddenSeries instanceof Set ? this._host._hiddenSeries : /* @__PURE__ */ new Set(); + const visibleEntityIds = (Array.isArray(config.series_settings) ? config.series_settings : []).map((entry) => entry?.entity_id || entry?.entity || entry?.entityId || null).filter((entityId) => typeof entityId === "string" && entityId.length > 0 && !hiddenSeries.has(entityId)); + if (visibleEntityIds.length > 0) return normalizeTargetSelection({ entity_id: [...new Set(visibleEntityIds)] }); + return normalizeTargetSelection({ entity_id: (this._host?._entityIds || []).filter(Boolean) }); + } + bindFields(hover) { + if (!this._panelEl) return; + const prefill = hover?.annotationPrefill && typeof hover.annotationPrefill === "object" ? hover.annotationPrefill : {}; + const messageEl = this._panelEl.querySelector("#chart-context-message"); + const annotationEl = this._panelEl.querySelector("#chart-context-annotation"); + const iconPicker = this._panelEl.querySelector("#chart-context-icon"); + if (iconPicker) { + iconPicker.hass = this._host._hass; + iconPicker.value = prefill.icon || hover?.event?.icon || "mdi:bookmark"; + } + const targetSel = this._panelEl.querySelector("#chart-context-target"); + if (targetSel) { + targetSel.hass = this._host._hass; + targetSel.value = "{}"; + targetSel.addEventListener("value-changed", (ev) => { + this._target = normalizeTargetSelection(ev.detail.value || {}); + }); + } + if (messageEl) messageEl.value = prefill.message || ""; + if (annotationEl) annotationEl.value = prefill.annotation || ""; + const chipContainer = this._panelEl.querySelector("#chart-context-linked-targets"); + if (chipContainer && !this._chipRowEl) { + const chipRow = document.createElement("annotation-chip-row"); + chipRow.hass = this._host._hass ?? null; + chipRow.addEventListener("dp-target-remove", (ev) => { + const detail = ev.detail; + this.removeLinkedTarget(detail.type, detail.id); + }); + chipContainer.appendChild(chipRow); + this._chipRowEl = chipRow; + } + if (this._chipRowEl) this._chipRowEl.chips = this._buildChips(this._linkedTarget); + this.bindTargetChipActions(); + const colorInput = this._panelEl.querySelector("#chart-context-color"); + const colorPreview = this._panelEl.querySelector("#chart-context-color-preview"); + const syncColor = () => { + if (colorPreview && colorInput) colorPreview.style.background = colorInput.value; + }; + if (colorInput) { + syncColor(); + colorInput.addEventListener("input", syncColor); + colorInput.addEventListener("change", syncColor); + } + this._dialogEl?.querySelector("#chart-context-cancel")?.addEventListener("click", () => this.close()); + this._dialogEl?.querySelector("#chart-context-save")?.addEventListener("click", () => this.submit()); + [messageEl, annotationEl].forEach((field) => { + field?.addEventListener("keydown", (ev) => { + const kev = ev; + if (kev.key === "Enter" && (kev.ctrlKey || kev.metaKey)) { + kev.preventDefault(); + this.submit(); + } + }); + }); + } + async submit() { + if (!this._panelEl || !this._host._hass) return; + const messageEl = this._panelEl.querySelector("#chart-context-message"); + const annotationEl = this._panelEl.querySelector("#chart-context-annotation"); + const dateEl = this._panelEl.querySelector("#chart-context-date"); + const iconPicker = this._panelEl.querySelector("#chart-context-icon"); + const colorInput = this._panelEl.querySelector("#chart-context-color"); + const saveButton = this._dialogEl?.querySelector("#chart-context-save"); + const feedbackEl = this._panelEl.querySelector("#chart-context-feedback"); + const message = (messageEl?.value || "").trim(); + if (!message) { + messageEl?.focus?.(); + this._shake(); + return; + } + const mergedTarget = mergeTargetSelections(this._linkedTarget, this._target || {}); + const payload = { message }; + const annotation = (annotationEl?.value || "").trim(); + if (annotation) payload.annotation = annotation; + const dateVal = (dateEl?.value || "").trim(); + if (dateVal) { + const parsedDate = new Date(dateVal); + payload.date = Number.isFinite(parsedDate.getTime()) ? parsedDate.toISOString() : dateVal; + } + const icon = iconPicker?.value; + if (icon) payload.icon = icon; + payload.color = colorInput?.value || "#03a9f4"; + if (mergedTarget.entity_id.length) payload.entity_ids = mergedTarget.entity_id; + if (mergedTarget.device_id.length) payload.device_ids = mergedTarget.device_id; + if (mergedTarget.area_id.length) payload.area_ids = mergedTarget.area_id; + if (mergedTarget.label_id.length) payload.label_ids = mergedTarget.label_id; + if (saveButton) saveButton.disabled = true; + if (feedbackEl) { + feedbackEl.hidden = true; + feedbackEl.textContent = ""; + } + try { + await this._host._hass.callService(DOMAIN, "record", payload); + invalidateEventsCache(); + window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); + this.close(); + } catch (err) { + if (feedbackEl) { + feedbackEl.hidden = false; + feedbackEl.textContent = err?.message || "Failed to create annotation."; + } + this._shake(); + logger$1.error("[hass-datapoints history-card]", err); + } finally { + if (saveButton) saveButton.disabled = false; + } + } + open(hover) { + if (this._dialogEl && !this._dialogEl.open) this.teardown(); + this.ensureDialog(); + if (!this._dialogEl || !this._panelEl || this._dialogEl.open) return; + this.resetFormState(); + const prefillLinkedTarget = hover?.annotationPrefill?.linkedTarget; + if (prefillLinkedTarget && typeof prefillLinkedTarget === "object") this._linkedTarget = normalizeTargetSelection(prefillLinkedTarget); + else this._linkedTarget = this._getDefaultLinkedTarget(); + const defaultColor = hover?.annotationPrefill?.color || hover?.primary?.color || hover?.event?.color || "#03a9f4"; + this._panelEl.innerHTML = ` + +
+
+
+
+
+ +
Use a short title that will be shown in the chart tooltip and records list.
+ +
+
+ +
Add any longer context, outcome, or note you want to keep with this data point.
+ +
+
+
+ +
Optionally add more entities, devices, areas, or labels that should also be linked to this annotation.
+ +
+
+
+
+ +
The annotation will be placed at this exact moment on the chart.
+ +
+
+ +
Choose the icon shown for this data point in the chart and records list.
+ +
+
+ +
+ Pick a color for the point marker and its related timeline indicators. + +
+
+ + +
+
+
+
+
+ +
+ `; + if (this._dialogEl) { + this._dialogEl.hass = this._host._hass; + this._dialogEl.dialogInitialFocus = "#chart-context-message"; + } + const targetSel = this._panelEl.querySelector("#chart-context-target"); + if (targetSel) targetSel.selector = { target: {} }; + this.bindFields(hover); + this._dialogEl.open = true; + this._host._creatingContextAnnotation = true; + window.requestAnimationFrame(() => { + (this._panelEl?.querySelector("#chart-context-message"))?.focus?.(); + }); + } + close() { + this._host._creatingContextAnnotation = false; + if (this._dialogEl) this._dialogEl.open = false; + window.setTimeout(() => this.finalizeClose(), 0); + } + }; + //#endregion + //#region custom_components/hass_datapoints/src/lib/data/statistics-api.ts + async function fetchStatisticsDuringPeriod(hass, startTime, endTime, statisticIds, options = {}) { + const normalizedStatisticIds = normalizeCacheIdList(statisticIds); + const normalizedTypes = normalizeCacheIdList(options.types); + return withStableRangeCache(JSON.stringify({ + type: "recorder/statistics_during_period", + start_time: startTime, + end_time: endTime, + statistic_ids: normalizedStatisticIds, + period: options.period || "hour", + types: normalizedTypes + }), endTime, () => hass.connection.sendMessagePromise({ + type: "recorder/statistics_during_period", + start_time: startTime, + end_time: endTime, + statistic_ids: normalizedStatisticIds, + period: options.period || "hour", + types: normalizedTypes, + units: options.units || {} + })); + } + //#endregion + //#region custom_components/hass_datapoints/src/lib/history-page/history-session-state.ts + /** + * Session and preference ownership helpers for the history page. + */ + var PANEL_HISTORY_PREFERENCES_KEY = `${DOMAIN}:panel_history_preferences`; + var PANEL_HISTORY_SESSION_KEY = `${DOMAIN}:panel_history_session`; + function readHistoryPageSessionState() { + try { + const raw = window.sessionStorage?.getItem(PANEL_HISTORY_SESSION_KEY); + if (!raw) return null; + const parsed = JSON.parse(raw); + return parsed && typeof parsed === "object" ? parsed : null; + } catch { + return null; + } + } + function buildHistoryPageSessionState(source) { + return { + entities: source._entities, + series_rows: source._seriesRows, + target_selection: source._targetSelection, + target_selection_raw: source._targetSelectionRaw, + datapoint_scope: source._datapointScope, + show_chart_datapoint_icons: source._showChartDatapointIcons, + show_chart_datapoint_lines: source._showChartDatapointLines, + show_chart_tooltips: source._showChartTooltips, + show_chart_emphasized_hover_guides: source._showChartEmphasizedHoverGuides, + chart_hover_snap_mode: source._chartHoverSnapMode, + delink_chart_y_axis: source._delinkChartYAxis, + split_chart_view: source._splitChartView, + show_chart_trend_lines: false, + show_chart_summary_stats: false, + show_chart_rate_of_change: false, + show_chart_threshold_analysis: false, + show_chart_threshold_shading: false, + show_chart_anomalies: false, + chart_anomaly_method: "trend_residual", + chart_anomaly_rate_window: "1h", + chart_anomaly_zscore_window: "24h", + chart_anomaly_persistence_window: "1h", + chart_anomaly_comparison_window_id: null, + hide_chart_source_series: false, + show_chart_trend_crosshairs: false, + chart_trend_method: "rolling_average", + chart_trend_window: "24h", + chart_rate_window: "1h", + chart_anomaly_sensitivity: "medium", + chart_threshold_values: {}, + chart_threshold_directions: {}, + show_chart_delta_analysis: false, + show_chart_delta_tooltip: true, + show_chart_delta_lines: false, + show_chart_correlated_anomalies: source._showCorrelatedAnomalies, + chart_anomaly_overlap_mode: source._chartAnomalyOverlapMode || "all", + show_data_gaps: source._showDataGaps, + data_gap_threshold: source._dataGapThreshold, + content_split_ratio: source._contentSplitRatio, + start_time: source._startTime?.toISOString() || null, + end_time: source._endTime?.toISOString() || null, + zoom_start_time: source._chartZoomCommittedRange ? new Date(source._chartZoomCommittedRange.start).toISOString() : null, + zoom_end_time: source._chartZoomCommittedRange ? new Date(source._chartZoomCommittedRange.end).toISOString() : null, + date_windows: normalizeDateWindows(source._comparisonWindows), + hours: source._hours, + sidebar_collapsed: source._sidebarCollapsed, + sidebar_accordion_targets_open: source._sidebarAccordionTargetsOpen !== false, + sidebar_accordion_datapoints_open: source._sidebarAccordionDatapointsOpen !== false, + sidebar_accordion_analysis_open: source._sidebarAccordionAnalysisOpen !== false, + sidebar_accordion_chart_open: source._sidebarAccordionChartOpen !== false + }; + } + function writeHistoryPageSessionState(source) { + try { + window.sessionStorage?.setItem(PANEL_HISTORY_SESSION_KEY, JSON.stringify(buildHistoryPageSessionState(source))); + } catch {} + } + function normalizeHistoryPagePreferences(preferences, options = {}) { + const zoomValues = new Set((options.zoomOptions || []).map((option) => option.value)); + const snapValues = new Set((options.snapOptions || []).map((option) => option.value)); + let shouldPersistDefaults = false; + const normalized = { + zoomLevel: "auto", + dateSnapping: "hour", + preferredSeriesColors: {}, + comparisonWindows: [], + pageState: null, + shouldPersistDefaults + }; + if (preferences && typeof preferences === "object") { + if (preferences.zoom_level && zoomValues.has(preferences.zoom_level)) normalized.zoomLevel = preferences.zoom_level; + else shouldPersistDefaults = true; + if (preferences.date_snapping && snapValues.has(preferences.date_snapping)) normalized.dateSnapping = preferences.date_snapping; + else shouldPersistDefaults = true; + normalized.preferredSeriesColors = preferences.series_colors && typeof preferences.series_colors === "object" ? Object.entries(preferences.series_colors).reduce((acc, [entityId, color]) => { + if (typeof entityId === "string" && /^#[0-9a-f]{6}$/i.test(color || "")) acc[entityId] = color; + return acc; + }, {}) : {}; + normalized.comparisonWindows = normalizeDateWindows(preferences.date_windows); + normalized.pageState = preferences.page_state && typeof preferences.page_state === "object" ? { + ...preferences.page_state, + date_windows: normalizeDateWindows(preferences.page_state?.date_windows) + } : null; + } else shouldPersistDefaults = true; + normalized.shouldPersistDefaults = shouldPersistDefaults; + return normalized; + } + function buildHistoryPagePreferencesPayload(source) { + const preferredSeriesColors = source._seriesRows.reduce((acc, row) => { + const entityId = typeof row === "object" && row && "entity_id" in row ? row.entity_id : null; + const color = typeof row === "object" && row && "color" in row ? row.color : null; + if (typeof entityId === "string" && /^#[0-9a-f]{6}$/i.test(String(color || ""))) acc[entityId] = String(color); + return acc; + }, { ...source._preferredSeriesColors }); + return { + zoom_level: source._zoomLevel, + date_snapping: source._dateSnapping, + series_colors: preferredSeriesColors, + date_windows: normalizeDateWindows(source._comparisonWindows), + page_state: buildHistoryPageSessionState(source) + }; + } + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/history.ts + var HassRecordsHistoryCard = class extends ChartCardBase { + constructor() { + super(); + _defineProperty(this, "_hiddenSeries", /* @__PURE__ */ new Set()); + _defineProperty(this, "_hiddenEventIds", /* @__PURE__ */ new Set()); + _defineProperty(this, "_zoomRange", null); + _defineProperty(this, "_configKey", void 0); + _defineProperty(this, "_comparisonRequestId", 0); + _defineProperty(this, "_comparisonDataCache", /* @__PURE__ */ new Map()); + _defineProperty(this, "_lastComparisonResults", null); + _defineProperty(this, "_lastHistResult", null); + _defineProperty(this, "_lastStatsResult", null); + _defineProperty(this, "_lastEvents", null); + _defineProperty(this, "_lastT0", 0); + _defineProperty(this, "_lastT1", 0); + _defineProperty(this, "_scrollSyncSuspended", false); + _defineProperty(this, "_lastProgrammaticScrollLeft", null); + _defineProperty(this, "_ignoreNextProgrammaticScrollEvent", false); + _defineProperty(this, "_adjustComparisonAxisScale", false); + _defineProperty(this, "_drawRequestId", 0); + _defineProperty(this, "_zoomReloadTimer", null); + _defineProperty(this, "_chartScrollViewportEl", null); + _defineProperty(this, "_annotationDialog", void 0); + _defineProperty(this, "_onWindowKeyDown", void 0); + _defineProperty(this, "_onChartScroll", void 0); + _defineProperty(this, "_onZoomApply", void 0); + this._annotationDialog = new HistoryAnnotationDialogController(this); + this._onWindowKeyDown = (ev) => this._handleWindowKeyDown(ev); + this._onChartScroll = () => this._handleChartScroll(); + this._onZoomApply = (ev) => { + const detail = ev.detail; + this._zoomRange = detail ? { + start: detail.start, + end: detail.end + } : null; + this._dispatchZoomRange(detail ? "zoom" : "reset"); + if (this._lastHistResult && this._lastEvents) this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._lastEvents, this._lastT0, this._lastT1); + this._scheduleZoomReload(); + }; + } + /** + * Override the base setter to prevent a full history refetch on every HA + * state update. History data is fetched from the history/statistics APIs and + * does not change when entity states tick; reloads are already triggered by: + * - config changes (setConfig) + * - new datapoints recorded (hass_datapoints_event_recorded subscription) + * - auto-refresh interval (base class) + * + * We still call requestUpdate() so that entity-friendly-names in the legend + * stay fresh, and we propagate the new hass reference down to hass-datapoints-history-chart. + */ + set hass(hass) { + this._hass = hass; + this.requestUpdate(); + const chartEl = this._chartEl(); + if (chartEl) chartEl.hass = hass; + } + get hass() { + return this._hass; + } + connectedCallback() { + window.addEventListener("keydown", this._onWindowKeyDown); + this.addEventListener("hass-datapoints-zoom-apply", this._onZoomApply); + super.connectedCallback(); + } + disconnectedCallback() { + window.removeEventListener("keydown", this._onWindowKeyDown); + this.removeEventListener("hass-datapoints-zoom-apply", this._onZoomApply); + if (this._zoomReloadTimer != null) { + window.clearTimeout(this._zoomReloadTimer); + this._zoomReloadTimer = null; + } + if (this._chartScrollViewportEl) this._chartScrollViewportEl.removeEventListener("scroll", this._onChartScroll); + this._annotationDialog?.teardown(); + super.disconnectedCallback(); + } + setConfig(config) { + if (!config.entity && !config.entities && !config.target && !(Array.isArray(config.series_settings) && config.series_settings.length > 0)) throw new Error("hass-datapoints-history-card: define `target`, `entity`, `entities` or `series_settings`"); + const nextConfig = { + hours_to_show: 24, + ...config, + target: config.target ? normalizeTargetValue(config.target) : void 0, + series_settings: Array.isArray(config.series_settings) ? config.series_settings.map((entry) => ({ + ...entry, + analysis: entry?.analysis && typeof entry.analysis === "object" ? { ...entry.analysis } : entry?.analysis + })) : config.series_settings, + hidden_event_ids: Array.isArray(config.hidden_event_ids) ? [...config.hidden_event_ids] : config.hidden_event_ids, + hovered_event_ids: Array.isArray(config.hovered_event_ids) ? [...config.hovered_event_ids] : config.hovered_event_ids, + comparison_windows: Array.isArray(config.comparison_windows) ? config.comparison_windows.map((entry) => ({ ...entry })) : config.comparison_windows, + preload_comparison_windows: Array.isArray(config.preload_comparison_windows) ? config.preload_comparison_windows.map((entry) => ({ ...entry })) : config.preload_comparison_windows, + comparison_preview_overlay: config.comparison_preview_overlay ? { ...config.comparison_preview_overlay } : null, + selected_comparison_window_id: config.selected_comparison_window_id || null, + hovered_comparison_window_id: config.hovered_comparison_window_id || null, + show_trend_lines: config.show_trend_lines === true, + show_summary_stats: config.show_summary_stats === true, + show_rate_of_change: config.show_rate_of_change === true, + show_threshold_analysis: config.show_threshold_analysis === true, + show_threshold_shading: config.show_threshold_shading === true, + hide_raw_data: config.hide_raw_data === true, + show_trend_crosshairs: config.show_trend_crosshairs === true, + trend_method: config.trend_method || "rolling_average", + trend_window: config.trend_window || "24h", + rate_window: config.rate_window || "1h", + threshold_values: config.threshold_values && typeof config.threshold_values === "object" ? { ...config.threshold_values } : {}, + threshold_directions: config.threshold_directions && typeof config.threshold_directions === "object" ? { ...config.threshold_directions } : {}, + show_delta_analysis: config.show_delta_analysis === true, + show_delta_tooltip: config.show_delta_tooltip !== false, + show_delta_lines: config.show_delta_lines === true, + hide_delta_source_series: config.hide_delta_source_series === true, + delink_y_axis: config.delink_y_axis === true, + split_view: config.split_view === true, + show_data_gaps: config.show_data_gaps !== false, + data_gap_threshold: config.data_gap_threshold || "2h" + }; + const currentConfig = this._config || {}; + const currentDataKey = JSON.stringify({ + target: currentConfig.target || null, + entities: currentConfig.entities, + entity: currentConfig.entity, + series_entities: Array.isArray(currentConfig.series_settings) ? currentConfig.series_settings.map((entry) => entry?.entity_id || entry?.entity || entry?.entityId || null) : null, + datapoint_scope: currentConfig.datapoint_scope, + hours_to_show: currentConfig.hours_to_show, + start_time: currentConfig.start_time, + end_time: currentConfig.end_time + }); + const nextDataKey = JSON.stringify({ + target: nextConfig.target || null, + entities: nextConfig.entities, + entity: nextConfig.entity, + series_entities: Array.isArray(nextConfig.series_settings) ? nextConfig.series_settings.map((entry) => entry?.entity_id || entry?.entity || entry?.entityId || null) : null, + datapoint_scope: nextConfig.datapoint_scope, + hours_to_show: nextConfig.hours_to_show, + start_time: nextConfig.start_time, + end_time: nextConfig.end_time + }); + const currentViewKey = JSON.stringify({ + series_settings: currentConfig.series_settings || [], + zoom_start_time: currentConfig.zoom_start_time, + zoom_end_time: currentConfig.zoom_end_time, + message_filter: currentConfig.message_filter || "", + hidden_event_ids: currentConfig.hidden_event_ids || [], + hovered_event_ids: currentConfig.hovered_event_ids || [], + show_event_markers: currentConfig.show_event_markers !== false, + show_event_lines: currentConfig.show_event_lines !== false, + show_tooltips: currentConfig.show_tooltips !== false, + emphasize_hover_guides: currentConfig.emphasize_hover_guides === true, + hover_snap_mode: currentConfig.hover_snap_mode || "follow_series", + show_correlated_anomalies: currentConfig.show_correlated_anomalies === true, + show_trend_lines: currentConfig.show_trend_lines === true, + show_summary_stats: currentConfig.show_summary_stats === true, + show_rate_of_change: currentConfig.show_rate_of_change === true, + show_threshold_analysis: currentConfig.show_threshold_analysis === true, + show_threshold_shading: currentConfig.show_threshold_shading === true, + show_anomalies: currentConfig.show_anomalies === true, + hide_raw_data: currentConfig.hide_raw_data === true, + show_trend_crosshairs: currentConfig.show_trend_crosshairs === true, + trend_method: currentConfig.trend_method || "rolling_average", + trend_window: currentConfig.trend_window || "24h", + rate_window: currentConfig.rate_window || "1h", + anomaly_sensitivity: currentConfig.anomaly_sensitivity || "medium", + threshold_values: currentConfig.threshold_values || {}, + threshold_directions: currentConfig.threshold_directions || {}, + show_delta_analysis: currentConfig.show_delta_analysis === true, + show_delta_tooltip: currentConfig.show_delta_tooltip !== false, + show_delta_lines: currentConfig.show_delta_lines === true, + hide_delta_source_series: currentConfig.hide_delta_source_series === true, + delink_y_axis: currentConfig.delink_y_axis === true, + split_view: currentConfig.split_view === true, + show_data_gaps: currentConfig.show_data_gaps !== false, + data_gap_threshold: currentConfig.data_gap_threshold || "2h", + comparison_hover_active: currentConfig.comparison_hover_active === true, + selected_comparison_window_id: currentConfig.selected_comparison_window_id || null, + hovered_comparison_window_id: currentConfig.hovered_comparison_window_id || null + }); + const nextViewKey = JSON.stringify({ + series_settings: nextConfig.series_settings || [], + zoom_start_time: nextConfig.zoom_start_time, + zoom_end_time: nextConfig.zoom_end_time, + message_filter: nextConfig.message_filter || "", + hidden_event_ids: nextConfig.hidden_event_ids || [], + hovered_event_ids: nextConfig.hovered_event_ids || [], + show_event_markers: nextConfig.show_event_markers !== false, + show_event_lines: nextConfig.show_event_lines !== false, + show_tooltips: nextConfig.show_tooltips !== false, + emphasize_hover_guides: nextConfig.emphasize_hover_guides === true, + hover_snap_mode: nextConfig.hover_snap_mode || "follow_series", + show_correlated_anomalies: nextConfig.show_correlated_anomalies === true, + show_trend_lines: nextConfig.show_trend_lines === true, + show_summary_stats: nextConfig.show_summary_stats === true, + show_rate_of_change: nextConfig.show_rate_of_change === true, + show_threshold_analysis: nextConfig.show_threshold_analysis === true, + show_threshold_shading: nextConfig.show_threshold_shading === true, + show_anomalies: nextConfig.show_anomalies === true, + hide_raw_data: nextConfig.hide_raw_data === true, + show_trend_crosshairs: nextConfig.show_trend_crosshairs === true, + trend_method: nextConfig.trend_method || "rolling_average", + trend_window: nextConfig.trend_window || "24h", + rate_window: nextConfig.rate_window || "1h", + anomaly_sensitivity: nextConfig.anomaly_sensitivity || "medium", + threshold_values: nextConfig.threshold_values || {}, + threshold_directions: nextConfig.threshold_directions || {}, + show_delta_analysis: nextConfig.show_delta_analysis === true, + show_delta_tooltip: nextConfig.show_delta_tooltip !== false, + show_delta_lines: nextConfig.show_delta_lines === true, + hide_delta_source_series: nextConfig.hide_delta_source_series === true, + delink_y_axis: nextConfig.delink_y_axis === true, + split_view: nextConfig.split_view === true, + show_data_gaps: nextConfig.show_data_gaps !== false, + data_gap_threshold: nextConfig.data_gap_threshold || "2h", + comparison_hover_active: nextConfig.comparison_hover_active === true, + selected_comparison_window_id: nextConfig.selected_comparison_window_id || null, + hovered_comparison_window_id: nextConfig.hovered_comparison_window_id || null + }); + const currentComparisonKey = JSON.stringify(currentConfig.comparison_windows || []); + const nextComparisonKey = JSON.stringify(nextConfig.comparison_windows || []); + const currentPreloadComparisonKey = JSON.stringify(currentConfig.preload_comparison_windows || []); + const nextPreloadComparisonKey = JSON.stringify(nextConfig.preload_comparison_windows || []); + const currentComparisonOverlayKey = JSON.stringify(currentConfig.comparison_preview_overlay || null); + const nextComparisonOverlayKey = JSON.stringify(nextConfig.comparison_preview_overlay || null); + const dataChanged = currentDataKey !== nextDataKey; + const viewChanged = currentViewKey !== nextViewKey; + const comparisonChanged = currentComparisonKey !== nextComparisonKey; + const preloadComparisonChanged = currentPreloadComparisonKey !== nextPreloadComparisonKey; + const comparisonOverlayChanged = currentComparisonOverlayKey !== nextComparisonOverlayKey; + if (!dataChanged && !viewChanged && !comparisonChanged && !preloadComparisonChanged && !comparisonOverlayChanged && this._configKey) return; + this._config = nextConfig; + this._configKey = JSON.stringify(nextConfig); + this._hiddenSeries = createHiddenSeriesSet(nextConfig.series_settings); + this._hiddenEventIds = createHiddenEventIdSet(nextConfig.hidden_event_ids); + this._zoomRange = createChartZoomRange(nextConfig.zoom_start_time, nextConfig.zoom_end_time); + if (dataChanged || comparisonChanged || preloadComparisonChanged) { + this._pruneComparisonDataCache(nextConfig); + this._lastComparisonResults = null; + } + const chartEl = this._chartEl(); + if (chartEl) { + chartEl.hass = this._hass; + chartEl._config = this._config; + chartEl._hiddenSeries = this._hiddenSeries; + chartEl._hiddenEventIds = this._hiddenEventIds; + chartEl._zoomRange = this._zoomRange; + chartEl._lastComparisonResults = this._getResolvedComparisonResults(); + if (comparisonOverlayChanged && typeof chartEl._renderComparisonPreviewOverlay === "function") chartEl._renderComparisonPreviewOverlay(); + } + if (dataChanged || !Array.isArray(nextConfig.comparison_windows) || !nextConfig.comparison_windows.length) this._adjustComparisonAxisScale = false; + if (this._hass && dataChanged) { + this._load(); + return; + } + if (this._hass && comparisonChanged) { + this._loadComparisonWindows({ + redraw: true, + showLoading: true + }); + return; + } + if (this._hass && preloadComparisonChanged) this._preloadComparisonWindows().catch(() => {}); + if (this._hass && comparisonOverlayChanged && this._lastHistResult && this._lastEvents) { + this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._lastEvents, this._lastT0, this._lastT1); + return; + } + if (this._hass && viewChanged && this._lastHistResult && this._lastEvents) this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._lastEvents, this._lastT0, this._lastT1); + } + get _entityIds() { + if (Array.isArray(this._config.series_settings)) { + const ids = this._config.series_settings.map((entry) => entry?.entity_id || entry?.entity || entry?.entityId || null).filter((value) => typeof value === "string" && !!value); + if (ids.length) return [...new Set(ids)]; + } + if (this._config.target) return resolveEntityIdsFromTarget(this._hass, this._config.target); + if (this._config.entities) return this._config.entities.map((e) => typeof e === "string" ? e : e.entity_id ?? e.entity); + return [this._config.entity]; + } + get _statisticsEntityIds() { + return this._entityIds.filter((entityId) => !String(entityId).startsWith("binary_sensor.")); + } + _getRange() { + const end = this._config.end_time ? new Date(this._config.end_time) : /* @__PURE__ */ new Date(); + return { + start: this._config.start_time ? new Date(this._config.start_time) : /* @__PURE__ */ new Date(end.getTime() - this._config.hours_to_show * 3600 * 1e3), + end + }; + } + _pruneComparisonDataCache(config) { + if (!this._comparisonDataCache.size) return; + const end = config.end_time ? new Date(config.end_time) : /* @__PURE__ */ new Date(); + const start = config.start_time ? new Date(config.start_time) : /* @__PURE__ */ new Date(end.getTime() - Number(config.hours_to_show || 24) * 3600 * 1e3); + const windows = [...Array.isArray(config.comparison_windows) ? config.comparison_windows : [], ...Array.isArray(config.preload_comparison_windows) ? config.preload_comparison_windows : []]; + const keepKeys = /* @__PURE__ */ new Set(); + for (const win of windows) { + if (win?.time_offset_ms == null) continue; + const winStart = new Date(start.getTime() + win.time_offset_ms); + const winEnd = new Date(end.getTime() + win.time_offset_ms); + keepKeys.add(this._getComparisonCacheKey(win, winStart, winEnd)); + } + for (const cacheKey of this._comparisonDataCache.keys()) if (!keepKeys.has(cacheKey)) this._comparisonDataCache.delete(cacheKey); + } + get _comparisonWindows() { + return Array.isArray(this._config?.comparison_windows) ? this._config.comparison_windows.filter((w) => w?.time_offset_ms != null) : []; + } + get _preloadComparisonWindowsConfig() { + return Array.isArray(this._config?.preload_comparison_windows) ? this._config.preload_comparison_windows.filter((w) => w?.time_offset_ms != null) : []; + } + _getComparisonCacheKey(win, start, end) { + return JSON.stringify({ + id: win?.id || "", + start: start?.toISOString?.() || "", + end: end?.toISOString?.() || "", + entities: this._entityIds, + statistics_entities: this._statisticsEntityIds + }); + } + _getResolvedComparisonResults() { + const { start, end } = this._getRange(); + const seenWindowIds = /* @__PURE__ */ new Set(); + const resolvedResults = []; + const comparisonWindows = [...this._comparisonWindows, ...this._preloadComparisonWindowsConfig]; + for (const win of comparisonWindows) { + const id = String(win?.id || ""); + if (!id || seenWindowIds.has(id)) continue; + seenWindowIds.add(id); + const winStart = new Date(start.getTime() + win.time_offset_ms); + const winEnd = new Date(end.getTime() + win.time_offset_ms); + const cacheKey = this._getComparisonCacheKey(win, winStart, winEnd); + const cached = this._comparisonDataCache.get(cacheKey); + if (!cached) continue; + resolvedResults.push(cached); + } + return resolvedResults; + } + async _loadComparisonWindowData(win, start, end) { + const hass = this._hass; + if (!hass) return { + id: win.id, + time_offset_ms: win.time_offset_ms, + histResult: {}, + statsResult: {} + }; + const cacheKey = this._getComparisonCacheKey(win, start, end); + const cached = this._comparisonDataCache.get(cacheKey); + if (cached) return cached; + const historyPromise = fetchHistoryDuringPeriod(hass, start.toISOString(), end.toISOString(), this._entityIds, { + include_start_time_state: true, + significant_changes_only: false, + no_attributes: true + }).catch(() => ({})); + const statisticsPromise = this._statisticsEntityIds.length ? fetchStatisticsDuringPeriod(hass, start.toISOString(), end.toISOString(), this._statisticsEntityIds, { + period: "hour", + types: ["mean"], + units: {} + }).catch(() => ({})) : Promise.resolve({}); + const [histResult, statsResult] = await Promise.all([historyPromise, statisticsPromise]); + const result = { + ...win, + histResult: histResult || {}, + statsResult: statsResult || {} + }; + this._comparisonDataCache.set(cacheKey, result); + return result; + } + _preloadComparisonWindows() { + const { start, end } = this._getRange(); + const comparisonWindows = this._preloadComparisonWindowsConfig; + if (!comparisonWindows.length) return Promise.resolve([]); + return Promise.all(comparisonWindows.map(async (win) => { + const winStart = new Date(start.getTime() + win.time_offset_ms); + const winEnd = new Date(end.getTime() + win.time_offset_ms); + const result = await this._loadComparisonWindowData(win, winStart, winEnd); + return { + ...win, + id: result.id, + histResult: result.histResult, + statsResult: result.statsResult + }; + })).then((results) => { + this._lastComparisonResults = this._getResolvedComparisonResults(); + if (this._config?.hovered_comparison_window_id && this._lastHistResult && this._lastEvents) this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._lastEvents, this._lastT0, this._lastT1); + return results; + }).catch((error) => { + logger$1.warn("[hass-datapoints history-card] comparison preload:failed", { message: error?.message || String(error) }); + return []; + }); + } + _loadComparisonWindows({ redraw = false, requestId = null, showLoading = false } = {}) { + const { start, end } = this._getRange(); + const comparisonWindows = this._comparisonWindows; + const targetRequestId = requestId ?? this._loadRequestId; + const comparisonRequestId = ++this._comparisonRequestId; + if (!comparisonWindows.length) { + this._lastComparisonResults = []; + this.dispatchEvent(new CustomEvent("hass-datapoints-comparison-loading", { + bubbles: true, + composed: true, + detail: { + ids: [], + loading: false + } + })); + if (redraw && this._lastHistResult && this._lastEvents) this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._lastEvents, this._lastT0, this._lastT1); + return Promise.resolve([]); + } + const cachedResults = []; + const windowsToFetch = []; + for (const win of comparisonWindows) { + const winStart = new Date(start.getTime() + win.time_offset_ms); + const winEnd = new Date(end.getTime() + win.time_offset_ms); + const cacheKey = this._getComparisonCacheKey(win, winStart, winEnd); + const cached = this._comparisonDataCache.get(cacheKey); + if (cached) cachedResults.push(cached); + else windowsToFetch.push({ + win, + winStart, + winEnd + }); + } + if (!windowsToFetch.length) { + this._lastComparisonResults = this._getResolvedComparisonResults(); + if (redraw && this._lastHistResult && this._lastEvents) this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._lastEvents, this._lastT0, this._lastT1); + return Promise.resolve(this._lastComparisonResults); + } + this._lastComparisonResults = cachedResults.length ? this._getResolvedComparisonResults() : null; + if (showLoading) this._setChartLoading(true); + this.dispatchEvent(new CustomEvent("hass-datapoints-comparison-loading", { + bubbles: true, + composed: true, + detail: { + ids: windowsToFetch.map(({ win }) => win.id).filter(Boolean), + loading: true + } + })); + return Promise.all(windowsToFetch.map(async ({ win, winStart, winEnd }) => this._loadComparisonWindowData(win, winStart, winEnd))).then(() => { + if (comparisonRequestId !== this._comparisonRequestId) return this._lastComparisonResults || []; + if (targetRequestId != null && targetRequestId !== this._loadRequestId) return this._lastComparisonResults || []; + this._lastComparisonResults = this._getResolvedComparisonResults(); + this.dispatchEvent(new CustomEvent("hass-datapoints-comparison-loading", { + bubbles: true, + composed: true, + detail: { + ids: windowsToFetch.map(({ win }) => win.id).filter(Boolean), + loading: false + } + })); + if (redraw && this._lastHistResult && this._lastEvents) this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._lastEvents, this._lastT0, this._lastT1); + else if (showLoading) this._setChartLoading(false); + return this._lastComparisonResults; + }).catch(() => { + if (comparisonRequestId === this._comparisonRequestId) { + this._lastComparisonResults = []; + logger$1.warn("[hass-datapoints history-card] comparison load:failed", { + comparisonRequestId, + ids: comparisonWindows.map((win) => win.id).filter(Boolean) + }); + this.dispatchEvent(new CustomEvent("hass-datapoints-comparison-loading", { + bubbles: true, + composed: true, + detail: { + ids: windowsToFetch.map(({ win }) => win.id).filter(Boolean), + loading: false + } + })); + if (redraw && this._lastHistResult && this._lastEvents) this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, this._lastEvents, this._lastT0, this._lastT1); + else if (showLoading) this._setChartLoading(false); + } else if (showLoading) this._setChartLoading(false); + return []; + }); + } + async _load() { + const { start, end } = this._getRange(); + const t0 = start.getTime(); + const t1 = end.getTime(); + const requestId = ++this._loadRequestId; + this._setChartLoading(true); + logger$1.log("[hass-datapoints history-card] load triggered", { + requestId, + entityIds: this._entityIds, + start: start.toISOString(), + end: end.toISOString() + }); + const partial = { + histResult: null, + statsResult: this._statisticsEntityIds.length ? null : {}, + events: null, + histDone: false, + statsDone: !this._statisticsEntityIds.length, + eventsDone: false, + histFailed: false, + statsFailed: false, + eventsFailed: false, + hasDrawnDrawable: false, + lastDrawState: null, + lastDrawQuality: null + }; + const logChartRedrawData = (reason, histResult, statsResult, events) => { + logger$1.log("[hass-datapoints history] redraw data update", { + reason, + requestId, + entityIds: this._entityIds, + start: start.toISOString(), + end: end.toISOString(), + histResult, + statsResult, + events + }); + }; + const maybeDraw = () => { + if (requestId !== this._loadRequestId) return; + const hasDrawableData = this._hasDrawableHistoryData(partial.histResult || {}, partial.statsResult || {}); + const numericRequestsFinished = partial.histDone && partial.statsDone; + if (!hasDrawableData && !numericRequestsFinished) return; + if (partial.hasDrawnDrawable) { + const drawQuality = hasDrawableData ? this._getDrawableHistoryQuality(partial.histResult || {}, partial.statsResult || {}) : null; + const redrawForHistory = hasDrawableData && !partial.lastDrawState?.histDone && partial.histDone; + const redrawForStats = hasDrawableData && !partial.lastDrawState?.statsDone && partial.statsDone; + const redrawForEvents = hasDrawableData && !partial.lastDrawState?.eventsDone && partial.eventsDone; + const shouldRedraw = redrawForHistory || redrawForStats || redrawForEvents; + const wouldDowngradeDraw = !!drawQuality && !!partial.lastDrawQuality && drawQuality.totalPoints < partial.lastDrawQuality.totalPoints; + if (!shouldRedraw) { + if (partial.histDone && partial.statsDone && partial.eventsDone) this._setChartLoading(false); + return; + } + if (wouldDowngradeDraw) { + if (partial.histDone && partial.statsDone && partial.eventsDone) this._setChartLoading(false); + return; + } + if (redrawForEvents && !redrawForHistory && this._lastHistResult && Number.isFinite(this._lastT0) && Number.isFinite(this._lastT1)) { + const filteredEvents = this._filterEvents(partial.events || []); + logChartRedrawData("events_update", this._lastHistResult, this._lastStatsResult || {}, filteredEvents); + partial.lastDrawState = { + histDone: partial.histDone, + statsDone: partial.statsDone, + eventsDone: partial.eventsDone + }; + this._queueDrawChart(this._lastHistResult, this._lastStatsResult || {}, partial.events || [], this._lastT0, this._lastT1, { loading: !(partial.histDone && partial.statsDone && partial.eventsDone) }); + return; + } + } + if (hasDrawableData) { + const drawQuality = this._getDrawableHistoryQuality(partial.histResult || {}, partial.statsResult || {}); + partial.hasDrawnDrawable = true; + partial.lastDrawState = { + histDone: partial.histDone, + statsDone: partial.statsDone, + eventsDone: partial.eventsDone + }; + partial.lastDrawQuality = drawQuality; + } + const filteredEvents = this._filterEvents(partial.events || []); + logChartRedrawData(partial.hasDrawnDrawable ? "data_update" : "initial_data_draw", partial.histResult || {}, partial.statsResult || {}, filteredEvents); + this._queueDrawChart(partial.histResult || {}, partial.statsResult || {}, partial.events || [], t0, t1, { loading: !(partial.histDone && partial.statsDone && partial.eventsDone) }); + }; + const finalize = () => { + if (requestId !== this._loadRequestId) return; + if (!(partial.histDone && partial.statsDone && partial.eventsDone)) return; + if (partial.histFailed && partial.statsFailed || partial.histResult == null && partial.statsResult == null) { + this._setChartMessage("Failed to load data."); + this._setChartLoading(false); + return; + } + if (partial.hasDrawnDrawable) this._setChartLoading(false); + this._preloadComparisonWindows().catch(() => {}); + }; + this._loadComparisonWindows({ + redraw: true, + requestId + }).catch(() => {}); + try { + const hass = this._hass; + if (!hass) { + this._setChartMessage("Failed to load data."); + this._setChartLoading(false); + return; + } + fetchHistoryDuringPeriod(hass, start.toISOString(), end.toISOString(), this._entityIds, { + include_start_time_state: true, + significant_changes_only: false, + no_attributes: true + }).then((histResult) => { + partial.histResult = histResult || {}; + partial.histDone = true; + maybeDraw(); + finalize(); + }).catch((err) => { + partial.histDone = true; + partial.histFailed = true; + logger$1.error("[hass-datapoints history-card] history load failed", err); + maybeDraw(); + finalize(); + }); + if (this._statisticsEntityIds.length) fetchStatisticsDuringPeriod(hass, start.toISOString(), end.toISOString(), this._statisticsEntityIds, { + period: "hour", + types: ["mean"], + units: {} + }).then((statsResult) => { + partial.statsResult = statsResult || {}; + partial.statsDone = true; + maybeDraw(); + finalize(); + }).catch((err) => { + partial.statsDone = true; + partial.statsFailed = true; + logger$1.error("[hass-datapoints history-card] statistics load failed", err); + maybeDraw(); + finalize(); + }); + if (this._config.datapoint_scope === "hidden") { + partial.events = []; + partial.eventsDone = true; + maybeDraw(); + finalize(); + } else fetchEvents(hass, start.toISOString(), end.toISOString(), this._config.datapoint_scope === "all" ? void 0 : this._entityIds).then((events) => { + partial.events = events || []; + partial.eventsDone = true; + maybeDraw(); + finalize(); + }).catch((err) => { + partial.eventsDone = true; + partial.eventsFailed = true; + logger$1.error("[hass-datapoints history-card] event load failed", err); + maybeDraw(); + finalize(); + }); + } catch (err) { + this._setChartMessage("Failed to load data."); + this._setChartLoading(false); + logger$1.error("[hass-datapoints history-card]", err); + } + } + /** Delegates to the hass-datapoints-history-chart sub-component for resize-replay. */ + _drawChart(...args) { + const chartEl = this._chartEl(); + if (chartEl) { + chartEl.hass = this._hass; + chartEl._config = this._config; + chartEl._hiddenSeries = this._hiddenSeries; + chartEl._hiddenEventIds = this._hiddenEventIds; + chartEl._zoomRange = this._zoomRange; + chartEl._lastComparisonResults = this._lastComparisonResults; + chartEl._drawChart(...args); + } + } + /** Returns the hass-datapoints-history-chart element once it is in the shadow DOM. */ + _chartEl() { + return this.shadowRoot?.querySelector("hass-datapoints-history-chart") ?? null; + } + /** + * Queue a draw cycle. Delegates to hass-datapoints-history-chart and also records last + * draw args so the base-class ResizeObserver can replay via _drawChart(). + */ + _queueDrawChart(histResult, statsResult, events, t0, t1, options = {}) { + const drawRequestId = ++this._drawRequestId; + const filteredEvents = this._filterEvents(events); + this._lastHistResult = histResult; + this._lastStatsResult = statsResult; + this._lastEvents = events; + this._lastT0 = t0; + this._lastT1 = t1; + this._lastDrawArgs = [ + histResult, + statsResult, + events, + t0, + t1, + { + ...options, + drawRequestId + } + ]; + const chartEl = this._chartEl(); + if (chartEl) { + chartEl.hass = this._hass; + chartEl._config = this._config; + chartEl._hiddenSeries = this._hiddenSeries; + chartEl._hiddenEventIds = this._hiddenEventIds; + chartEl._zoomRange = this._zoomRange; + chartEl._lastComparisonResults = this._lastComparisonResults; + chartEl._queueDrawChart(histResult, statsResult, filteredEvents, t0, t1, { + ...options, + drawRequestId + }); + } + } + _setChartLoading(isLoading) { + const chartEl = this._chartEl(); + if (chartEl?._setChartLoading) chartEl._setChartLoading(isLoading); + } + _setChartMessage(message = "") { + const chartEl = this._chartEl(); + if (chartEl?._setChartMessage) chartEl._setChartMessage(message); + } + /** + * Returns true if there is at least one entity with drawable data points + * in either the history or statistics result. + */ + _hasDrawableHistoryData(histResult, statsResult) { + return this._entityIds.some((entityId) => { + const histData = histResult[entityId]; + const statsData = statsResult[entityId]; + const histPoints = Array.isArray(histData) ? histData.length : 0; + const statsPoints = Array.isArray(statsData) ? statsData.length : 0; + return histPoints > 0 || statsPoints > 0; + }); + } + /** + * Returns a quality descriptor for the current drawable data — used to + * avoid downgrading a high-resolution draw with a lower-resolution one. + */ + _getDrawableHistoryQuality(histResult, statsResult) { + let totalPoints = 0; + for (const entityId of this._entityIds) { + const histData = histResult[entityId]; + const statsData = statsResult[entityId]; + totalPoints += Array.isArray(histData) ? histData.length : 0; + totalPoints += Array.isArray(statsData) ? statsData.length : 0; + } + return { totalPoints }; + } + _filterEvents(events) { + const query = String(this._config?.message_filter || "").trim().toLowerCase(); + const visibleEvents = events.filter((event) => !this._hiddenEventIds.has(event?.id ?? "")); + if (!query) return visibleEvents; + return visibleEvents.filter((event) => { + const ev = event; + return [ + ev?.message || "", + ev?.annotation || "", + ...(ev?.entity_ids || []).filter(Boolean) + ].join("\n").toLowerCase().includes(query); + }); + } + _buildNavigationPageState() { + const range = this._getRange(); + const fallbackZoomRange = typeof this._config.zoom_start_time !== "undefined" && typeof this._config.zoom_end_time !== "undefined" ? { + start: this._config.zoom_start_time, + end: this._config.zoom_end_time + } : null; + const targetSelection = this._config.target && typeof this._config.target === "object" ? normalizeTargetValue(this._config.target) : { entity_id: this._entityIds }; + return { + ...buildHistoryPageSessionState({ + _entities: this._entityIds, + _seriesRows: Array.isArray(this._config.series_settings) ? [...this._config.series_settings] : this._entityIds.map((entityId) => ({ entity_id: entityId })), + _targetSelection: targetSelection, + _targetSelectionRaw: targetSelection, + _datapointScope: this._config.datapoint_scope || "linked", + _showChartDatapointIcons: this._config.show_event_markers !== false, + _showChartDatapointLines: this._config.show_event_lines !== false, + _showChartTooltips: this._config.show_tooltips !== false, + _showChartEmphasizedHoverGuides: this._config.emphasize_hover_guides === true, + _chartHoverSnapMode: this._config.hover_snap_mode || "follow_series", + _delinkChartYAxis: this._config.delink_y_axis === true, + _splitChartView: this._config.split_view === true, + _showCorrelatedAnomalies: this._config.show_correlated_anomalies === true, + _chartAnomalyOverlapMode: "all", + _showDataGaps: this._config.show_data_gaps !== false, + _dataGapThreshold: this._config.data_gap_threshold || "2h", + _contentSplitRatio: .62, + _startTime: range.start, + _endTime: range.end, + _chartZoomCommittedRange: this._zoomRange ? { + start: this._zoomRange.start, + end: this._zoomRange.end + } : fallbackZoomRange, + _comparisonWindows: Array.isArray(this._config.comparison_windows) ? [...this._config.comparison_windows] : [], + _hours: Number(this._config.hours_to_show || 24), + _sidebarCollapsed: false, + _sidebarAccordionTargetsOpen: true, + _sidebarAccordionDatapointsOpen: true, + _sidebarAccordionAnalysisOpen: true, + _sidebarAccordionChartOpen: true, + _zoomLevel: "auto", + _dateSnapping: "hour", + _preferredSeriesColors: {} + }), + show_chart_trend_lines: this._config.show_trend_lines === true, + show_chart_summary_stats: this._config.show_summary_stats === true, + show_chart_rate_of_change: this._config.show_rate_of_change === true, + show_chart_threshold_analysis: this._config.show_threshold_analysis === true, + show_chart_threshold_shading: this._config.show_threshold_shading === true, + show_chart_anomalies: this._config.show_anomalies === true, + show_chart_trend_crosshairs: this._config.show_trend_crosshairs === true, + chart_trend_method: this._config.trend_method || "rolling_average", + chart_trend_window: this._config.trend_window || "24h", + chart_rate_window: this._config.rate_window || "1h", + chart_anomaly_sensitivity: this._config.anomaly_sensitivity || "medium", + chart_threshold_values: this._config.threshold_values || {}, + chart_threshold_directions: this._config.threshold_directions || {}, + show_chart_delta_analysis: this._config.show_delta_analysis === true, + show_chart_delta_tooltip: this._config.show_delta_tooltip !== false, + show_chart_delta_lines: this._config.show_delta_lines === true, + hide_chart_source_series: this._config.hide_raw_data === true, + chart_anomaly_rate_window: this._config.rate_window || "1h" + }; + } + _navigateToPanel(event) { + event.preventDefault(); + event.stopPropagation(); + const range = this._getRange(); + const zoomStartTime = this._zoomRange?.start ? this._zoomRange.start : this._config.zoom_start_time; + const zoomEndTime = this._zoomRange?.end ? this._zoomRange.end : this._config.zoom_end_time; + navigateToDataPointsHistory(this, this._config.target && typeof this._config.target === "object" ? normalizeTargetValue(this._config.target) : { entity_id: this._entityIds }, { + datapoint_scope: this._config.datapoint_scope || void 0, + start_time: Number.isFinite(this._lastT0) ? this._lastT0 : range.start, + end_time: Number.isFinite(this._lastT1) ? this._lastT1 : range.end, + zoom_start_time: zoomStartTime, + zoom_end_time: zoomEndTime, + page_state: this._buildNavigationPageState() + }); + } + _handleWindowKeyDown(ev) { + if (ev.key !== "Escape") return; + if (this._annotationDialog?.isOpen?.()) { + ev.preventDefault(); + return; + } + if (!this._zoomRange) return; + ev.preventDefault(); + this._zoomRange = null; + this._dispatchZoomRange("reset"); + } + _handleChartScroll() { + if (this._scrollSyncSuspended || !this._zoomRange) return; + if (this._ignoreNextProgrammaticScrollEvent) { + this._ignoreNextProgrammaticScrollEvent = false; + this._lastProgrammaticScrollLeft = null; + return; + } + if (this._lastProgrammaticScrollLeft != null && Math.abs((this._chartScrollViewportEl?.scrollLeft || 0) - this._lastProgrammaticScrollLeft) < 1) { + this._lastProgrammaticScrollLeft = null; + return; + } + this._lastProgrammaticScrollLeft = null; + } + _dispatchZoomRange(source) { + this.dispatchEvent(new CustomEvent("hass-datapoints-chart-zoom", { + bubbles: true, + composed: true, + detail: this._zoomRange ? { + startTime: this._zoomRange.start, + endTime: this._zoomRange.end, + preview: false, + source + } : { + startTime: null, + endTime: null, + preview: false, + source + } + })); + } + _scheduleZoomReload() { + if (this._zoomReloadTimer != null) window.clearTimeout(this._zoomReloadTimer); + this._zoomReloadTimer = window.setTimeout(() => { + this._zoomReloadTimer = null; + this._load(); + }, 140); + } + render() { + return b` + + ${this._config?.title ? b` +

+ ${this._config.title} + + + +

+ ` : ""} + +
+ `; + } + static getStubConfig() { + return { + title: "History with Events", + entity: "sensor.example", + hours_to_show: 24 + }; + } + static getConfigElement() { + return document.createElement("hass-datapoints-history-card-editor"); + } + }; + _defineProperty(HassRecordsHistoryCard, "styles", styles$55); + customElements.define("hass-datapoints-history-card", HassRecordsHistoryCard); + //#endregion + //#region custom_components/hass_datapoints/src/cards/history/editor.styles.ts + var styles$51 = i$5``; + //#endregion + //#region custom_components/hass_datapoints/src/molecules/analysis-group-shared/analysis-group-shared.styles.ts + var sharedStyles = i$5` + :host { + display: block; + --dp-spacing-xs: calc(var(--spacing, 8px) * 0.5); + --dp-spacing-sm: var(--spacing, 8px); + --dp-spacing-md: calc(var(--spacing, 8px) * 1.5); + --dp-spacing-lg: calc(var(--spacing, 8px) * 2); } - .context-form-field { + .field { display: grid; - gap: 6px; - min-width: 0; + gap: 4px; + justify-items: start; } - .context-form-label { - font-size: 0.9rem; + .field-label { + font-size: 0.72rem; font-weight: 600; - color: var(--primary-text-color); + letter-spacing: 0.02em; + color: var(--secondary-text-color); } - .context-form-help { - font-size: 0.8rem; - color: var(--secondary-text-color); - line-height: 1.45; + .select, + .input { + width: auto; + max-width: 100%; + min-width: 0; + box-sizing: border-box; + padding: calc(var(--spacing, 8px) * 0.75) calc(var(--spacing, 8px) * 0.875); + border-radius: 10px; + border: 1px solid + color-mix( + in srgb, + var(--divider-color, rgba(0, 0, 0, 0.12)) 88%, + transparent + ); + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font: inherit; + font-size: 0.84rem; } - .context-chip-row { + .toggle-group { display: flex; - flex-wrap: wrap; - gap: 6px; - } -`; - const styles$Q = i$5` - :host { - display: inline-flex; - } - .context-chip { - display: inline-flex; + gap: calc(var(--spacing, 8px) * 0.625); align-items: center; - gap: 6px; - padding: 4px 10px; - border-radius: 999px; - background: color-mix(in srgb, var(--primary-color) 12%, transparent); - color: var(--primary-color); - font-size: 0.82rem; - font-family: inherit; } - .context-chip ha-icon, - .context-chip ha-state-icon { - --mdc-icon-size: 14px; - flex: 0 0 auto; + + .option { + display: flex; + align-items: center; + gap: calc(var(--spacing, 8px) * 0.75); + color: var(--primary-text-color); + font-size: 0.84rem; + cursor: pointer; } - .context-chip-text { - display: inline-flex; - flex-direction: column; - min-width: 0; - line-height: 1.15; + + .option input[type="checkbox"] { + margin: 0; + accent-color: var(--primary-color, #03a9f4); + cursor: pointer; } - .context-chip-primary, - .context-chip-secondary { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +`; + //#endregion + //#region custom_components/hass_datapoints/src/molecules/analysis-sample-group/analysis-sample-group.styles.ts + var styles$50 = i$5``; + //#endregion + //#region custom_components/hass_datapoints/src/atoms/analysis/analysis-group/analysis-group.styles.ts + var styles$49 = i$5` + :host { + display: block; + --dp-spacing-xs: calc(var(--spacing, 8px) * 0.5); + --dp-spacing-sm: var(--spacing, 8px); + --dp-spacing-md: calc(var(--spacing, 8px) * 1.5); + --dp-spacing-lg: calc(var(--spacing, 8px) * 2); } - .context-chip-primary { - font-weight: 600; + + .group { + display: grid; + gap: var(--dp-spacing-sm); + border-radius: 6px; } - .context-chip-secondary { - font-size: 0.74rem; - opacity: 0.8; + + .group-body { + display: grid; + gap: var(--dp-spacing-sm); + padding: var(--dp-spacing-sm); + border-left: 3px solid var(--primary-color); + margin-left: 5px; + padding-left: var(--dp-spacing-md); } - .context-chip-remove { - display: inline-flex; + + .option { + display: flex; align-items: center; - justify-content: center; - width: 20px; - height: 20px; - padding: 0; - border: none; - border-radius: 50%; - background: transparent; - color: currentColor; + gap: calc(var(--spacing, 8px) * 0.75); + color: var(--primary-text-color); + font-size: 0.84rem; cursor: pointer; - flex: 0 0 auto; } - .context-chip-remove:hover { - background: color-mix(in srgb, currentColor 12%, transparent); + + .option.top { + align-items: flex-start; } - .context-chip-remove ha-icon { - --mdc-icon-size: 12px; + + .option.is-disabled { + opacity: 0.4; pointer-events: none; } -`; - var __create$J = Object.create; - var __defProp$U = Object.defineProperty; - var __getOwnPropDesc$J = Object.getOwnPropertyDescriptor; - var __knownSymbol$J = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$J = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$U = (obj, key, value) => key in obj ? __defProp$U(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$J = (base) => [, , , __create$J(base?.[__knownSymbol$J("metadata")] ?? null)]; - var __decoratorStrings$J = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$J = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$J("Function expected") : fn; - var __decoratorContext$J = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$J[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$J("Already initialized") : fns.push(__expectFn$J(fn || null)) }); - var __decoratorMetadata$J = (array, target) => __defNormalProp$U(target, __knownSymbol$J("metadata"), array[3]); - var __runInitializers$J = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$J = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$J[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$J({ get [name]() { - return __privateGet$I(this, extra); - }, set [name](x2) { - return __privateSet$I(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$J(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$J(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$J("Object expected"); - else __expectFn$J(fn = it.get) && (desc.get = fn), __expectFn$J(fn = it.set) && (desc.set = fn), __expectFn$J(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$U(target, name, desc), target; - }; - var __publicField$U = (obj, key, value) => __defNormalProp$U(obj, key + "", value); - var __accessCheck$I = (obj, member, msg2) => member.has(obj) || __typeError$J("Cannot " + msg2); - var __privateGet$I = (obj, member, getter) => (__accessCheck$I(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$I = (obj, member, value) => member.has(obj) ? __typeError$J("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$I = (obj, member, value, setter) => (__accessCheck$I(obj, member, "write to private field"), member.set(obj, value), value); - var _hass_dec$9, _stateObj_dec$2, _secondaryText_dec, _name_dec$3, _icon_dec$1, _itemId_dec, _type_dec, _a$J, _init$J, _type, _itemId, _icon$1, _name$3, _secondaryText, _stateObj$2, _hass$9; - class AnnotationChip extends (_a$J = i$2, _type_dec = [n({ type: String })], _itemId_dec = [n({ type: String, attribute: "item-id" })], _icon_dec$1 = [n({ type: String })], _name_dec$3 = [n({ type: String })], _secondaryText_dec = [n({ type: String, attribute: "secondary-text" })], _stateObj_dec$2 = [n({ type: Object, attribute: false })], _hass_dec$9 = [n({ type: Object, attribute: false })], _a$J) { - constructor() { - super(...arguments); - __privateAdd$I(this, _type, __runInitializers$J(_init$J, 8, this, "")), __runInitializers$J(_init$J, 11, this); - __privateAdd$I(this, _itemId, __runInitializers$J(_init$J, 12, this, "")), __runInitializers$J(_init$J, 15, this); - __privateAdd$I(this, _icon$1, __runInitializers$J(_init$J, 16, this, "")), __runInitializers$J(_init$J, 19, this); - __privateAdd$I(this, _name$3, __runInitializers$J(_init$J, 20, this, "")), __runInitializers$J(_init$J, 23, this); - __privateAdd$I(this, _secondaryText, __runInitializers$J(_init$J, 24, this, "")), __runInitializers$J(_init$J, 27, this); - __privateAdd$I(this, _stateObj$2, __runInitializers$J(_init$J, 28, this, null)), __runInitializers$J(_init$J, 31, this); - __privateAdd$I(this, _hass$9, __runInitializers$J(_init$J, 32, this, null)), __runInitializers$J(_init$J, 35, this); - } - _onRemove() { - this.dispatchEvent( - new CustomEvent("dp-chip-remove", { - detail: { type: this.type, itemId: this.itemId }, - bubbles: true, - composed: true - }) - ); - } - render() { - return b` - - ${this.stateObj ? b`` : b``} - - ${this.name} - ${this.secondaryText ? b`${this.secondaryText}` : b``} - - - - `; - } + + .option input[type="checkbox"] { + margin: 0; + accent-color: var(--primary-color, #03a9f4); + cursor: pointer; } - _init$J = __decoratorStart$J(_a$J); - _type = /* @__PURE__ */ new WeakMap(); - _itemId = /* @__PURE__ */ new WeakMap(); - _icon$1 = /* @__PURE__ */ new WeakMap(); - _name$3 = /* @__PURE__ */ new WeakMap(); - _secondaryText = /* @__PURE__ */ new WeakMap(); - _stateObj$2 = /* @__PURE__ */ new WeakMap(); - _hass$9 = /* @__PURE__ */ new WeakMap(); - __decorateElement$J(_init$J, 4, "type", _type_dec, AnnotationChip, _type); - __decorateElement$J(_init$J, 4, "itemId", _itemId_dec, AnnotationChip, _itemId); - __decorateElement$J(_init$J, 4, "icon", _icon_dec$1, AnnotationChip, _icon$1); - __decorateElement$J(_init$J, 4, "name", _name_dec$3, AnnotationChip, _name$3); - __decorateElement$J(_init$J, 4, "secondaryText", _secondaryText_dec, AnnotationChip, _secondaryText); - __decorateElement$J(_init$J, 4, "stateObj", _stateObj_dec$2, AnnotationChip, _stateObj$2); - __decorateElement$J(_init$J, 4, "hass", _hass_dec$9, AnnotationChip, _hass$9); - __decoratorMetadata$J(_init$J, AnnotationChip); - __publicField$U(AnnotationChip, "styles", styles$Q); - customElements.define("annotation-chip", AnnotationChip); - var __create$I = Object.create; - var __defProp$T = Object.defineProperty; - var __getOwnPropDesc$I = Object.getOwnPropertyDescriptor; - var __knownSymbol$I = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$I = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$T = (obj, key, value) => key in obj ? __defProp$T(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$I = (base) => [, , , __create$I(base?.[__knownSymbol$I("metadata")] ?? null)]; - var __decoratorStrings$I = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$I = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$I("Function expected") : fn; - var __decoratorContext$I = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$I[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$I("Already initialized") : fns.push(__expectFn$I(fn || null)) }); - var __decoratorMetadata$I = (array, target) => __defNormalProp$T(target, __knownSymbol$I("metadata"), array[3]); - var __runInitializers$I = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$I = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$I[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$I({ get [name]() { - return __privateGet$H(this, extra); - }, set [name](x2) { - return __privateSet$H(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$I(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$I(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$I("Object expected"); - else __expectFn$I(fn = it.get) && (desc.get = fn), __expectFn$I(fn = it.set) && (desc.set = fn), __expectFn$I(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$T(target, name, desc), target; - }; - var __publicField$T = (obj, key, value) => __defNormalProp$T(obj, key + "", value); - var __accessCheck$H = (obj, member, msg2) => member.has(obj) || __typeError$I("Cannot " + msg2); - var __privateGet$H = (obj, member, getter) => (__accessCheck$H(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$H = (obj, member, value) => member.has(obj) ? __typeError$I("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$H = (obj, member, value, setter) => (__accessCheck$H(obj, member, "write to private field"), member.set(obj, value), value); - var _emptyText_dec, _helpText_dec, _label_dec$9, _hass_dec$8, _chips_dec, _a$I, _init$I, _chips, _hass$8, _label$9, _helpText, _emptyText; - class AnnotationChipRow extends (_a$I = i$2, _chips_dec = [n({ type: Array })], _hass_dec$8 = [n({ type: Object, attribute: false })], _label_dec$9 = [n({ type: String })], _helpText_dec = [n({ type: String, attribute: "help-text" })], _emptyText_dec = [n({ type: String, attribute: "empty-text" })], _a$I) { - constructor() { - super(...arguments); - __privateAdd$H(this, _chips, __runInitializers$I(_init$I, 8, this, [])), __runInitializers$I(_init$I, 11, this); - __privateAdd$H(this, _hass$8, __runInitializers$I(_init$I, 12, this, null)), __runInitializers$I(_init$I, 15, this); - __privateAdd$H(this, _label$9, __runInitializers$I(_init$I, 16, this, "Linked targets")), __runInitializers$I(_init$I, 19, this); - __privateAdd$H(this, _helpText, __runInitializers$I(_init$I, 20, this, "These targets will be associated with the new data point by default. Remove any that should not be linked.")), __runInitializers$I(_init$I, 23, this); - __privateAdd$H(this, _emptyText, __runInitializers$I(_init$I, 24, this, "No linked targets will be associated with this data point.")), __runInitializers$I(_init$I, 27, this); - } - _onChipRemove(ev) { - const detail = ev.detail; - ev.stopPropagation(); - this.dispatchEvent( - new CustomEvent("dp-target-remove", { - detail: { type: detail.type, id: detail.itemId }, - bubbles: true, - composed: true - }) - ); - } - render() { - return b` -
- -
- ${this.chips.length > 0 ? this.helpText : this.emptyText} -
- ${this.chips.length > 0 ? b` -
- ${c( - this.chips, - (chip) => `${chip.type}:${chip.itemId}`, - (chip) => b` - - ` - )} + + .help-text { + display: inline-block; + color: var(--secondary-text-color); + opacity: 0.8; + padding-top: 2px; + } +`; + //#endregion + //#region custom_components/hass_datapoints/src/atoms/analysis/analysis-group/analysis-group.ts + var AnalysisGroup = class extends i$2 { + @n$1({ type: String }) accessor label = ""; + @n$1({ type: Boolean }) accessor checked = false; + @n$1({ type: Boolean }) accessor disabled = false; + @n$1({ + type: Boolean, + attribute: "align-top" + }) accessor alignTop = false; + _onChange(e) { + const checked = e.target.checked; + this.checked = checked; + this.dispatchEvent(new CustomEvent("dp-group-change", { + detail: { checked }, + bubbles: true, + composed: true + })); + } + render() { + return b` +
+ + ${this.checked ? b` +
+
` : A}
`; - } - } - _init$I = __decoratorStart$I(_a$I); - _chips = /* @__PURE__ */ new WeakMap(); - _hass$8 = /* @__PURE__ */ new WeakMap(); - _label$9 = /* @__PURE__ */ new WeakMap(); - _helpText = /* @__PURE__ */ new WeakMap(); - _emptyText = /* @__PURE__ */ new WeakMap(); - __decorateElement$I(_init$I, 4, "chips", _chips_dec, AnnotationChipRow, _chips); - __decorateElement$I(_init$I, 4, "hass", _hass_dec$8, AnnotationChipRow, _hass$8); - __decorateElement$I(_init$I, 4, "label", _label_dec$9, AnnotationChipRow, _label$9); - __decorateElement$I(_init$I, 4, "helpText", _helpText_dec, AnnotationChipRow, _helpText); - __decorateElement$I(_init$I, 4, "emptyText", _emptyText_dec, AnnotationChipRow, _emptyText); - __decoratorMetadata$I(_init$I, AnnotationChipRow); - __publicField$T(AnnotationChipRow, "styles", styles$R); - customElements.define("annotation-chip-row", AnnotationChipRow); - var __defProp$S = Object.defineProperty; - var __defNormalProp$S = (obj, key, value) => key in obj ? __defProp$S(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$S = (obj, key, value) => __defNormalProp$S(obj, typeof key !== "symbol" ? key + "" : key, value); - class HistoryAnnotationDialogController { - constructor(host) { - __publicField$S(this, "_host"); - __publicField$S(this, "_dialogEl"); - __publicField$S(this, "_panelEl"); - __publicField$S(this, "_chipRowEl"); - __publicField$S(this, "_linkedTarget"); - __publicField$S(this, "_target"); - this._host = host; - this._dialogEl = null; - this._panelEl = null; - this._chipRowEl = null; - this._linkedTarget = normalizeTargetSelection({}); - this._target = normalizeTargetSelection({}); - } - isOpen() { - return !!this._dialogEl?.open; - } - ensureDialog() { - if (this._dialogEl || !this._host.shadowRoot) { - return; - } - const dialog = document.createElement( - "ha-dialog" - ); - dialog.id = "chart-context-dialog"; - dialog.scrimClickAction = true; - dialog.escapeKeyAction = true; - dialog.open = false; - dialog.headerTitle = "Create data point"; - dialog.style.setProperty( - "--dialog-content-padding", - "0 var(--dp-spacing-lg, 24px) var(--dp-spacing-lg, 24px)" - ); - dialog.style.setProperty("--mdc-dialog-min-width", "min(920px, 96vw)"); - dialog.style.setProperty("--mdc-dialog-max-width", "96vw"); - if (this._host._hass) { - dialog.hass = this._host._hass; - } - dialog.innerHTML = ` -
+ } + }; + _defineProperty(AnalysisGroup, "styles", styles$49); + customElements.define("analysis-group", AnalysisGroup); + //#endregion + //#region custom_components/hass_datapoints/src/molecules/analysis-sample-group/analysis-sample-group.ts + var SAMPLE_INTERVAL_OPTIONS = [ + { + value: "raw", + label: "Raw (no sampling)" + }, + { + value: "5s", + label: "5 seconds" + }, + { + value: "10s", + label: "10 seconds" + }, + { + value: "15s", + label: "15 seconds" + }, + { + value: "30s", + label: "30 seconds" + }, + { + value: "1m", + label: "1 minute" + }, + { + value: "2m", + label: "2 minutes" + }, + { + value: "5m", + label: "5 minutes" + }, + { + value: "10m", + label: "10 minutes" + }, + { + value: "15m", + label: "15 minutes" + }, + { + value: "30m", + label: "30 minutes" + }, + { + value: "1h", + label: "1 hour" + }, + { + value: "2h", + label: "2 hours" + }, + { + value: "3h", + label: "3 hours" + }, + { + value: "4h", + label: "4 hours" + }, + { + value: "6h", + label: "6 hours" + }, + { + value: "12h", + label: "12 hours" + }, + { + value: "24h", + label: "24 hours" + } + ]; + var SAMPLE_AGGREGATE_OPTIONS = [ + { + value: "mean", + label: "Mean (average)" + }, + { + value: "min", + label: "Min" + }, + { + value: "max", + label: "Max" + }, + { + value: "median", + label: "Median" + }, + { + value: "first", + label: "First" + }, + { + value: "last", + label: "Last" + } + ]; + var AnalysisSampleGroup = @localized() class extends i$2 { + @n$1({ type: Object }) accessor analysis = {}; + @n$1({ + type: String, + attribute: "entity-id" + }) accessor entityId = ""; + _emit(key, value) { + this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { + detail: { + entityId: this.entityId, + key, + value + }, + bubbles: true, + composed: true + })); + } + _localizedOptions(options) { + return options.map((opt) => ({ + ...opt, + label: msg(opt.label) + })); + } + _renderSelect(key, options, value) { + return b` + `; - dialog.addEventListener("closed", () => this.finalizeClose()); - this._host.shadowRoot.appendChild(dialog); - this._dialogEl = dialog; - this._panelEl = dialog.querySelector("#chart-context-dialog-panel"); - } - teardown() { - this.resetFormState(); - this._dialogEl?.remove(); - this._dialogEl = null; - this._panelEl = null; - this._chipRowEl = null; - } - resetFormState() { - this._linkedTarget = normalizeTargetSelection({}); - this._target = normalizeTargetSelection({}); - } - finalizeClose() { - this.teardown(); - this._host._creatingContextAnnotation = false; - } - _shake() { - if (!this._dialogEl) return; - const shadowRoot = this._host.shadowRoot; - if (!shadowRoot) return; - if (!shadowRoot.getElementById("dp-dialog-shake-style")) { - const style = document.createElement("style"); - style.id = "dp-dialog-shake-style"; - style.textContent = ` - @keyframes dp-dialog-shake { - 10%, 90% { transform: translate3d(-2px, 0, 0); } - 20%, 80% { transform: translate3d(4px, 0, 0); } - 30%, 50%, 70% { transform: translate3d(-6px, 0, 0); } - 40%, 60% { transform: translate3d(6px, 0, 0); } - } - .dp-shaking { - animation: dp-dialog-shake 0.5s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; - } - `; - shadowRoot.appendChild(style); - } - this._dialogEl.classList.remove("dp-shaking"); - void this._dialogEl.offsetWidth; - this._dialogEl.classList.add("dp-shaking"); - this._dialogEl.addEventListener( - "animationend", - () => this._dialogEl?.classList.remove("dp-shaking"), - { once: true } - ); - } - formatDate(timeMs) { - const value = new Date(timeMs); - const yyyy = value.getFullYear(); - const mm = String(value.getMonth() + 1).padStart(2, "0"); - const dd = String(value.getDate()).padStart(2, "0"); - const hh = String(value.getHours()).padStart(2, "0"); - const min = String(value.getMinutes()).padStart(2, "0"); - return `${yyyy}-${mm}-${dd}T${hh}:${min}`; - } - _buildChips(target) { - return [ - ...(target.entity_id || []).map((id) => { - const name = entityName(this._host._hass, id); - return { - type: "entity_id", - itemId: id, - icon: entityIcon(this._host._hass, id), - name, - secondaryText: name !== id ? id : "", - stateObj: this._host?._hass?.states?.[id] ?? null - }; - }), - ...(target.device_id || []).map((id) => ({ - type: "device_id", - itemId: id, - icon: deviceIcon(this._host._hass, id), - name: deviceName(this._host._hass, id) - })), - ...(target.area_id || []).map((id) => ({ - type: "area_id", - itemId: id, - icon: areaIcon(this._host._hass, id), - name: areaName(this._host._hass, id) - })), - ...(target.label_id || []).map((id) => ({ - type: "label_id", - itemId: id, - icon: labelIcon(this._host._hass, id), - name: labelName(this._host._hass, id) - })) - ]; - } - removeLinkedTarget(type, id) { - if (!this._linkedTarget || !type || !id) { - return; - } - const current = normalizeTargetSelection(this._linkedTarget); - if (!current[type]) { - return; - } - current[type] = current[type].filter((value) => value !== id); - this._linkedTarget = current; - if (this._chipRowEl) { - this._chipRowEl.hass = this._host._hass ?? null; - this._chipRowEl.chips = this._buildChips(this._linkedTarget); - } - } - bindTargetChipActions() { - } - _getDefaultLinkedTarget() { - const config = this._host?._config || {}; - const hiddenSeries = this._host?._hiddenSeries instanceof Set ? this._host._hiddenSeries : /* @__PURE__ */ new Set(); - const seriesSettings = Array.isArray(config.series_settings) ? config.series_settings : []; - const visibleEntityIds = seriesSettings.map( - (entry) => entry?.entity_id || entry?.entity || entry?.entityId || null - ).filter( - (entityId) => typeof entityId === "string" && entityId.length > 0 && !hiddenSeries.has(entityId) - ); - if (visibleEntityIds.length > 0) { - return normalizeTargetSelection({ - entity_id: [...new Set(visibleEntityIds)] - }); - } - return normalizeTargetSelection({ - entity_id: (this._host?._entityIds || []).filter(Boolean) - }); - } - bindFields(hover) { - if (!this._panelEl) { - return; - } - const prefill = hover?.annotationPrefill && typeof hover.annotationPrefill === "object" ? hover.annotationPrefill : {}; - const messageEl = this._panelEl.querySelector( - "#chart-context-message" - ); - const annotationEl = this._panelEl.querySelector( - "#chart-context-annotation" - ); - const iconPicker = this._panelEl.querySelector( - "#chart-context-icon" - ); - if (iconPicker) { - iconPicker.hass = this._host._hass; - iconPicker.value = prefill.icon || hover?.event?.icon || "mdi:bookmark"; - } - const targetSel = this._panelEl.querySelector( - "#chart-context-target" - ); - if (targetSel) { - targetSel.hass = this._host._hass; - targetSel.value = "{}"; - targetSel.addEventListener("value-changed", (ev) => { - this._target = normalizeTargetSelection( - ev.detail.value || {} - ); - }); - } - if (messageEl) { - messageEl.value = prefill.message || ""; - } - if (annotationEl) { - annotationEl.value = prefill.annotation || ""; - } - const chipContainer = this._panelEl.querySelector( - "#chart-context-linked-targets" - ); - if (chipContainer && !this._chipRowEl) { - const chipRow = document.createElement( - "annotation-chip-row" - ); - chipRow.hass = this._host._hass ?? null; - chipRow.addEventListener("dp-target-remove", (ev) => { - const detail = ev.detail; - this.removeLinkedTarget(detail.type, detail.id); - }); - chipContainer.appendChild(chipRow); - this._chipRowEl = chipRow; - } - if (this._chipRowEl) { - this._chipRowEl.chips = this._buildChips(this._linkedTarget); - } - this.bindTargetChipActions(); - const colorInput = this._panelEl.querySelector( - "#chart-context-color" - ); - const colorPreview = this._panelEl.querySelector( - "#chart-context-color-preview" - ); - const syncColor = () => { - if (colorPreview && colorInput) { - colorPreview.style.background = colorInput.value; - } - }; - if (colorInput) { - syncColor(); - colorInput.addEventListener("input", syncColor); - colorInput.addEventListener("change", syncColor); - } - this._dialogEl?.querySelector("#chart-context-cancel")?.addEventListener("click", () => this.close()); - this._dialogEl?.querySelector("#chart-context-save")?.addEventListener("click", () => this.submit()); - [messageEl, annotationEl].forEach((field) => { - field?.addEventListener("keydown", (ev) => { - const kev = ev; - if (kev.key === "Enter" && (kev.ctrlKey || kev.metaKey)) { - kev.preventDefault(); - this.submit(); - } - }); - }); - } - async submit() { - if (!this._panelEl || !this._host._hass) { - return; - } - const messageEl = this._panelEl.querySelector( - "#chart-context-message" - ); - const annotationEl = this._panelEl.querySelector( - "#chart-context-annotation" - ); - const dateEl = this._panelEl.querySelector( - "#chart-context-date" - ); - const iconPicker = this._panelEl.querySelector( - "#chart-context-icon" - ); - const colorInput = this._panelEl.querySelector( - "#chart-context-color" - ); - const saveButton = this._dialogEl?.querySelector( - "#chart-context-save" - ); - const feedbackEl = this._panelEl.querySelector( - "#chart-context-feedback" - ); - const message = (messageEl?.value || "").trim(); - if (!message) { - messageEl?.focus?.(); - this._shake(); - return; - } - const mergedTarget = mergeTargetSelections( - this._linkedTarget, - this._target || {} - ); - const payload = { message }; - const annotation = (annotationEl?.value || "").trim(); - if (annotation) { - payload.annotation = annotation; - } - const dateVal = (dateEl?.value || "").trim(); - if (dateVal) { - const parsedDate = new Date(dateVal); - payload.date = Number.isFinite(parsedDate.getTime()) ? parsedDate.toISOString() : dateVal; - } - const icon = iconPicker?.value; - if (icon) { - payload.icon = icon; - } - payload.color = colorInput?.value || "#03a9f4"; - if (mergedTarget.entity_id.length) { - payload.entity_ids = mergedTarget.entity_id; - } - if (mergedTarget.device_id.length) { - payload.device_ids = mergedTarget.device_id; - } - if (mergedTarget.area_id.length) { - payload.area_ids = mergedTarget.area_id; - } - if (mergedTarget.label_id.length) { - payload.label_ids = mergedTarget.label_id; - } - if (saveButton) saveButton.disabled = true; - if (feedbackEl) { - feedbackEl.hidden = true; - feedbackEl.textContent = ""; - } - try { - await this._host._hass.callService(DOMAIN, "record", payload); - invalidateEventsCache(); - window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); - this.close(); - } catch (err) { - if (feedbackEl) { - feedbackEl.hidden = false; - feedbackEl.textContent = err?.message || "Failed to create annotation."; - } - this._shake(); - logger$1.error("[hass-datapoints history-card]", err); - } finally { - if (saveButton) saveButton.disabled = false; - } - } - open(hover) { - if (this._dialogEl && !this._dialogEl.open) { - this.teardown(); - } - this.ensureDialog(); - if (!this._dialogEl || !this._panelEl || this._dialogEl.open) return; - this.resetFormState(); - const prefillLinkedTarget = hover?.annotationPrefill?.linkedTarget; - if (prefillLinkedTarget && typeof prefillLinkedTarget === "object") { - this._linkedTarget = normalizeTargetSelection(prefillLinkedTarget); - } else { - this._linkedTarget = this._getDefaultLinkedTarget(); - } - const defaultColor = hover?.annotationPrefill?.color || hover?.primary?.color || hover?.event?.color || "#03a9f4"; - this._panelEl.innerHTML = ` - -
-
-
-
-
- -
Use a short title that will be shown in the chart tooltip and records list.
- -
-
- -
Add any longer context, outcome, or note you want to keep with this data point.
- -
-
-
- -
Optionally add more entities, devices, areas, or labels that should also be linked to this annotation.
- -
-
-
-
- -
The annotation will be placed at this exact moment on the chart.
- -
-
- -
Choose the icon shown for this data point in the chart and records list.
- -
-
- -
- Pick a color for the point marker and its related timeline indicators. - -
-
- - -
-
-
-
-
- + + ${a.show_threshold_shading ? b` + + ` : A} + `; - if (this._dialogEl) { - this._dialogEl.hass = this._host._hass; - this._dialogEl.dialogInitialFocus = "#chart-context-message"; - } - const targetSel = this._panelEl.querySelector( - "#chart-context-target" - ); - if (targetSel) { - targetSel.selector = { target: {} }; - } - this.bindFields(hover); - this._dialogEl.open = true; - this._host._creatingContextAnnotation = true; - window.requestAnimationFrame(() => { - this._panelEl?.querySelector( - "#chart-context-message" - )?.focus?.(); - }); - } - close() { - this._host._creatingContextAnnotation = false; - if (this._dialogEl) this._dialogEl.open = false; - window.setTimeout(() => this.finalizeClose(), 0); - } + } + }; + _defineProperty(AnalysisThresholdGroup, "styles", [sharedStyles, styles$45]); + customElements.define("analysis-threshold-group", AnalysisThresholdGroup); + //#endregion + //#region custom_components/hass_datapoints/src/molecules/analysis-anomaly-group/analysis-anomaly-group.styles.ts + var styles$44 = i$5` + .method-computing-indicator { + display: inline-flex; + align-items: center; + gap: 4px; + margin-left: 6px; + vertical-align: middle; + flex-shrink: 0; } - async function fetchStatisticsDuringPeriod(hass, startTime, endTime, statisticIds, options = {}) { - const normalizedStatisticIds = normalizeCacheIdList(statisticIds); - const normalizedTypes = normalizeCacheIdList(options.types); - const cacheKey = JSON.stringify({ - type: "recorder/statistics_during_period", - start_time: startTime, - end_time: endTime, - statistic_ids: normalizedStatisticIds, - period: options.period || "hour", - types: normalizedTypes - }); - return withStableRangeCache( - cacheKey, - endTime, - () => hass.connection.sendMessagePromise({ - type: "recorder/statistics_during_period", - start_time: startTime, - end_time: endTime, - statistic_ids: normalizedStatisticIds, - period: options.period || "hour", - types: normalizedTypes, - units: options.units || {} - }) - ); + + .method-computing-spinner { + display: inline-block; + width: 8px; + height: 8px; + border: 1.5px solid var(--divider-color, #ccc); + border-top-color: var(--primary-color, #03a9f4); + border-radius: 50%; + animation: analysis-spin 0.7s linear infinite; + flex-shrink: 0; } - const PANEL_HISTORY_PREFERENCES_KEY = `${DOMAIN}:panel_history_preferences`; - const PANEL_HISTORY_SESSION_KEY = `${DOMAIN}:panel_history_session`; - function readHistoryPageSessionState() { - try { - const raw = window.sessionStorage?.getItem(PANEL_HISTORY_SESSION_KEY); - if (!raw) { - return null; - } - const parsed = JSON.parse(raw); - return parsed && typeof parsed === "object" ? parsed : null; - } catch { - return null; - } + + .method-computing-progress { + font-size: 0.72rem; + font-weight: 500; + color: var(--primary-color, #03a9f4); + line-height: 1; } - function buildHistoryPageSessionState(source) { - return { - entities: source._entities, - series_rows: source._seriesRows, - target_selection: source._targetSelection, - target_selection_raw: source._targetSelectionRaw, - datapoint_scope: source._datapointScope, - show_chart_datapoint_icons: source._showChartDatapointIcons, - show_chart_datapoint_lines: source._showChartDatapointLines, - show_chart_tooltips: source._showChartTooltips, - show_chart_emphasized_hover_guides: source._showChartEmphasizedHoverGuides, - chart_hover_snap_mode: source._chartHoverSnapMode, - delink_chart_y_axis: source._delinkChartYAxis, - split_chart_view: source._splitChartView, - show_chart_trend_lines: false, - show_chart_summary_stats: false, - show_chart_rate_of_change: false, - show_chart_threshold_analysis: false, - show_chart_threshold_shading: false, - show_chart_anomalies: false, - chart_anomaly_method: "trend_residual", - chart_anomaly_rate_window: "1h", - chart_anomaly_zscore_window: "24h", - chart_anomaly_persistence_window: "1h", - chart_anomaly_comparison_window_id: null, - hide_chart_source_series: false, - show_chart_trend_crosshairs: false, - chart_trend_method: "rolling_average", - chart_trend_window: "24h", - chart_rate_window: "1h", - chart_anomaly_sensitivity: "medium", - chart_threshold_values: {}, - chart_threshold_directions: {}, - show_chart_delta_analysis: false, - show_chart_delta_tooltip: true, - show_chart_delta_lines: false, - show_chart_correlated_anomalies: source._showCorrelatedAnomalies, - chart_anomaly_overlap_mode: source._chartAnomalyOverlapMode || "all", - show_data_gaps: source._showDataGaps, - data_gap_threshold: source._dataGapThreshold, - content_split_ratio: source._contentSplitRatio, - start_time: source._startTime?.toISOString() || null, - end_time: source._endTime?.toISOString() || null, - zoom_start_time: source._chartZoomCommittedRange ? new Date(source._chartZoomCommittedRange.start).toISOString() : null, - zoom_end_time: source._chartZoomCommittedRange ? new Date(source._chartZoomCommittedRange.end).toISOString() : null, - date_windows: normalizeDateWindows(source._comparisonWindows), - hours: source._hours, - sidebar_collapsed: source._sidebarCollapsed, - sidebar_accordion_targets_open: source._sidebarAccordionTargetsOpen !== false, - sidebar_accordion_datapoints_open: source._sidebarAccordionDatapointsOpen !== false, - sidebar_accordion_analysis_open: source._sidebarAccordionAnalysisOpen !== false, - sidebar_accordion_chart_open: source._sidebarAccordionChartOpen !== false - }; - } - function writeHistoryPageSessionState(source) { - try { - window.sessionStorage?.setItem( - PANEL_HISTORY_SESSION_KEY, - JSON.stringify(buildHistoryPageSessionState(source)) - ); - } catch { + + @keyframes analysis-spin { + to { + transform: rotate(360deg); } } - function normalizeHistoryPagePreferences(preferences, options = {}) { - const zoomValues = new Set( - (options.zoomOptions || []).map((option) => option.value) - ); - const snapValues = new Set( - (options.snapOptions || []).map((option) => option.value) - ); - let shouldPersistDefaults = false; - const normalized = { - zoomLevel: "auto", - dateSnapping: "hour", - preferredSeriesColors: {}, - comparisonWindows: [], - pageState: null, - shouldPersistDefaults - }; - if (preferences && typeof preferences === "object") { - if (preferences.zoom_level && zoomValues.has(preferences.zoom_level)) { - normalized.zoomLevel = preferences.zoom_level; - } else { - shouldPersistDefaults = true; - } - if (preferences.date_snapping && snapValues.has(preferences.date_snapping)) { - normalized.dateSnapping = preferences.date_snapping; - } else { - shouldPersistDefaults = true; - } - normalized.preferredSeriesColors = preferences.series_colors && typeof preferences.series_colors === "object" ? Object.entries( - preferences.series_colors - ).reduce((acc, [entityId, color]) => { - if (typeof entityId === "string" && /^#[0-9a-f]{6}$/i.test(color || "")) { - acc[entityId] = color; - } - return acc; - }, {}) : {}; - normalized.comparisonWindows = normalizeDateWindows( - preferences.date_windows - ); - normalized.pageState = preferences.page_state && typeof preferences.page_state === "object" ? { - ...preferences.page_state, - date_windows: normalizeDateWindows( - preferences.page_state?.date_windows - ) - } : null; - } else { - shouldPersistDefaults = true; - } - normalized.shouldPersistDefaults = shouldPersistDefaults; - return normalized; - } - function buildHistoryPagePreferencesPayload(source) { - const preferredSeriesColors = source._seriesRows.reduce( - (acc, row) => { - const entityId = typeof row === "object" && row && "entity_id" in row ? row.entity_id : null; - const color = typeof row === "object" && row && "color" in row ? row.color : null; - if (typeof entityId === "string" && /^#[0-9a-f]{6}$/i.test(String(color || ""))) { - acc[entityId] = String(color); - } - return acc; - }, - { ...source._preferredSeriesColors } - ); - return { - zoom_level: source._zoomLevel, - date_snapping: source._dateSnapping, - series_colors: preferredSeriesColors, - date_windows: normalizeDateWindows(source._comparisonWindows), - page_state: buildHistoryPageSessionState(source) - }; - } - var __defProp$R = Object.defineProperty; - var __defNormalProp$R = (obj, key, value) => key in obj ? __defProp$R(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$R = (obj, key, value) => __defNormalProp$R(obj, typeof key !== "symbol" ? key + "" : key, value); - class HassRecordsHistoryCard extends ChartCardBase { - // ── Constructor ──────────────────────────────────────────────────────────── - constructor() { - super(); - __publicField$R(this, "_hiddenSeries", /* @__PURE__ */ new Set()); - __publicField$R(this, "_hiddenEventIds", /* @__PURE__ */ new Set()); - __publicField$R(this, "_zoomRange", null); - __publicField$R(this, "_configKey"); - __publicField$R(this, "_comparisonRequestId", 0); - __publicField$R(this, "_comparisonDataCache", /* @__PURE__ */ new Map()); - __publicField$R(this, "_lastComparisonResults", null); - __publicField$R(this, "_lastHistResult", null); - __publicField$R(this, "_lastStatsResult", null); - __publicField$R(this, "_lastEvents", null); - __publicField$R(this, "_lastT0", 0); - __publicField$R(this, "_lastT1", 0); - __publicField$R(this, "_scrollSyncSuspended", false); - __publicField$R(this, "_lastProgrammaticScrollLeft", null); - __publicField$R(this, "_ignoreNextProgrammaticScrollEvent", false); - __publicField$R(this, "_adjustComparisonAxisScale", false); - __publicField$R(this, "_drawRequestId", 0); - __publicField$R(this, "_zoomReloadTimer", null); - __publicField$R(this, "_chartScrollViewportEl", null); - __publicField$R(this, "_annotationDialog"); - __publicField$R(this, "_onWindowKeyDown"); - __publicField$R(this, "_onChartScroll"); - __publicField$R(this, "_onZoomApply"); - this._annotationDialog = new HistoryAnnotationDialogController( - this - ); - this._onWindowKeyDown = (ev) => this._handleWindowKeyDown(ev); - this._onChartScroll = () => this._handleChartScroll(); - this._onZoomApply = (ev) => { - const detail = ev.detail; - this._zoomRange = detail ? { start: detail.start, end: detail.end } : null; - this._dispatchZoomRange(detail ? "zoom" : "reset"); - if (this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._lastEvents, - this._lastT0, - this._lastT1 - ); - } - this._scheduleZoomReload(); - }; - } - // ── Hass setter — suppress reload-on-every-state-change ─────────────────── - /** - * Override the base setter to prevent a full history refetch on every HA - * state update. History data is fetched from the history/statistics APIs and - * does not change when entity states tick; reloads are already triggered by: - * - config changes (setConfig) - * - new datapoints recorded (hass_datapoints_event_recorded subscription) - * - auto-refresh interval (base class) - * - * We still call requestUpdate() so that entity-friendly-names in the legend - * stay fresh, and we propagate the new hass reference down to hass-datapoints-history-chart. - */ - set hass(hass) { - this._hass = hass; - this.requestUpdate(); - const chartEl = this._chartEl(); - if (chartEl) { - chartEl.hass = hass; - } - } - get hass() { - return this._hass; - } - // ── Lifecycle ────────────────────────────────────────────────────────────── - connectedCallback() { - window.addEventListener("keydown", this._onWindowKeyDown); - this.addEventListener("hass-datapoints-zoom-apply", this._onZoomApply); - super.connectedCallback(); - } - disconnectedCallback() { - window.removeEventListener("keydown", this._onWindowKeyDown); - this.removeEventListener("hass-datapoints-zoom-apply", this._onZoomApply); - if (this._zoomReloadTimer != null) { - window.clearTimeout(this._zoomReloadTimer); - this._zoomReloadTimer = null; - } - if (this._chartScrollViewportEl) { - this._chartScrollViewportEl.removeEventListener( - "scroll", - this._onChartScroll - ); - } - this._annotationDialog?.teardown(); - super.disconnectedCallback(); - } - // ── Configuration ────────────────────────────────────────────────────────── - setConfig(config) { - if (!config.entity && !config.entities && !config.target && !(Array.isArray(config.series_settings) && config.series_settings.length > 0)) { - throw new Error( - "hass-datapoints-history-card: define `target`, `entity`, `entities` or `series_settings`" - ); - } - const nextConfig = { - hours_to_show: 24, - ...config, - target: config.target ? normalizeTargetValue(config.target) : void 0, - series_settings: Array.isArray(config.series_settings) ? config.series_settings.map((entry) => ({ - ...entry, - analysis: entry?.analysis && typeof entry.analysis === "object" ? { ...entry.analysis } : entry?.analysis - })) : config.series_settings, - hidden_event_ids: Array.isArray(config.hidden_event_ids) ? [...config.hidden_event_ids] : config.hidden_event_ids, - hovered_event_ids: Array.isArray(config.hovered_event_ids) ? [...config.hovered_event_ids] : config.hovered_event_ids, - comparison_windows: Array.isArray(config.comparison_windows) ? config.comparison_windows.map((entry) => ({ - ...entry - })) : config.comparison_windows, - preload_comparison_windows: Array.isArray( - config.preload_comparison_windows - ) ? config.preload_comparison_windows.map( - (entry) => ({ ...entry }) - ) : config.preload_comparison_windows, - comparison_preview_overlay: config.comparison_preview_overlay ? { ...config.comparison_preview_overlay } : null, - selected_comparison_window_id: config.selected_comparison_window_id || null, - hovered_comparison_window_id: config.hovered_comparison_window_id || null, - show_trend_lines: config.show_trend_lines === true, - show_summary_stats: config.show_summary_stats === true, - show_rate_of_change: config.show_rate_of_change === true, - show_threshold_analysis: config.show_threshold_analysis === true, - show_threshold_shading: config.show_threshold_shading === true, - hide_raw_data: config.hide_raw_data === true, - show_trend_crosshairs: config.show_trend_crosshairs === true, - trend_method: config.trend_method || "rolling_average", - trend_window: config.trend_window || "24h", - rate_window: config.rate_window || "1h", - threshold_values: config.threshold_values && typeof config.threshold_values === "object" ? { ...config.threshold_values } : {}, - threshold_directions: config.threshold_directions && typeof config.threshold_directions === "object" ? { ...config.threshold_directions } : {}, - show_delta_analysis: config.show_delta_analysis === true, - show_delta_tooltip: config.show_delta_tooltip !== false, - show_delta_lines: config.show_delta_lines === true, - hide_delta_source_series: config.hide_delta_source_series === true, - delink_y_axis: config.delink_y_axis === true, - split_view: config.split_view === true, - show_data_gaps: config.show_data_gaps !== false, - data_gap_threshold: config.data_gap_threshold || "2h" - }; - const currentConfig = this._config || {}; - const currentDataKey = JSON.stringify({ - target: currentConfig.target || null, - entities: currentConfig.entities, - entity: currentConfig.entity, - series_entities: Array.isArray( - currentConfig.series_settings - ) ? currentConfig.series_settings.map( - (entry) => entry?.entity_id || entry?.entity || entry?.entityId || null - ) : null, - datapoint_scope: currentConfig.datapoint_scope, - hours_to_show: currentConfig.hours_to_show, - start_time: currentConfig.start_time, - end_time: currentConfig.end_time - }); - const nextDataKey = JSON.stringify({ - target: nextConfig.target || null, - entities: nextConfig.entities, - entity: nextConfig.entity, - series_entities: Array.isArray(nextConfig.series_settings) ? nextConfig.series_settings.map( - (entry) => entry?.entity_id || entry?.entity || entry?.entityId || null - ) : null, - datapoint_scope: nextConfig.datapoint_scope, - hours_to_show: nextConfig.hours_to_show, - start_time: nextConfig.start_time, - end_time: nextConfig.end_time - }); - const currentViewKey = JSON.stringify({ - series_settings: currentConfig.series_settings || [], - zoom_start_time: currentConfig.zoom_start_time, - zoom_end_time: currentConfig.zoom_end_time, - message_filter: currentConfig.message_filter || "", - hidden_event_ids: currentConfig.hidden_event_ids || [], - hovered_event_ids: currentConfig.hovered_event_ids || [], - show_event_markers: currentConfig.show_event_markers !== false, - show_event_lines: currentConfig.show_event_lines !== false, - show_tooltips: currentConfig.show_tooltips !== false, - emphasize_hover_guides: currentConfig.emphasize_hover_guides === true, - hover_snap_mode: currentConfig.hover_snap_mode || "follow_series", - show_correlated_anomalies: currentConfig.show_correlated_anomalies === true, - show_trend_lines: currentConfig.show_trend_lines === true, - show_summary_stats: currentConfig.show_summary_stats === true, - show_rate_of_change: currentConfig.show_rate_of_change === true, - show_threshold_analysis: currentConfig.show_threshold_analysis === true, - show_threshold_shading: currentConfig.show_threshold_shading === true, - show_anomalies: currentConfig.show_anomalies === true, - hide_raw_data: currentConfig.hide_raw_data === true, - show_trend_crosshairs: currentConfig.show_trend_crosshairs === true, - trend_method: currentConfig.trend_method || "rolling_average", - trend_window: currentConfig.trend_window || "24h", - rate_window: currentConfig.rate_window || "1h", - anomaly_sensitivity: currentConfig.anomaly_sensitivity || "medium", - threshold_values: currentConfig.threshold_values || {}, - threshold_directions: currentConfig.threshold_directions || {}, - show_delta_analysis: currentConfig.show_delta_analysis === true, - show_delta_tooltip: currentConfig.show_delta_tooltip !== false, - show_delta_lines: currentConfig.show_delta_lines === true, - hide_delta_source_series: currentConfig.hide_delta_source_series === true, - delink_y_axis: currentConfig.delink_y_axis === true, - split_view: currentConfig.split_view === true, - show_data_gaps: currentConfig.show_data_gaps !== false, - data_gap_threshold: currentConfig.data_gap_threshold || "2h", - comparison_hover_active: currentConfig.comparison_hover_active === true, - selected_comparison_window_id: currentConfig.selected_comparison_window_id || null, - hovered_comparison_window_id: currentConfig.hovered_comparison_window_id || null - }); - const nextViewKey = JSON.stringify({ - series_settings: nextConfig.series_settings || [], - zoom_start_time: nextConfig.zoom_start_time, - zoom_end_time: nextConfig.zoom_end_time, - message_filter: nextConfig.message_filter || "", - hidden_event_ids: nextConfig.hidden_event_ids || [], - hovered_event_ids: nextConfig.hovered_event_ids || [], - show_event_markers: nextConfig.show_event_markers !== false, - show_event_lines: nextConfig.show_event_lines !== false, - show_tooltips: nextConfig.show_tooltips !== false, - emphasize_hover_guides: nextConfig.emphasize_hover_guides === true, - hover_snap_mode: nextConfig.hover_snap_mode || "follow_series", - show_correlated_anomalies: nextConfig.show_correlated_anomalies === true, - show_trend_lines: nextConfig.show_trend_lines === true, - show_summary_stats: nextConfig.show_summary_stats === true, - show_rate_of_change: nextConfig.show_rate_of_change === true, - show_threshold_analysis: nextConfig.show_threshold_analysis === true, - show_threshold_shading: nextConfig.show_threshold_shading === true, - show_anomalies: nextConfig.show_anomalies === true, - hide_raw_data: nextConfig.hide_raw_data === true, - show_trend_crosshairs: nextConfig.show_trend_crosshairs === true, - trend_method: nextConfig.trend_method || "rolling_average", - trend_window: nextConfig.trend_window || "24h", - rate_window: nextConfig.rate_window || "1h", - anomaly_sensitivity: nextConfig.anomaly_sensitivity || "medium", - threshold_values: nextConfig.threshold_values || {}, - threshold_directions: nextConfig.threshold_directions || {}, - show_delta_analysis: nextConfig.show_delta_analysis === true, - show_delta_tooltip: nextConfig.show_delta_tooltip !== false, - show_delta_lines: nextConfig.show_delta_lines === true, - hide_delta_source_series: nextConfig.hide_delta_source_series === true, - delink_y_axis: nextConfig.delink_y_axis === true, - split_view: nextConfig.split_view === true, - show_data_gaps: nextConfig.show_data_gaps !== false, - data_gap_threshold: nextConfig.data_gap_threshold || "2h", - comparison_hover_active: nextConfig.comparison_hover_active === true, - selected_comparison_window_id: nextConfig.selected_comparison_window_id || null, - hovered_comparison_window_id: nextConfig.hovered_comparison_window_id || null - }); - const currentComparisonKey = JSON.stringify( - currentConfig.comparison_windows || [] - ); - const nextComparisonKey = JSON.stringify( - nextConfig.comparison_windows || [] - ); - const currentPreloadComparisonKey = JSON.stringify( - currentConfig.preload_comparison_windows || [] - ); - const nextPreloadComparisonKey = JSON.stringify( - nextConfig.preload_comparison_windows || [] - ); - const currentComparisonOverlayKey = JSON.stringify( - currentConfig.comparison_preview_overlay || null - ); - const nextComparisonOverlayKey = JSON.stringify( - nextConfig.comparison_preview_overlay || null - ); - const dataChanged = currentDataKey !== nextDataKey; - const viewChanged = currentViewKey !== nextViewKey; - const comparisonChanged = currentComparisonKey !== nextComparisonKey; - const preloadComparisonChanged = currentPreloadComparisonKey !== nextPreloadComparisonKey; - const comparisonOverlayChanged = currentComparisonOverlayKey !== nextComparisonOverlayKey; - if (!dataChanged && !viewChanged && !comparisonChanged && !preloadComparisonChanged && !comparisonOverlayChanged && this._configKey) { - return; - } - this._config = nextConfig; - this._configKey = JSON.stringify(nextConfig); - this._hiddenSeries = createHiddenSeriesSet( - nextConfig.series_settings - ); - this._hiddenEventIds = createHiddenEventIdSet( - nextConfig.hidden_event_ids - ); - this._zoomRange = createChartZoomRange( - nextConfig.zoom_start_time, - nextConfig.zoom_end_time - ); - if (dataChanged || comparisonChanged || preloadComparisonChanged) { - this._pruneComparisonDataCache(nextConfig); - this._lastComparisonResults = null; - } - const chartEl = this._chartEl(); - if (chartEl) { - chartEl.hass = this._hass; - chartEl._config = this._config; - chartEl._hiddenSeries = this._hiddenSeries; - chartEl._hiddenEventIds = this._hiddenEventIds; - chartEl._zoomRange = this._zoomRange; - chartEl._lastComparisonResults = this._getResolvedComparisonResults(); - if (comparisonOverlayChanged && typeof chartEl._renderComparisonPreviewOverlay === "function") { - chartEl._renderComparisonPreviewOverlay(); - } - } - if (dataChanged || !Array.isArray(nextConfig.comparison_windows) || !nextConfig.comparison_windows.length) { - this._adjustComparisonAxisScale = false; - } - if (this._hass && dataChanged) { - this._load(); - return; - } - if (this._hass && comparisonChanged) { - this._loadComparisonWindows({ redraw: true, showLoading: true }); - return; - } - if (this._hass && preloadComparisonChanged) { - this._preloadComparisonWindows().catch(() => { - }); - } - if (this._hass && comparisonOverlayChanged && this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._lastEvents, - this._lastT0, - this._lastT1 - ); - return; - } - if (this._hass && viewChanged && this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._lastEvents, - this._lastT0, - this._lastT1 - ); - } - } - // ── Entity helpers ───────────────────────────────────────────────────────── - get _entityIds() { - if (Array.isArray(this._config.series_settings)) { - const ids = this._config.series_settings.map( - (entry) => entry?.entity_id || entry?.entity || entry?.entityId || null - ).filter( - (value) => typeof value === "string" && !!value - ); - if (ids.length) { - return [...new Set(ids)]; - } - } - if (this._config.target) { - return resolveEntityIdsFromTarget(this._hass, this._config.target); - } - if (this._config.entities) { - return this._config.entities.map((e2) => typeof e2 === "string" ? e2 : e2.entity_id ?? e2.entity); - } - return [this._config.entity]; - } - get _statisticsEntityIds() { - return this._entityIds.filter( - (entityId) => !String(entityId).startsWith("binary_sensor.") - ); - } - // ── Time range ───────────────────────────────────────────────────────────── - _getRange() { - const end = this._config.end_time ? new Date(this._config.end_time) : /* @__PURE__ */ new Date(); - const start = this._config.start_time ? new Date(this._config.start_time) : new Date( - end.getTime() - this._config.hours_to_show * 3600 * 1e3 - ); - return { start, end }; - } - // ── Comparison windows ───────────────────────────────────────────────────── - _pruneComparisonDataCache(config) { - if (!this._comparisonDataCache.size) { - return; - } - const end = config.end_time ? new Date(config.end_time) : /* @__PURE__ */ new Date(); - const start = config.start_time ? new Date(config.start_time) : new Date( - end.getTime() - Number(config.hours_to_show || 24) * 3600 * 1e3 - ); - const windows = [ - ...Array.isArray(config.comparison_windows) ? config.comparison_windows : [], - ...Array.isArray(config.preload_comparison_windows) ? config.preload_comparison_windows : [] - ]; - const keepKeys = /* @__PURE__ */ new Set(); - for (const win of windows) { - if (win?.time_offset_ms == null) { - continue; - } - const winStart = new Date(start.getTime() + win.time_offset_ms); - const winEnd = new Date(end.getTime() + win.time_offset_ms); - keepKeys.add(this._getComparisonCacheKey(win, winStart, winEnd)); - } - for (const cacheKey of this._comparisonDataCache.keys()) { - if (!keepKeys.has(cacheKey)) { - this._comparisonDataCache.delete(cacheKey); - } - } - } - get _comparisonWindows() { - return Array.isArray(this._config?.comparison_windows) ? this._config.comparison_windows.filter( - (w) => w?.time_offset_ms != null - ) : []; - } - get _preloadComparisonWindowsConfig() { - return Array.isArray(this._config?.preload_comparison_windows) ? this._config.preload_comparison_windows.filter( - (w) => w?.time_offset_ms != null - ) : []; - } - _getComparisonCacheKey(win, start, end) { - return JSON.stringify({ - id: win?.id || "", - start: start?.toISOString?.() || "", - end: end?.toISOString?.() || "", - entities: this._entityIds, - statistics_entities: this._statisticsEntityIds - }); - } - _getResolvedComparisonResults() { - const { start, end } = this._getRange(); - const seenWindowIds = /* @__PURE__ */ new Set(); - const resolvedResults = []; - const comparisonWindows = [ - ...this._comparisonWindows, - ...this._preloadComparisonWindowsConfig - ]; - for (const win of comparisonWindows) { - const id = String(win?.id || ""); - if (!id || seenWindowIds.has(id)) { - continue; - } - seenWindowIds.add(id); - const winStart = new Date(start.getTime() + win.time_offset_ms); - const winEnd = new Date(end.getTime() + win.time_offset_ms); - const cacheKey = this._getComparisonCacheKey(win, winStart, winEnd); - const cached = this._comparisonDataCache.get(cacheKey); - if (!cached) { - continue; - } - resolvedResults.push(cached); - } - return resolvedResults; - } - async _loadComparisonWindowData(win, start, end) { - const hass = this._hass; - if (!hass) { - return { - id: win.id, - time_offset_ms: win.time_offset_ms, - histResult: {}, - statsResult: {} - }; - } - const cacheKey = this._getComparisonCacheKey(win, start, end); - const cached = this._comparisonDataCache.get(cacheKey); - if (cached) { - return cached; - } - const historyPromise = fetchHistoryDuringPeriod( - hass, - start.toISOString(), - end.toISOString(), - this._entityIds, - { - include_start_time_state: true, - significant_changes_only: false, - no_attributes: true - } - ).catch(() => ({})); - const statisticsPromise = this._statisticsEntityIds.length ? fetchStatisticsDuringPeriod( - hass, - start.toISOString(), - end.toISOString(), - this._statisticsEntityIds, - { - period: "hour", - types: ["mean"], - units: {} - } - ).catch(() => ({})) : Promise.resolve({}); - const [histResult, statsResult] = await Promise.all([ - historyPromise, - statisticsPromise - ]); - const result = { - ...win, - histResult: histResult || {}, - statsResult: statsResult || {} - }; - this._comparisonDataCache.set(cacheKey, result); - return result; - } - _preloadComparisonWindows() { - const { start, end } = this._getRange(); - const comparisonWindows = this._preloadComparisonWindowsConfig; - if (!comparisonWindows.length) { - return Promise.resolve([]); - } - return Promise.all( - comparisonWindows.map(async (win) => { - const winStart = new Date(start.getTime() + win.time_offset_ms); - const winEnd = new Date(end.getTime() + win.time_offset_ms); - const result = await this._loadComparisonWindowData( - win, - winStart, - winEnd - ); - return { - ...win, - id: result.id, - histResult: result.histResult, - statsResult: result.statsResult - }; - }) - ).then((results) => { - this._lastComparisonResults = this._getResolvedComparisonResults(); - if (this._config?.hovered_comparison_window_id && this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._lastEvents, - this._lastT0, - this._lastT1 - ); - } - return results; - }).catch((error) => { - logger$1.warn( - "[hass-datapoints history-card] comparison preload:failed", - { - message: error?.message || String(error) - } - ); - return []; - }); - } - _loadComparisonWindows({ - redraw = false, - requestId: requestId2 = null, - showLoading = false - } = {}) { - const { start, end } = this._getRange(); - const comparisonWindows = this._comparisonWindows; - const targetRequestId = requestId2 ?? this._loadRequestId; - const comparisonRequestId = ++this._comparisonRequestId; - if (!comparisonWindows.length) { - this._lastComparisonResults = []; - this.dispatchEvent( - new CustomEvent("hass-datapoints-comparison-loading", { - bubbles: true, - composed: true, - detail: { ids: [], loading: false } - }) - ); - if (redraw && this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._lastEvents, - this._lastT0, - this._lastT1 - ); - } - return Promise.resolve([]); - } - const cachedResults = []; - const windowsToFetch = []; - for (const win of comparisonWindows) { - const winStart = new Date(start.getTime() + win.time_offset_ms); - const winEnd = new Date(end.getTime() + win.time_offset_ms); - const cacheKey = this._getComparisonCacheKey(win, winStart, winEnd); - const cached = this._comparisonDataCache.get(cacheKey); - if (cached) { - cachedResults.push(cached); - } else { - windowsToFetch.push({ win, winStart, winEnd }); - } - } - if (!windowsToFetch.length) { - this._lastComparisonResults = this._getResolvedComparisonResults(); - if (redraw && this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._lastEvents, - this._lastT0, - this._lastT1 - ); - } - return Promise.resolve(this._lastComparisonResults); - } - this._lastComparisonResults = cachedResults.length ? this._getResolvedComparisonResults() : null; - if (showLoading) { - this._setChartLoading(true); - } - this.dispatchEvent( - new CustomEvent("hass-datapoints-comparison-loading", { - bubbles: true, - composed: true, - detail: { - ids: windowsToFetch.map(({ win }) => win.id).filter(Boolean), - loading: true - } - }) - ); - return Promise.all( - windowsToFetch.map( - async ({ win, winStart, winEnd }) => this._loadComparisonWindowData(win, winStart, winEnd) - ) - ).then(() => { - if (comparisonRequestId !== this._comparisonRequestId) { - return this._lastComparisonResults || []; - } - if (targetRequestId != null && targetRequestId !== this._loadRequestId) { - return this._lastComparisonResults || []; - } - this._lastComparisonResults = this._getResolvedComparisonResults(); - this.dispatchEvent( - new CustomEvent("hass-datapoints-comparison-loading", { - bubbles: true, - composed: true, - detail: { - ids: windowsToFetch.map(({ win }) => win.id).filter(Boolean), - loading: false - } - }) - ); - if (redraw && this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._lastEvents, - this._lastT0, - this._lastT1 - ); - } else if (showLoading) { - this._setChartLoading(false); - } - return this._lastComparisonResults; - }).catch(() => { - if (comparisonRequestId === this._comparisonRequestId) { - this._lastComparisonResults = []; - logger$1.warn("[hass-datapoints history-card] comparison load:failed", { - comparisonRequestId, - ids: comparisonWindows.map((win) => win.id).filter(Boolean) - }); - this.dispatchEvent( - new CustomEvent("hass-datapoints-comparison-loading", { - bubbles: true, - composed: true, - detail: { - ids: windowsToFetch.map(({ win }) => win.id).filter(Boolean), - loading: false - } - }) - ); - if (redraw && this._lastHistResult && this._lastEvents) { - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - this._lastEvents, - this._lastT0, - this._lastT1 - ); - } else if (showLoading) { - this._setChartLoading(false); - } - } else if (showLoading) { - this._setChartLoading(false); - } - return []; - }); - } - // ── Data loading ─────────────────────────────────────────────────────────── - async _load() { - const { start, end } = this._getRange(); - const t0 = start.getTime(); - const t1 = end.getTime(); - const requestId2 = ++this._loadRequestId; - this._setChartLoading(true); - logger$1.log("[hass-datapoints history-card] load triggered", { - requestId: requestId2, - entityIds: this._entityIds, - start: start.toISOString(), - end: end.toISOString() - }); - const partial = { - histResult: null, - statsResult: this._statisticsEntityIds.length ? null : {}, - events: null, - histDone: false, - statsDone: !this._statisticsEntityIds.length, - eventsDone: false, - histFailed: false, - statsFailed: false, - eventsFailed: false, - hasDrawnDrawable: false, - lastDrawState: null, - lastDrawQuality: null - }; - const logChartRedrawData = (reason, histResult, statsResult, events) => { - logger$1.log("[hass-datapoints history] redraw data update", { - reason, - requestId: requestId2, - entityIds: this._entityIds, - start: start.toISOString(), - end: end.toISOString(), - histResult, - statsResult, - events - }); - }; - const maybeDraw = () => { - if (requestId2 !== this._loadRequestId) { - return; - } - const hasDrawableData = this._hasDrawableHistoryData( - partial.histResult || {}, - partial.statsResult || {} - ); - const numericRequestsFinished = partial.histDone && partial.statsDone; - if (!hasDrawableData && !numericRequestsFinished) { - return; - } - if (partial.hasDrawnDrawable) { - const drawQuality = hasDrawableData ? this._getDrawableHistoryQuality( - partial.histResult || {}, - partial.statsResult || {} - ) : null; - const redrawForHistory = hasDrawableData && !partial.lastDrawState?.histDone && partial.histDone; - const redrawForStats = hasDrawableData && !partial.lastDrawState?.statsDone && partial.statsDone; - const redrawForEvents = hasDrawableData && !partial.lastDrawState?.eventsDone && partial.eventsDone; - const shouldRedraw = redrawForHistory || redrawForStats || redrawForEvents; - const wouldDowngradeDraw = !!drawQuality && !!partial.lastDrawQuality && drawQuality.totalPoints < partial.lastDrawQuality.totalPoints; - if (!shouldRedraw) { - if (partial.histDone && partial.statsDone && partial.eventsDone) { - this._setChartLoading(false); - } - return; - } - if (wouldDowngradeDraw) { - if (partial.histDone && partial.statsDone && partial.eventsDone) { - this._setChartLoading(false); - } - return; - } - if (redrawForEvents && !redrawForHistory && this._lastHistResult && Number.isFinite(this._lastT0) && Number.isFinite(this._lastT1)) { - const filteredEvents2 = this._filterEvents(partial.events || []); - logChartRedrawData( - "events_update", - this._lastHistResult, - this._lastStatsResult || {}, - filteredEvents2 - ); - partial.lastDrawState = { - histDone: partial.histDone, - statsDone: partial.statsDone, - eventsDone: partial.eventsDone - }; - this._queueDrawChart( - this._lastHistResult, - this._lastStatsResult || {}, - partial.events || [], - this._lastT0, - this._lastT1, - { - loading: !(partial.histDone && partial.statsDone && partial.eventsDone) - } - ); - return; - } - } - if (hasDrawableData) { - const drawQuality = this._getDrawableHistoryQuality( - partial.histResult || {}, - partial.statsResult || {} - ); - partial.hasDrawnDrawable = true; - partial.lastDrawState = { - histDone: partial.histDone, - statsDone: partial.statsDone, - eventsDone: partial.eventsDone - }; - partial.lastDrawQuality = drawQuality; - } - const filteredEvents = this._filterEvents(partial.events || []); - logChartRedrawData( - partial.hasDrawnDrawable ? "data_update" : "initial_data_draw", - partial.histResult || {}, - partial.statsResult || {}, - filteredEvents - ); - this._queueDrawChart( - partial.histResult || {}, - partial.statsResult || {}, - partial.events || [], - t0, - t1, - { - loading: !(partial.histDone && partial.statsDone && partial.eventsDone) - } - ); - }; - const finalize = () => { - if (requestId2 !== this._loadRequestId) { - return; - } - if (!(partial.histDone && partial.statsDone && partial.eventsDone)) { - return; - } - if (partial.histFailed && partial.statsFailed || partial.histResult == null && partial.statsResult == null) { - this._setChartMessage("Failed to load data."); - this._setChartLoading(false); - return; - } - if (partial.hasDrawnDrawable) { - this._setChartLoading(false); - } - this._preloadComparisonWindows().catch(() => { - }); - }; - this._loadComparisonWindows({ redraw: true, requestId: requestId2 }).catch(() => { - }); - try { - const hass = this._hass; - if (!hass) { - this._setChartMessage("Failed to load data."); - this._setChartLoading(false); - return; - } - fetchHistoryDuringPeriod( - hass, - start.toISOString(), - end.toISOString(), - this._entityIds, - { - include_start_time_state: true, - significant_changes_only: false, - no_attributes: true - } - ).then((histResult) => { - partial.histResult = histResult || {}; - partial.histDone = true; - maybeDraw(); - finalize(); - }).catch((err) => { - partial.histDone = true; - partial.histFailed = true; - logger$1.error( - "[hass-datapoints history-card] history load failed", - err - ); - maybeDraw(); - finalize(); - }); - if (this._statisticsEntityIds.length) { - fetchStatisticsDuringPeriod( - hass, - start.toISOString(), - end.toISOString(), - this._statisticsEntityIds, - { - period: "hour", - types: ["mean"], - units: {} - } - ).then((statsResult) => { - partial.statsResult = statsResult || {}; - partial.statsDone = true; - maybeDraw(); - finalize(); - }).catch((err) => { - partial.statsDone = true; - partial.statsFailed = true; - logger$1.error( - "[hass-datapoints history-card] statistics load failed", - err - ); - maybeDraw(); - finalize(); - }); - } - if (this._config.datapoint_scope === "hidden") { - partial.events = []; - partial.eventsDone = true; - maybeDraw(); - finalize(); - } else { - fetchEvents( - hass, - start.toISOString(), - end.toISOString(), - this._config.datapoint_scope === "all" ? void 0 : this._entityIds - ).then((events) => { - partial.events = events || []; - partial.eventsDone = true; - maybeDraw(); - finalize(); - }).catch((err) => { - partial.eventsDone = true; - partial.eventsFailed = true; - logger$1.error( - "[hass-datapoints history-card] event load failed", - err - ); - maybeDraw(); - finalize(); - }); - } - } catch (err) { - this._setChartMessage("Failed to load data."); - this._setChartLoading(false); - logger$1.error("[hass-datapoints history-card]", err); - } - } - // ── Drawing ──────────────────────────────────────────────────────────────── - /** Delegates to the hass-datapoints-history-chart sub-component for resize-replay. */ - _drawChart(...args) { - const chartEl = this._chartEl(); - if (chartEl) { - chartEl.hass = this._hass; - chartEl._config = this._config; - chartEl._hiddenSeries = this._hiddenSeries; - chartEl._hiddenEventIds = this._hiddenEventIds; - chartEl._zoomRange = this._zoomRange; - chartEl._lastComparisonResults = this._lastComparisonResults; - chartEl._drawChart(...args); - } - } - /** Returns the hass-datapoints-history-chart element once it is in the shadow DOM. */ - _chartEl() { - return this.shadowRoot?.querySelector( - "hass-datapoints-history-chart" - ) ?? null; - } - /** - * Queue a draw cycle. Delegates to hass-datapoints-history-chart and also records last - * draw args so the base-class ResizeObserver can replay via _drawChart(). - */ - _queueDrawChart(histResult, statsResult, events, t0, t1, options = {}) { - const drawRequestId = ++this._drawRequestId; - const filteredEvents = this._filterEvents(events); - this._lastHistResult = histResult; - this._lastStatsResult = statsResult; - this._lastEvents = events; - this._lastT0 = t0; - this._lastT1 = t1; - this._lastDrawArgs = [ - histResult, - statsResult, - events, - t0, - t1, - { ...options, drawRequestId } - ]; - const chartEl = this._chartEl(); - if (chartEl) { - chartEl.hass = this._hass; - chartEl._config = this._config; - chartEl._hiddenSeries = this._hiddenSeries; - chartEl._hiddenEventIds = this._hiddenEventIds; - chartEl._zoomRange = this._zoomRange; - chartEl._lastComparisonResults = this._lastComparisonResults; - chartEl._queueDrawChart( - histResult, - statsResult, - filteredEvents, - t0, - t1, - { ...options, drawRequestId } - ); - } - } - // ── Loading/message UI helpers ───────────────────────────────────────────── - _setChartLoading(isLoading) { - const chartEl = this._chartEl(); - if (chartEl?._setChartLoading) { - chartEl._setChartLoading(isLoading); - } - } - _setChartMessage(message = "") { - const chartEl = this._chartEl(); - if (chartEl?._setChartMessage) { - chartEl._setChartMessage(message); - } - } - // ── History data quality helpers ─────────────────────────────────────────── - /** - * Returns true if there is at least one entity with drawable data points - * in either the history or statistics result. - */ - _hasDrawableHistoryData(histResult, statsResult) { - return this._entityIds.some((entityId) => { - const histData = histResult[entityId]; - const statsData = statsResult[entityId]; - const histPoints = Array.isArray(histData) ? histData.length : 0; - const statsPoints = Array.isArray(statsData) ? statsData.length : 0; - return histPoints > 0 || statsPoints > 0; - }); - } - /** - * Returns a quality descriptor for the current drawable data — used to - * avoid downgrading a high-resolution draw with a lower-resolution one. - */ - _getDrawableHistoryQuality(histResult, statsResult) { - let totalPoints = 0; - for (const entityId of this._entityIds) { - const histData = histResult[entityId]; - const statsData = statsResult[entityId]; - totalPoints += Array.isArray(histData) ? histData.length : 0; - totalPoints += Array.isArray(statsData) ? statsData.length : 0; - } - return { totalPoints }; - } - // ── Event filtering ──────────────────────────────────────────────────────── - _filterEvents(events) { - const query = String(this._config?.message_filter || "").trim().toLowerCase(); - const visibleEvents = events.filter( - (event) => !this._hiddenEventIds.has(event?.id ?? "") - ); - if (!query) { - return visibleEvents; - } - return visibleEvents.filter((event) => { - const ev = event; - const haystack = [ - ev?.message || "", - ev?.annotation || "", - ...(ev?.entity_ids || []).filter(Boolean) - ].join("\n").toLowerCase(); - return haystack.includes(query); - }); - } - _buildNavigationPageState() { - const range = this._getRange(); - const fallbackZoomRange = typeof this._config.zoom_start_time !== "undefined" && typeof this._config.zoom_end_time !== "undefined" ? { - start: this._config.zoom_start_time, - end: this._config.zoom_end_time - } : null; - const targetSelection = this._config.target && typeof this._config.target === "object" ? normalizeTargetValue(this._config.target) : { entity_id: this._entityIds }; - const baseState = buildHistoryPageSessionState({ - _entities: this._entityIds, - _seriesRows: Array.isArray(this._config.series_settings) ? [...this._config.series_settings] : this._entityIds.map((entityId) => ({ entity_id: entityId })), - _targetSelection: targetSelection, - _targetSelectionRaw: targetSelection, - _datapointScope: this._config.datapoint_scope || "linked", - _showChartDatapointIcons: this._config.show_event_markers !== false, - _showChartDatapointLines: this._config.show_event_lines !== false, - _showChartTooltips: this._config.show_tooltips !== false, - _showChartEmphasizedHoverGuides: this._config.emphasize_hover_guides === true, - _chartHoverSnapMode: this._config.hover_snap_mode || "follow_series", - _delinkChartYAxis: this._config.delink_y_axis === true, - _splitChartView: this._config.split_view === true, - _showCorrelatedAnomalies: this._config.show_correlated_anomalies === true, - _chartAnomalyOverlapMode: "all", - _showDataGaps: this._config.show_data_gaps !== false, - _dataGapThreshold: this._config.data_gap_threshold || "2h", - _contentSplitRatio: 0.62, - _startTime: range.start, - _endTime: range.end, - _chartZoomCommittedRange: this._zoomRange ? { - start: this._zoomRange.start, - end: this._zoomRange.end - } : fallbackZoomRange, - _comparisonWindows: Array.isArray(this._config.comparison_windows) ? [...this._config.comparison_windows] : [], - _hours: Number(this._config.hours_to_show || 24), - _sidebarCollapsed: false, - _sidebarAccordionTargetsOpen: true, - _sidebarAccordionDatapointsOpen: true, - _sidebarAccordionAnalysisOpen: true, - _sidebarAccordionChartOpen: true - }); - return { - ...baseState, - show_chart_trend_lines: this._config.show_trend_lines === true, - show_chart_summary_stats: this._config.show_summary_stats === true, - show_chart_rate_of_change: this._config.show_rate_of_change === true, - show_chart_threshold_analysis: this._config.show_threshold_analysis === true, - show_chart_threshold_shading: this._config.show_threshold_shading === true, - show_chart_anomalies: this._config.show_anomalies === true, - show_chart_trend_crosshairs: this._config.show_trend_crosshairs === true, - chart_trend_method: this._config.trend_method || "rolling_average", - chart_trend_window: this._config.trend_window || "24h", - chart_rate_window: this._config.rate_window || "1h", - chart_anomaly_sensitivity: this._config.anomaly_sensitivity || "medium", - chart_threshold_values: this._config.threshold_values || {}, - chart_threshold_directions: this._config.threshold_directions || {}, - show_chart_delta_analysis: this._config.show_delta_analysis === true, - show_chart_delta_tooltip: this._config.show_delta_tooltip !== false, - show_chart_delta_lines: this._config.show_delta_lines === true, - hide_chart_source_series: this._config.hide_raw_data === true, - chart_anomaly_rate_window: this._config.rate_window || "1h" - }; - } - _navigateToPanel(event) { - event.preventDefault(); - event.stopPropagation(); - const range = this._getRange(); - const zoomStartTime = this._zoomRange?.start ? this._zoomRange.start : this._config.zoom_start_time; - const zoomEndTime = this._zoomRange?.end ? this._zoomRange.end : this._config.zoom_end_time; - navigateToDataPointsHistory( - this, - this._config.target && typeof this._config.target === "object" ? normalizeTargetValue(this._config.target) : { entity_id: this._entityIds }, - { - datapoint_scope: this._config.datapoint_scope || void 0, - start_time: Number.isFinite(this._lastT0) ? this._lastT0 : range.start, - end_time: Number.isFinite(this._lastT1) ? this._lastT1 : range.end, - zoom_start_time: zoomStartTime, - zoom_end_time: zoomEndTime, - page_state: this._buildNavigationPageState() - } - ); - } - // ── Keyboard / scroll handlers ───────────────────────────────────────────── - _handleWindowKeyDown(ev) { - if (ev.key !== "Escape") { - return; - } - if (this._annotationDialog?.isOpen?.()) { - ev.preventDefault(); - return; - } - if (!this._zoomRange) { - return; - } - ev.preventDefault(); - this._zoomRange = null; - this._dispatchZoomRange("reset"); - } - _handleChartScroll() { - if (this._scrollSyncSuspended || !this._zoomRange) { - return; - } - if (this._ignoreNextProgrammaticScrollEvent) { - this._ignoreNextProgrammaticScrollEvent = false; - this._lastProgrammaticScrollLeft = null; - return; - } - if (this._lastProgrammaticScrollLeft != null && Math.abs( - (this._chartScrollViewportEl?.scrollLeft || 0) - this._lastProgrammaticScrollLeft - ) < 1) { - this._lastProgrammaticScrollLeft = null; - return; - } - this._lastProgrammaticScrollLeft = null; - } - _dispatchZoomRange(source) { - this.dispatchEvent( - new CustomEvent("hass-datapoints-chart-zoom", { - bubbles: true, - composed: true, - detail: this._zoomRange ? { - startTime: this._zoomRange.start, - endTime: this._zoomRange.end, - preview: false, - source - } : { - startTime: null, - endTime: null, - preview: false, - source - } - }) - ); - } - _scheduleZoomReload() { - if (this._zoomReloadTimer != null) { - window.clearTimeout(this._zoomReloadTimer); - } - this._zoomReloadTimer = window.setTimeout(() => { - this._zoomReloadTimer = null; - this._load(); - }, 140); - } - // ── Render ───────────────────────────────────────────────────────────────── - render() { - return b` - - ${this._config?.title ? b` -

- ${this._config.title} - - - -

- ` : ""} - -
- `; - } - // ── Static API ───────────────────────────────────────────────────────────── - static getStubConfig() { - return { - title: "History with Events", - entity: "sensor.example", - hours_to_show: 24 - }; - } - static getConfigElement() { - return document.createElement("hass-datapoints-history-card-editor"); - } - } - __publicField$R(HassRecordsHistoryCard, "styles", styles$T); - customElements.define("hass-datapoints-history-card", HassRecordsHistoryCard); - const styles$P = i$5``; - const sharedStyles = i$5` - :host { - display: block; - --dp-spacing-xs: calc(var(--spacing, 8px) * 0.5); - --dp-spacing-sm: var(--spacing, 8px); - --dp-spacing-md: calc(var(--spacing, 8px) * 1.5); - --dp-spacing-lg: calc(var(--spacing, 8px) * 2); - } - .field { + .method-list { display: grid; - gap: 4px; - justify-items: start; + gap: var(--dp-spacing-sm, 8px); } - .field-label { - font-size: 0.72rem; - font-weight: 600; - letter-spacing: 0.02em; - color: var(--secondary-text-color); + .method-item { + display: grid; + gap: var(--dp-spacing-sm, 8px); } - .select, - .input { - width: auto; - max-width: 100%; - min-width: 0; - box-sizing: border-box; - padding: calc(var(--spacing, 8px) * 0.75) calc(var(--spacing, 8px) * 0.875); - border-radius: 10px; - border: 1px solid - color-mix( - in srgb, - var(--divider-color, rgba(0, 0, 0, 0.12)) 88%, - transparent - ); - background: var(--card-background-color, #fff); - color: var(--primary-text-color); - font: inherit; - font-size: 0.84rem; + ha-tooltip { + --ha-tooltip-padding: var(--dp-spacing-md, calc(var(--spacing, 8px) * 1.5)); } - .toggle-group { - display: flex; - gap: calc(var(--spacing, 8px) * 0.625); - align-items: center; + ha-tooltip::part(body) { + padding: var(--dp-spacing-md, calc(var(--spacing, 8px) * 1.5)); } - .option { - display: flex; + .method-help { + display: inline-flex; align-items: center; - gap: calc(var(--spacing, 8px) * 0.75); - color: var(--primary-text-color); - font-size: 0.84rem; - cursor: pointer; + justify-content: center; + width: 10px; + height: 10px; + flex: 0 0 auto; + border-radius: 50%; + border: 1px solid var(--secondary-text-color, #888); + background: transparent; + color: var(--secondary-text-color, #888); + font-size: 9px; + font-weight: 700; + cursor: help; + padding: 0; + vertical-align: middle; + appearance: none; + -webkit-appearance: none; } - .option input[type="checkbox"] { - margin: 0; - accent-color: var(--primary-color, #03a9f4); - cursor: pointer; + .method-help:focus-visible { + outline: 2px solid var(--primary-color, #03a9f4); + outline-offset: 2px; } `; - const styles$O = i$5``; - const styles$N = i$5` + //#endregion + //#region custom_components/hass_datapoints/src/atoms/analysis/analysis-method-subopts/analysis-method-subopts.styles.ts + var styles$43 = i$5` :host { display: block; - --dp-spacing-xs: calc(var(--spacing, 8px) * 0.5); --dp-spacing-sm: var(--spacing, 8px); - --dp-spacing-md: calc(var(--spacing, 8px) * 1.5); - --dp-spacing-lg: calc(var(--spacing, 8px) * 2); - } - - .group { - display: grid; - gap: var(--dp-spacing-sm); - border-radius: 6px; } - .group-body { + .subopts { + padding-left: calc(var(--spacing, 8px) * 1.5); display: grid; gap: var(--dp-spacing-sm); - padding: var(--dp-spacing-sm); + justify-items: start; border-left: 3px solid var(--primary-color); margin-left: 5px; - padding-left: var(--dp-spacing-md); - } - - .option { - display: flex; - align-items: center; - gap: calc(var(--spacing, 8px) * 0.75); - color: var(--primary-text-color); - font-size: 0.84rem; - cursor: pointer; - } - - .option.top { - align-items: flex-start; - } - - .option.is-disabled { - opacity: 0.4; - pointer-events: none; - } - - .option input[type="checkbox"] { - margin: 0; - accent-color: var(--primary-color, #03a9f4); - cursor: pointer; - } - - .help-text { - display: inline-block; - color: var(--secondary-text-color); - opacity: 0.8; - padding-top: 2px; } `; - var __create$H = Object.create; - var __defProp$Q = Object.defineProperty; - var __getOwnPropDesc$H = Object.getOwnPropertyDescriptor; - var __knownSymbol$H = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$H = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$Q = (obj, key, value) => key in obj ? __defProp$Q(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$H = (base) => [, , , __create$H(base?.[__knownSymbol$H("metadata")] ?? null)]; - var __decoratorStrings$H = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$H = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$H("Function expected") : fn; - var __decoratorContext$H = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$H[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$H("Already initialized") : fns.push(__expectFn$H(fn || null)) }); - var __decoratorMetadata$H = (array, target) => __defNormalProp$Q(target, __knownSymbol$H("metadata"), array[3]); - var __runInitializers$H = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$H = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$H[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$H({ get [name]() { - return __privateGet$G(this, extra); - }, set [name](x2) { - return __privateSet$G(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$H(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$H(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$H("Object expected"); - else __expectFn$H(fn = it.get) && (desc.get = fn), __expectFn$H(fn = it.set) && (desc.set = fn), __expectFn$H(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$Q(target, name, desc), target; - }; - var __publicField$Q = (obj, key, value) => __defNormalProp$Q(obj, key + "", value); - var __accessCheck$G = (obj, member, msg2) => member.has(obj) || __typeError$H("Cannot " + msg2); - var __privateGet$G = (obj, member, getter) => (__accessCheck$G(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$G = (obj, member, value) => member.has(obj) ? __typeError$H("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$G = (obj, member, value, setter) => (__accessCheck$G(obj, member, "write to private field"), member.set(obj, value), value); - var _alignTop_dec, _disabled_dec$1, _checked_dec, _label_dec$8, _a$H, _init$H, _label$8, _checked, _disabled$1, _alignTop; - class AnalysisGroup extends (_a$H = i$2, _label_dec$8 = [n({ type: String })], _checked_dec = [n({ type: Boolean })], _disabled_dec$1 = [n({ type: Boolean })], _alignTop_dec = [n({ type: Boolean, attribute: "align-top" })], _a$H) { - constructor() { - super(...arguments); - __privateAdd$G(this, _label$8, __runInitializers$H(_init$H, 8, this, "")), __runInitializers$H(_init$H, 11, this); - __privateAdd$G(this, _checked, __runInitializers$H(_init$H, 12, this, false)), __runInitializers$H(_init$H, 15, this); - __privateAdd$G(this, _disabled$1, __runInitializers$H(_init$H, 16, this, false)), __runInitializers$H(_init$H, 19, this); - __privateAdd$G(this, _alignTop, __runInitializers$H(_init$H, 20, this, false)), __runInitializers$H(_init$H, 23, this); - } - _onChange(e2) { - const checked = e2.target.checked; - this.checked = checked; - this.dispatchEvent( - new CustomEvent("dp-group-change", { - detail: { checked }, - bubbles: true, - composed: true - }) - ); - } - render() { - const groupClass = ["group", this.checked ? "is-open" : ""].filter(Boolean).join(" "); - const optionClass = [ - "option", - this.alignTop ? "top" : "", - this.disabled ? "is-disabled" : "" - ].filter(Boolean).join(" "); - return b` -
- - ${this.checked ? b` -
- -
- ` : A} -
- `; - } - } - _init$H = __decoratorStart$H(_a$H); - _label$8 = /* @__PURE__ */ new WeakMap(); - _checked = /* @__PURE__ */ new WeakMap(); - _disabled$1 = /* @__PURE__ */ new WeakMap(); - _alignTop = /* @__PURE__ */ new WeakMap(); - __decorateElement$H(_init$H, 4, "label", _label_dec$8, AnalysisGroup, _label$8); - __decorateElement$H(_init$H, 4, "checked", _checked_dec, AnalysisGroup, _checked); - __decorateElement$H(_init$H, 4, "disabled", _disabled_dec$1, AnalysisGroup, _disabled$1); - __decorateElement$H(_init$H, 4, "alignTop", _alignTop_dec, AnalysisGroup, _alignTop); - __decoratorMetadata$H(_init$H, AnalysisGroup); - __publicField$Q(AnalysisGroup, "styles", styles$N); - customElements.define("analysis-group", AnalysisGroup); - var __create$G = Object.create; - var __defProp$P = Object.defineProperty; - var __getOwnPropDesc$G = Object.getOwnPropertyDescriptor; - var __knownSymbol$G = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$G = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$P = (obj, key, value) => key in obj ? __defProp$P(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __name$j = (target, value) => __defProp$P(target, "name", { value, configurable: true }); - var __decoratorStart$G = (base) => [, , , __create$G(base?.[__knownSymbol$G("metadata")] ?? null)]; - var __decoratorStrings$G = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$G = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$G("Function expected") : fn; - var __decoratorContext$G = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$G[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$G("Already initialized") : fns.push(__expectFn$G(fn || null)) }); - var __decoratorMetadata$G = (array, target) => __defNormalProp$P(target, __knownSymbol$G("metadata"), array[3]); - var __runInitializers$G = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$G = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = !!(flags & 8), p2 = !!(flags & 16); - var j2 = k2 > 3 ? array.length + 1 : k2 ? s2 ? 1 : 2 : 0, key = __decoratorStrings$G[k2 + 5]; - var initializers = k2 > 3 && (array[j2 - 1] = []), extraInitializers = array[j2] || (array[j2] = []); - var desc = k2 && (!p2 && !s2 && (target = target.prototype), k2 < 5 && (k2 > 3 || !p2) && __getOwnPropDesc$G(k2 < 4 ? target : { get [name]() { - return __privateGet$F(this, extra); - }, set [name](x2) { - return __privateSet$F(this, extra, x2); - } }, name)); - k2 ? p2 && k2 < 4 && __name$j(extra, (k2 > 2 ? "set " : k2 > 1 ? "get " : "") + name) : __name$j(target, name); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$G(k2, name, done = {}, array[3], extraInitializers); - if (k2) { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: p2 ? (x2) => __privateIn$i(target, x2) : (x2) => name in x2 }; - if (k2 ^ 3) access.get = p2 ? (x2) => (k2 ^ 1 ? __privateGet$F : __privateMethod$i)(x2, target, k2 ^ 4 ? extra : desc.get) : (x2) => x2[name]; - if (k2 > 2) access.set = p2 ? (x2, y2) => __privateSet$F(x2, target, y2, k2 ^ 4 ? extra : desc.set) : (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])(k2 ? k2 < 4 ? p2 ? extra : desc[key] : k2 > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1; - if (k2 ^ 4 || it === void 0) __expectFn$G(it) && (k2 > 4 ? initializers.unshift(it) : k2 ? p2 ? extra = it : desc[key] = it : target = it); - else if (typeof it !== "object" || it === null) __typeError$G("Object expected"); - else __expectFn$G(fn = it.get) && (desc.get = fn), __expectFn$G(fn = it.set) && (desc.set = fn), __expectFn$G(fn = it.init) && initializers.unshift(fn); - } - return k2 || __decoratorMetadata$G(array, target), desc && __defProp$P(target, name, desc), p2 ? k2 ^ 4 ? extra : desc : target; - }; - var __publicField$P = (obj, key, value) => __defNormalProp$P(obj, key + "", value); - var __accessCheck$F = (obj, member, msg2) => member.has(obj) || __typeError$G("Cannot " + msg2); - var __privateIn$i = (member, obj) => Object(obj) !== obj ? __typeError$G('Cannot use the "in" operator on this value') : member.has(obj); - var __privateGet$F = (obj, member, getter) => (__accessCheck$F(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); - var __privateAdd$F = (obj, member, value) => member.has(obj) ? __typeError$G("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$F = (obj, member, value, setter) => (__accessCheck$F(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); - var __privateMethod$i = (obj, member, method) => (__accessCheck$F(obj, member, "access private method"), method); - var _entityId_dec$7, _analysis_dec$7, _a$G, _AnalysisSampleGroup_decorators, _init$G, _analysis$7, _entityId$7; - const SAMPLE_INTERVAL_OPTIONS = [ - { value: "raw", label: "Raw (no sampling)" }, - { value: "5s", label: "5 seconds" }, - { value: "10s", label: "10 seconds" }, - { value: "15s", label: "15 seconds" }, - { value: "30s", label: "30 seconds" }, - { value: "1m", label: "1 minute" }, - { value: "2m", label: "2 minutes" }, - { value: "5m", label: "5 minutes" }, - { value: "10m", label: "10 minutes" }, - { value: "15m", label: "15 minutes" }, - { value: "30m", label: "30 minutes" }, - { value: "1h", label: "1 hour" }, - { value: "2h", label: "2 hours" }, - { value: "3h", label: "3 hours" }, - { value: "4h", label: "4 hours" }, - { value: "6h", label: "6 hours" }, - { value: "12h", label: "12 hours" }, - { value: "24h", label: "24 hours" } - ]; - const SAMPLE_AGGREGATE_OPTIONS = [ - { value: "mean", label: "Mean (average)" }, - { value: "min", label: "Min" }, - { value: "max", label: "Max" }, - { value: "median", label: "Median" }, - { value: "first", label: "First" }, - { value: "last", label: "Last" } - ]; - _AnalysisSampleGroup_decorators = [localized()]; - class AnalysisSampleGroup extends (_a$G = i$2, _analysis_dec$7 = [n({ type: Object })], _entityId_dec$7 = [n({ type: String, attribute: "entity-id" })], _a$G) { - constructor() { - super(...arguments); - __privateAdd$F(this, _analysis$7, __runInitializers$G(_init$G, 8, this, {})), __runInitializers$G(_init$G, 11, this); - __privateAdd$F(this, _entityId$7, __runInitializers$G(_init$G, 12, this, "")), __runInitializers$G(_init$G, 15, this); - } - _emit(key, value) { - this.dispatchEvent( - new CustomEvent("dp-group-analysis-change", { - detail: { entityId: this.entityId, key, value }, - bubbles: true, - composed: true - }) - ); - } - _localizedOptions(options) { - return options.map((opt) => ({ - ...opt, - label: msg(opt.label) - })); - } - _renderSelect(key, options, value) { - return b` + //#endregion + //#region custom_components/hass_datapoints/src/atoms/analysis/analysis-method-subopts/analysis-method-subopts.ts + var AnalysisMethodSubopts = class extends i$2 { + render() { + return b`
`; + } + }; + _defineProperty(AnalysisMethodSubopts, "styles", styles$43); + customElements.define("analysis-method-subopts", AnalysisMethodSubopts); + //#endregion + //#region custom_components/hass_datapoints/src/molecules/analysis-anomaly-group/analysis-anomaly-group.ts + var ANALYSIS_ANOMALY_SENSITIVITY_OPTIONS = [ + { + value: "low", + label: "Low" + }, + { + value: "medium", + label: "Medium" + }, + { + value: "high", + label: "High" + } + ]; + var ANALYSIS_ANOMALY_METHOD_OPTIONS = [ + { + value: "trend_residual", + label: "Trend deviation", + help: "Flags points that deviate significantly from a fitted trend line. Good for catching gradual drift or sudden jumps away from a steady baseline." + }, + { + value: "rate_of_change", + label: "Sudden change", + help: "Flags unusually fast rises or drops compared to the typical rate of change. Best for detecting spikes, crashes, or rapid transitions." + }, + { + value: "iqr", + label: "Statistical outlier (IQR)", + help: "Uses the interquartile range to flag values far outside the normal spread of data. Robust against outliers that skew averages." + }, + { + value: "rolling_zscore", + label: "Rolling Z-score", + help: "Compares each value to a rolling mean and standard deviation. Catches unusual readings relative to recent context rather than the whole series." + }, + { + value: "persistence", + label: "Flat-line / stuck value", + help: "Flags when a sensor reports nearly the same value for an unusually long time. Useful for detecting stuck sensors or frozen readings." + }, + { + value: "comparison_window", + label: "Comparison window deviation", + help: "Compares the current period to a reference date window. Highlights differences from an expected historical pattern, such as last week or the same day last year." + } + ]; + var ANALYSIS_ANOMALY_RATE_WINDOW_OPTIONS = [ + { + value: "1h", + label: "1 hour" + }, + { + value: "6h", + label: "6 hours" + }, + { + value: "24h", + label: "24 hours" + } + ]; + var ANALYSIS_ANOMALY_ZSCORE_WINDOW_OPTIONS = [ + { + value: "1h", + label: "1 hour" + }, + { + value: "6h", + label: "6 hours" + }, + { + value: "24h", + label: "24 hours" + }, + { + value: "7d", + label: "7 days" + } + ]; + var ANALYSIS_ANOMALY_PERSISTENCE_WINDOW_OPTIONS = [ + { + value: "30m", + label: "30 minutes" + }, + { + value: "1h", + label: "1 hour" + }, + { + value: "3h", + label: "3 hours" + }, + { + value: "6h", + label: "6 hours" + }, + { + value: "12h", + label: "12 hours" + }, + { + value: "24h", + label: "24 hours" + } + ]; + var ANALYSIS_ANOMALY_OVERLAP_MODE_OPTIONS$2 = [{ + value: "all", + label: "Show all anomalies" + }, { + value: "only", + label: "Overlaps only" + }]; + var AnalysisAnomalyGroup = @localized() class extends i$2 { + @n$1({ type: Object }) accessor analysis = {}; + @n$1({ + type: String, + attribute: "entity-id" + }) accessor entityId = ""; + @n$1({ + type: Array, + attribute: "comparison-windows" + }) accessor comparisonWindows = []; + /** Whether analysis is currently being computed in the worker for this entity. */ + @n$1({ + type: Boolean, + attribute: false + }) accessor computing = false; + /** Overall analysis computation progress (0–100) shown in the group header. */ + @n$1({ + type: Number, + attribute: false + }) accessor computingProgress = 0; + /** Set of anomaly method names still in-flight in the worker. Each method shows its own spinner. */ + @n$1({ + type: Object, + attribute: false + }) accessor computingMethods = /* @__PURE__ */ new Set(); + _emit(key, value) { + this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { + detail: { + entityId: this.entityId, + key, + value + }, + bubbles: true, + composed: true + })); + } + _renderSelect(key, options, value) { + return b` `; - } - _onGroupChange(e2) { - const enabled = e2.detail.checked; - this._emit("sample_interval", enabled ? "5m" : "raw"); - } - render() { - const a2 = this.analysis; - const interval = a2.sample_interval ?? "raw"; - const isEnabled = interval !== "raw"; - return b` + } + _onGroupChange(e) { + this._emit("show_anomalies", e.detail.checked); + } + _localizedOptions(options) { + return options.map((option) => ({ + ...option, + label: msg(option.label), + help: option.help ? msg(option.help) : void 0 + })); + } + _renderMethodSubopts(opt, a) { + if (opt.value === "rate_of_change") return b` + + + + `; + if (opt.value === "rolling_zscore") return b` + + + + `; + if (opt.value === "persistence") return b` + + + + `; + if (opt.value === "comparison_window") return b` + + + + `; + return A; + } + render() { + const a = this.analysis; + const sensitivityOptions = this._localizedOptions(ANALYSIS_ANOMALY_SENSITIVITY_OPTIONS); + const methodOptions = this._localizedOptions(ANALYSIS_ANOMALY_METHOD_OPTIONS); + const overlapOptions = this._localizedOptions(ANALYSIS_ANOMALY_OVERLAP_MODE_OPTIONS$2); + return b` - ${isEnabled ? b` - - `; - } - } - _init$G = __decoratorStart$G(_a$G); - _analysis$7 = /* @__PURE__ */ new WeakMap(); - _entityId$7 = /* @__PURE__ */ new WeakMap(); - __decorateElement$G(_init$G, 4, "analysis", _analysis_dec$7, AnalysisSampleGroup, _analysis$7); - __decorateElement$G(_init$G, 4, "entityId", _entityId_dec$7, AnalysisSampleGroup, _entityId$7); - AnalysisSampleGroup = __decorateElement$G(_init$G, 0, "AnalysisSampleGroup", _AnalysisSampleGroup_decorators, AnalysisSampleGroup); - __publicField$P(AnalysisSampleGroup, "styles", [sharedStyles, styles$O]); - __runInitializers$G(_init$G, 1, AnalysisSampleGroup); - customElements.define("analysis-sample-group", AnalysisSampleGroup); - const styles$M = i$5``; - var __create$F = Object.create; - var __defProp$O = Object.defineProperty; - var __getOwnPropDesc$F = Object.getOwnPropertyDescriptor; - var __knownSymbol$F = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$F = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$O = (obj, key, value) => key in obj ? __defProp$O(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __name$i = (target, value) => __defProp$O(target, "name", { value, configurable: true }); - var __decoratorStart$F = (base) => [, , , __create$F(base?.[__knownSymbol$F("metadata")] ?? null)]; - var __decoratorStrings$F = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$F = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$F("Function expected") : fn; - var __decoratorContext$F = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$F[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$F("Already initialized") : fns.push(__expectFn$F(fn || null)) }); - var __decoratorMetadata$F = (array, target) => __defNormalProp$O(target, __knownSymbol$F("metadata"), array[3]); - var __runInitializers$F = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$F = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = !!(flags & 8), p2 = !!(flags & 16); - var j2 = k2 > 3 ? array.length + 1 : k2 ? s2 ? 1 : 2 : 0, key = __decoratorStrings$F[k2 + 5]; - var initializers = k2 > 3 && (array[j2 - 1] = []), extraInitializers = array[j2] || (array[j2] = []); - var desc = k2 && (!p2 && !s2 && (target = target.prototype), k2 < 5 && (k2 > 3 || !p2) && __getOwnPropDesc$F(k2 < 4 ? target : { get [name]() { - return __privateGet$E(this, extra); - }, set [name](x2) { - return __privateSet$E(this, extra, x2); - } }, name)); - k2 ? p2 && k2 < 4 && __name$i(extra, (k2 > 2 ? "set " : k2 > 1 ? "get " : "") + name) : __name$i(target, name); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$F(k2, name, done = {}, array[3], extraInitializers); - if (k2) { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: p2 ? (x2) => __privateIn$h(target, x2) : (x2) => name in x2 }; - if (k2 ^ 3) access.get = p2 ? (x2) => (k2 ^ 1 ? __privateGet$E : __privateMethod$h)(x2, target, k2 ^ 4 ? extra : desc.get) : (x2) => x2[name]; - if (k2 > 2) access.set = p2 ? (x2, y2) => __privateSet$E(x2, target, y2, k2 ^ 4 ? extra : desc.set) : (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])(k2 ? k2 < 4 ? p2 ? extra : desc[key] : k2 > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1; - if (k2 ^ 4 || it === void 0) __expectFn$F(it) && (k2 > 4 ? initializers.unshift(it) : k2 ? p2 ? extra = it : desc[key] = it : target = it); - else if (typeof it !== "object" || it === null) __typeError$F("Object expected"); - else __expectFn$F(fn = it.get) && (desc.get = fn), __expectFn$F(fn = it.set) && (desc.set = fn), __expectFn$F(fn = it.init) && initializers.unshift(fn); - } - return k2 || __decoratorMetadata$F(array, target), desc && __defProp$O(target, name, desc), p2 ? k2 ^ 4 ? extra : desc : target; - }; - var __publicField$O = (obj, key, value) => __defNormalProp$O(obj, key + "", value); - var __accessCheck$E = (obj, member, msg2) => member.has(obj) || __typeError$F("Cannot " + msg2); - var __privateIn$h = (member, obj) => Object(obj) !== obj ? __typeError$F('Cannot use the "in" operator on this value') : member.has(obj); - var __privateGet$E = (obj, member, getter) => (__accessCheck$E(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); - var __privateAdd$E = (obj, member, value) => member.has(obj) ? __typeError$F("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$E = (obj, member, value, setter) => (__accessCheck$E(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); - var __privateMethod$h = (obj, member, method) => (__accessCheck$E(obj, member, "access private method"), method); - var _entityId_dec$6, _analysis_dec$6, _a$F, _AnalysisTrendGroup_decorators, _init$F, _analysis$6, _entityId$6; - const ANALYSIS_TREND_METHOD_OPTIONS = [ - { value: "rolling_average", label: "Rolling average" }, - { value: "linear_trend", label: "Linear trend" } - ]; - const ANALYSIS_TREND_WINDOW_OPTIONS = [ - { value: "1h", label: "1 hour" }, - { value: "6h", label: "6 hours" }, - { value: "24h", label: "24 hours" }, - { value: "7d", label: "7 days" }, - { value: "14d", label: "14 days" }, - { value: "21d", label: "21 days" }, - { value: "28d", label: "28 days" } - ]; - _AnalysisTrendGroup_decorators = [localized()]; - class AnalysisTrendGroup extends (_a$F = i$2, _analysis_dec$6 = [n({ type: Object })], _entityId_dec$6 = [n({ type: String, attribute: "entity-id" })], _a$F) { - constructor() { - super(...arguments); - __privateAdd$E(this, _analysis$6, __runInitializers$F(_init$F, 8, this, {})), __runInitializers$F(_init$F, 11, this); - __privateAdd$E(this, _entityId$6, __runInitializers$F(_init$F, 12, this, "")), __runInitializers$F(_init$F, 15, this); - } - _emit(key, value) { - this.dispatchEvent( - new CustomEvent("dp-group-analysis-change", { - detail: { entityId: this.entityId, key, value }, - bubbles: true, - composed: true - }) - ); - } - _localizedOptions(options) { - return options.map((opt) => ({ - ...opt, - label: msg(opt.label) - })); - } - _renderSelect(key, options, value) { - return b` - - `; - } - _onGroupChange(e2) { - this._emit("show_trend_lines", e2.detail.checked); - } - _onCheckbox(key, e2) { - this._emit(key, e2.target.checked); - } - render() { - const a2 = this.analysis; - return b` - - - - ${a2.trend_method === "rolling_average" ? b` +
+ ${methodOptions.map((opt) => { + const isChecked = Array.isArray(a.anomaly_methods) && a.anomaly_methods.includes(opt.value); + const isComputing = isChecked && (this.computingMethods?.has(opt.value) ?? false); + const helpId = `anomaly-help-${opt.value}`; + return b` +
+ + ${isChecked ? this._renderMethodSubopts(opt, a) : A} +
+ `; + })} +
+ ${Array.isArray(a.anomaly_methods) && a.anomaly_methods.length >= 2 ? b` ` : A}
`; - } + } + }; + _defineProperty(AnalysisAnomalyGroup, "styles", [sharedStyles, styles$44]); + customElements.define("analysis-anomaly-group", AnalysisAnomalyGroup); + //#endregion + //#region custom_components/hass_datapoints/src/molecules/analysis-delta-group/analysis-delta-group.styles.ts + var styles$42 = i$5` + .help-text { + display: inline-block; + color: var(--secondary-text-color); + opacity: 0.8; + padding-top: 2px; } - _init$F = __decoratorStart$F(_a$F); - _analysis$6 = /* @__PURE__ */ new WeakMap(); - _entityId$6 = /* @__PURE__ */ new WeakMap(); - __decorateElement$F(_init$F, 4, "analysis", _analysis_dec$6, AnalysisTrendGroup, _analysis$6); - __decorateElement$F(_init$F, 4, "entityId", _entityId_dec$6, AnalysisTrendGroup, _entityId$6); - AnalysisTrendGroup = __decorateElement$F(_init$F, 0, "AnalysisTrendGroup", _AnalysisTrendGroup_decorators, AnalysisTrendGroup); - __publicField$O(AnalysisTrendGroup, "styles", [sharedStyles, styles$M]); - __runInitializers$F(_init$F, 1, AnalysisTrendGroup); - customElements.define("analysis-trend-group", AnalysisTrendGroup); - const styles$L = i$5``; - var __create$E = Object.create; - var __defProp$N = Object.defineProperty; - var __getOwnPropDesc$E = Object.getOwnPropertyDescriptor; - var __knownSymbol$E = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$E = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$N = (obj, key, value) => key in obj ? __defProp$N(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __name$h = (target, value) => __defProp$N(target, "name", { value, configurable: true }); - var __decoratorStart$E = (base) => [, , , __create$E(base?.[__knownSymbol$E("metadata")] ?? null)]; - var __decoratorStrings$E = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$E = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$E("Function expected") : fn; - var __decoratorContext$E = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$E[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$E("Already initialized") : fns.push(__expectFn$E(fn || null)) }); - var __decoratorMetadata$E = (array, target) => __defNormalProp$N(target, __knownSymbol$E("metadata"), array[3]); - var __runInitializers$E = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$E = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = !!(flags & 8), p2 = !!(flags & 16); - var j2 = k2 > 3 ? array.length + 1 : k2 ? s2 ? 1 : 2 : 0, key = __decoratorStrings$E[k2 + 5]; - var initializers = k2 > 3 && (array[j2 - 1] = []), extraInitializers = array[j2] || (array[j2] = []); - var desc = k2 && (!p2 && !s2 && (target = target.prototype), k2 < 5 && (k2 > 3 || !p2) && __getOwnPropDesc$E(k2 < 4 ? target : { get [name]() { - return __privateGet$D(this, extra); - }, set [name](x2) { - return __privateSet$D(this, extra, x2); - } }, name)); - k2 ? p2 && k2 < 4 && __name$h(extra, (k2 > 2 ? "set " : k2 > 1 ? "get " : "") + name) : __name$h(target, name); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$E(k2, name, done = {}, array[3], extraInitializers); - if (k2) { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: p2 ? (x2) => __privateIn$g(target, x2) : (x2) => name in x2 }; - if (k2 ^ 3) access.get = p2 ? (x2) => (k2 ^ 1 ? __privateGet$D : __privateMethod$g)(x2, target, k2 ^ 4 ? extra : desc.get) : (x2) => x2[name]; - if (k2 > 2) access.set = p2 ? (x2, y2) => __privateSet$D(x2, target, y2, k2 ^ 4 ? extra : desc.set) : (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])(k2 ? k2 < 4 ? p2 ? extra : desc[key] : k2 > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1; - if (k2 ^ 4 || it === void 0) __expectFn$E(it) && (k2 > 4 ? initializers.unshift(it) : k2 ? p2 ? extra = it : desc[key] = it : target = it); - else if (typeof it !== "object" || it === null) __typeError$E("Object expected"); - else __expectFn$E(fn = it.get) && (desc.get = fn), __expectFn$E(fn = it.set) && (desc.set = fn), __expectFn$E(fn = it.init) && initializers.unshift(fn); - } - return k2 || __decoratorMetadata$E(array, target), desc && __defProp$N(target, name, desc), p2 ? k2 ^ 4 ? extra : desc : target; - }; - var __publicField$N = (obj, key, value) => __defNormalProp$N(obj, key + "", value); - var __accessCheck$D = (obj, member, msg2) => member.has(obj) || __typeError$E("Cannot " + msg2); - var __privateIn$g = (member, obj) => Object(obj) !== obj ? __typeError$E('Cannot use the "in" operator on this value') : member.has(obj); - var __privateGet$D = (obj, member, getter) => (__accessCheck$D(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); - var __privateAdd$D = (obj, member, value) => member.has(obj) ? __typeError$E("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$D = (obj, member, value, setter) => (__accessCheck$D(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); - var __privateMethod$g = (obj, member, method) => (__accessCheck$D(obj, member, "access private method"), method); - var _entityId_dec$5, _analysis_dec$5, _a$E, _AnalysisSummaryGroup_decorators, _init$E, _analysis$5, _entityId$5; - _AnalysisSummaryGroup_decorators = [localized()]; - class AnalysisSummaryGroup extends (_a$E = i$2, _analysis_dec$5 = [n({ type: Object })], _entityId_dec$5 = [n({ type: String, attribute: "entity-id" })], _a$E) { - constructor() { - super(...arguments); - __privateAdd$D(this, _analysis$5, __runInitializers$E(_init$E, 8, this, {})), __runInitializers$E(_init$E, 11, this); - __privateAdd$D(this, _entityId$5, __runInitializers$E(_init$E, 12, this, "")), __runInitializers$E(_init$E, 15, this); - } - _emit(key, value) { - this.dispatchEvent( - new CustomEvent("dp-group-analysis-change", { - detail: { entityId: this.entityId, key, value }, - bubbles: true, - composed: true - }) - ); - } - _onGroupChange(e2) { - this._emit("show_summary_stats", e2.detail.checked); - } - _onCheckbox(key, e2) { - this._emit(key, e2.target.checked); - } - render() { - const a2 = this.analysis; - return b` +`; + //#endregion + //#region custom_components/hass_datapoints/src/molecules/analysis-delta-group/analysis-delta-group.ts + var AnalysisDeltaGroup = @localized() class extends i$2 { + @n$1({ type: Object }) accessor analysis = {}; + @n$1({ + type: String, + attribute: "entity-id" + }) accessor entityId = ""; + @n$1({ + type: Boolean, + attribute: "can-show-delta-analysis" + }) accessor canShowDeltaAnalysis = false; + _emit(key, value) { + this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { + detail: { + entityId: this.entityId, + key, + value + }, + bubbles: true, + composed: true + })); + } + _onGroupChange(e) { + this._emit("show_delta_analysis", e.detail.checked); + } + _onCheckbox(key, e) { + this._emit(key, e.target.checked); + } + render() { + const a = this.analysis; + const isOpen = a.show_delta_analysis && this.canShowDeltaAnalysis; + return b` + ${!this.canShowDeltaAnalysis ? b` +
${msg("Select a date window tab to enable delta analysis.")}
+ ` : A} -
- `; - } - } - _init$E = __decoratorStart$E(_a$E); - _analysis$5 = /* @__PURE__ */ new WeakMap(); - _entityId$5 = /* @__PURE__ */ new WeakMap(); - __decorateElement$E(_init$E, 4, "analysis", _analysis_dec$5, AnalysisSummaryGroup, _analysis$5); - __decorateElement$E(_init$E, 4, "entityId", _entityId_dec$5, AnalysisSummaryGroup, _entityId$5); - AnalysisSummaryGroup = __decorateElement$E(_init$E, 0, "AnalysisSummaryGroup", _AnalysisSummaryGroup_decorators, AnalysisSummaryGroup); - __publicField$N(AnalysisSummaryGroup, "styles", [sharedStyles, styles$L]); - __runInitializers$E(_init$E, 1, AnalysisSummaryGroup); - customElements.define("analysis-summary-group", AnalysisSummaryGroup); - const styles$K = i$5``; - var __create$D = Object.create; - var __defProp$M = Object.defineProperty; - var __getOwnPropDesc$D = Object.getOwnPropertyDescriptor; - var __knownSymbol$D = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$D = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$M = (obj, key, value) => key in obj ? __defProp$M(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __name$g = (target, value) => __defProp$M(target, "name", { value, configurable: true }); - var __decoratorStart$D = (base) => [, , , __create$D(base?.[__knownSymbol$D("metadata")] ?? null)]; - var __decoratorStrings$D = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$D = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$D("Function expected") : fn; - var __decoratorContext$D = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$D[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$D("Already initialized") : fns.push(__expectFn$D(fn || null)) }); - var __decoratorMetadata$D = (array, target) => __defNormalProp$M(target, __knownSymbol$D("metadata"), array[3]); - var __runInitializers$D = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$D = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = !!(flags & 8), p2 = !!(flags & 16); - var j2 = k2 > 3 ? array.length + 1 : k2 ? s2 ? 1 : 2 : 0, key = __decoratorStrings$D[k2 + 5]; - var initializers = k2 > 3 && (array[j2 - 1] = []), extraInitializers = array[j2] || (array[j2] = []); - var desc = k2 && (!p2 && !s2 && (target = target.prototype), k2 < 5 && (k2 > 3 || !p2) && __getOwnPropDesc$D(k2 < 4 ? target : { get [name]() { - return __privateGet$C(this, extra); - }, set [name](x2) { - return __privateSet$C(this, extra, x2); - } }, name)); - k2 ? p2 && k2 < 4 && __name$g(extra, (k2 > 2 ? "set " : k2 > 1 ? "get " : "") + name) : __name$g(target, name); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$D(k2, name, done = {}, array[3], extraInitializers); - if (k2) { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: p2 ? (x2) => __privateIn$f(target, x2) : (x2) => name in x2 }; - if (k2 ^ 3) access.get = p2 ? (x2) => (k2 ^ 1 ? __privateGet$C : __privateMethod$f)(x2, target, k2 ^ 4 ? extra : desc.get) : (x2) => x2[name]; - if (k2 > 2) access.set = p2 ? (x2, y2) => __privateSet$C(x2, target, y2, k2 ^ 4 ? extra : desc.set) : (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])(k2 ? k2 < 4 ? p2 ? extra : desc[key] : k2 > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1; - if (k2 ^ 4 || it === void 0) __expectFn$D(it) && (k2 > 4 ? initializers.unshift(it) : k2 ? p2 ? extra = it : desc[key] = it : target = it); - else if (typeof it !== "object" || it === null) __typeError$D("Object expected"); - else __expectFn$D(fn = it.get) && (desc.get = fn), __expectFn$D(fn = it.set) && (desc.set = fn), __expectFn$D(fn = it.init) && initializers.unshift(fn); - } - return k2 || __decoratorMetadata$D(array, target), desc && __defProp$M(target, name, desc), p2 ? k2 ^ 4 ? extra : desc : target; - }; - var __publicField$M = (obj, key, value) => __defNormalProp$M(obj, key + "", value); - var __accessCheck$C = (obj, member, msg2) => member.has(obj) || __typeError$D("Cannot " + msg2); - var __privateIn$f = (member, obj) => Object(obj) !== obj ? __typeError$D('Cannot use the "in" operator on this value') : member.has(obj); - var __privateGet$C = (obj, member, getter) => (__accessCheck$C(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); - var __privateAdd$C = (obj, member, value) => member.has(obj) ? __typeError$D("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$C = (obj, member, value, setter) => (__accessCheck$C(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); - var __privateMethod$f = (obj, member, method) => (__accessCheck$C(obj, member, "access private method"), method); - var _entityId_dec$4, _analysis_dec$4, _a$D, _AnalysisRateGroup_decorators, _init$D, _analysis$4, _entityId$4; - const ANALYSIS_RATE_WINDOW_OPTIONS = [ - { value: "point_to_point", label: "Point to point" }, - { value: "1h", label: "1 hour" }, - { value: "6h", label: "6 hours" }, - { value: "24h", label: "24 hours" } - ]; - _AnalysisRateGroup_decorators = [localized()]; - class AnalysisRateGroup extends (_a$D = i$2, _analysis_dec$4 = [n({ type: Object })], _entityId_dec$4 = [n({ type: String, attribute: "entity-id" })], _a$D) { - constructor() { - super(...arguments); - __privateAdd$C(this, _analysis$4, __runInitializers$D(_init$D, 8, this, {})), __runInitializers$D(_init$D, 11, this); - __privateAdd$C(this, _entityId$4, __runInitializers$D(_init$D, 12, this, "")), __runInitializers$D(_init$D, 15, this); - } - _emit(key, value) { - this.dispatchEvent( - new CustomEvent("dp-group-analysis-change", { - detail: { entityId: this.entityId, key, value }, - bubbles: true, - composed: true - }) - ); - } - _localizedOptions(options) { - return options.map((opt) => ({ - ...opt, - label: msg(opt.label) - })); - } - _renderSelect(key, options, value) { - return b` - - `; - } - _onGroupChange(e2) { - this._emit("show_rate_of_change", e2.detail.checked); - } - _onCheckbox(key, e2) { - this._emit(key, e2.target.checked); - } - render() { - const a2 = this.analysis; - return b` - - `; - } + } + }; + _defineProperty(AnalysisDeltaGroup, "styles", [sharedStyles, styles$42]); + customElements.define("analysis-delta-group", AnalysisDeltaGroup); + //#endregion + //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sections/sidebar-datapoints-section.styles.ts + var styles$41 = i$5` + :host { + display: block; } - _init$D = __decoratorStart$D(_a$D); - _analysis$4 = /* @__PURE__ */ new WeakMap(); - _entityId$4 = /* @__PURE__ */ new WeakMap(); - __decorateElement$D(_init$D, 4, "analysis", _analysis_dec$4, AnalysisRateGroup, _analysis$4); - __decorateElement$D(_init$D, 4, "entityId", _entityId_dec$4, AnalysisRateGroup, _entityId$4); - AnalysisRateGroup = __decorateElement$D(_init$D, 0, "AnalysisRateGroup", _AnalysisRateGroup_decorators, AnalysisRateGroup); - __publicField$M(AnalysisRateGroup, "styles", [sharedStyles, styles$K]); - __runInitializers$D(_init$D, 1, AnalysisRateGroup); - customElements.define("analysis-rate-group", AnalysisRateGroup); - const styles$J = i$5``; - var __create$C = Object.create; - var __defProp$L = Object.defineProperty; - var __getOwnPropDesc$C = Object.getOwnPropertyDescriptor; - var __knownSymbol$C = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$C = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$L = (obj, key, value) => key in obj ? __defProp$L(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __name$f = (target, value) => __defProp$L(target, "name", { value, configurable: true }); - var __decoratorStart$C = (base) => [, , , __create$C(base?.[__knownSymbol$C("metadata")] ?? null)]; - var __decoratorStrings$C = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$C = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$C("Function expected") : fn; - var __decoratorContext$C = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$C[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$C("Already initialized") : fns.push(__expectFn$C(fn || null)) }); - var __decoratorMetadata$C = (array, target) => __defNormalProp$L(target, __knownSymbol$C("metadata"), array[3]); - var __runInitializers$C = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$C = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = !!(flags & 8), p2 = !!(flags & 16); - var j2 = k2 > 3 ? array.length + 1 : k2 ? s2 ? 1 : 2 : 0, key = __decoratorStrings$C[k2 + 5]; - var initializers = k2 > 3 && (array[j2 - 1] = []), extraInitializers = array[j2] || (array[j2] = []); - var desc = k2 && (!p2 && !s2 && (target = target.prototype), k2 < 5 && (k2 > 3 || !p2) && __getOwnPropDesc$C(k2 < 4 ? target : { get [name]() { - return __privateGet$B(this, extra); - }, set [name](x2) { - return __privateSet$B(this, extra, x2); - } }, name)); - k2 ? p2 && k2 < 4 && __name$f(extra, (k2 > 2 ? "set " : k2 > 1 ? "get " : "") + name) : __name$f(target, name); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$C(k2, name, done = {}, array[3], extraInitializers); - if (k2) { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: p2 ? (x2) => __privateIn$e(target, x2) : (x2) => name in x2 }; - if (k2 ^ 3) access.get = p2 ? (x2) => (k2 ^ 1 ? __privateGet$B : __privateMethod$e)(x2, target, k2 ^ 4 ? extra : desc.get) : (x2) => x2[name]; - if (k2 > 2) access.set = p2 ? (x2, y2) => __privateSet$B(x2, target, y2, k2 ^ 4 ? extra : desc.set) : (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])(k2 ? k2 < 4 ? p2 ? extra : desc[key] : k2 > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1; - if (k2 ^ 4 || it === void 0) __expectFn$C(it) && (k2 > 4 ? initializers.unshift(it) : k2 ? p2 ? extra = it : desc[key] = it : target = it); - else if (typeof it !== "object" || it === null) __typeError$C("Object expected"); - else __expectFn$C(fn = it.get) && (desc.get = fn), __expectFn$C(fn = it.set) && (desc.set = fn), __expectFn$C(fn = it.init) && initializers.unshift(fn); - } - return k2 || __decoratorMetadata$C(array, target), desc && __defProp$L(target, name, desc), p2 ? k2 ^ 4 ? extra : desc : target; - }; - var __publicField$L = (obj, key, value) => __defNormalProp$L(obj, key + "", value); - var __accessCheck$B = (obj, member, msg2) => member.has(obj) || __typeError$C("Cannot " + msg2); - var __privateIn$e = (member, obj) => Object(obj) !== obj ? __typeError$C('Cannot use the "in" operator on this value') : member.has(obj); - var __privateGet$B = (obj, member, getter) => (__accessCheck$B(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); - var __privateAdd$B = (obj, member, value) => member.has(obj) ? __typeError$C("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$B = (obj, member, value, setter) => (__accessCheck$B(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); - var __privateMethod$e = (obj, member, method) => (__accessCheck$B(obj, member, "access private method"), method); - var _unit_dec$1, _entityId_dec$3, _analysis_dec$3, _a$C, _AnalysisThresholdGroup_decorators, _init$C, _analysis$3, _entityId$3, _unit$1; - _AnalysisThresholdGroup_decorators = [localized()]; - class AnalysisThresholdGroup extends (_a$C = i$2, _analysis_dec$3 = [n({ type: Object })], _entityId_dec$3 = [n({ type: String, attribute: "entity-id" })], _unit_dec$1 = [n({ type: String })], _a$C) { - constructor() { - super(...arguments); - __privateAdd$B(this, _analysis$3, __runInitializers$C(_init$C, 8, this, {})), __runInitializers$C(_init$C, 11, this); - __privateAdd$B(this, _entityId$3, __runInitializers$C(_init$C, 12, this, "")), __runInitializers$C(_init$C, 15, this); - __privateAdd$B(this, _unit$1, __runInitializers$C(_init$C, 16, this, "")), __runInitializers$C(_init$C, 19, this); - } - _emit(key, value) { - this.dispatchEvent( - new CustomEvent("dp-group-analysis-change", { - detail: { entityId: this.entityId, key, value }, - bubbles: true, - composed: true - }) - ); - } - _localizedOptions(options) { - return options.map((opt) => ({ - ...opt, - label: msg(opt.label) - })); - } - _renderSelect(key, options, value) { - return b` - - `; - } - _onGroupChange(e2) { - this._emit("show_threshold_analysis", e2.detail.checked); - } - _onCheckbox(key, e2) { - this._emit(key, e2.target.checked); - } - _onInput(key, e2) { - this._emit(key, e2.target.value); - } - render() { - const a2 = this.analysis; - return b` - - - - ${a2.show_threshold_shading ? b` - - ` : A} - - `; - } - } - _init$C = __decoratorStart$C(_a$C); - _analysis$3 = /* @__PURE__ */ new WeakMap(); - _entityId$3 = /* @__PURE__ */ new WeakMap(); - _unit$1 = /* @__PURE__ */ new WeakMap(); - __decorateElement$C(_init$C, 4, "analysis", _analysis_dec$3, AnalysisThresholdGroup, _analysis$3); - __decorateElement$C(_init$C, 4, "entityId", _entityId_dec$3, AnalysisThresholdGroup, _entityId$3); - __decorateElement$C(_init$C, 4, "unit", _unit_dec$1, AnalysisThresholdGroup, _unit$1); - AnalysisThresholdGroup = __decorateElement$C(_init$C, 0, "AnalysisThresholdGroup", _AnalysisThresholdGroup_decorators, AnalysisThresholdGroup); - __publicField$L(AnalysisThresholdGroup, "styles", [sharedStyles, styles$J]); - __runInitializers$C(_init$C, 1, AnalysisThresholdGroup); - customElements.define("analysis-threshold-group", AnalysisThresholdGroup); - const styles$I = i$5` - .method-computing-indicator { - display: inline-flex; - align-items: center; - gap: 4px; - margin-left: 6px; - vertical-align: middle; - flex-shrink: 0; - } - - .method-computing-spinner { - display: inline-block; - width: 8px; - height: 8px; - border: 1.5px solid var(--divider-color, #ccc); - border-top-color: var(--primary-color, #03a9f4); - border-radius: 50%; - animation: analysis-spin 0.7s linear infinite; - flex-shrink: 0; +`; + //#endregion + //#region custom_components/hass_datapoints/src/atoms/display/sidebar-options-section/sidebar-options-section.styles.ts + var styles$40 = i$5` + :host { + display: block; + --dp-spacing-xs: calc(var(--spacing, 8px) * 0.5); + --dp-spacing-sm: var(--spacing, 8px); } - .method-computing-progress { - font-size: 0.72rem; - font-weight: 500; - color: var(--primary-color, #03a9f4); - line-height: 1; + .section { + display: grid; + gap: var(--dp-spacing-sm); } - - @keyframes analysis-spin { - to { - transform: rotate(360deg); - } +`; + //#endregion + //#region custom_components/hass_datapoints/src/atoms/display/sidebar-section-header/sidebar-section-header.styles.ts + var styles$39 = i$5` + :host { + display: block; } - - .method-list { + .sidebar-section-header { display: grid; - gap: var(--dp-spacing-sm, 8px); + gap: var(--dp-spacing-xs); } - - .method-item { - display: grid; - gap: var(--dp-spacing-sm, 8px); + .sidebar-section-header.is-collapsible { + cursor: pointer; + user-select: none; } - - ha-tooltip { - --ha-tooltip-padding: var(--dp-spacing-md, calc(var(--spacing, 8px) * 1.5)); + .sidebar-section-header-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 4px; } - - ha-tooltip::part(body) { - padding: var(--dp-spacing-md, calc(var(--spacing, 8px) * 1.5)); + .sidebar-section-title { + font-size: 0.95rem; + font-weight: 600; + color: var(--primary-text-color); } - - .method-help { - display: inline-flex; + .sidebar-section-subtitle { + font-size: 0.82rem; + color: var(--secondary-text-color); + } + .sidebar-section-toggle { + display: flex; align-items: center; justify-content: center; - width: 10px; - height: 10px; - flex: 0 0 auto; - border-radius: 50%; - border: 1px solid var(--secondary-text-color, #888); + width: 24px; + height: 24px; + border: none; background: transparent; - color: var(--secondary-text-color, #888); - font-size: 9px; - font-weight: 700; - cursor: help; - padding: 0; - vertical-align: middle; - appearance: none; - -webkit-appearance: none; + color: var(--secondary-text-color); + cursor: pointer; + border-radius: 4px; + flex-shrink: 0; + transition: background-color 120ms ease; } - - .method-help:focus-visible { - outline: 2px solid var(--primary-color, #03a9f4); - outline-offset: 2px; + .sidebar-section-toggle:hover, + .sidebar-section-toggle:focus-visible { + background: color-mix( + in srgb, + var(--primary-text-color, #111) 8%, + transparent + ); + } + .sidebar-section-toggle ha-icon { + --mdc-icon-size: 18px; + display: block; + transition: transform 140ms ease; + } + .sidebar-section-toggle.is-open ha-icon { + transform: rotate(180deg); } `; - const styles$H = i$5` + //#endregion + //#region custom_components/hass_datapoints/src/atoms/display/sidebar-section-header/sidebar-section-header.ts + var SidebarSectionHeader = class extends i$2 { + @n$1({ type: String }) accessor title = ""; + @n$1({ type: String }) accessor subtitle = ""; + @n$1({ type: Boolean }) accessor collapsible = false; + @n$1({ type: Boolean }) accessor open = true; + _emitToggle() { + this.dispatchEvent(new CustomEvent("dp-section-toggle", { + bubbles: true, + composed: true + })); + } + _onHeaderClick() { + if (!this.collapsible) return; + this._emitToggle(); + } + _onHeaderKeydown(e) { + if (!this.collapsible) return; + if (e.key !== "Enter" && e.key !== " ") return; + e.preventDefault(); + this._emitToggle(); + } + _onButtonClick(e) { + e.stopPropagation(); + this._emitToggle(); + } + render() { + return b` + + `; + } + }; + _defineProperty(SidebarSectionHeader, "styles", styles$39); + customElements.define("sidebar-section-header", SidebarSectionHeader); + //#endregion + //#region custom_components/hass_datapoints/src/atoms/display/sidebar-options-section/sidebar-options-section.ts + var SidebarOptionsSection = class extends i$2 { + @n$1({ type: String }) accessor title = ""; + @n$1({ type: String }) accessor subtitle = ""; + @n$1({ type: Boolean }) accessor collapsible = false; + @n$1({ type: Boolean }) accessor open = true; + _onToggle(e) { + e.stopPropagation(); + this.open = !this.open; + this.dispatchEvent(new CustomEvent("dp-section-toggle", { + detail: { open: this.open }, + bubbles: true, + composed: true + })); + } + render() { + return b` +
+ + ${this.collapsible && !this.open ? A : b``} +
+ `; + } + }; + _defineProperty(SidebarOptionsSection, "styles", styles$40); + customElements.define("sidebar-options-section", SidebarOptionsSection); + //#endregion + //#region custom_components/hass_datapoints/src/atoms/form/radio-group/radio-group.styles.ts + var styles$38 = i$5` :host { display: block; - --dp-spacing-sm: var(--spacing, 8px); } - - .subopts { - padding-left: calc(var(--spacing, 8px) * 1.5); + fieldset { + border: none; + margin: 0; + padding: 0; + } + .radio-group { display: grid; - gap: var(--dp-spacing-sm); - justify-items: start; - border-left: 3px solid var(--primary-color); - margin-left: 5px; + gap: var(--dp-spacing-xs, 4px); } -`; - var __defProp$K = Object.defineProperty; - var __defNormalProp$K = (obj, key, value) => key in obj ? __defProp$K(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$K = (obj, key, value) => __defNormalProp$K(obj, key + "", value); - class AnalysisMethodSubopts extends i$2 { - render() { - return b`
`; - } + .radio-option { + display: flex; + align-items: center; + gap: var(--dp-spacing-xs, 4px); + font-size: 0.9rem; + color: var(--primary-text-color); + cursor: pointer; } - __publicField$K(AnalysisMethodSubopts, "styles", styles$H); - customElements.define("analysis-method-subopts", AnalysisMethodSubopts); - var __create$B = Object.create; - var __defProp$J = Object.defineProperty; - var __getOwnPropDesc$B = Object.getOwnPropertyDescriptor; - var __knownSymbol$B = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$B = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$J = (obj, key, value) => key in obj ? __defProp$J(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __name$e = (target, value) => __defProp$J(target, "name", { value, configurable: true }); - var __decoratorStart$B = (base) => [, , , __create$B(base?.[__knownSymbol$B("metadata")] ?? null)]; - var __decoratorStrings$B = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$B = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$B("Function expected") : fn; - var __decoratorContext$B = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$B[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$B("Already initialized") : fns.push(__expectFn$B(fn || null)) }); - var __decoratorMetadata$B = (array, target) => __defNormalProp$J(target, __knownSymbol$B("metadata"), array[3]); - var __runInitializers$B = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$B = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = !!(flags & 8), p2 = !!(flags & 16); - var j2 = k2 > 3 ? array.length + 1 : k2 ? s2 ? 1 : 2 : 0, key = __decoratorStrings$B[k2 + 5]; - var initializers = k2 > 3 && (array[j2 - 1] = []), extraInitializers = array[j2] || (array[j2] = []); - var desc = k2 && (!p2 && !s2 && (target = target.prototype), k2 < 5 && (k2 > 3 || !p2) && __getOwnPropDesc$B(k2 < 4 ? target : { get [name]() { - return __privateGet$A(this, extra); - }, set [name](x2) { - return __privateSet$A(this, extra, x2); - } }, name)); - k2 ? p2 && k2 < 4 && __name$e(extra, (k2 > 2 ? "set " : k2 > 1 ? "get " : "") + name) : __name$e(target, name); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$B(k2, name, done = {}, array[3], extraInitializers); - if (k2) { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: p2 ? (x2) => __privateIn$d(target, x2) : (x2) => name in x2 }; - if (k2 ^ 3) access.get = p2 ? (x2) => (k2 ^ 1 ? __privateGet$A : __privateMethod$d)(x2, target, k2 ^ 4 ? extra : desc.get) : (x2) => x2[name]; - if (k2 > 2) access.set = p2 ? (x2, y2) => __privateSet$A(x2, target, y2, k2 ^ 4 ? extra : desc.set) : (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])(k2 ? k2 < 4 ? p2 ? extra : desc[key] : k2 > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1; - if (k2 ^ 4 || it === void 0) __expectFn$B(it) && (k2 > 4 ? initializers.unshift(it) : k2 ? p2 ? extra = it : desc[key] = it : target = it); - else if (typeof it !== "object" || it === null) __typeError$B("Object expected"); - else __expectFn$B(fn = it.get) && (desc.get = fn), __expectFn$B(fn = it.set) && (desc.set = fn), __expectFn$B(fn = it.init) && initializers.unshift(fn); - } - return k2 || __decoratorMetadata$B(array, target), desc && __defProp$J(target, name, desc), p2 ? k2 ^ 4 ? extra : desc : target; - }; - var __publicField$J = (obj, key, value) => __defNormalProp$J(obj, key + "", value); - var __accessCheck$A = (obj, member, msg2) => member.has(obj) || __typeError$B("Cannot " + msg2); - var __privateIn$d = (member, obj) => Object(obj) !== obj ? __typeError$B('Cannot use the "in" operator on this value') : member.has(obj); - var __privateGet$A = (obj, member, getter) => (__accessCheck$A(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); - var __privateAdd$A = (obj, member, value) => member.has(obj) ? __typeError$B("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$A = (obj, member, value, setter) => (__accessCheck$A(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); - var __privateMethod$d = (obj, member, method) => (__accessCheck$A(obj, member, "access private method"), method); - var _computingMethods_dec$1, _computingProgress_dec$1, _computing_dec$1, _comparisonWindows_dec$3, _entityId_dec$2, _analysis_dec$2, _a$B, _AnalysisAnomalyGroup_decorators, _init$B, _analysis$2, _entityId$2, _comparisonWindows$3, _computing$1, _computingProgress$1, _computingMethods$1; - const ANALYSIS_ANOMALY_SENSITIVITY_OPTIONS = [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High" } - ]; - const ANALYSIS_ANOMALY_METHOD_OPTIONS = [ - { - value: "trend_residual", - label: "Trend deviation", - help: "Flags points that deviate significantly from a fitted trend line. Good for catching gradual drift or sudden jumps away from a steady baseline." - }, - { - value: "rate_of_change", - label: "Sudden change", - help: "Flags unusually fast rises or drops compared to the typical rate of change. Best for detecting spikes, crashes, or rapid transitions." - }, - { - value: "iqr", - label: "Statistical outlier (IQR)", - help: "Uses the interquartile range to flag values far outside the normal spread of data. Robust against outliers that skew averages." - }, - { - value: "rolling_zscore", - label: "Rolling Z-score", - help: "Compares each value to a rolling mean and standard deviation. Catches unusual readings relative to recent context rather than the whole series." - }, - { - value: "persistence", - label: "Flat-line / stuck value", - help: "Flags when a sensor reports nearly the same value for an unusually long time. Useful for detecting stuck sensors or frozen readings." - }, - { - value: "comparison_window", - label: "Comparison window deviation", - help: "Compares the current period to a reference date window. Highlights differences from an expected historical pattern, such as last week or the same day last year." - } - ]; - const ANALYSIS_ANOMALY_RATE_WINDOW_OPTIONS = [ - { value: "1h", label: "1 hour" }, - { value: "6h", label: "6 hours" }, - { value: "24h", label: "24 hours" } - ]; - const ANALYSIS_ANOMALY_ZSCORE_WINDOW_OPTIONS = [ - { value: "1h", label: "1 hour" }, - { value: "6h", label: "6 hours" }, - { value: "24h", label: "24 hours" }, - { value: "7d", label: "7 days" } - ]; - const ANALYSIS_ANOMALY_PERSISTENCE_WINDOW_OPTIONS = [ - { value: "30m", label: "30 minutes" }, - { value: "1h", label: "1 hour" }, - { value: "3h", label: "3 hours" }, - { value: "6h", label: "6 hours" }, - { value: "12h", label: "12 hours" }, - { value: "24h", label: "24 hours" } - ]; - const ANALYSIS_ANOMALY_OVERLAP_MODE_OPTIONS$2 = [ - { value: "all", label: "Show all anomalies" }, - { value: "only", label: "Overlaps only" } - ]; - _AnalysisAnomalyGroup_decorators = [localized()]; - class AnalysisAnomalyGroup extends (_a$B = i$2, _analysis_dec$2 = [n({ type: Object })], _entityId_dec$2 = [n({ type: String, attribute: "entity-id" })], _comparisonWindows_dec$3 = [n({ type: Array, attribute: "comparison-windows" })], _computing_dec$1 = [n({ type: Boolean, attribute: false })], _computingProgress_dec$1 = [n({ type: Number, attribute: false })], _computingMethods_dec$1 = [n({ type: Object, attribute: false })], _a$B) { - constructor() { - super(...arguments); - __privateAdd$A(this, _analysis$2, __runInitializers$B(_init$B, 8, this, {})), __runInitializers$B(_init$B, 11, this); - __privateAdd$A(this, _entityId$2, __runInitializers$B(_init$B, 12, this, "")), __runInitializers$B(_init$B, 15, this); - __privateAdd$A(this, _comparisonWindows$3, __runInitializers$B(_init$B, 16, this, [])), __runInitializers$B(_init$B, 19, this); - __privateAdd$A(this, _computing$1, __runInitializers$B(_init$B, 20, this, false)), __runInitializers$B(_init$B, 23, this); - __privateAdd$A(this, _computingProgress$1, __runInitializers$B(_init$B, 24, this, 0)), __runInitializers$B(_init$B, 27, this); - __privateAdd$A(this, _computingMethods$1, __runInitializers$B(_init$B, 28, this, /* @__PURE__ */ new Set())), __runInitializers$B(_init$B, 31, this); - } - _emit(key, value) { - this.dispatchEvent( - new CustomEvent("dp-group-analysis-change", { - detail: { entityId: this.entityId, key, value }, - bubbles: true, - composed: true - }) - ); - } - _renderSelect(key, options, value) { - return b` - - `; - } - _onGroupChange(e2) { - this._emit("show_anomalies", e2.detail.checked); - } - _localizedOptions(options) { - return options.map((option) => ({ - ...option, - label: msg(option.label), - help: option.help ? msg(option.help) : void 0 - })); - } - _renderMethodSubopts(opt, a2) { - if (opt.value === "rate_of_change") { - return b` - - - - `; - } - if (opt.value === "rolling_zscore") { - return b` - - - - `; - } - if (opt.value === "persistence") { - return b` - - - - `; - } - if (opt.value === "comparison_window") { - return b` - - - - `; - } - return A; - } - render() { - const a2 = this.analysis; - const sensitivityOptions = this._localizedOptions( - ANALYSIS_ANOMALY_SENSITIVITY_OPTIONS - ); - const methodOptions = this._localizedOptions( - ANALYSIS_ANOMALY_METHOD_OPTIONS - ); - const overlapOptions = this._localizedOptions( - ANALYSIS_ANOMALY_OVERLAP_MODE_OPTIONS$2 - ); - return b` - - - ${a2.sample_interval && a2.sample_interval !== "raw" ? b` -
`; - } - } - _init$8 = __decoratorStart$8(_a$8); - _eventRecord$1 = /* @__PURE__ */ new WeakMap(); - _hass$2 = /* @__PURE__ */ new WeakMap(); - _color = /* @__PURE__ */ new WeakMap(); - _language = /* @__PURE__ */ new WeakMap(); - __message = /* @__PURE__ */ new WeakMap(); - __annotation = /* @__PURE__ */ new WeakMap(); - __icon = /* @__PURE__ */ new WeakMap(); - __decorateElement$8(_init$8, 4, "eventRecord", _eventRecord_dec$1, CardListEditForm, _eventRecord$1); - __decorateElement$8(_init$8, 4, "hass", _hass_dec$2, CardListEditForm, _hass$2); - __decorateElement$8(_init$8, 4, "color", _color_dec, CardListEditForm, _color); - __decorateElement$8(_init$8, 4, "language", _language_dec, CardListEditForm, _language); - __decorateElement$8(_init$8, 4, "_message", __message_dec, CardListEditForm, __message); - __decorateElement$8(_init$8, 4, "_annotation", __annotation_dec, CardListEditForm, __annotation); - __decorateElement$8(_init$8, 4, "_icon", __icon_dec, CardListEditForm, __icon); - __decoratorMetadata$8(_init$8, CardListEditForm); - __publicField$e(CardListEditForm, "styles", styles$c); - customElements.define("list-edit-form", CardListEditForm); - var __create$7 = Object.create; - var __defProp$d = Object.defineProperty; - var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor; - var __knownSymbol$7 = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$7 = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$d = (obj, key, value) => key in obj ? __defProp$d(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$7 = (base) => [, , , __create$7(base?.[__knownSymbol$7("metadata")] ?? null)]; - var __decoratorStrings$7 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$7 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$7("Function expected") : fn; - var __decoratorContext$7 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$7[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$7("Already initialized") : fns.push(__expectFn$7(fn || null)) }); - var __decoratorMetadata$7 = (array, target) => __defNormalProp$d(target, __knownSymbol$7("metadata"), array[3]); - var __runInitializers$7 = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$7 = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$7[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$7({ get [name]() { - return __privateGet$6(this, extra); - }, set [name](x2) { - return __privateSet$6(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$7(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$7(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$7("Object expected"); - else __expectFn$7(fn = it.get) && (desc.get = fn), __expectFn$7(fn = it.set) && (desc.set = fn), __expectFn$7(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$d(target, name, desc), target; - }; - var __publicField$d = (obj, key, value) => __defNormalProp$d(obj, key + "", value); - var __accessCheck$6 = (obj, member, msg2) => member.has(obj) || __typeError$7("Cannot " + msg2); - var __privateGet$6 = (obj, member, getter) => (__accessCheck$6(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$6 = (obj, member, value) => member.has(obj) ? __typeError$7("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$6 = (obj, member, value, setter) => (__accessCheck$6(obj, member, "write to private field"), member.set(obj, value), value); - var __annotationExpanded_dec, _context_dec, _eventRecord_dec, _a$7, _init$7, _eventRecord, _context, __annotationExpanded; - const DEFAULT_LANGUAGE = { - showAnnotation: "Show annotation", - openHistory: "Open related data point history", - editRecord: "Edit record", - deleteRecord: "Delete record", - showChartMarker: "Show chart marker", - hideChartMarker: "Hide chart marker", - chooseColor: "Choose colour", - save: "Save", - cancel: "Cancel", - message: "Message", - annotationFullMessage: "Annotation / full message" - }; - class CardListEventItem extends (_a$7 = i$2, _eventRecord_dec = [n({ attribute: false })], _context_dec = [n({ attribute: false })], __annotationExpanded_dec = [r()], _a$7) { - constructor() { - super(...arguments); - __privateAdd$6(this, _eventRecord, __runInitializers$7(_init$7, 8, this, null)), __runInitializers$7(_init$7, 11, this); - __privateAdd$6(this, _context, __runInitializers$7(_init$7, 12, this, { - hass: null, - showActions: true, - canEdit: true, - showEntities: true, - showFullMessage: true, - hidden: false, - editing: false, - editColor: "#03a9f4", - language: DEFAULT_LANGUAGE - })), __runInitializers$7(_init$7, 15, this); - __privateAdd$6(this, __annotationExpanded, __runInitializers$7(_init$7, 16, this, false)), __runInitializers$7(_init$7, 19, this); - } - _dispatch(name, detail = {}) { - this.dispatchEvent( - new CustomEvent(name, { - detail, - bubbles: true, - composed: true - }) - ); - } - render() { - if (!this.eventRecord) { - return b``; - } - const ev = this.eventRecord; - const showActions = this.context.showActions; - const canEdit = this.context.canEdit; - const showEntities = this.context.showEntities; - const showFullMessage = this.context.showFullMessage; - const annText = ev.annotation && ev.annotation !== ev.message ? ev.annotation.trim() : ""; - const color = ev.color || "#03a9f4"; - const icon = ev.icon || "mdi:bookmark"; - const iconColor = contrastColor(color); - const entities = ev.entity_ids || []; - const devices = ev.device_ids || []; - const areas = ev.area_ids || []; - const labels = ev.label_ids || []; - const hasRelated = entities.length || devices.length || areas.length || labels.length; - const isExpandable = !showFullMessage && !!annText; - const isHidden = this.context.hidden; - const visibilityIcon = isHidden ? "mdi:eye" : "mdi:eye-off"; - const visibilityLabel = isHidden ? this.context.language.showChartMarker : this.context.language.hideChartMarker; - const isEditing = this.context.editing; - const isSimple = !annText && !hasRelated; - return b` + } + }; + _defineProperty(CardListEditForm, "styles", styles$12); + customElements.define("list-edit-form", CardListEditForm); + //#endregion + //#region custom_components/hass_datapoints/src/cards/list/list-event-item/list-event-item.ts + var DEFAULT_LANGUAGE = { + showAnnotation: "Show annotation", + openHistory: "Open related data point history", + editRecord: "Edit record", + deleteRecord: "Delete record", + showChartMarker: "Show chart marker", + hideChartMarker: "Hide chart marker", + chooseColor: "Choose colour", + save: "Save", + cancel: "Cancel", + message: "Message", + annotationFullMessage: "Annotation / full message" + }; + var CardListEventItem = class extends i$2 { + @n$1({ attribute: false }) accessor eventRecord = null; + @n$1({ attribute: false }) accessor context = { + hass: null, + showActions: true, + canEdit: true, + showEntities: true, + showFullMessage: true, + hidden: false, + editing: false, + editColor: "#03a9f4", + language: DEFAULT_LANGUAGE + }; + @r$1() accessor _annotationExpanded = false; + _dispatch(name, detail = {}) { + this.dispatchEvent(new CustomEvent(name, { + detail, + bubbles: true, + composed: true + })); + } + render() { + if (!this.eventRecord) return b``; + const ev = this.eventRecord; + const showActions = this.context.showActions; + const canEdit = this.context.canEdit; + const showEntities = this.context.showEntities; + const showFullMessage = this.context.showFullMessage; + const annText = ev.annotation && ev.annotation !== ev.message ? ev.annotation.trim() : ""; + const color = ev.color || "#03a9f4"; + const icon = ev.icon || "mdi:bookmark"; + const iconColor = contrastColor(color); + const entities = ev.entity_ids || []; + const devices = ev.device_ids || []; + const areas = ev.area_ids || []; + const labels = ev.label_ids || []; + const hasRelated = entities.length || devices.length || areas.length || labels.length; + const isExpandable = !showFullMessage && !!annText; + const isHidden = this.context.hidden; + const visibilityIcon = isHidden ? "mdi:eye" : "mdi:eye-off"; + const visibilityLabel = isHidden ? this.context.language.showChartMarker : this.context.language.hideChartMarker; + const isEditing = this.context.editing; + return b`
{ - this._dispatch("dp-hover-event-record", { - eventId: ev.id, - hovered: true, - eventRecord: ev - }); - }} + this._dispatch("dp-hover-event-record", { + eventId: ev.id, + hovered: true, + eventRecord: ev + }); + }} @mouseleave=${() => { - this._dispatch("dp-hover-event-record", { - eventId: ev.id, - hovered: false, - eventRecord: ev - }); - }} + this._dispatch("dp-hover-event-record", { + eventId: ev.id, + hovered: false, + eventRecord: ev + }); + }} @click=${isExpandable ? (event) => { - const target = event.target; - if (target.closest( - ".ev-actions, .ev-entity-chip, .edit-form, ha-icon-button, ha-button" - )) { - return; - } - this._annotationExpanded = !this._annotationExpanded; - } : void 0} + if (event.target.closest(".ev-actions, .ev-entity-chip, .edit-form, ha-icon-button, ha-button")) return; + this._annotationExpanded = !this._annotationExpanded; + } : void 0} >
{ - event.preventDefault(); - event.stopPropagation(); - this._dispatch("dp-open-history", { eventRecord: ev }); - }} + event.preventDefault(); + event.stopPropagation(); + this._dispatch("dp-open-history", { eventRecord: ev }); + }} > { - event.stopPropagation(); - this._dispatch("dp-toggle-visibility", { - eventId: ev.id - }); - }} + event.stopPropagation(); + this._dispatch("dp-toggle-visibility", { eventId: ev.id }); + }} > @@ -35229,11 +31511,9 @@ ${content.alert}` : "", { - event.stopPropagation(); - this._dispatch("dp-edit-event", { - eventRecord: ev - }); - }} + event.stopPropagation(); + this._dispatch("dp-edit-event", { eventRecord: ev }); + }} > @@ -35241,11 +31521,9 @@ ${content.alert}` : "", label=${this.context.language.deleteRecord} style="--icon-primary-color:var(--error-color,#f44336)" @click=${(event) => { - event.stopPropagation(); - this._dispatch("dp-delete-event", { - eventRecord: ev - }); - }} + event.stopPropagation(); + this._dispatch("dp-delete-event", { eventRecord: ev }); + }} > @@ -35260,31 +31538,24 @@ ${content.alert}` : "",
` : ""} ${showEntities && hasRelated ? b`
- ${entities.map( - (entityId) => b` + ${entities.map((entityId) => b` ${(() => { - const entityLabel = entityName( - this.context.hass, - entityId - ); - const showSecondary = entityLabel !== entityId; - return b` + const entityLabel = entityName(this.context.hass, entityId); + const showSecondary = entityLabel !== entityId; + return b` `; - })()} - ` - )} - ${devices.map( - (id) => b` + })()} + `)} + ${devices.map((id) => b` ${deviceName(this.context.hass, id)} - ` - )} - ${areas.map( - (id) => b` + `)} + ${areas.map((id) => b` ${areaName(this.context.hass, id)} - ` - )} - ${labels.map( - (id) => b` + `)} + ${labels.map((id) => b` ${labelName(this.context.hass, id)} - ` - )} + `)}
` : ""} ${showActions && canEdit && isEditing ? b` @@ -35338,357 +31602,273 @@ ${content.alert}` : "", .color=${this.context.editColor} .language=${this.context.language} @dp-save-edit=${(event) => { - this._dispatch("dp-save-edit", { - eventRecord: ev, - values: event.detail - }); - }} + this._dispatch("dp-save-edit", { + eventRecord: ev, + values: event.detail + }); + }} @dp-cancel-edit=${() => { - this._dispatch("dp-cancel-edit", { eventRecord: ev }); - }} + this._dispatch("dp-cancel-edit", { eventRecord: ev }); + }} > ` : ""}
`; - } - } - _init$7 = __decoratorStart$7(_a$7); - _eventRecord = /* @__PURE__ */ new WeakMap(); - _context = /* @__PURE__ */ new WeakMap(); - __annotationExpanded = /* @__PURE__ */ new WeakMap(); - __decorateElement$7(_init$7, 4, "eventRecord", _eventRecord_dec, CardListEventItem, _eventRecord); - __decorateElement$7(_init$7, 4, "context", _context_dec, CardListEventItem, _context); - __decorateElement$7(_init$7, 4, "_annotationExpanded", __annotationExpanded_dec, CardListEventItem, __annotationExpanded); - __decoratorMetadata$7(_init$7, CardListEventItem); - __publicField$d(CardListEventItem, "styles", styles$d); - customElements.define("list-event-item", CardListEventItem); - var __create$6 = Object.create; - var __defProp$c = Object.defineProperty; - var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor; - var __knownSymbol$6 = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$6 = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$c = (obj, key, value) => key in obj ? __defProp$c(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __name = (target, value) => __defProp$c(target, "name", { value, configurable: true }); - var __decoratorStart$6 = (base) => [, , , __create$6(base?.[__knownSymbol$6("metadata")] ?? null)]; - var __decoratorStrings$6 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$6 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$6("Function expected") : fn; - var __decoratorContext$6 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$6[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$6("Already initialized") : fns.push(__expectFn$6(fn || null)) }); - var __decoratorMetadata$6 = (array, target) => __defNormalProp$c(target, __knownSymbol$6("metadata"), array[3]); - var __runInitializers$6 = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) fns[i2].call(self2); - return value; - }; - var __decorateElement$6 = (array, flags, name, decorators, target, extra) => { - var it, done, ctx, k2 = flags & 7, p2 = false; - var j2 = 0; - var extraInitializers = array[j2] || (array[j2] = []); - var desc = k2 && (target = target.prototype, k2 < 5 && (k2 > 3 || !p2) && __getOwnPropDesc$6(target, name)); - __name(target, name); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$6(k2, name, done = {}, array[3], extraInitializers); - it = (0, decorators[i2])(target, ctx), done._ = 1; - __expectFn$6(it) && (target = it); - } - return __decoratorMetadata$6(array, target), desc && __defProp$c(target, name, desc), p2 ? k2 ^ 4 ? extra : desc : target; - }; - var __publicField$c = (obj, key, value) => __defNormalProp$c(obj, typeof key !== "symbol" ? key + "" : key, value); - var _HassRecordsListCard_decorators, _init$6, _a$6; - _HassRecordsListCard_decorators = [localized()]; - class HassRecordsListCard extends (_a$6 = i$2) { - constructor() { - super(); - __publicField$c(this, "_pageSize", 15); - __publicField$c(this, "_configKey", ""); - __publicField$c(this, "_unsubscribe", null); - __publicField$c(this, "_windowListener", null); - __publicField$c(this, "_initialized", false); - this._config = {}; - this._hass = null; - this._allEvents = []; - this._searchQuery = ""; - this._page = 0; - this._editingId = null; - this._editColor = "#03a9f4"; - } - setConfig(config) { - const nextKey = JSON.stringify(config); - if (this._configKey === nextKey) { - return; - } - this._configKey = nextKey; - this._config = config || {}; - if (config.page_size) this._pageSize = config.page_size; - if (this._hass) this._load(); - } - set hass(hass) { - const isFirst = !this._hass; - this._hass = hass; - if (isFirst) { - this._load(); - this._setupAutoRefresh(); - } - } - get hass() { - return this._hass; - } - connectedCallback() { - super.connectedCallback(); - this._windowListener = () => this._load(); - window.addEventListener( - "hass-datapoints-event-recorded", - this._windowListener - ); - } - disconnectedCallback() { - super.disconnectedCallback(); - if (this._unsubscribe) { - this._unsubscribe(); - this._unsubscribe = null; - } - if (this._windowListener) { - window.removeEventListener( - "hass-datapoints-event-recorded", - this._windowListener - ); - this._windowListener = null; - } - } - _setupAutoRefresh() { - if (!this._hass) return; - this._hass.connection.subscribeEvents(() => this._load(), `${DOMAIN}_event_recorded`).then((unsub) => { - this._unsubscribe = unsub; - }).catch(() => { - }); - } - async _load() { - if (!this._hass || !this._config) return; - const cfg = this._config; - const endTime = cfg.zoom_end_time || cfg.end_time || void 0; - let startTime = cfg.zoom_start_time || cfg.start_time || void 0; - if (!startTime && cfg.hours_to_show) { - const end = endTime ? new Date(endTime) : /* @__PURE__ */ new Date(); - startTime = new Date( - end.getTime() - cfg.hours_to_show * 3600 * 1e3 - ).toISOString(); - } - if (!startTime) { - startTime = (/* @__PURE__ */ new Date(0)).toISOString(); - } - const effectiveEndTime = endTime || (/* @__PURE__ */ new Date()).toISOString(); - let entityIds; - if (cfg.target) { - const resolved = resolveEntityIdsFromTarget(this._hass, cfg.target); - entityIds = resolved.length ? resolved : void 0; - } else if (cfg.entity) { - entityIds = [cfg.entity]; - } else if (cfg.entities) { - entityIds = cfg.entities.map( - (e2) => typeof e2 === "string" ? e2 : e2.entity - ); - } else { - entityIds = void 0; - } - const hass = this._hass; - if (!hass) { - this._allEvents = []; - return; - } - const events = await fetchEvents( - hass, - startTime, - effectiveEndTime, - cfg.datapoint_scope === "all" ? void 0 : entityIds - ); - this._allEvents = [...events].reverse(); - } - _filtered() { - const msgFilter = (this._config.message_filter || "").toLowerCase().trim(); - const searchQ = this._searchQuery.toLowerCase().trim(); - return this._allEvents.filter((e2) => { - const haystack = [ - e2.message.toLowerCase(), - (e2.annotation || "").toLowerCase(), - ...(e2.entity_ids || []).map((id) => id.toLowerCase()) - ]; - if (msgFilter && !haystack.some((h2) => h2.includes(msgFilter))) - return false; - if (searchQ && !haystack.some((h2) => h2.includes(searchQ))) return false; - return true; - }); - } - _onSearch(e2) { - this._searchQuery = e2.detail.query; - this._page = 0; - } - _onPageChange(e2) { - this._page = e2.detail.page; - this.shadowRoot?.querySelector(".list-scroll")?.scrollTo(0, 0); - } - _navigateToEventHistory(ev) { - const range = this._getNavigationContextForEvent(ev); - navigateToDataPointsHistory( - this, - { - entity_id: ev?.entity_ids || [], - device_id: ev?.device_ids || [], - area_id: ev?.area_ids || [], - label_id: ev?.label_ids || [] - }, - { - start_time: range?.start_time, - end_time: range?.end_time, - zoom_start_time: range?.zoom_start_time, - zoom_end_time: range?.zoom_end_time, - datapoint_scope: typeof this._config?.datapoint_scope === "string" ? this._config.datapoint_scope : void 0 - } - ); - } - _getNavigationContextForEvent(ev) { - const cfg = this._config || {}; - const startTime = cfg.start_time || null; - const endTime = cfg.end_time || null; - const zoomStartTime = cfg.zoom_start_time || null; - const zoomEndTime = cfg.zoom_end_time || null; - if (startTime && endTime) { - return { - start_time: startTime, - end_time: endTime, - zoom_start_time: zoomStartTime, - zoom_end_time: zoomEndTime - }; - } - const eventTime = ev?.timestamp ? new Date(ev.timestamp) : null; - if (!eventTime || !Number.isFinite(eventTime.getTime())) return null; - const start = new Date(eventTime.getTime() - 12 * 3600 * 1e3); - const end = new Date(eventTime.getTime() + 12 * 3600 * 1e3); - return { start_time: start.toISOString(), end_time: end.toISOString() }; - } - _openEdit(ev) { - this._editingId = ev.id; - this._editColor = ev.color || "#03a9f4"; - } - _closeEdit() { - this._editingId = null; - } - async _saveEdit(ev, values) { - const message = values.message.trim(); - const ann = values.annotation.trim(); - const icon = values.icon || "mdi:bookmark"; - const color = values.color || this._editColor; - if (!message) { - return; - } - const hass = this._hass; - if (!hass) { - return; - } - try { - await updateEvent(hass, ev.id, { - message, - annotation: ann || message, - icon, - color - }); - this._closeEdit(); - await this._load(); - } catch (err) { - logger$1.error("[hass-datapoints list-card] update failed", err); - } - } - async _deleteEvent(ev) { - const message = ev.message || "this record"; - const confirmed = await confirmDestructiveAction(this, { - title: msg("Delete record"), - message: `${msg("Delete")} ${message}?`, - confirmLabel: msg("Delete record") - }); - if (!confirmed) return; - const hass = this._hass; - if (!hass) { - return; - } - try { - await deleteEvent(hass, ev.id); - await this._load(); - } catch (err) { - logger$1.error("[hass-datapoints list-card] delete failed", err); - } - } - _toggleVisibility(ev) { - this.dispatchEvent( - new CustomEvent("hass-datapoints-toggle-event-visibility", { - bubbles: true, - composed: true, - detail: { eventId: ev.id } - }) - ); - } - _fireMoreInfo(entityId) { - this.dispatchEvent( - new CustomEvent("hass-more-info", { - bubbles: true, - composed: true, - detail: { entityId } - }) - ); - } - _handleHoverEventRecord(event) { - this.dispatchEvent( - new CustomEvent("hass-datapoints-hover-event-record", { - bubbles: true, - composed: true, - detail: { - eventId: event.detail.eventId, - hovered: event.detail.hovered === true, - eventRecord: event.detail.eventRecord - } - }) - ); - } - _itemContext(ev) { - const cfg = this._config; - const hidden = (cfg.hidden_event_ids || []).includes(ev.id); - if (hidden !== ev._lastHidden) { - ev._lastHidden = hidden; - } - return { - hass: this._hass, - showActions: cfg.show_actions !== false, - canEdit: this._hass?.user?.is_admin === true, - showEntities: cfg.show_entities !== false, - showFullMessage: cfg.show_full_message !== false, - hidden, - editing: this._editingId === ev.id, - editColor: this._editColor, - language: { - showAnnotation: msg("Show annotation"), - openHistory: msg("Open related data point history"), - editRecord: msg("Edit record"), - deleteRecord: msg("Delete record"), - showChartMarker: msg("Show chart marker"), - hideChartMarker: msg("Hide chart marker"), - chooseColor: msg("Choose colour"), - save: msg("Save"), - cancel: msg("Cancel"), - message: msg("Message"), - annotationFullMessage: msg("Annotation / full message") - } - }; - } - render() { - const cfg = this._config; - const showSearch = cfg.show_search !== false; - const filtered = this._filtered(); - const total = filtered.length; - const pageSize = this._pageSize; - const totalPages = Math.max(1, Math.ceil(total / pageSize)); - const page = Math.min(this._page, totalPages - 1); - const slice = filtered.slice(page * pageSize, (page + 1) * pageSize); - const showPagination = totalPages > 1; - return b` + } + }; + _defineProperty(CardListEventItem, "styles", styles$13); + customElements.define("list-event-item", CardListEventItem); + //#endregion + //#region custom_components/hass_datapoints/src/cards/list/list.ts + var HassRecordsListCard = @localized() class extends i$2 { + constructor() { + super(); + _defineProperty(this, "_pageSize", 15); + _defineProperty(this, "_configKey", ""); + _defineProperty(this, "_unsubscribe", null); + _defineProperty(this, "_windowListener", null); + _defineProperty(this, "_initialized", false); + this._config = {}; + this._hass = null; + this._allEvents = []; + this._searchQuery = ""; + this._page = 0; + this._editingId = null; + this._editColor = "#03a9f4"; + } + setConfig(config) { + const nextKey = JSON.stringify(config); + if (this._configKey === nextKey) return; + this._configKey = nextKey; + this._config = config || {}; + if (config.page_size) this._pageSize = config.page_size; + if (this._hass) this._load(); + } + set hass(hass) { + const isFirst = !this._hass; + this._hass = hass; + if (isFirst) { + this._load(); + this._setupAutoRefresh(); + } + } + get hass() { + return this._hass; + } + connectedCallback() { + super.connectedCallback(); + this._windowListener = () => this._load(); + window.addEventListener("hass-datapoints-event-recorded", this._windowListener); + } + disconnectedCallback() { + super.disconnectedCallback(); + if (this._unsubscribe) { + this._unsubscribe(); + this._unsubscribe = null; + } + if (this._windowListener) { + window.removeEventListener("hass-datapoints-event-recorded", this._windowListener); + this._windowListener = null; + } + } + _setupAutoRefresh() { + if (!this._hass) return; + this._hass.connection.subscribeEvents(() => this._load(), `${DOMAIN}_event_recorded`).then((unsub) => { + this._unsubscribe = unsub; + }).catch(() => {}); + } + async _load() { + if (!this._hass || !this._config) return; + const cfg = this._config; + const endTime = cfg.zoom_end_time || cfg.end_time || void 0; + let startTime = cfg.zoom_start_time || cfg.start_time || void 0; + if (!startTime && cfg.hours_to_show) { + const end = endTime ? new Date(endTime) : /* @__PURE__ */ new Date(); + startTime = (/* @__PURE__ */ new Date(end.getTime() - cfg.hours_to_show * 3600 * 1e3)).toISOString(); + } + if (!startTime) startTime = (/* @__PURE__ */ new Date(0)).toISOString(); + const effectiveEndTime = endTime || (/* @__PURE__ */ new Date()).toISOString(); + let entityIds; + if (cfg.target) { + const resolved = resolveEntityIdsFromTarget(this._hass, cfg.target); + entityIds = resolved.length ? resolved : void 0; + } else if (cfg.entity) entityIds = [cfg.entity]; + else if (cfg.entities) entityIds = cfg.entities.map((e) => typeof e === "string" ? e : e.entity); + else entityIds = void 0; + const hass = this._hass; + if (!hass) { + this._allEvents = []; + return; + } + this._allEvents = [...await fetchEvents(hass, startTime, effectiveEndTime, cfg.datapoint_scope === "all" ? void 0 : entityIds)].reverse(); + } + _filtered() { + const msgFilter = (this._config.message_filter || "").toLowerCase().trim(); + const searchQ = this._searchQuery.toLowerCase().trim(); + return this._allEvents.filter((e) => { + const haystack = [ + e.message.toLowerCase(), + (e.annotation || "").toLowerCase(), + ...(e.entity_ids || []).map((id) => id.toLowerCase()) + ]; + if (msgFilter && !haystack.some((h) => h.includes(msgFilter))) return false; + if (searchQ && !haystack.some((h) => h.includes(searchQ))) return false; + return true; + }); + } + _onSearch(e) { + this._searchQuery = e.detail.query; + this._page = 0; + } + _onPageChange(e) { + this._page = e.detail.page; + this.shadowRoot?.querySelector(".list-scroll")?.scrollTo(0, 0); + } + _navigateToEventHistory(ev) { + const range = this._getNavigationContextForEvent(ev); + navigateToDataPointsHistory(this, { + entity_id: ev?.entity_ids || [], + device_id: ev?.device_ids || [], + area_id: ev?.area_ids || [], + label_id: ev?.label_ids || [] + }, { + start_time: range?.start_time, + end_time: range?.end_time, + zoom_start_time: range?.zoom_start_time, + zoom_end_time: range?.zoom_end_time, + datapoint_scope: typeof this._config?.datapoint_scope === "string" ? this._config.datapoint_scope : void 0 + }); + } + _getNavigationContextForEvent(ev) { + const cfg = this._config || {}; + const startTime = cfg.start_time || null; + const endTime = cfg.end_time || null; + const zoomStartTime = cfg.zoom_start_time || null; + const zoomEndTime = cfg.zoom_end_time || null; + if (startTime && endTime) return { + start_time: startTime, + end_time: endTime, + zoom_start_time: zoomStartTime, + zoom_end_time: zoomEndTime + }; + const eventTime = ev?.timestamp ? new Date(ev.timestamp) : null; + if (!eventTime || !Number.isFinite(eventTime.getTime())) return null; + const start = /* @__PURE__ */ new Date(eventTime.getTime() - 12 * 3600 * 1e3); + const end = new Date(eventTime.getTime() + 12 * 3600 * 1e3); + return { + start_time: start.toISOString(), + end_time: end.toISOString() + }; + } + _openEdit(ev) { + this._editingId = ev.id; + this._editColor = ev.color || "#03a9f4"; + } + _closeEdit() { + this._editingId = null; + } + async _saveEdit(ev, values) { + const message = values.message.trim(); + const ann = values.annotation.trim(); + const icon = values.icon || "mdi:bookmark"; + const color = values.color || this._editColor; + if (!message) return; + const hass = this._hass; + if (!hass) return; + try { + await updateEvent(hass, ev.id, { + message, + annotation: ann || message, + icon, + color + }); + this._closeEdit(); + await this._load(); + } catch (err) { + logger$1.error("[hass-datapoints list-card] update failed", err); + } + } + async _deleteEvent(ev) { + const message = ev.message || "this record"; + if (!await confirmDestructiveAction(this, { + title: msg("Delete record"), + message: `${msg("Delete")} ${message}?`, + confirmLabel: msg("Delete record") + })) return; + const hass = this._hass; + if (!hass) return; + try { + await deleteEvent(hass, ev.id); + await this._load(); + } catch (err) { + logger$1.error("[hass-datapoints list-card] delete failed", err); + } + } + _toggleVisibility(ev) { + this.dispatchEvent(new CustomEvent("hass-datapoints-toggle-event-visibility", { + bubbles: true, + composed: true, + detail: { eventId: ev.id } + })); + } + _fireMoreInfo(entityId) { + this.dispatchEvent(new CustomEvent("hass-more-info", { + bubbles: true, + composed: true, + detail: { entityId } + })); + } + _handleHoverEventRecord(event) { + this.dispatchEvent(new CustomEvent("hass-datapoints-hover-event-record", { + bubbles: true, + composed: true, + detail: { + eventId: event.detail.eventId, + hovered: event.detail.hovered === true, + eventRecord: event.detail.eventRecord + } + })); + } + _itemContext(ev) { + const cfg = this._config; + const hidden = (cfg.hidden_event_ids || []).includes(ev.id); + if (hidden !== ev._lastHidden) ev._lastHidden = hidden; + return { + hass: this._hass, + showActions: cfg.show_actions !== false, + canEdit: this._hass?.user?.is_admin === true, + showEntities: cfg.show_entities !== false, + showFullMessage: cfg.show_full_message !== false, + hidden, + editing: this._editingId === ev.id, + editColor: this._editColor, + language: { + showAnnotation: msg("Show annotation"), + openHistory: msg("Open related data point history"), + editRecord: msg("Edit record"), + deleteRecord: msg("Delete record"), + showChartMarker: msg("Show chart marker"), + hideChartMarker: msg("Hide chart marker"), + chooseColor: msg("Choose colour"), + save: msg("Save"), + cancel: msg("Cancel"), + message: msg("Message"), + annotationFullMessage: msg("Annotation / full message") + } + }; + } + render() { + const cfg = this._config; + const showSearch = cfg.show_search !== false; + const filtered = this._filtered(); + const total = filtered.length; + const pageSize = this._pageSize; + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + const page = Math.min(this._page, totalPages - 1); + const slice = filtered.slice(page * pageSize, (page + 1) * pageSize); + const showPagination = totalPages > 1; + return b` ${cfg.title ? b`
${cfg.title}
` : ""} ${showSearch ? b` @@ -35707,38 +31887,36 @@ ${content.alert}` : "", ${this._searchQuery ? "No matching datapoints." : "No datapoints yet."}
- ` : slice.map( - (ev) => b` + ` : slice.map((ev) => b` { - this._navigateToEventHistory(ev); - }} + this._navigateToEventHistory(ev); + }} @dp-edit-event=${() => { - this._openEdit(ev); - }} + this._openEdit(ev); + }} @dp-delete-event=${() => { - this._deleteEvent(ev); - }} + this._deleteEvent(ev); + }} @dp-toggle-visibility=${() => { - this._toggleVisibility(ev); - }} + this._toggleVisibility(ev); + }} @dp-hover-event-record=${(event) => { - this._handleHoverEventRecord(event); - }} + this._handleHoverEventRecord(event); + }} @dp-more-info=${(event) => { - this._fireMoreInfo(event.detail.entityId); - }} + this._fireMoreInfo(event.detail.entityId); + }} @dp-save-edit=${(event) => { - this._saveEdit(ev, event.detail.values); - }} + this._saveEdit(ev, event.detail.values); + }} @dp-cancel-edit=${() => { - this._closeEdit(); - }} + this._closeEdit(); + }} > - ` - )} + `)} ${showPagination ? b` @@ -35754,136 +31932,117 @@ ${content.alert}` : "", ` : ""} `; - } - static getConfigElement() { - return document.createElement("hass-datapoints-list-card-editor"); - } - static getStubConfig() { - return {}; - } - getGridOptions() { - const rows = this._config?.show_search !== false ? 4 : 3; - return { rows, min_rows: rows }; - } - } - _init$6 = __decoratorStart$6(_a$6); - HassRecordsListCard = __decorateElement$6(_init$6, 0, "HassRecordsListCard", _HassRecordsListCard_decorators, HassRecordsListCard); - __publicField$c(HassRecordsListCard, "properties", { - _config: { state: true }, - _hass: { state: true }, - _allEvents: { state: true }, - _searchQuery: { state: true }, - _page: { state: true }, - _editingId: { state: true }, - _editColor: { state: true } - }); - __publicField$c(HassRecordsListCard, "styles", styles$g); - __runInitializers$6(_init$6, 1, HassRecordsListCard); - const styles$b = i$5``; - var __defProp$b = Object.defineProperty; - var __defNormalProp$b = (obj, key, value) => key in obj ? __defProp$b(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$b = (obj, key, value) => __defNormalProp$b(obj, typeof key !== "symbol" ? key + "" : key, value); - class HassRecordsListCardEditor extends EditorBase { - constructor() { - super(...arguments); - __publicField$b(this, "_onTargetChanged", (e2) => { - const val = e2.detail.value; - const isEmpty = !val || Object.values(val).every((value) => !value?.length); - this._setTargetConfig(isEmpty ? void 0 : val); - }); - } - _configTarget() { - return normalizeTargetValue( - this._config.target ?? { - entity_id: Array.isArray(this._config.entities) && this._config.entities.length ? this._config.entities : this._config.entity - } - ) ?? {}; - } - _syncTargetPicker() { - const tp = this.shadowRoot?.querySelector( - "#target-picker" - ); - if (!tp) { - return; - } - if (this.hass) { - tp.hass = this.hass; - } - tp.value = this._configTarget(); - } - _setTargetConfig(target) { - const cfg = { ...this._config }; - delete cfg.entity; - delete cfg.entities; - if (!target || Object.values(target).every((value) => !value?.length)) { - delete cfg.target; - } else { - cfg.target = target; - } - this._config = cfg; - this._fire(cfg); - } - updated(changedProps) { - if (changedProps.has("hass") || changedProps.has("_config")) { - this._syncTargetPicker(); - } - } - render() { - const c2 = this._config; - return b` + } + static getConfigElement() { + return document.createElement("hass-datapoints-list-card-editor"); + } + static getStubConfig() { + return {}; + } + getGridOptions() { + const rows = this._config?.show_search !== false ? 4 : 3; + return { + rows, + min_rows: rows + }; + } + }; + _defineProperty(HassRecordsListCard, "properties", { + _config: { state: true }, + _hass: { state: true }, + _allEvents: { state: true }, + _searchQuery: { state: true }, + _page: { state: true }, + _editingId: { state: true }, + _editColor: { state: true } + }); + _defineProperty(HassRecordsListCard, "styles", styles$16); + //#endregion + //#region custom_components/hass_datapoints/src/cards/list/editor.styles.ts + var styles$11 = i$5``; + //#endregion + //#region custom_components/hass_datapoints/src/cards/list/editor.ts + var HassRecordsListCardEditor = class extends EditorBase { + constructor(..._args) { + super(..._args); + _defineProperty(this, "_onTargetChanged", (e) => { + const val = e.detail.value; + const isEmpty = !val || Object.values(val).every((value) => !value?.length); + this._setTargetConfig(isEmpty ? void 0 : val); + }); + } + _configTarget() { + return normalizeTargetValue(this._config.target ?? { entity_id: Array.isArray(this._config.entities) && this._config.entities.length ? this._config.entities : this._config.entity }) ?? {}; + } + _syncTargetPicker() { + const tp = this.shadowRoot?.querySelector("#target-picker"); + if (!tp) return; + if (this.hass) tp.hass = this.hass; + tp.value = this._configTarget(); + } + _setTargetConfig(target) { + const cfg = { ...this._config }; + delete cfg.entity; + delete cfg.entities; + if (!target || Object.values(target).every((value) => !value?.length)) delete cfg.target; + else cfg.target = target; + this._config = cfg; + this._fire(cfg); + } + updated(changedProps) { + if (changedProps.has("hass") || changedProps.has("_config")) this._syncTargetPicker(); + } + render() { + const c = this._config; + return b`
this._set("title", e2.detail.value)} + .value=${c.title || ""} + @dp-field-change=${(e) => this._set("title", e.detail.value)} > this._set("hours_to_show", e2.detail.value)} + .value=${c.hours_to_show != null ? String(c.hours_to_show) : ""} + @dp-field-change=${(e) => this._set("hours_to_show", e.detail.value)} > this._set("page_size", e2.detail.value)} + .value=${String(c.page_size ?? 15)} + @dp-field-change=${(e) => this._set("page_size", e.detail.value)} > this._set("message_filter", e2.detail.value)} + .value=${c.message_filter || ""} + @dp-field-change=${(e) => this._set("message_filter", e.detail.value)} > this._set("show_search", e2.detail.checked ? void 0 : false)} + .checked=${c.show_search !== false} + @dp-switch-change=${(e) => this._set("show_search", e.detail.checked ? void 0 : false)} > this._set("show_entities", e2.detail.checked ? void 0 : false)} + .checked=${c.show_entities !== false} + @dp-switch-change=${(e) => this._set("show_entities", e.detail.checked ? void 0 : false)} > this._set("show_actions", e2.detail.checked ? void 0 : false)} + .checked=${c.show_actions !== false} + @dp-switch-change=${(e) => this._set("show_actions", e.detail.checked ? void 0 : false)} > this._set( - "show_full_message", - e2.detail.checked ? void 0 : false - )} + .checked=${c.show_full_message !== false} + .tooltip=${msg("User will be able to expand the row if hidden", { id: "User will be able to expand the row if hidden" })} + @dp-switch-change=${(e) => this._set("show_full_message", e.detail.checked ? void 0 : false)} > @@ -35897,10 +32056,12 @@ ${content.alert}` : "", >
`; - } - } - __publicField$b(HassRecordsListCardEditor, "styles", [EditorBase.styles, styles$b]); - const styles$a = i$5` + } + }; + _defineProperty(HassRecordsListCardEditor, "styles", [EditorBase.styles, styles$11]); + //#endregion + //#region custom_components/hass_datapoints/src/cards/quick/quick.styles.ts + var styles$10 = i$5` :host { display: block; height: 100%; @@ -35956,7 +32117,9 @@ ${content.alert}` : "", margin: 0; } `; - const styles$9 = i$5` + //#endregion + //#region custom_components/hass_datapoints/src/cards/quick/quick-annotation/quick-annotation.styles.ts + var styles$9 = i$5` :host { display: block; } @@ -35990,73 +32153,22 @@ ${content.alert}` : "", line-height: 1.45; } `; - var __create$5 = Object.create; - var __defProp$a = Object.defineProperty; - var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor; - var __knownSymbol$5 = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$5 = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$a = (obj, key, value) => key in obj ? __defProp$a(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$5 = (base) => [, , , __create$5(base?.[__knownSymbol$5("metadata")] ?? null)]; - var __decoratorStrings$5 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$5 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$5("Function expected") : fn; - var __decoratorContext$5 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$5[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$5("Already initialized") : fns.push(__expectFn$5(fn || null)) }); - var __decoratorMetadata$5 = (array, target) => __defNormalProp$a(target, __knownSymbol$5("metadata"), array[3]); - var __runInitializers$5 = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$5 = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$5[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$5({ get [name]() { - return __privateGet$5(this, extra); - }, set [name](x2) { - return __privateSet$5(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$5(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$5(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$5("Object expected"); - else __expectFn$5(fn = it.get) && (desc.get = fn), __expectFn$5(fn = it.set) && (desc.set = fn), __expectFn$5(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$a(target, name, desc), target; - }; - var __publicField$a = (obj, key, value) => __defNormalProp$a(obj, key + "", value); - var __accessCheck$5 = (obj, member, msg2) => member.has(obj) || __typeError$5("Cannot " + msg2); - var __privateGet$5 = (obj, member, getter) => (__accessCheck$5(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$5 = (obj, member, value) => member.has(obj) ? __typeError$5("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$5 = (obj, member, value, setter) => (__accessCheck$5(obj, member, "write to private field"), member.set(obj, value), value); - var _value_dec$3, _placeholder_dec, _label_dec$2, _a$5, _init$5, _label$2, _placeholder, _value$3; - class CardQuickAnnotation extends (_a$5 = i$2, _label_dec$2 = [n({ type: String })], _placeholder_dec = [n({ type: String })], _value_dec$3 = [n({ type: String })], _a$5) { - constructor() { - super(...arguments); - __privateAdd$5(this, _label$2, __runInitializers$5(_init$5, 8, this, "Annotation")), __runInitializers$5(_init$5, 11, this); - __privateAdd$5(this, _placeholder, __runInitializers$5(_init$5, 12, this, "Detailed note shown on chart hover…")), __runInitializers$5(_init$5, 15, this); - __privateAdd$5(this, _value$3, __runInitializers$5(_init$5, 16, this, "")), __runInitializers$5(_init$5, 19, this); - } - _onInput(event) { - this.value = event.currentTarget.value; - this.dispatchEvent( - new CustomEvent("dp-annotation-input", { - detail: { - value: this.value - }, - bubbles: true, - composed: true - }) - ); - } - render() { - return b` + //#endregion + //#region custom_components/hass_datapoints/src/cards/quick/quick-annotation/quick-annotation.ts + var CardQuickAnnotation = class extends i$2 { + @n$1({ type: String }) accessor label = "Annotation"; + @n$1({ type: String }) accessor placeholder = "Detailed note shown on chart hover…"; + @n$1({ type: String }) accessor value = ""; + _onInput(event) { + this.value = event.currentTarget.value; + this.dispatchEvent(new CustomEvent("dp-annotation-input", { + detail: { value: this.value }, + bubbles: true, + composed: true + })); + } + render() { + return b`
`; - } - } - _init$5 = __decoratorStart$5(_a$5); - _label$2 = /* @__PURE__ */ new WeakMap(); - _placeholder = /* @__PURE__ */ new WeakMap(); - _value$3 = /* @__PURE__ */ new WeakMap(); - __decorateElement$5(_init$5, 4, "label", _label_dec$2, CardQuickAnnotation, _label$2); - __decorateElement$5(_init$5, 4, "placeholder", _placeholder_dec, CardQuickAnnotation, _placeholder); - __decorateElement$5(_init$5, 4, "value", _value_dec$3, CardQuickAnnotation, _value$3); - __decoratorMetadata$5(_init$5, CardQuickAnnotation); - __publicField$a(CardQuickAnnotation, "styles", styles$9); - customElements.define("quick-annotation", CardQuickAnnotation); - var __defProp$9 = Object.defineProperty; - var __defNormalProp$9 = (obj, key, value) => key in obj ? __defProp$9(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$9 = (obj, key, value) => __defNormalProp$9(obj, typeof key !== "symbol" ? key + "" : key, value); - class HassRecordsQuickCard extends i$2 { - constructor() { - super(); - this._config = {}; - this._hass = null; - this._feedbackClass = ""; - this._feedbackText = ""; - this._feedbackVisible = false; - this._annotation = ""; - } - setConfig(config) { - this._config = config || {}; - } - set hass(hass) { - this._hass = hass; - } - get hass() { - return this._hass; - } - firstUpdated() { - const msgEl = this.shadowRoot?.querySelector("#msg"); - if (msgEl) { - msgEl.addEventListener("keydown", (event) => { - if (event.key === "Enter") { - event.preventDefault(); - this._record(); - } - }); - } - } - async _record() { - const msgEl = this.shadowRoot?.querySelector("#msg"); - const message = (msgEl?.value || "").trim(); - if (!message) { - msgEl?.focus(); - return; - } - const btn = this.shadowRoot?.querySelector("#btn"); - if (btn) { - btn.disabled = true; - } - const cfg = this._config; - const data = { - message, - icon: cfg.icon || "mdi:bookmark", - color: cfg.color || AMBER - }; - const annotation = this._annotation.trim(); - if (annotation) { - data.annotation = annotation; - } - let entityIds; - if (cfg.target) { - entityIds = resolveEntityIdsFromTarget(this._hass, cfg.target); - } else if (cfg.entity) { - entityIds = [cfg.entity]; - } else if (cfg.entities) { - entityIds = Array.isArray(cfg.entities) ? cfg.entities : [cfg.entities]; - } else { - entityIds = []; - } - if (entityIds.length) { - data.entity_ids = entityIds; - } - try { - const hass = this._hass; - if (!hass) { - return; - } - await hass.callService(DOMAIN, "record", data); - window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); - if (msgEl) { - msgEl.value = ""; - } - this._annotation = ""; - this._feedbackClass = "ok"; - this._feedbackText = "Recorded!"; - this._feedbackVisible = true; - setTimeout(() => { - if (this) { - this._feedbackVisible = false; - } - }, 2500); - } catch (e2) { - const error = e2; - this._feedbackClass = "err"; - this._feedbackText = `Error: ${error.message || "unknown error"}`; - this._feedbackVisible = true; - logger$1.error("[hass-datapoints quick-card]", e2); - } - if (btn) { - btn.disabled = false; - } - } - get _isAdmin() { - return this._hass?.user?.is_admin === true; - } - render() { - const cfg = this._config; - const cfgIcon = cfg.icon || "mdi:bookmark"; - const cfgColor = cfg.color || AMBER; - const hasTitle = !!cfg.title; - const showAnnotation = !!cfg.show_annotation; - return b` + } + }; + _defineProperty(CardQuickAnnotation, "styles", styles$9); + customElements.define("quick-annotation", CardQuickAnnotation); + //#endregion + //#region custom_components/hass_datapoints/src/cards/quick/quick.ts + var HassRecordsQuickCard = class extends i$2 { + constructor() { + super(); + this._config = {}; + this._hass = null; + this._feedbackClass = ""; + this._feedbackText = ""; + this._feedbackVisible = false; + this._annotation = ""; + } + setConfig(config) { + this._config = config || {}; + } + set hass(hass) { + this._hass = hass; + } + get hass() { + return this._hass; + } + firstUpdated() { + const msgEl = this.shadowRoot?.querySelector("#msg"); + if (msgEl) msgEl.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + event.preventDefault(); + this._record(); + } + }); + } + async _record() { + const msgEl = this.shadowRoot?.querySelector("#msg"); + const message = (msgEl?.value || "").trim(); + if (!message) { + msgEl?.focus(); + return; + } + const btn = this.shadowRoot?.querySelector("#btn"); + if (btn) btn.disabled = true; + const cfg = this._config; + const data = { + message, + icon: cfg.icon || "mdi:bookmark", + color: cfg.color || "#ff9800" + }; + const annotation = this._annotation.trim(); + if (annotation) data.annotation = annotation; + let entityIds; + if (cfg.target) entityIds = resolveEntityIdsFromTarget(this._hass, cfg.target); + else if (cfg.entity) entityIds = [cfg.entity]; + else if (cfg.entities) entityIds = Array.isArray(cfg.entities) ? cfg.entities : [cfg.entities]; + else entityIds = []; + if (entityIds.length) data.entity_ids = entityIds; + try { + const hass = this._hass; + if (!hass) return; + await hass.callService(DOMAIN, "record", data); + window.dispatchEvent(new CustomEvent("hass-datapoints-event-recorded")); + if (msgEl) msgEl.value = ""; + this._annotation = ""; + this._feedbackClass = "ok"; + this._feedbackText = "Recorded!"; + this._feedbackVisible = true; + setTimeout(() => { + if (this) this._feedbackVisible = false; + }, 2500); + } catch (e) { + const error = e; + this._feedbackClass = "err"; + this._feedbackText = `Error: ${error.message || "unknown error"}`; + this._feedbackVisible = true; + logger$1.error("[hass-datapoints quick-card]", e); + } + if (btn) btn.disabled = false; + } + get _isAdmin() { + return this._hass?.user?.is_admin === true; + } + render() { + const cfg = this._config; + const cfgIcon = cfg.icon || "mdi:bookmark"; + const cfgColor = cfg.color || "#ff9800"; + const hasTitle = !!cfg.title; + const showAnnotation = !!cfg.show_annotation; + return b` ${hasTitle ? b`
${cfg.title}
` : ""} ${this._isAdmin ? b` @@ -36209,8 +32291,8 @@ ${content.alert}` : "", { - this._annotation = event.detail.value; - }} + this._annotation = event.detail.value; + }} > ` : ""} @@ -36227,129 +32309,128 @@ ${content.alert}` : "", `}
`; - } - static getConfigElement() { - return document.createElement("hass-datapoints-quick-card-editor"); - } - static getStubConfig() { - return { title: "Quick Record" }; - } - getGridOptions() { - const hasAnnotation = !!this._config?.show_annotation; - const hasTitle = !!this._config?.title; - const baseRows = hasAnnotation ? 3 : 1; - const rows = hasTitle ? baseRows + 1 : baseRows; - return { - rows, - min_rows: rows, - max_rows: rows - }; - } - getCardSize() { - const baseRows = this._config?.show_annotation ? 3 : 1; - return this._config?.title ? baseRows + 1 : baseRows; - } - } - __publicField$9(HassRecordsQuickCard, "properties", { - _config: { type: Object, state: true }, - _hass: { type: Object, state: true }, - _feedbackClass: { type: String, state: true }, - _feedbackText: { type: String, state: true }, - _feedbackVisible: { type: Boolean, state: true }, - _annotation: { type: String, state: true } - }); - __publicField$9(HassRecordsQuickCard, "styles", styles$a); - const styles$8 = i$5` + } + static getConfigElement() { + return document.createElement("hass-datapoints-quick-card-editor"); + } + static getStubConfig() { + return { title: "Quick Record" }; + } + getGridOptions() { + const hasAnnotation = !!this._config?.show_annotation; + const hasTitle = !!this._config?.title; + const baseRows = hasAnnotation ? 3 : 1; + const rows = hasTitle ? baseRows + 1 : baseRows; + return { + rows, + min_rows: rows, + max_rows: rows + }; + } + getCardSize() { + const baseRows = this._config?.show_annotation ? 3 : 1; + return this._config?.title ? baseRows + 1 : baseRows; + } + }; + _defineProperty(HassRecordsQuickCard, "properties", { + _config: { + type: Object, + state: true + }, + _hass: { + type: Object, + state: true + }, + _feedbackClass: { + type: String, + state: true + }, + _feedbackText: { + type: String, + state: true + }, + _feedbackVisible: { + type: Boolean, + state: true + }, + _annotation: { + type: String, + state: true + } + }); + _defineProperty(HassRecordsQuickCard, "styles", styles$10); + //#endregion + //#region custom_components/hass_datapoints/src/cards/quick/editor.styles.ts + var styles$8 = i$5` .note { font-size: 0.78rem; color: var(--secondary-text-color); } `; - var __defProp$8 = Object.defineProperty; - var __defNormalProp$8 = (obj, key, value) => key in obj ? __defProp$8(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$8 = (obj, key, value) => __defNormalProp$8(obj, typeof key !== "symbol" ? key + "" : key, value); - class HassRecordsQuickCardEditor extends EditorBase { - constructor() { - super(...arguments); - __publicField$8(this, "_onTargetChanged", (e2) => { - const val = e2.detail.value; - const isEmpty = !val || Object.values(val).every((value) => !value?.length); - this._setTargetConfig(isEmpty ? void 0 : val); - }); - } - _configTarget() { - return normalizeTargetValue( - this._config.target ?? { - entity_id: Array.isArray(this._config.entities) && this._config.entities.length ? this._config.entities : this._config.entity - } - ) ?? {}; - } - _syncTargetPicker() { - const tp = this.shadowRoot?.querySelector( - "#target-picker" - ); - if (!tp) { - return; - } - if (this.hass) { - tp.hass = this.hass; - } - tp.value = this._configTarget(); - } - _setTargetConfig(target) { - const cfg = { ...this._config }; - delete cfg.entity; - delete cfg.entities; - if (!target || Object.values(target).every((value) => !value?.length)) { - delete cfg.target; - } else { - cfg.target = target; - } - this._config = cfg; - this._fire(cfg); - } - updated(changedProps) { - if (changedProps.has("hass") || changedProps.has("_config")) { - this._syncTargetPicker(); - } - } - render() { - const c2 = this._config; - return b` + //#endregion + //#region custom_components/hass_datapoints/src/cards/quick/editor.ts + var HassRecordsQuickCardEditor = class extends EditorBase { + constructor(..._args) { + super(..._args); + _defineProperty(this, "_onTargetChanged", (e) => { + const val = e.detail.value; + const isEmpty = !val || Object.values(val).every((value) => !value?.length); + this._setTargetConfig(isEmpty ? void 0 : val); + }); + } + _configTarget() { + return normalizeTargetValue(this._config.target ?? { entity_id: Array.isArray(this._config.entities) && this._config.entities.length ? this._config.entities : this._config.entity }) ?? {}; + } + _syncTargetPicker() { + const tp = this.shadowRoot?.querySelector("#target-picker"); + if (!tp) return; + if (this.hass) tp.hass = this.hass; + tp.value = this._configTarget(); + } + _setTargetConfig(target) { + const cfg = { ...this._config }; + delete cfg.entity; + delete cfg.entities; + if (!target || Object.values(target).every((value) => !value?.length)) delete cfg.target; + else cfg.target = target; + this._config = cfg; + this._fire(cfg); + } + updated(changedProps) { + if (changedProps.has("hass") || changedProps.has("_config")) this._syncTargetPicker(); + } + render() { + const c = this._config; + return b`
this._set("title", e2.detail.value)} + .value=${c.title || ""} + @dp-field-change=${(e) => this._set("title", e.detail.value)} > this._set("placeholder", e2.detail.value)} + .value=${c.placeholder || ""} + @dp-field-change=${(e) => this._set("placeholder", e.detail.value)} > this._set("icon", e2.detail.value)} + @dp-icon-change=${(e) => this._set("icon", e.detail.value)} > this._set("color", e2.detail.color)} + .color=${c.color || "#ff9800"} + @dp-color-change=${(e) => this._set("color", e.detail.color)} >
- ${msg( - "These items will be linked to every record made with this card.", - { - id: "These items will be linked to every record made with this card." - } - )} + ${msg("These items will be linked to every record made with this card.", { id: "These items will be linked to every record made with this card." })}
this._set("show_annotation", e2.detail.checked || void 0)} + .checked=${!!c.show_annotation} + @dp-switch-change=${(e) => this._set("show_annotation", e.detail.checked || void 0)} >
`; - } - } - __publicField$8(HassRecordsQuickCardEditor, "styles", [EditorBase.styles, styles$8]); - const styles$7 = i$5` + } + }; + _defineProperty(HassRecordsQuickCardEditor, "styles", [EditorBase.styles, styles$8]); + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor.styles.ts + var styles$7 = i$5` :host { display: block; height: 100%; @@ -36429,7 +32512,9 @@ ${content.alert}` : "", position: relative; } `; - const styles$6 = i$5` + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor-header/sensor-header.styles.ts + var styles$6 = i$5` :host { display: block; } @@ -36496,73 +32581,30 @@ ${content.alert}` : "", margin-inline-start: initial; } `; - var __create$4 = Object.create; - var __defProp$7 = Object.defineProperty; - var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor; - var __knownSymbol$4 = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$4 = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$7 = (obj, key, value) => key in obj ? __defProp$7(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$4 = (base) => [, , , __create$4(base?.[__knownSymbol$4("metadata")] ?? null)]; - var __decoratorStrings$4 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$4 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$4("Function expected") : fn; - var __decoratorContext$4 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$4[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$4("Already initialized") : fns.push(__expectFn$4(fn || null)) }); - var __decoratorMetadata$4 = (array, target) => __defNormalProp$7(target, __knownSymbol$4("metadata"), array[3]); - var __runInitializers$4 = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$4 = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$4[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$4({ get [name]() { - return __privateGet$4(this, extra); - }, set [name](x2) { - return __privateSet$4(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$4(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$4(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$4("Object expected"); - else __expectFn$4(fn = it.get) && (desc.get = fn), __expectFn$4(fn = it.set) && (desc.set = fn), __expectFn$4(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$7(target, name, desc), target; - }; - var __publicField$7 = (obj, key, value) => __defNormalProp$7(obj, key + "", value); - var __accessCheck$4 = (obj, member, msg2) => member.has(obj) || __typeError$4("Cannot " + msg2); - var __privateGet$4 = (obj, member, getter) => (__accessCheck$4(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$4 = (obj, member, value) => member.has(obj) ? __typeError$4("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$4 = (obj, member, value, setter) => (__accessCheck$4(obj, member, "write to private field"), member.set(obj, value), value); - var _hass_dec$1, _stateObj_dec, _unit_dec, _value_dec$2, _name_dec, _a$4, _init$4, _name, _value$2, _unit, _stateObj, _hass$1; - class SensorHeader extends (_a$4 = i$2, _name_dec = [n({ type: String })], _value_dec$2 = [n({ type: String })], _unit_dec = [n({ type: String })], _stateObj_dec = [n({ type: Object, attribute: false })], _hass_dec$1 = [n({ type: Object, attribute: false })], _a$4) { - constructor() { - super(...arguments); - __privateAdd$4(this, _name, __runInitializers$4(_init$4, 8, this, "—")), __runInitializers$4(_init$4, 11, this); - __privateAdd$4(this, _value$2, __runInitializers$4(_init$4, 12, this, "—")), __runInitializers$4(_init$4, 15, this); - __privateAdd$4(this, _unit, __runInitializers$4(_init$4, 16, this, "")), __runInitializers$4(_init$4, 19, this); - __privateAdd$4(this, _stateObj, __runInitializers$4(_init$4, 20, this, null)), __runInitializers$4(_init$4, 23, this); - __privateAdd$4(this, _hass$1, __runInitializers$4(_init$4, 24, this, null)), __runInitializers$4(_init$4, 27, this); - } - _onHeaderClick(event) { - event.preventDefault(); - event.stopPropagation(); - this.dispatchEvent( - new CustomEvent("dp-sensor-header-click", { - bubbles: true, - composed: true - }) - ); - } - render() { - return b` + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor-header/sensor-header.ts + var SensorHeader = class extends i$2 { + @n$1({ type: String }) accessor name = "—"; + @n$1({ type: String }) accessor value = "—"; + @n$1({ type: String }) accessor unit = ""; + @n$1({ + type: Object, + attribute: false + }) accessor stateObj = null; + @n$1({ + type: Object, + attribute: false + }) accessor hass = null; + _onHeaderClick(event) { + event.preventDefault(); + event.stopPropagation(); + this.dispatchEvent(new CustomEvent("dp-sensor-header-click", { + bubbles: true, + composed: true + })); + } + render() { + return b`
${this.name}
@@ -36577,23 +32619,13 @@ ${content.alert}` : "", ${this.unit}
`; - } - } - _init$4 = __decoratorStart$4(_a$4); - _name = /* @__PURE__ */ new WeakMap(); - _value$2 = /* @__PURE__ */ new WeakMap(); - _unit = /* @__PURE__ */ new WeakMap(); - _stateObj = /* @__PURE__ */ new WeakMap(); - _hass$1 = /* @__PURE__ */ new WeakMap(); - __decorateElement$4(_init$4, 4, "name", _name_dec, SensorHeader, _name); - __decorateElement$4(_init$4, 4, "value", _value_dec$2, SensorHeader, _value$2); - __decorateElement$4(_init$4, 4, "unit", _unit_dec, SensorHeader, _unit); - __decorateElement$4(_init$4, 4, "stateObj", _stateObj_dec, SensorHeader, _stateObj); - __decorateElement$4(_init$4, 4, "hass", _hass_dec$1, SensorHeader, _hass$1); - __decoratorMetadata$4(_init$4, SensorHeader); - __publicField$7(SensorHeader, "styles", styles$6); - customElements.define("sensor-header", SensorHeader); - const styles$5 = i$5` + } + }; + _defineProperty(SensorHeader, "styles", styles$6); + customElements.define("sensor-header", SensorHeader); + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.styles.ts + var styles$5 = i$5` :host { display: block; height: 100%; @@ -36746,338 +32778,290 @@ ${content.alert}` : "", } } `; - var __defProp$6 = Object.defineProperty; - var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$6 = (obj, key, value) => __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value); - class SensorChart extends i$2 { - constructor() { - super(); - __publicField$6(this, "_hass", null); - __publicField$6(this, "_canvasClickHandler", null); - __publicField$6(this, "_canvasMoveHandler", null); - __publicField$6(this, "_canvasLeaveHandler", null); - __publicField$6(this, "_previousSeriesEndpoints", /* @__PURE__ */ new Map()); - __publicField$6(this, "_lastDrawArgs", null); - __publicField$6(this, "_resizeObserver", null); - this._chartReady = false; - this._loadMessage = "Loading…"; - this.showAnnotationTooltips = false; - } - get hass() { - return this._hass; - } - set hass(value) { - this._hass = value; - } - firstUpdated() { - this._setupResizeObserver(); - } - connectedCallback() { - super.connectedCallback(); - this._setupResizeObserver(); - } - disconnectedCallback() { - super.disconnectedCallback(); - if (this._resizeObserver) { - this._resizeObserver.disconnect(); - this._resizeObserver = null; - } - const canvas = this.shadowRoot?.querySelector("canvas#chart"); - if (canvas && this._canvasClickHandler) - canvas.removeEventListener("click", this._canvasClickHandler); - if (canvas && this._canvasMoveHandler) - canvas.removeEventListener("mousemove", this._canvasMoveHandler); - if (canvas && this._canvasLeaveHandler) - canvas.removeEventListener("mouseleave", this._canvasLeaveHandler); - } - _findHitAtPointer(hits, clientX, clientY, canvas) { - const rect = canvas.getBoundingClientRect(); - const x2 = clientX - rect.left; - const y2 = clientY - rect.top; - return hits.reduce( - (closest, hit) => { - const dist = Math.hypot(hit.x - x2, hit.y - y2); - if (dist > 18) { - return closest; - } - if (!closest || dist < closest.dist) { - return { hit, dist }; - } - return closest; - }, - null - )?.hit ?? null; - } - _showAnnotationTooltip(hit, clientX, clientY, unit) { - const wrap = this.shadowRoot?.querySelector(".chart-wrap"); - const tooltip = this.shadowRoot?.querySelector("#tooltip"); - const timeEl = this.shadowRoot?.querySelector("#tt-time"); - const valueEl = this.shadowRoot?.querySelector("#tt-value"); - const dotEl = this.shadowRoot?.querySelector("#tt-dot"); - const messageEl = this.shadowRoot?.querySelector("#tt-message"); - const rowEl = this.shadowRoot?.querySelector("#tt-message-row"); - const annotationEl = this.shadowRoot?.querySelector("#tt-annotation"); - const entitiesEl = this.shadowRoot?.querySelector("#tt-entities"); - if (!tooltip || !wrap || !timeEl || !valueEl || !dotEl || !messageEl || !rowEl || !annotationEl || !entitiesEl) { - return; - } - const eventTime = new Date(hit.event.timestamp); - timeEl.textContent = eventTime.toLocaleString(); - valueEl.style.display = "block"; - valueEl.textContent = Number.isFinite(hit.value) ? `${hit.value.toFixed(2)}${unit ? ` ${unit}` : ""}` : ""; - dotEl.style.background = hit.event.color || "#03a9f4"; - messageEl.textContent = hit.event.message || ""; - rowEl.style.display = "flex"; - if (hit.event.annotation) { - annotationEl.style.display = "block"; - annotationEl.textContent = hit.event.annotation; - } else { - annotationEl.style.display = "none"; - annotationEl.textContent = ""; - } - entitiesEl.style.display = "none"; - entitiesEl.textContent = ""; - tooltip.style.display = "block"; - const wrapRect = wrap.getBoundingClientRect(); - const tooltipRect = tooltip.getBoundingClientRect(); - const offset = 12; - const maxLeft = Math.max(8, wrapRect.width - tooltipRect.width - 8); - const maxTop = Math.max(8, wrapRect.height - tooltipRect.height - 8); - const localLeft = Math.min( - maxLeft, - Math.max(8, clientX - wrapRect.left + offset) - ); - const localTop = Math.min( - maxTop, - Math.max(8, clientY - wrapRect.top + offset) - ); - tooltip.style.left = `${localLeft}px`; - tooltip.style.top = `${localTop}px`; - } - _hideAnnotationTooltip() { - const tooltip = this.shadowRoot?.querySelector("#tooltip"); - if (tooltip) { - tooltip.style.display = "none"; - } - } - _attachTooltipHitTarget(overlay, hit, unit) { - const el = document.createElement("div"); - el.className = "ann-hit"; - el.style.left = `${hit.x}px`; - el.style.top = `${hit.y}px`; - el.dataset.eventId = hit.event.id; - this._attachTooltipInteractions(el, hit, unit); - overlay.appendChild(el); - } - _attachTooltipInteractions(el, hit, unit) { - const show = (clientX, clientY) => { - this._showAnnotationTooltip(hit, clientX, clientY, unit); - }; - el.addEventListener("mouseenter", (e2) => { - show(e2.clientX, e2.clientY); - }); - el.addEventListener("mousemove", (e2) => { - show(e2.clientX, e2.clientY); - }); - el.addEventListener("click", (e2) => { - e2.preventDefault(); - e2.stopPropagation(); - show(e2.clientX, e2.clientY); - }); - el.addEventListener("mouseleave", () => { - this._hideAnnotationTooltip(); - }); - } - _setupResizeObserver() { - if (this._resizeObserver) return; - if (!window.ResizeObserver) return; - this._resizeObserver = new ResizeObserver(() => { - if (this._lastDrawArgs) this.draw(...this._lastDrawArgs); - }); - this._resizeObserver.observe(this); - } - /** Draw the chart. Called by the parent card after data loads or on resize/toggle. */ - draw(histResult, events, t0, t1, config, unit, hiddenEventIds) { - this._lastDrawArgs = [ - histResult, - events, - t0, - t1, - config, - unit, - hiddenEventIds - ]; - const canvas = this.shadowRoot?.querySelector("canvas#chart"); - const wrap = this.shadowRoot?.querySelector(".chart-wrap"); - if (!canvas || !wrap) return; - const { w, h: h2 } = setupCanvas(canvas, wrap, null); - const renderer = new ChartRenderer(canvas, w, h2); - const topPadPx = Math.max(6, Math.round(h2 * 0.05)); - renderer.pad = { top: topPadPx, right: 0, bottom: 0, left: 0 }; - renderer.clear(); - const entityId = config.entity; - const lineColor = config.graph_color || COLORS[0]; - const stateList = this._getHistoryStatesForEntity(entityId, histResult); - const pts = []; - const allVals = []; - for (const s2 of stateList) { - const v2 = parseFloat(s2.s); - if (!Number.isNaN(v2)) { - pts.push([Math.round(s2.lu * 1e3), v2]); - allVals.push(v2); - } - } - if (!allVals.length) { - this._loadMessage = "No numeric data in the selected time range."; - this._chartReady = false; - return; - } - this._loadMessage = ""; - this._chartReady = true; - const annotationStyle = config.annotation_style ?? "circle"; - const visibleEvents = events.filter((ev) => !hiddenEventIds.has(ev.id)); - const series = [{ entityId, pts, color: lineColor }]; - const vMin = Math.min(...allVals); - const vMax = Math.max(...allVals); - const range = vMax - vMin; - const chartMin = vMin - (range * 0.03 || 0.2); - const chartMax = vMax + (range * 0.54 || 0.8); - for (const s2 of series) { - renderer.drawLine(s2.pts, s2.color, t0, t1, chartMin, chartMax, { - fillAlpha: 0.18 - }); - if (s2.pts.length) { - const lastPt = s2.pts[s2.pts.length - 1]; - const prev = this._previousSeriesEndpoints.get(s2.entityId); - if (prev && (lastPt[0] !== prev.t || lastPt[1] !== prev.v)) { - const cx = renderer.xOf(lastPt[0], t0, t1); - const cy = renderer.yOf(lastPt[1], chartMin, chartMax); - renderer.drawBlip(cx, cy, s2.color); - } - this._previousSeriesEndpoints.set(s2.entityId, { - t: lastPt[0], - v: lastPt[1] - }); - } - } - const hits = annotationStyle === "line" ? renderer.drawAnnotationLinesOnLine( - visibleEvents, - series, - t0, - t1, - chartMin, - chartMax - ) : renderer.drawAnnotationsOnLine( - visibleEvents, - series, - t0, - t1, - chartMin, - chartMax - ); - const overlay = this.shadowRoot?.querySelector(".icon-overlay"); - if (overlay) { - overlay.innerHTML = ""; - if (annotationStyle === "circle") { - for (const hit of hits) { - const bgColor = hit.event.color || "#03a9f4"; - const el = document.createElement("div"); - el.className = "ann-icon"; - el.style.left = `${hit.x}px`; - el.style.top = `${hit.y}px`; - el.style.background = bgColor; - el.innerHTML = ``; - el.dataset.eventId = hit.event.id; - el.addEventListener("click", (e2) => { - if (this.showAnnotationTooltips) { - e2.preventDefault(); - e2.stopPropagation(); - this._showAnnotationTooltip(hit, e2.clientX, e2.clientY, unit); - return; - } - e2.preventDefault(); - e2.stopPropagation(); - this._emitAnnotationClick(hit.event); - }); - if (this.showAnnotationTooltips) { - this._attachTooltipInteractions(el, hit, unit); - } - overlay.appendChild(el); - if (this.showAnnotationTooltips) { - this._attachTooltipHitTarget(overlay, hit, unit); - } - } - } else if (this.showAnnotationTooltips) { - for (const hit of hits) { - this._attachTooltipHitTarget(overlay, hit, unit); - } - } - } - if (this._canvasClickHandler) - canvas.removeEventListener("click", this._canvasClickHandler); - if (this._canvasMoveHandler) - canvas.removeEventListener("mousemove", this._canvasMoveHandler); - if (this._canvasLeaveHandler) - canvas.removeEventListener("mouseleave", this._canvasLeaveHandler); - this._canvasMoveHandler = () => { - this._hideAnnotationTooltip(); - }; - this._canvasLeaveHandler = () => { - this._hideAnnotationTooltip(); - }; - this._canvasClickHandler = (e2) => { - const best = this._findHitAtPointer(hits, e2.clientX, e2.clientY, canvas); - if (best) { - if (this.showAnnotationTooltips) { - e2.preventDefault(); - e2.stopPropagation(); - this._showAnnotationTooltip(best, e2.clientX, e2.clientY, unit); - return; - } - e2.preventDefault(); - e2.stopPropagation(); - this._emitAnnotationClick(best.event); - } - }; - canvas.addEventListener("mousemove", this._canvasMoveHandler); - canvas.addEventListener("mouseleave", this._canvasLeaveHandler); - canvas.addEventListener("click", this._canvasClickHandler); - } - _emitAnnotationClick(event) { - this.dispatchEvent( - new CustomEvent("dp-sensor-annotation-click", { - detail: { event }, - bubbles: true, - composed: true - }) - ); - } - _getHistoryStatesForEntity(entityId, histResult) { - if (!histResult) return []; - const r2 = histResult; - if (Array.isArray(r2[entityId])) return r2[entityId]; - if (Array.isArray(r2)) { - const rArr = r2; - if (Array.isArray(rArr[0])) return rArr[0] || []; - if (rArr.every( - (e2) => e2 && typeof e2 === "object" && !Array.isArray(e2) - )) - return rArr.filter( - (e2) => e2.entity_id === entityId - ); - } - const rObj = histResult; - if (rObj && typeof rObj === "object") { - const result = rObj.result; - if (Array.isArray(result?.[entityId])) - return result[entityId]; - if (Array.isArray(result?.[0])) - return result[0] || []; - } - return []; - } - render() { - return b` + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.ts + var SensorChart = class extends i$2 { + get hass() { + return this._hass; + } + set hass(value) { + this._hass = value; + } + constructor() { + super(); + _defineProperty(this, "_hass", null); + _defineProperty(this, "_canvasClickHandler", null); + _defineProperty(this, "_canvasMoveHandler", null); + _defineProperty(this, "_canvasLeaveHandler", null); + _defineProperty(this, "_previousSeriesEndpoints", /* @__PURE__ */ new Map()); + _defineProperty(this, "_lastDrawArgs", null); + _defineProperty(this, "_resizeObserver", null); + this._chartReady = false; + this._loadMessage = "Loading…"; + this.showAnnotationTooltips = false; + } + firstUpdated() { + this._setupResizeObserver(); + } + connectedCallback() { + super.connectedCallback(); + this._setupResizeObserver(); + } + disconnectedCallback() { + super.disconnectedCallback(); + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + this._resizeObserver = null; + } + const canvas = this.shadowRoot?.querySelector("canvas#chart"); + if (canvas && this._canvasClickHandler) canvas.removeEventListener("click", this._canvasClickHandler); + if (canvas && this._canvasMoveHandler) canvas.removeEventListener("mousemove", this._canvasMoveHandler); + if (canvas && this._canvasLeaveHandler) canvas.removeEventListener("mouseleave", this._canvasLeaveHandler); + } + _findHitAtPointer(hits, clientX, clientY, canvas) { + const rect = canvas.getBoundingClientRect(); + const x = clientX - rect.left; + const y = clientY - rect.top; + return hits.reduce((closest, hit) => { + const dist = Math.hypot(hit.x - x, hit.y - y); + if (dist > 18) return closest; + if (!closest || dist < closest.dist) return { + hit, + dist + }; + return closest; + }, null)?.hit ?? null; + } + _showAnnotationTooltip(hit, clientX, clientY, unit) { + const wrap = this.shadowRoot?.querySelector(".chart-wrap"); + const tooltip = this.shadowRoot?.querySelector("#tooltip"); + const timeEl = this.shadowRoot?.querySelector("#tt-time"); + const valueEl = this.shadowRoot?.querySelector("#tt-value"); + const dotEl = this.shadowRoot?.querySelector("#tt-dot"); + const messageEl = this.shadowRoot?.querySelector("#tt-message"); + const rowEl = this.shadowRoot?.querySelector("#tt-message-row"); + const annotationEl = this.shadowRoot?.querySelector("#tt-annotation"); + const entitiesEl = this.shadowRoot?.querySelector("#tt-entities"); + if (!tooltip || !wrap || !timeEl || !valueEl || !dotEl || !messageEl || !rowEl || !annotationEl || !entitiesEl) return; + timeEl.textContent = new Date(hit.event.timestamp).toLocaleString(); + valueEl.style.display = "block"; + valueEl.textContent = Number.isFinite(hit.value) ? `${hit.value.toFixed(2)}${unit ? ` ${unit}` : ""}` : ""; + dotEl.style.background = hit.event.color || "#03a9f4"; + messageEl.textContent = hit.event.message || ""; + rowEl.style.display = "flex"; + if (hit.event.annotation) { + annotationEl.style.display = "block"; + annotationEl.textContent = hit.event.annotation; + } else { + annotationEl.style.display = "none"; + annotationEl.textContent = ""; + } + entitiesEl.style.display = "none"; + entitiesEl.textContent = ""; + tooltip.style.display = "block"; + const wrapRect = wrap.getBoundingClientRect(); + const tooltipRect = tooltip.getBoundingClientRect(); + const offset = 12; + const maxLeft = Math.max(8, wrapRect.width - tooltipRect.width - 8); + const maxTop = Math.max(8, wrapRect.height - tooltipRect.height - 8); + const localLeft = Math.min(maxLeft, Math.max(8, clientX - wrapRect.left + offset)); + const localTop = Math.min(maxTop, Math.max(8, clientY - wrapRect.top + offset)); + tooltip.style.left = `${localLeft}px`; + tooltip.style.top = `${localTop}px`; + } + _hideAnnotationTooltip() { + const tooltip = this.shadowRoot?.querySelector("#tooltip"); + if (tooltip) tooltip.style.display = "none"; + } + _attachTooltipHitTarget(overlay, hit, unit) { + const el = document.createElement("div"); + el.className = "ann-hit"; + el.style.left = `${hit.x}px`; + el.style.top = `${hit.y}px`; + el.dataset.eventId = hit.event.id; + this._attachTooltipInteractions(el, hit, unit); + overlay.appendChild(el); + } + _attachTooltipInteractions(el, hit, unit) { + const show = (clientX, clientY) => { + this._showAnnotationTooltip(hit, clientX, clientY, unit); + }; + el.addEventListener("mouseenter", (e) => { + show(e.clientX, e.clientY); + }); + el.addEventListener("mousemove", (e) => { + show(e.clientX, e.clientY); + }); + el.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + show(e.clientX, e.clientY); + }); + el.addEventListener("mouseleave", () => { + this._hideAnnotationTooltip(); + }); + } + _setupResizeObserver() { + if (this._resizeObserver) return; + if (!window.ResizeObserver) return; + this._resizeObserver = new ResizeObserver(() => { + if (this._lastDrawArgs) this.draw(...this._lastDrawArgs); + }); + this._resizeObserver.observe(this); + } + /** Draw the chart. Called by the parent card after data loads or on resize/toggle. */ + draw(histResult, events, t0, t1, config, unit, hiddenEventIds) { + this._lastDrawArgs = [ + histResult, + events, + t0, + t1, + config, + unit, + hiddenEventIds + ]; + const canvas = this.shadowRoot?.querySelector("canvas#chart"); + const wrap = this.shadowRoot?.querySelector(".chart-wrap"); + if (!canvas || !wrap) return; + const { w, h } = setupCanvas(canvas, wrap, null); + const renderer = new ChartRenderer(canvas, w, h); + renderer.pad = { + top: Math.max(6, Math.round(h * .05)), + right: 0, + bottom: 0, + left: 0 + }; + renderer.clear(); + const entityId = config.entity; + const lineColor = config.graph_color || COLORS[0]; + const stateList = this._getHistoryStatesForEntity(entityId, histResult); + const pts = []; + const allVals = []; + for (const s of stateList) { + const v = parseFloat(s.s); + if (!Number.isNaN(v)) { + pts.push([Math.round(s.lu * 1e3), v]); + allVals.push(v); + } + } + if (!allVals.length) { + this._loadMessage = "No numeric data in the selected time range."; + this._chartReady = false; + return; + } + this._loadMessage = ""; + this._chartReady = true; + const annotationStyle = config.annotation_style ?? "circle"; + const visibleEvents = events.filter((ev) => !hiddenEventIds.has(ev.id)); + const series = [{ + entityId, + pts, + color: lineColor + }]; + const vMin = Math.min(...allVals); + const vMax = Math.max(...allVals); + const range = vMax - vMin; + const chartMin = vMin - (range * .03 || .2); + const chartMax = vMax + (range * .54 || .8); + for (const s of series) { + renderer.drawLine(s.pts, s.color, t0, t1, chartMin, chartMax, { fillAlpha: .18 }); + if (s.pts.length) { + const lastPt = s.pts[s.pts.length - 1]; + const prev = this._previousSeriesEndpoints.get(s.entityId); + if (prev && (lastPt[0] !== prev.t || lastPt[1] !== prev.v)) { + const cx = renderer.xOf(lastPt[0], t0, t1); + const cy = renderer.yOf(lastPt[1], chartMin, chartMax); + renderer.drawBlip(cx, cy, s.color); + } + this._previousSeriesEndpoints.set(s.entityId, { + t: lastPt[0], + v: lastPt[1] + }); + } + } + const hits = annotationStyle === "line" ? renderer.drawAnnotationLinesOnLine(visibleEvents, series, t0, t1, chartMin, chartMax) : renderer.drawAnnotationsOnLine(visibleEvents, series, t0, t1, chartMin, chartMax); + const overlay = this.shadowRoot?.querySelector(".icon-overlay"); + if (overlay) { + overlay.innerHTML = ""; + if (annotationStyle === "circle") for (const hit of hits) { + const bgColor = hit.event.color || "#03a9f4"; + const el = document.createElement("div"); + el.className = "ann-icon"; + el.style.left = `${hit.x}px`; + el.style.top = `${hit.y}px`; + el.style.background = bgColor; + el.innerHTML = ``; + el.dataset.eventId = hit.event.id; + el.addEventListener("click", (e) => { + if (this.showAnnotationTooltips) { + e.preventDefault(); + e.stopPropagation(); + this._showAnnotationTooltip(hit, e.clientX, e.clientY, unit); + return; + } + e.preventDefault(); + e.stopPropagation(); + this._emitAnnotationClick(hit.event); + }); + if (this.showAnnotationTooltips) this._attachTooltipInteractions(el, hit, unit); + overlay.appendChild(el); + if (this.showAnnotationTooltips) this._attachTooltipHitTarget(overlay, hit, unit); + } + else if (this.showAnnotationTooltips) for (const hit of hits) this._attachTooltipHitTarget(overlay, hit, unit); + } + if (this._canvasClickHandler) canvas.removeEventListener("click", this._canvasClickHandler); + if (this._canvasMoveHandler) canvas.removeEventListener("mousemove", this._canvasMoveHandler); + if (this._canvasLeaveHandler) canvas.removeEventListener("mouseleave", this._canvasLeaveHandler); + this._canvasMoveHandler = () => { + this._hideAnnotationTooltip(); + }; + this._canvasLeaveHandler = () => { + this._hideAnnotationTooltip(); + }; + this._canvasClickHandler = (e) => { + const best = this._findHitAtPointer(hits, e.clientX, e.clientY, canvas); + if (best) { + if (this.showAnnotationTooltips) { + e.preventDefault(); + e.stopPropagation(); + this._showAnnotationTooltip(best, e.clientX, e.clientY, unit); + return; + } + e.preventDefault(); + e.stopPropagation(); + this._emitAnnotationClick(best.event); + } + }; + canvas.addEventListener("mousemove", this._canvasMoveHandler); + canvas.addEventListener("mouseleave", this._canvasLeaveHandler); + canvas.addEventListener("click", this._canvasClickHandler); + } + _emitAnnotationClick(event) { + this.dispatchEvent(new CustomEvent("dp-sensor-annotation-click", { + detail: { event }, + bubbles: true, + composed: true + })); + } + _getHistoryStatesForEntity(entityId, histResult) { + if (!histResult) return []; + const r = histResult; + if (Array.isArray(r[entityId])) return r[entityId]; + if (Array.isArray(r)) { + const rArr = r; + if (Array.isArray(rArr[0])) return rArr[0] || []; + if (rArr.every((e) => e && typeof e === "object" && !Array.isArray(e))) return rArr.filter((e) => e.entity_id === entityId); + } + const rObj = histResult; + if (rObj && typeof rObj === "object") { + const result = rObj.result; + if (Array.isArray(result?.[entityId])) return result[entityId]; + if (Array.isArray(result?.[0])) return result[0] || []; + } + return []; + } + render() { + return b`
${!this._chartReady ? b` @@ -37111,19 +33095,21 @@ ${content.alert}` : "",
`; - } - } - __publicField$6(SensorChart, "properties", { - _chartReady: { state: true }, - _loadMessage: { state: true }, - showAnnotationTooltips: { - type: Boolean, - attribute: "show-annotation-tooltips" - } - }); - __publicField$6(SensorChart, "styles", styles$5); - customElements.define("sensor-chart", SensorChart); - const styles$4 = i$5` + } + }; + _defineProperty(SensorChart, "properties", { + _chartReady: { state: true }, + _loadMessage: { state: true }, + showAnnotationTooltips: { + type: Boolean, + attribute: "show-annotation-tooltips" + } + }); + _defineProperty(SensorChart, "styles", styles$5); + customElements.define("sensor-chart", SensorChart); + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor-records/sensor-records.styles.ts + var styles$4 = i$5` :host { display: block; flex: 1 1 0; @@ -37161,7 +33147,9 @@ ${content.alert}` : "", font-size: 0.85em; } `; - const styles$3 = i$5` + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor-record-item/sensor-record-item.styles.ts + var styles$3 = i$5` :host { display: block; } @@ -37318,99 +33306,54 @@ ${content.alert}` : "", font-family: inherit; } `; - var __create$3 = Object.create; - var __defProp$5 = Object.defineProperty; - var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor; - var __knownSymbol$3 = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$3 = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$3 = (base) => [, , , __create$3(base?.[__knownSymbol$3("metadata")] ?? null)]; - var __decoratorStrings$3 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$3 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$3("Function expected") : fn; - var __decoratorContext$3 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$3[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$3("Already initialized") : fns.push(__expectFn$3(fn || null)) }); - var __decoratorMetadata$3 = (array, target) => __defNormalProp$5(target, __knownSymbol$3("metadata"), array[3]); - var __runInitializers$3 = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$3 = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$3[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$3({ get [name]() { - return __privateGet$3(this, extra); - }, set [name](x2) { - return __privateSet$3(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$3(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$3(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$3("Object expected"); - else __expectFn$3(fn = it.get) && (desc.get = fn), __expectFn$3(fn = it.set) && (desc.set = fn), __expectFn$3(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$5(target, name, desc), target; - }; - var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, key + "", value); - var __accessCheck$3 = (obj, member, msg2) => member.has(obj) || __typeError$3("Cannot " + msg2); - var __privateGet$3 = (obj, member, getter) => (__accessCheck$3(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$3 = (obj, member, value) => member.has(obj) ? __typeError$3("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$3 = (obj, member, value, setter) => (__accessCheck$3(obj, member, "write to private field"), member.set(obj, value), value); - var __noteExpanded_dec, _showFullMessage_dec$1, _hidden_dec, _event_dec, _a$3, _init$3, _event, _hidden, _showFullMessage$1, __noteExpanded; - class SensorRecordItem extends (_a$3 = i$2, _event_dec = [n({ type: Object, attribute: false })], _hidden_dec = [n({ type: Boolean })], _showFullMessage_dec$1 = [n({ type: Boolean, attribute: "show-full-message" })], __noteExpanded_dec = [r()], _a$3) { - constructor() { - super(...arguments); - __privateAdd$3(this, _event, __runInitializers$3(_init$3, 8, this, null)), __runInitializers$3(_init$3, 11, this); - __privateAdd$3(this, _hidden, __runInitializers$3(_init$3, 12, this, false)), __runInitializers$3(_init$3, 15, this); - __privateAdd$3(this, _showFullMessage$1, __runInitializers$3(_init$3, 16, this, true)), __runInitializers$3(_init$3, 19, this); - __privateAdd$3(this, __noteExpanded, __runInitializers$3(_init$3, 20, this, false)), __runInitializers$3(_init$3, 23, this); - } - _onToggleVisibility(e2) { - e2.preventDefault(); - e2.stopPropagation(); - this.dispatchEvent( - new CustomEvent("dp-sensor-record-toggle-visibility", { - detail: { id: this.event?.id }, - bubbles: true, - composed: true - }) - ); - } - _onNavigate(e2) { - e2.preventDefault(); - e2.stopPropagation(); - this.dispatchEvent( - new CustomEvent("dp-sensor-record-navigate", { - detail: { event: this.event }, - bubbles: true, - composed: true - }) - ); - } - render() { - const ev = this.event; - if (!ev) return b``; - const color = ev.color || "#03a9f4"; - const icon = ev.icon || "mdi:bookmark"; - const annText = ev.annotation && ev.annotation !== ev.message ? ev.annotation : ""; - const isSimple = !annText; - const visibilityIcon = this.hidden ? "mdi:eye" : "mdi:eye-off"; - const visibilityLabel = this.hidden ? "Show chart marker" : "Hide chart marker"; - const showNoteInline = this.showFullMessage; - const noteHidden = !showNoteInline && !this._noteExpanded; - return b` + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor-record-item/sensor-record-item.ts + var SensorRecordItem = class extends i$2 { + @n$1({ + type: Object, + attribute: false + }) accessor event = null; + @n$1({ type: Boolean }) accessor hidden = false; + @n$1({ + type: Boolean, + attribute: "show-full-message" + }) accessor showFullMessage = true; + @r$1() accessor _noteExpanded = false; + _onToggleVisibility(e) { + e.preventDefault(); + e.stopPropagation(); + this.dispatchEvent(new CustomEvent("dp-sensor-record-toggle-visibility", { + detail: { id: this.event?.id }, + bubbles: true, + composed: true + })); + } + _onNavigate(e) { + e.preventDefault(); + e.stopPropagation(); + this.dispatchEvent(new CustomEvent("dp-sensor-record-navigate", { + detail: { event: this.event }, + bubbles: true, + composed: true + })); + } + render() { + const ev = this.event; + if (!ev) return b``; + const color = ev.color || "#03a9f4"; + const icon = ev.icon || "mdi:bookmark"; + const annText = ev.annotation && ev.annotation !== ev.message ? ev.annotation : ""; + const isSimple = !annText; + const visibilityIcon = this.hidden ? "mdi:eye" : "mdi:eye-off"; + const visibilityLabel = this.hidden ? "Show chart marker" : "Hide chart marker"; + const showNoteInline = this.showFullMessage; + const noteHidden = !showNoteInline && !this._noteExpanded; + return b`
{ - this._noteExpanded = !this._noteExpanded; - } : void 0} + this._noteExpanded = !this._noteExpanded; + } : void 0} >
`; - } - } - _init$3 = __decoratorStart$3(_a$3); - _event = /* @__PURE__ */ new WeakMap(); - _hidden = /* @__PURE__ */ new WeakMap(); - _showFullMessage$1 = /* @__PURE__ */ new WeakMap(); - __noteExpanded = /* @__PURE__ */ new WeakMap(); - __decorateElement$3(_init$3, 4, "event", _event_dec, SensorRecordItem, _event); - __decorateElement$3(_init$3, 4, "hidden", _hidden_dec, SensorRecordItem, _hidden); - __decorateElement$3(_init$3, 4, "showFullMessage", _showFullMessage_dec$1, SensorRecordItem, _showFullMessage$1); - __decorateElement$3(_init$3, 4, "_noteExpanded", __noteExpanded_dec, SensorRecordItem, __noteExpanded); - __decoratorMetadata$3(_init$3, SensorRecordItem); - __publicField$5(SensorRecordItem, "styles", styles$3); - customElements.define("sensor-record-item", SensorRecordItem); - var __create$2 = Object.create; - var __defProp$4 = Object.defineProperty; - var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor; - var __knownSymbol$2 = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$2 = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$2 = (base) => [, , , __create$2(base?.[__knownSymbol$2("metadata")] ?? null)]; - var __decoratorStrings$2 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$2 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$2("Function expected") : fn; - var __decoratorContext$2 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$2[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$2("Already initialized") : fns.push(__expectFn$2(fn || null)) }); - var __decoratorMetadata$2 = (array, target) => __defNormalProp$4(target, __knownSymbol$2("metadata"), array[3]); - var __runInitializers$2 = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$2 = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$2[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$2({ get [name]() { - return __privateGet$2(this, extra); - }, set [name](x2) { - return __privateSet$2(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$2(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$2(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$2("Object expected"); - else __expectFn$2(fn = it.get) && (desc.get = fn), __expectFn$2(fn = it.set) && (desc.set = fn), __expectFn$2(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$4(target, name, desc), target; - }; - var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value); - var __accessCheck$2 = (obj, member, msg2) => member.has(obj) || __typeError$2("Cannot " + msg2); - var __privateGet$2 = (obj, member, getter) => (__accessCheck$2(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$2 = (obj, member, value) => member.has(obj) ? __typeError$2("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$2 = (obj, member, value, setter) => (__accessCheck$2(obj, member, "write to private field"), member.set(obj, value), value); - var __page_dec, _showFullMessage_dec, _limit_dec, _pageSize_dec, _hiddenEventIds_dec, _events_dec, _a$2, _init$2, _events, _hiddenEventIds, _pageSize, _limit, _showFullMessage, __page; - class SensorRecords extends (_a$2 = i$2, _events_dec = [n({ type: Array, attribute: false })], _hiddenEventIds_dec = [n({ type: Object, attribute: false })], _pageSize_dec = [n({ type: Number, attribute: "page-size" })], _limit_dec = [n({ type: Number })], _showFullMessage_dec = [n({ type: Boolean, attribute: "show-full-message" })], __page_dec = [r()], _a$2) { - constructor() { - super(...arguments); - __privateAdd$2(this, _events, __runInitializers$2(_init$2, 8, this, [])), __runInitializers$2(_init$2, 11, this); - __privateAdd$2(this, _hiddenEventIds, __runInitializers$2(_init$2, 12, this, /* @__PURE__ */ new Set())), __runInitializers$2(_init$2, 15, this); - __privateAdd$2(this, _pageSize, __runInitializers$2(_init$2, 16, this, null)), __runInitializers$2(_init$2, 19, this); - __privateAdd$2(this, _limit, __runInitializers$2(_init$2, 20, this, null)), __runInitializers$2(_init$2, 23, this); - __privateAdd$2(this, _showFullMessage, __runInitializers$2(_init$2, 24, this, true)), __runInitializers$2(_init$2, 27, this); - __privateAdd$2(this, __page, __runInitializers$2(_init$2, 28, this, 0)), __runInitializers$2(_init$2, 31, this); - __publicField$4(this, "_paginationNotifyRaf", null); - } - updated(changedProps) { - if (changedProps.has("events") || changedProps.has("pageSize") || changedProps.has("limit") || changedProps.has("_page")) { - const sorted = [...this.events].sort( - (a2, b2) => new Date(b2.timestamp).getTime() - new Date(a2.timestamp).getTime() - ); - const limited = this.limit ? sorted.slice(0, this.limit) : sorted; - const total = limited.length; - const totalPages = this.pageSize ? Math.max(1, Math.ceil(total / this.pageSize)) : 1; - if (this._paginationNotifyRaf !== null) { - window.cancelAnimationFrame(this._paginationNotifyRaf); - } - this._paginationNotifyRaf = window.requestAnimationFrame(() => { - this._paginationNotifyRaf = null; - const paginationEl = this.shadowRoot?.querySelector("pagination-nav"); - const paginationHeight = paginationEl?.getBoundingClientRect().height ?? 0; - this.dispatchEvent( - new CustomEvent("dp-sensor-pagination-visibility-change", { - detail: { - visible: totalPages > 1, - height: totalPages > 1 ? paginationHeight : 0 - }, - bubbles: true, - composed: true - }) - ); - }); - } - } - disconnectedCallback() { - super.disconnectedCallback(); - if (this._paginationNotifyRaf !== null) { - window.cancelAnimationFrame(this._paginationNotifyRaf); - this._paginationNotifyRaf = null; - } - } - render() { - const sorted = [...this.events].sort( - (a2, b2) => new Date(b2.timestamp).getTime() - new Date(a2.timestamp).getTime() - ); - const limited = this.limit ? sorted.slice(0, this.limit) : sorted; - const total = limited.length; - if (!total) { - return b` + } + }; + _defineProperty(SensorRecordItem, "styles", styles$3); + customElements.define("sensor-record-item", SensorRecordItem); + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor-records/sensor-records.ts + var SensorRecords = class extends i$2 { + constructor(..._args) { + super(..._args); + _defineProperty(this, "_paginationNotifyRaf", null); + } + @n$1({ + type: Array, + attribute: false + }) accessor events = []; + @n$1({ + type: Object, + attribute: false + }) accessor hiddenEventIds = /* @__PURE__ */ new Set(); + @n$1({ + type: Number, + attribute: "page-size" + }) accessor pageSize = null; + @n$1({ type: Number }) accessor limit = null; + @n$1({ + type: Boolean, + attribute: "show-full-message" + }) accessor showFullMessage = true; + @r$1() accessor _page = 0; + updated(changedProps) { + if (changedProps.has("events") || changedProps.has("pageSize") || changedProps.has("limit") || changedProps.has("_page")) { + const sorted = [...this.events].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + const total = (this.limit ? sorted.slice(0, this.limit) : sorted).length; + const totalPages = this.pageSize ? Math.max(1, Math.ceil(total / this.pageSize)) : 1; + if (this._paginationNotifyRaf !== null) window.cancelAnimationFrame(this._paginationNotifyRaf); + this._paginationNotifyRaf = window.requestAnimationFrame(() => { + this._paginationNotifyRaf = null; + const paginationHeight = (this.shadowRoot?.querySelector("pagination-nav"))?.getBoundingClientRect().height ?? 0; + this.dispatchEvent(new CustomEvent("dp-sensor-pagination-visibility-change", { + detail: { + visible: totalPages > 1, + height: totalPages > 1 ? paginationHeight : 0 + }, + bubbles: true, + composed: true + })); + }); + } + } + disconnectedCallback() { + super.disconnectedCallback(); + if (this._paginationNotifyRaf !== null) { + window.cancelAnimationFrame(this._paginationNotifyRaf); + this._paginationNotifyRaf = null; + } + } + render() { + const sorted = [...this.events].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + const limited = this.limit ? sorted.slice(0, this.limit) : sorted; + const total = limited.length; + if (!total) return b`
No records in this time window.
`; - } - const totalPages = this.pageSize ? Math.max(1, Math.ceil(total / this.pageSize)) : 1; - const page = Math.min(this._page, totalPages - 1); - const slice = this.pageSize ? limited.slice(page * this.pageSize, (page + 1) * this.pageSize) : limited; - const showPagination = totalPages > 1; - return b` + const totalPages = this.pageSize ? Math.max(1, Math.ceil(total / this.pageSize)) : 1; + const page = Math.min(this._page, totalPages - 1); + const slice = this.pageSize ? limited.slice(page * this.pageSize, (page + 1) * this.pageSize) : limited; + const showPagination = totalPages > 1; + return b`
- ${slice.map( - (ev) => b` + ${slice.map((ev) => b` - ` - )} + `)}
${showPagination ? b` { - this._page = e2.detail.page; - this.shadowRoot?.querySelector(".ann-list")?.scrollTo(0, 0); - }} + @dp-page-change=${(e) => { + this._page = e.detail.page; + this.shadowRoot?.querySelector(".ann-list")?.scrollTo(0, 0); + }} > ` : ""}
`; - } - } - _init$2 = __decoratorStart$2(_a$2); - _events = /* @__PURE__ */ new WeakMap(); - _hiddenEventIds = /* @__PURE__ */ new WeakMap(); - _pageSize = /* @__PURE__ */ new WeakMap(); - _limit = /* @__PURE__ */ new WeakMap(); - _showFullMessage = /* @__PURE__ */ new WeakMap(); - __page = /* @__PURE__ */ new WeakMap(); - __decorateElement$2(_init$2, 4, "events", _events_dec, SensorRecords, _events); - __decorateElement$2(_init$2, 4, "hiddenEventIds", _hiddenEventIds_dec, SensorRecords, _hiddenEventIds); - __decorateElement$2(_init$2, 4, "pageSize", _pageSize_dec, SensorRecords, _pageSize); - __decorateElement$2(_init$2, 4, "limit", _limit_dec, SensorRecords, _limit); - __decorateElement$2(_init$2, 4, "showFullMessage", _showFullMessage_dec, SensorRecords, _showFullMessage); - __decorateElement$2(_init$2, 4, "_page", __page_dec, SensorRecords, __page); - __decoratorMetadata$2(_init$2, SensorRecords); - __publicField$4(SensorRecords, "styles", styles$4); - customElements.define("sensor-records", SensorRecords); - var __defProp$3 = Object.defineProperty; - var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value); - class HassRecordsSensorCard extends i$2 { - constructor() { - super(); - __publicField$3(this, "_initialized", false); - __publicField$3(this, "_lastHistResult", null); - __publicField$3(this, "_lastEvents", []); - __publicField$3(this, "_lastT0", null); - __publicField$3(this, "_lastT1", null); - __publicField$3(this, "_unsubscribe", null); - __publicField$3(this, "_resizeObserver", null); - this._config = {}; - this._hass = null; - this._annEvents = []; - this._hiddenEventIds = /* @__PURE__ */ new Set(); - this._recordsFooterHeight = 0; - } - setConfig(config) { - if (!config.entity) { - throw new Error("hass-datapoints-sensor-card: `entity` is required"); - } - this._config = { - hours_to_show: 24, - annotation_style: "circle", - show_records: false, - records_page_size: null, - records_limit: null, - ...config - }; - } - set hass(hass) { - this._hass = hass; - if (!this._initialized) { - this._initialized = true; - this._setupAutoRefresh(); - } - } - get hass() { - return this._hass; - } - firstUpdated() { - this._setupResizeObserver(); - if (this._hass) this._load(); - } - connectedCallback() { - super.connectedCallback(); - this._setupResizeObserver(); - if (this._initialized && this._hass) this._load(); - } - disconnectedCallback() { - super.disconnectedCallback(); - if (this._unsubscribe) { - this._unsubscribe(); - this._unsubscribe = null; - } - if (this._resizeObserver) { - this._resizeObserver.disconnect(); - this._resizeObserver = null; - } - } - _setupAutoRefresh() { - if (!this.hass) return; - this.hass.connection.subscribeEvents(() => this._load(), `${DOMAIN}_event_recorded`).then((unsub) => { - this._unsubscribe = unsub; - }).catch(() => { - }); - } - _setupResizeObserver() { - if (this._resizeObserver) return; - if (!window.ResizeObserver) return; - this._resizeObserver = new ResizeObserver(() => { - this._applyLayoutSizing(); - }); - this._resizeObserver.observe(this); - } - _applyLayoutSizing() { - const shell = this.shadowRoot?.querySelector(".card-shell"); - if (!shell) return; - const gridRows = this._gridRows(); - if (!this._config?.show_records) { - shell.style.setProperty("--hr-body-rows", String(gridRows)); - shell.style.setProperty("--hr-footer-height", "0px"); - return; - } - const totalRows = Math.max(3, gridRows); - const bodyRows = Math.max(2, this._bodyRows(totalRows)); - shell.style.setProperty("--hr-body-rows", String(bodyRows)); - shell.style.setProperty( - "--hr-footer-height", - `${Math.max(0, this._recordsFooterHeight)}px` - ); - } - _gridRows() { - const raw = getComputedStyle(this).getPropertyValue("--row-size").trim(); - const rows = Number.parseInt(raw, 10); - if (Number.isFinite(rows) && rows > 0) return rows; - return this._config?.show_records ? 4 : 3; - } - _bodyRows(totalRows) { - if (!this._config?.show_records) return totalRows; - const bodyRows = Math.min( - totalRows - 1, - 3 + Math.floor(Math.max(0, totalRows - 4) / 4) - ); - return bodyRows; - } - _toggleEventVisibility(eventId) { - const next = new Set(this._hiddenEventIds); - if (next.has(eventId)) next.delete(eventId); - else next.add(eventId); - this._hiddenEventIds = next; - if (this._lastHistResult !== null) { - this._drawChart( - this._lastHistResult, - this._lastEvents, - this._lastT0, - this._lastT1 - ); - } - } - _navigateToEventHistory(ev) { - navigateToDataPointsHistory( - this, - { - entity_id: [ - this._config?.entity, - ...ev?.entity_ids || [] - ].filter(Boolean), - device_id: ev?.device_ids || [], - area_id: ev?.area_ids || [], - label_id: ev?.label_ids || [] - }, - { - start_time: Number.isFinite(this._lastT0) ? new Date(this._lastT0).toISOString() : null, - end_time: Number.isFinite(this._lastT1) ? new Date(this._lastT1).toISOString() : null - } - ); - } - _openTargetHistoryDialog() { - const entityId = this._config?.entity; - if (!entityId) { - return; - } - this.dispatchEvent( - new CustomEvent("hass-more-info", { - bubbles: true, - composed: true, - detail: { entityId } - }) - ); - } - _onCardClick(event) { - const path = event.composedPath(); - const interactiveTagNames = /* @__PURE__ */ new Set([ - "BUTTON", - "A", - "INPUT", - "TEXTAREA", - "SELECT", - "PAGINATION-NAV" - ]); - const hitInteractive = path.some((node) => { - if (!(node instanceof HTMLElement)) { - return false; - } - if (interactiveTagNames.has(node.tagName)) { - return true; - } - return !!node.closest("button,a,input,textarea,select,pagination-nav"); - }); - if (hitInteractive) { - return; - } - this._openTargetHistoryDialog(); - } - async _load() { - if (!this.hass) return; - const now = /* @__PURE__ */ new Date(); - const start = new Date( - now.getTime() - this._config.hours_to_show * 3600 * 1e3 - ); - const t0 = start.getTime(); - const t1 = now.getTime(); - const entityIds = [this._config.entity]; - try { - const [histResult, events] = await Promise.all([ - this.hass.connection.sendMessagePromise({ - type: "history/history_during_period", - start_time: start.toISOString(), - end_time: now.toISOString(), - entity_ids: entityIds, - include_start_time_state: true, - significant_changes_only: false, - no_attributes: true - }), - fetchEvents( - this.hass, - start.toISOString(), - now.toISOString(), - entityIds - ) - ]); - this._drawChart(histResult || {}, events || [], t0, t1); - } catch (err) { - logger$1.error("[hass-datapoints sensor-card]", err); - } - } - _drawChart(histResult, events, t0, t1) { - this._lastHistResult = histResult; - this._lastEvents = events; - this._lastT0 = t0; - this._lastT1 = t1; - this._annEvents = events; - const chartEl = this.shadowRoot?.querySelector("sensor-chart"); - if (!chartEl) return; - chartEl.hass = this.hass; - const entityId = this._config.entity; - const unit = this.hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; - chartEl.draw( - histResult, - events, - t0, - t1, - this._config, - unit, - this._hiddenEventIds - ); - } - _onAnnotationClick(e2) { - this._navigateToEventHistory(e2.detail.event); - } - _onHeaderClick(event) { - event.preventDefault(); - event.stopPropagation(); - this._openTargetHistoryDialog(); - } - render() { - const stateObj = this.hass?.states?.[this._config?.entity]; - const sensorName = this._config?.name || stateObj?.attributes?.friendly_name || this._config?.entity || "—"; - const sensorValue = stateObj?.state ?? "—"; - const sensorUnit = stateObj?.attributes?.unit_of_measurement || ""; - return b` + } + }; + _defineProperty(SensorRecords, "styles", styles$4); + customElements.define("sensor-records", SensorRecords); + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/sensor.ts + /** + * hass-datapoints-sensor-card – Sensor card with inline annotation icons. + * Canvas rendering is delegated to sensor-chart; annotation list to sensor-records. + */ + var HassRecordsSensorCard = class extends i$2 { + constructor() { + super(); + _defineProperty(this, "_initialized", false); + _defineProperty(this, "_lastHistResult", null); + _defineProperty(this, "_lastEvents", []); + _defineProperty(this, "_lastT0", null); + _defineProperty(this, "_lastT1", null); + _defineProperty(this, "_unsubscribe", null); + _defineProperty(this, "_resizeObserver", null); + this._config = {}; + this._hass = null; + this._annEvents = []; + this._hiddenEventIds = /* @__PURE__ */ new Set(); + this._recordsFooterHeight = 0; + } + setConfig(config) { + if (!config.entity) throw new Error("hass-datapoints-sensor-card: `entity` is required"); + this._config = { + hours_to_show: 24, + annotation_style: "circle", + show_records: false, + records_page_size: null, + records_limit: null, + ...config + }; + } + set hass(hass) { + this._hass = hass; + if (!this._initialized) { + this._initialized = true; + this._setupAutoRefresh(); + } + } + get hass() { + return this._hass; + } + firstUpdated() { + this._setupResizeObserver(); + if (this._hass) this._load(); + } + connectedCallback() { + super.connectedCallback(); + this._setupResizeObserver(); + if (this._initialized && this._hass) this._load(); + } + disconnectedCallback() { + super.disconnectedCallback(); + if (this._unsubscribe) { + this._unsubscribe(); + this._unsubscribe = null; + } + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + this._resizeObserver = null; + } + } + _setupAutoRefresh() { + if (!this.hass) return; + this.hass.connection.subscribeEvents(() => this._load(), `${DOMAIN}_event_recorded`).then((unsub) => { + this._unsubscribe = unsub; + }).catch(() => {}); + } + _setupResizeObserver() { + if (this._resizeObserver) return; + if (!window.ResizeObserver) return; + this._resizeObserver = new ResizeObserver(() => { + this._applyLayoutSizing(); + }); + this._resizeObserver.observe(this); + } + _applyLayoutSizing() { + const shell = this.shadowRoot?.querySelector(".card-shell"); + if (!shell) return; + const gridRows = this._gridRows(); + if (!this._config?.show_records) { + shell.style.setProperty("--hr-body-rows", String(gridRows)); + shell.style.setProperty("--hr-footer-height", "0px"); + return; + } + const totalRows = Math.max(3, gridRows); + const bodyRows = Math.max(2, this._bodyRows(totalRows)); + shell.style.setProperty("--hr-body-rows", String(bodyRows)); + shell.style.setProperty("--hr-footer-height", `${Math.max(0, this._recordsFooterHeight)}px`); + } + _gridRows() { + const raw = getComputedStyle(this).getPropertyValue("--row-size").trim(); + const rows = Number.parseInt(raw, 10); + if (Number.isFinite(rows) && rows > 0) return rows; + return this._config?.show_records ? 4 : 3; + } + _bodyRows(totalRows) { + if (!this._config?.show_records) return totalRows; + return Math.min(totalRows - 1, 3 + Math.floor(Math.max(0, totalRows - 4) / 4)); + } + _toggleEventVisibility(eventId) { + const next = new Set(this._hiddenEventIds); + if (next.has(eventId)) next.delete(eventId); + else next.add(eventId); + this._hiddenEventIds = next; + if (this._lastHistResult !== null) this._drawChart(this._lastHistResult, this._lastEvents, this._lastT0, this._lastT1); + } + _navigateToEventHistory(ev) { + navigateToDataPointsHistory(this, { + entity_id: [this._config?.entity, ...ev?.entity_ids || []].filter(Boolean), + device_id: ev?.device_ids || [], + area_id: ev?.area_ids || [], + label_id: ev?.label_ids || [] + }, { + start_time: Number.isFinite(this._lastT0) ? new Date(this._lastT0).toISOString() : null, + end_time: Number.isFinite(this._lastT1) ? new Date(this._lastT1).toISOString() : null + }); + } + _openTargetHistoryDialog() { + const entityId = this._config?.entity; + if (!entityId) return; + this.dispatchEvent(new CustomEvent("hass-more-info", { + bubbles: true, + composed: true, + detail: { entityId } + })); + } + _onCardClick(event) { + const path = event.composedPath(); + const interactiveTagNames = new Set([ + "BUTTON", + "A", + "INPUT", + "TEXTAREA", + "SELECT", + "PAGINATION-NAV" + ]); + if (path.some((node) => { + if (!(node instanceof HTMLElement)) return false; + if (interactiveTagNames.has(node.tagName)) return true; + return !!node.closest("button,a,input,textarea,select,pagination-nav"); + })) return; + this._openTargetHistoryDialog(); + } + async _load() { + if (!this.hass) return; + const now = /* @__PURE__ */ new Date(); + const start = /* @__PURE__ */ new Date(now.getTime() - this._config.hours_to_show * 3600 * 1e3); + const t0 = start.getTime(); + const t1 = now.getTime(); + const entityIds = [this._config.entity]; + try { + const [histResult, events] = await Promise.all([this.hass.connection.sendMessagePromise({ + type: "history/history_during_period", + start_time: start.toISOString(), + end_time: now.toISOString(), + entity_ids: entityIds, + include_start_time_state: true, + significant_changes_only: false, + no_attributes: true + }), fetchEvents(this.hass, start.toISOString(), now.toISOString(), entityIds)]); + this._drawChart(histResult || {}, events || [], t0, t1); + } catch (err) { + logger$1.error("[hass-datapoints sensor-card]", err); + } + } + _drawChart(histResult, events, t0, t1) { + this._lastHistResult = histResult; + this._lastEvents = events; + this._lastT0 = t0; + this._lastT1 = t1; + this._annEvents = events; + const chartEl = this.shadowRoot?.querySelector("sensor-chart"); + if (!chartEl) return; + chartEl.hass = this.hass; + const entityId = this._config.entity; + const unit = this.hass?.states?.[entityId]?.attributes?.unit_of_measurement || ""; + chartEl.draw(histResult, events, t0, t1, this._config, unit, this._hiddenEventIds); + } + _onAnnotationClick(e) { + this._navigateToEventHistory(e.detail.event); + } + _onHeaderClick(event) { + event.preventDefault(); + event.stopPropagation(); + this._openTargetHistoryDialog(); + } + render() { + const stateObj = this.hass?.states?.[this._config?.entity]; + const sensorName = this._config?.name || stateObj?.attributes?.friendly_name || this._config?.entity || "—"; + const sensorValue = stateObj?.state ?? "—"; + const sensorUnit = stateObj?.attributes?.unit_of_measurement || ""; + return b`
@@ -37898,47 +33721,60 @@ ${content.alert}` : "", .pageSize=${this._config.records_page_size ?? null} .limit=${this._config.records_limit ?? null} .showFullMessage=${this._config.records_show_full_message !== false} - @dp-sensor-record-toggle-visibility=${(e2) => { - this._toggleEventVisibility(e2.detail.id); - }} - @dp-sensor-record-navigate=${(e2) => { - this._navigateToEventHistory(e2.detail.event); - }} - @dp-sensor-pagination-visibility-change=${(e2) => { - this._recordsFooterHeight = e2.detail.visible === true ? e2.detail.height : 0; - }} + @dp-sensor-record-toggle-visibility=${(e) => { + this._toggleEventVisibility(e.detail.id); + }} + @dp-sensor-record-navigate=${(e) => { + this._navigateToEventHistory(e.detail.event); + }} + @dp-sensor-pagination-visibility-change=${(e) => { + this._recordsFooterHeight = e.detail.visible === true ? e.detail.height : 0; + }} > ` : ""}
`; - } - updated() { - this._applyLayoutSizing(); - } - static getConfigElement() { - return document.createElement("hass-datapoints-sensor-card-editor"); - } - static getStubConfig() { - return { entity: "sensor.example", hours_to_show: 24 }; - } - getGridOptions() { - if (this._config?.show_records) { - return { rows: 4, min_rows: 4, max_rows: 12 }; - } - return { rows: 3, min_rows: 2, max_rows: 5 }; - } - } - __publicField$3(HassRecordsSensorCard, "properties", { - _config: { state: true }, - _hass: { state: true }, - _annEvents: { state: true }, - _hiddenEventIds: { state: true }, - _recordsFooterHeight: { state: true } - }); - __publicField$3(HassRecordsSensorCard, "styles", styles$7); - const styles$2 = i$5``; - const styles$1 = i$5` + } + updated() { + this._applyLayoutSizing(); + } + static getConfigElement() { + return document.createElement("hass-datapoints-sensor-card-editor"); + } + static getStubConfig() { + return { + entity: "sensor.example", + hours_to_show: 24 + }; + } + getGridOptions() { + if (this._config?.show_records) return { + rows: 4, + min_rows: 4, + max_rows: 12 + }; + return { + rows: 3, + min_rows: 2, + max_rows: 5 + }; + } + }; + _defineProperty(HassRecordsSensorCard, "properties", { + _config: { state: true }, + _hass: { state: true }, + _annEvents: { state: true }, + _hiddenEventIds: { state: true }, + _recordsFooterHeight: { state: true } + }); + _defineProperty(HassRecordsSensorCard, "styles", styles$7); + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/editor.styles.ts + var styles$2 = i$5``; + //#endregion + //#region custom_components/hass_datapoints/src/atoms/form/editor-entity-picker/editor-entity-picker.styles.ts + var styles$1 = i$5` :host { display: block; } @@ -37947,112 +33783,45 @@ ${content.alert}` : "", width: 100%; } `; - var __create$1 = Object.create; - var __defProp$2 = Object.defineProperty; - var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor; - var __knownSymbol$1 = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError$1 = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart$1 = (base) => [, , , __create$1(base?.[__knownSymbol$1("metadata")] ?? null)]; - var __decoratorStrings$1 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn$1 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$1("Function expected") : fn; - var __decoratorContext$1 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$1[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$1("Already initialized") : fns.push(__expectFn$1(fn || null)) }); - var __decoratorMetadata$1 = (array, target) => __defNormalProp$2(target, __knownSymbol$1("metadata"), array[3]); - var __runInitializers$1 = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement$1 = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings$1[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc$1({ get [name]() { - return __privateGet$1(this, extra); - }, set [name](x2) { - return __privateSet$1(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext$1(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn$1(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError$1("Object expected"); - else __expectFn$1(fn = it.get) && (desc.get = fn), __expectFn$1(fn = it.set) && (desc.set = fn), __expectFn$1(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$2(target, name, desc), target; - }; - var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, key + "", value); - var __accessCheck$1 = (obj, member, msg2) => member.has(obj) || __typeError$1("Cannot " + msg2); - var __privateGet$1 = (obj, member, getter) => (__accessCheck$1(obj, member, "read from private field"), member.get(obj)); - var __privateAdd$1 = (obj, member, value) => member.has(obj) ? __typeError$1("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet$1 = (obj, member, value, setter) => (__accessCheck$1(obj, member, "write to private field"), member.set(obj, value), value); - var _hass_dec, _value_dec$1, _label_dec$1, _a$1, _init$1, _label$1, _value$1, _hass; - class EditorEntityPicker extends (_a$1 = i$2, _label_dec$1 = [n({ type: String })], _value_dec$1 = [n({ type: String })], _hass_dec = [n({ type: Object })], _a$1) { - constructor() { - super(...arguments); - __privateAdd$1(this, _label$1, __runInitializers$1(_init$1, 8, this, "")), __runInitializers$1(_init$1, 11, this); - __privateAdd$1(this, _value$1, __runInitializers$1(_init$1, 12, this, "")), __runInitializers$1(_init$1, 15, this); - __privateAdd$1(this, _hass, __runInitializers$1(_init$1, 16, this, null)), __runInitializers$1(_init$1, 19, this); - } - firstUpdated() { - const el = this.shadowRoot.querySelector( - "ha-selector" - ); - if (el) { - el.label = this.label; - el.selector = { entity: {} }; - if (this.hass) { - el.hass = this.hass; - } - el.value = this.value; - } - } - updated(changedProps) { - const el = this.shadowRoot.querySelector( - "ha-selector" - ); - if (!el) { - return; - } - if (changedProps.has("value")) { - el.value = this.value; - } - if (changedProps.has("hass") && this.hass) { - el.hass = this.hass; - } - } - _onValueChanged(e2) { - this.dispatchEvent( - new CustomEvent("dp-entity-change", { - detail: { value: e2.detail.value }, - bubbles: true, - composed: true - }) - ); - } - render() { - return b``; - } - } - _init$1 = __decoratorStart$1(_a$1); - _label$1 = /* @__PURE__ */ new WeakMap(); - _value$1 = /* @__PURE__ */ new WeakMap(); - _hass = /* @__PURE__ */ new WeakMap(); - __decorateElement$1(_init$1, 4, "label", _label_dec$1, EditorEntityPicker, _label$1); - __decorateElement$1(_init$1, 4, "value", _value_dec$1, EditorEntityPicker, _value$1); - __decorateElement$1(_init$1, 4, "hass", _hass_dec, EditorEntityPicker, _hass); - __decoratorMetadata$1(_init$1, EditorEntityPicker); - __publicField$2(EditorEntityPicker, "styles", styles$1); - customElements.define("editor-entity-picker", EditorEntityPicker); - const styles = i$5` + } + }; + _defineProperty(EditorEntityPicker, "styles", styles$1); + customElements.define("editor-entity-picker", EditorEntityPicker); + //#endregion + //#region custom_components/hass_datapoints/src/atoms/form/editor-select/editor-select.styles.ts + var styles = i$5` :host { display: block; } @@ -38061,2987 +33830,183 @@ ${content.alert}` : "", width: 100%; } `; - var __create = Object.create; - var __defProp$1 = Object.defineProperty; - var __getOwnPropDesc = Object.getOwnPropertyDescriptor; - var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); - var __typeError = (msg2) => { - throw TypeError(msg2); - }; - var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)]; - var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; - var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn; - var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) }); - var __decoratorMetadata = (array, target) => __defNormalProp$1(target, __knownSymbol("metadata"), array[3]); - var __runInitializers = (array, flags, self2, value) => { - for (var i2 = 0, fns = array[flags >> 1], n2 = fns && fns.length; i2 < n2; i2++) flags & 1 ? fns[i2].call(self2) : value = fns[i2].call(self2, value); - return value; - }; - var __decorateElement = (array, flags, name, decorators, target, extra) => { - var fn, it, done, ctx, access, k2 = flags & 7, s2 = false, p2 = false; - var j2 = array.length + 1, key = __decoratorStrings[k2 + 5]; - var initializers = array[j2 - 1] = [], extraInitializers = array[j2] || (array[j2] = []); - var desc = (target = target.prototype, __getOwnPropDesc({ get [name]() { - return __privateGet(this, extra); - }, set [name](x2) { - return __privateSet(this, extra, x2); - } }, name)); - for (var i2 = decorators.length - 1; i2 >= 0; i2--) { - ctx = __decoratorContext(k2, name, done = {}, array[3], extraInitializers); - { - ctx.static = s2, ctx.private = p2, access = ctx.access = { has: (x2) => name in x2 }; - access.get = (x2) => x2[name]; - access.set = (x2, y2) => x2[name] = y2; - } - it = (0, decorators[i2])({ get: desc.get, set: desc.set }, ctx), done._ = 1; - if (it === void 0) __expectFn(it) && (desc[key] = it); - else if (typeof it !== "object" || it === null) __typeError("Object expected"); - else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn); - } - return desc && __defProp$1(target, name, desc), target; - }; - var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, key + "", value); - var __accessCheck = (obj, member, msg2) => member.has(obj) || __typeError("Cannot " + msg2); - var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), member.get(obj)); - var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); - var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value); - var _options_dec, _value_dec, _label_dec, _a, _init, _label, _value, _options; - class EditorSelect extends (_a = i$2, _label_dec = [n({ type: String })], _value_dec = [n({ type: String })], _options_dec = [n({ type: Array })], _a) { - constructor() { - super(...arguments); - __privateAdd(this, _label, __runInitializers(_init, 8, this, "")), __runInitializers(_init, 11, this); - __privateAdd(this, _value, __runInitializers(_init, 12, this, "")), __runInitializers(_init, 15, this); - __privateAdd(this, _options, __runInitializers(_init, 16, this, [])), __runInitializers(_init, 19, this); - } - firstUpdated() { - const el = this.shadowRoot.querySelector( - "ha-selector" - ); - if (el) { - el.label = this.label; - el.selector = { select: { options: this.options } }; - el.value = this.value; - } - } - updated(changedProps) { - const el = this.shadowRoot.querySelector( - "ha-selector" - ); - if (!el) { - return; - } - if (changedProps.has("value")) { - el.value = this.value; - } - if (changedProps.has("options")) { - el.selector = { select: { options: this.options } }; - } - } - _onValueChanged(e2) { - this.dispatchEvent( - new CustomEvent("dp-select-change", { - detail: { value: e2.detail.value }, - bubbles: true, - composed: true - }) - ); - } - render() { - return b``; - } - } - _init = __decoratorStart(_a); - _label = /* @__PURE__ */ new WeakMap(); - _value = /* @__PURE__ */ new WeakMap(); - _options = /* @__PURE__ */ new WeakMap(); - __decorateElement(_init, 4, "label", _label_dec, EditorSelect, _label); - __decorateElement(_init, 4, "value", _value_dec, EditorSelect, _value); - __decorateElement(_init, 4, "options", _options_dec, EditorSelect, _options); - __decoratorMetadata(_init, EditorSelect); - __publicField$1(EditorSelect, "styles", styles); - customElements.define("editor-select", EditorSelect); - var __defProp = Object.defineProperty; - var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; - var __publicField = (obj, key, value) => __defNormalProp(obj, key + "", value); - class HassRecordsSensorCardEditor extends EditorBase { - render() { - const c2 = this._config; - const showRecords = !!c2.show_records; - return b` + } + }; + _defineProperty(EditorSelect, "styles", styles); + customElements.define("editor-select", EditorSelect); + //#endregion + //#region custom_components/hass_datapoints/src/cards/sensor/editor.ts + var HassRecordsSensorCardEditor = class extends EditorBase { + render() { + const c = this._config; + const showRecords = !!c.show_records; + return b`
this._set("entity", e2.detail.value)} + @dp-entity-change=${(e) => this._set("entity", e.detail.value)} > this._set("name", e2.detail.value)} + .value=${c.name || ""} + @dp-field-change=${(e) => this._set("name", e.detail.value)} > this._set("hours_to_show", e2.detail.value)} + .value=${String(c.hours_to_show ?? 24)} + @dp-field-change=${(e) => this._set("hours_to_show", e.detail.value)} > this._set("graph_color", e2.detail.color)} + .color=${c.graph_color || COLORS[0]} + @dp-color-change=${(e) => this._set("graph_color", e.detail.color)} > this._set("annotation_style", e2.detail.value)} + .value=${c.annotation_style || ""} + .options=${[{ + value: "circle", + label: msg("Circle on line") + }, { + value: "line", + label: msg("Dotted vertical line") + }]} + @dp-select-change=${(e) => this._set("annotation_style", e.detail.value)} > this._set( - "show_annotation_tooltips", - e2.detail.checked ? true : void 0 - )} + .checked=${c.show_annotation_tooltips === true} + @dp-switch-change=${(e) => this._set("show_annotation_tooltips", e.detail.checked ? true : void 0)} > this._set("show_records", e2.detail.checked || void 0)} + @dp-switch-change=${(e) => this._set("show_records", e.detail.checked || void 0)} > this._set("records_page_size", e2.detail.value)} + .value=${c.records_page_size != null ? String(c.records_page_size) : ""} + @dp-field-change=${(e) => this._set("records_page_size", e.detail.value)} > this._set("records_limit", e2.detail.value)} + .value=${c.records_limit != null ? String(c.records_limit) : ""} + @dp-field-change=${(e) => this._set("records_limit", e.detail.value)} > this._set( - "records_show_full_message", - e2.detail.checked ? void 0 : false - )} + .checked=${c.records_show_full_message !== false} + .tooltip=${msg("User will be able to expand the row if hidden", { id: "User will be able to expand the row if hidden" })} + @dp-switch-change=${(e) => this._set("records_show_full_message", e.detail.checked ? void 0 : false)} >
`; - } - } - __publicField(HassRecordsSensorCardEditor, "styles", [EditorBase.styles, styles$2]); - if (!customElements.get("hass-datapoints-action-card")) { - customElements.define("hass-datapoints-action-card", HassRecordsActionCard); - } - if (!customElements.get("hass-datapoints-quick-card")) { - customElements.define("hass-datapoints-quick-card", HassRecordsQuickCard); - } - if (!customElements.get("hass-datapoints-history-card")) { - customElements.define("hass-datapoints-history-card", HassRecordsHistoryCard); - } - if (!customElements.get("hass-datapoints-sensor-card")) { - customElements.define("hass-datapoints-sensor-card", HassRecordsSensorCard); - } - if (!customElements.get("hass-datapoints-list-card")) { - customElements.define("hass-datapoints-list-card", HassRecordsListCard); - } - if (!customElements.get("hass-datapoints-history-panel")) { - customElements.define( - "hass-datapoints-history-panel", - HassRecordsHistoryPanel - ); - } - if (!customElements.get("hass-datapoints-dev-tool-card")) { - customElements.define( - "hass-datapoints-dev-tool-card", - HassRecordsDevToolCard - ); - } - if (!customElements.get("hass-datapoints-action-card-editor")) { - customElements.define( - "hass-datapoints-action-card-editor", - HassRecordsActionCardEditor - ); - } - if (!customElements.get("hass-datapoints-quick-card-editor")) { - customElements.define( - "hass-datapoints-quick-card-editor", - HassRecordsQuickCardEditor - ); - } - if (!customElements.get("hass-datapoints-history-card-editor")) { - customElements.define( - "hass-datapoints-history-card-editor", - HassRecordsHistoryCardEditor - ); - } - if (!customElements.get("hass-datapoints-sensor-card-editor")) { - customElements.define( - "hass-datapoints-sensor-card-editor", - HassRecordsSensorCardEditor - ); - } - if (!customElements.get("hass-datapoints-list-card-editor")) { - customElements.define( - "hass-datapoints-list-card-editor", - HassRecordsListCardEditor - ); - } - if (!customElements.get("hass-datapoints-dev-tool-card-editor")) { - customElements.define( - "hass-datapoints-dev-tool-card-editor", - HassRecordsDevToolCardEditor - ); - } - window.customCards = window.customCards || []; - const registeredTypes = new Set(window.customCards.map((card) => card.type)); - const cardsToAdd = [ - { - type: "hass-datapoints-action-card", - name: "Hass Records – Action Card", - description: "Full form to record a custom event with message, annotation, icon, colour and entity association.", - preview: false - }, - { - type: "hass-datapoints-quick-card", - name: "Hass Records – Quick Card", - description: "Simple one-field card to quickly record a note with a bookmark icon.", - preview: false - }, - { - type: "hass-datapoints-history-card", - name: "Hass Records – History Card", - description: "History line chart with coloured annotation markers for recorded events.", - preview: false - }, - { - type: "hass-datapoints-sensor-card", - name: "Hass Records – Sensor Card", - description: "Sensor card with line chart — annotations shown as icons on the data line.", - preview: false - }, - { - type: "hass-datapoints-list-card", - name: "Hass Records – List Card", - description: "Activity-style datagrid to browse, search, edit and delete all recorded events.", - preview: false - }, - { - type: "hass-datapoints-dev-tool-card", - name: "Hass Records – Dev Tool", - description: "Generate demo datapoints from HA history and bulk-delete dev-flagged events.", - preview: false - } - ]; - cardsToAdd.forEach((card) => { - if (!registeredTypes.has(card.type)) { - window.customCards?.push(card); - } - }); - console.groupCollapsed( - "%c hass-datapoints %c v0.4.1 loaded ", - "color:#fff;background:#03a9f4;font-weight:bold;padding:2px 6px;border-radius:3px 0 0 3px", - "color:#03a9f4;background:#fff;font-weight:bold;padding:2px 6px;border:1px solid #03a9f4;border-radius:0 3px 3px 0" - ); - console.log( - "Enable debug logging by setting %cwindow.__HASS_DATAPOINTS_DEV__ = true", - "color:#333;background:#eee;border:1px solid #777;padding:2px 6px;border-radius:5px; font-family: Courier" - ); - console.groupEnd(); - const translations$2l = { - "Updates with new data": "Päivittyy uusilla tiedoilla", - "Scroll to selected range": "Siirry valittuun alueeseen", - "Start date and time": "Alkamispäivä ja -aika", - "End date and time": "Päättymispäivä ja -aika", - Select: "Valitse" - }; - const __vite_glob_0_0$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2l - }, Symbol.toStringTag, { value: "Module" })); - const translations$2k = { - General: "Yleiset", - "Related items": "Liittyvät kohteet", - "Datapoint Appearance": "Datapisteen ulkoasu", - "Form fields": "Lomakekentät", - "Card title (optional)": "Kortin otsikko (valinnainen)", - "Pre-fill entities, devices, areas or labels that are always linked to recordings from this card.": "Esitäytä entiteetit, laitteet, alueet tai merkinnät, jotka yhdistetään aina tämän kortin tallenteisiin.", - "Show always included targets on card": "Näytä aina mukana olevat kohteet kortilla", - "Allow user to add more related items": "Salli käyttäjän lisätä lisää liittyviä kohteita", - "Default icon": "Oletuskuvake", - "Default colour": "Oletusväri", - "Show date & time field": "Näytä päivämäärä- ja aikakenttä", - "Show annotation field": "Näytä huomautuskenttä" - }; - const __vite_glob_0_1$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2k - }, Symbol.toStringTag, { value: "Module" })); - const translations$2j = { - "Date window:": "Aikaikkuna:", - "Actual:": "Todellinen:" - }; - const __vite_glob_0_2$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2j - }, Symbol.toStringTag, { value: "Module" })); - const translations$2i = { - // Editor section headings (shared across editors) - General: "Yleiset", - Entity: "Entiteetti", - "Multiple entities": "Useita entiteettejä", - Display: "Näyttö", - // Editor field labels - "Card title (optional)": "Kortin otsikko (valinnainen)", - "Hours to show": "Näytettävät tunnit", - "Single entity": "Yksittäinen entiteetti", - "Show data gaps": "Näytä tietoaukot", - "Highlight missing data ranges with dashed lines and boundary markers": "Korosta puuttuvat tietoalueet katkoviivoilla ja rajamerkeillä" - }; - const __vite_glob_0_3$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2i - }, Symbol.toStringTag, { value: "Module" })); - const translations$2h = { - "Search datapoints…": "Hae datapisteitä…", - "Delete record": "Poista tietue", - Delete: "Poista", - "Show annotation": "Näytä huomautus", - "Open related data point history": "Avaa liittyvän datapisteen historia", - "Edit record": "Muokkaa tietuetta", - "Show chart marker": "Näytä kaavion merkki", - "Hide chart marker": "Piilota kaavion merkki", - "Choose colour": "Valitse väri", - Save: "Tallenna", - Cancel: "Peruuta", - Message: "Viesti", - "Annotation / full message": "Huomautus / koko viesti" - }; - const __vite_glob_0_4$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2h - }, Symbol.toStringTag, { value: "Module" })); - const translations$2g = { - General: "Yleiset", - "Icon & colour": "Kuvake ja väri", - "Related items": "Liittyvät kohteet", - "Multiple entities": "Useita entiteettejä", - "Form fields": "Lomakekentät", - "Card title (optional)": "Kortin otsikko (valinnainen)", - "Input placeholder text": "Syötteen ohjeteksti", - Icon: "Kuvake", - Colour: "Väri", - "These items will be linked to every record made with this card.": "Nämä kohteet yhdistetään kaikkiin tällä kortilla tehtyihin tallenteisiin.", - "Single entity (optional)": "Yksittäinen entiteetti (valinnainen)", - "Add related items": "Lisää liittyvät kohteet", - "Show annotation field": "Näytä huomautuskenttä" - }; - const __vite_glob_0_5$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2g - }, Symbol.toStringTag, { value: "Module" })); - const translations$2f = { - Entity: "Entiteetti", - Display: "Näyttö", - "Records list": "Tietueiden luettelo", - "Sensor entity *": "Anturientiteetti *", - "Override display name (optional)": "Korvaa näyttönimi (valinnainen)", - "Hours to show": "Näytettävät tunnit", - "Graph colour": "Kaavion väri", - "Annotation style": "Huomautustyyli", - "Circle on line": "Ympyrä viivalla", - "Dotted vertical line": "Pisteytetty pystylinja", - "Show records list below graph": "Näytä tietueiden luettelo kaavion alla", - "Records per page (blank = show all)": "Tietueita per sivu (tyhjä = kaikki)", - "Max records to show (blank = all)": "Enimmäismäärä näytettäviä tietueita (tyhjä = kaikki)", - "Show full message": "Näytä koko viesti", - "User will be able to expand the row if hidden": "Käyttäjä voi laajentaa rivin, jos se on piilotettu" - }; - const __vite_glob_0_6$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2f - }, Symbol.toStringTag, { value: "Module" })); - const translations$2e = { - // Anomaly tooltip titles / meta - "⚠️ Anomaly Insight": "⚠️ Poikkeavuushavainto", - "⚠️ Multi-method Anomaly": "⚠️ Monimenetelmäinen poikkeavuus", - "Click the highlighted circle to add an annotation.": "Klikkaa korostettua ympyrää lisätäksesi huomautuksen.", - "Alert:": "Hälytys:", - "Confirmed by": "Vahvistettu", - "methods:": "menetelmällä:", - // Anomaly method labels - "Trend deviation": "Trendipoikkeama", - "Sudden change": "Äkillinen muutos", - "Statistical outlier (IQR)": "Tilastollinen poikkeama (IQR)", - "Rolling Z-score": "Liukuva Z-arvo", - "Flat-line / stuck": "Tasainen / jumittunut", - "Comparison window": "Vertailuikkuna", - // Anomaly description templates ({0} = label, {1} = start, {2} = end) - "{0} deviates from its expected trend between {1} and {2}.": "{0} poikkeaa odotetusta trendistä välillä {1} – {2}.", - "{0} shows an unusual rate of change between {1} and {2}.": "{0} näyttää epätavallista muutosnopeutta välillä {1} – {2}.", - "{0} contains statistical outliers between {1} and {2}.": "{0} sisältää tilastollisia poikkeamia välillä {1} – {2}.", - "{0} shows statistically unusual values between {1} and {2}.": "{0} näyttää tilastollisesti epätavallisia arvoja välillä {1} – {2}.", - "{0} appears stuck or flat between {1} and {2}{3}.": "{0} vaikuttaa jumittuneelta tai tasaiselta välillä {1} – {2}{3}.", - "{0} deviates significantly from the comparison window between {1} and {2}.": "{0} poikkeaa merkittävästi vertailuikkunasta välillä {1} – {2}.", - // Anomaly alert templates ({0}, {1}, {2} = formatted values / timestamps) - "Peak deviation: {0} from a baseline of {1} at {2}.": "Huippupoikkeama: {0} perusarvosta {1} ajankohtana {2}.", - "Peak rate deviation: {0} from a typical rate of {1} at {2}.": "Huippumuutosnopeus: {0} tyypillisestä nopeudesta {1} ajankohtana {2}.", - "Peak value: {0}, deviating {1} from the median at {2}.": "Huippuarvo: {0}, poikkeaa {1} mediaanista ajankohtana {2}.", - "Peak deviation: {0} from a rolling mean of {1} at {2}.": "Huippupoikkeama: {0} liukuvasta keskiarvosta {1} ajankohtana {2}.", - "Value remained near {0} for an unusually long period.": "Arvo pysyi lähellä arvoa {0} epätavallisen pitkään.", - "Peak deviation from comparison: {0} at {1}.": "Huippupoikkeama vertailusta: {0} ajankohtana {1}.", - " (range: {0})": " (vaihteluväli: {0})", - // Tooltip series type labels - "Date window": "Aikaikkuna", - Trend: "Trendi", - Rate: "Muutosnopeus", - Delta: "Delta", - Threshold: "Kynnysarvo" - }; - const __vite_glob_0_7$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2e - }, Symbol.toStringTag, { value: "Module" })); - const translations$2d = { - "Confirm delete": "Vahvista poisto", - "Are you sure you want to delete this item?": "Oletko varma, että haluat poistaa tämän kohteen?", - Cancel: "Peruuta", - Delete: "Poista", - "Delete date window": "Poista aikaikkuna", - "this date window": "tämä aikaikkuna", - "Edit date window": "Muokkaa aikaikkunaa", - "Add date window": "Lisää aikaikkuna", - "Save date window": "Tallenna aikaikkuna", - "Create date window": "Luo aikaikkuna" - }; - const __vite_glob_0_8$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2d - }, Symbol.toStringTag, { value: "Module" })); - const translations$2c = { - Wk: "Vk", - "Week of": "Viikko" - }; - const __vite_glob_0_9$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2c - }, Symbol.toStringTag, { value: "Module" })); - const translations$2b = { - "Show anomalies": "Näytä poikkeamat", - Sensitivity: "Herkkyys", - "Use downsampled data for detection": "Käytä alasnäytteistettyä dataa havaitsemiseen", - "Rate window": "Muutosikkuna", - "Rolling window": "Liukuva ikkuna", - "Min flat duration": "Pienin tasainen kesto", - "Compare to window": "Vertaa ikkunaan", - "— select window —": "— valitse ikkuna —", - "When methods overlap": "Kun menetelmät menevät päällekkäin", - Low: "Matala", - Medium: "Keskitaso", - High: "Korkea", - "Trend deviation": "Trendipoikkeama", - "Sudden change": "Äkillinen muutos", - "Statistical outlier (IQR)": "Tilastollinen poikkeama (IQR)", - "Rolling Z-score": "Liukuva Z-arvo", - "Flat-line / stuck value": "Tasainen / jumittunut arvo", - "Comparison window deviation": "Vertailuikkunan poikkeama", - "Flags points that deviate significantly from a fitted trend line. Good for catching gradual drift or sudden jumps away from a steady baseline.": "Merkitsee pisteet, jotka poikkeavat selvästi sovitetusta trendiviivasta. Sopii asteittaisen ajautumisen tai äkillisten hyppyjen löytämiseen.", - "Flags unusually fast rises or drops compared to the typical rate of change. Best for detecting spikes, crashes, or rapid transitions.": "Merkitsee poikkeuksellisen nopeat nousut ja laskut verrattuna tyypilliseen muutosnopeuteen. Paras piikkien ja romahdusten havaitsemiseen.", - "Uses the interquartile range to flag values far outside the normal spread of data. Robust against outliers that skew averages.": "Käyttää interkvartiiliväliä arvojen merkitsemiseen, kun ne ovat kaukana normaalista vaihtelusta. Kestävä poikkeaville arvoille, jotka vääristävät keskiarvoja.", - "Compares each value to a rolling mean and standard deviation. Catches unusual readings relative to recent context rather than the whole series.": "Vertaa jokaista arvoa liukuvaan keskiarvoon ja keskihajontaan. Löytää epätavalliset lukemat suhteessa tuoreeseen kontekstiin.", - "Flags when a sensor reports nearly the same value for an unusually long time. Useful for detecting stuck sensors or frozen readings.": "Merkitsee tilanteet, joissa sensori raportoi lähes samaa arvoa poikkeuksellisen pitkään. Hyödyllinen jumittuneiden antureiden löytämiseen.", - "Compares the current period to a reference date window. Highlights differences from an expected historical pattern, such as last week or the same day last year.": "Vertaa nykyistä jaksoa viiteajanjaksoon. Korostaa erot odotetusta historiallisesta mallista, kuten viime viikkoon tai samaan päivään viime vuonna.", - "Show all anomalies": "Näytä kaikki poikkeamat", - "Overlaps only": "Vain päällekkäisyydet", - "Computing…": "Lasketaan…", - "1 hour": "1 tunti", - "3 hours": "3 tuntia", - "6 hours": "6 tuntia", - "12 hours": "12 tuntia", - "24 hours": "24 tuntia", - "7 days": "7 päivää", - "30 minutes": "30 minuuttia" - }; - const __vite_glob_0_10$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2b - }, Symbol.toStringTag, { value: "Module" })); - const translations$2a = { - "Show delta vs selected date window": "Näytä delta vs. valittu aikaikkuna", - "Select a date window tab to enable delta analysis.": "Valitse aikaikkuna-välilehti ottaaksesi delta-analyysin käyttöön.", - "Show delta in tooltip": "Näytä delta työkaluvihjeessä", - "Show delta lines": "Näytä deltaviivat" - }; - const __vite_glob_0_11$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2a - }, Symbol.toStringTag, { value: "Module" })); - const translations$29 = { - "Show rate of change": "Näytä muutosnopeus", - "Show rate of change crosshairs": "Näytä muutosnopeuden tähtäin", - "Rate window": "Muutosikkuna", - "Point to point": "Piste pisteeseen", - "1 hour": "1 tunti", - "6 hours": "6 tuntia", - "24 hours": "24 tuntia" - }; - const __vite_glob_0_12$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$29 - }, Symbol.toStringTag, { value: "Module" })); - const translations$28 = { - Downsampling: "Alasnäytteistys", - Interval: "Väli", - Aggregate: "Kooste", - "Raw (no sampling)": "Raaka (ei näytteistystä)", - "5 seconds": "5 sekuntia", - "10 seconds": "10 sekuntia", - "15 seconds": "15 sekuntia", - "30 seconds": "30 sekuntia", - "1 minute": "1 minuutti", - "2 minutes": "2 minuuttia", - "5 minutes": "5 minuuttia", - "10 minutes": "10 minuuttia", - "15 minutes": "15 minuuttia", - "30 minutes": "30 minuuttia", - "1 hour": "1 tunti", - "2 hours": "2 tuntia", - "3 hours": "3 tuntia", - "4 hours": "4 tuntia", - "6 hours": "6 tuntia", - "12 hours": "12 tuntia", - "24 hours": "24 tuntia", - "Mean (average)": "Keskiarvo", - Min: "Min", - Max: "Max", - Median: "Mediaani", - First: "Ensimmäinen", - Last: "Viimeinen" - }; - const __vite_glob_0_13$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$28 - }, Symbol.toStringTag, { value: "Module" })); - const translations$27 = { - "Show min / max / mean": "Näytä min / max / keskiarvo", - "Show range shading": "Näytä aluevarjostus" - }; - const __vite_glob_0_14$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$27 - }, Symbol.toStringTag, { value: "Module" })); - const translations$26 = { - "Show threshold analysis": "Näytä kynnysanalyysi", - "Shade threshold area": "Varjosta kynnysalue", - Threshold: "Kynnys", - "Shade area": "Varjostusalue", - "Shade above": "Varjosta yläpuolelle", - "Shade below": "Varjosta alapuolelle" - }; - const __vite_glob_0_15$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$26 - }, Symbol.toStringTag, { value: "Module" })); - const translations$25 = { - "Show trend lines": "Näytä trendiviivat", - "Show trend crosshairs": "Näytä trenditähtäin", - "Trend method": "Trendimenetelmä", - "Trend window": "Trendiikkuna", - "Rolling average": "Liukuva keskiarvo", - "Linear trend": "Lineaarinen trendi", - "1 hour": "1 tunti", - "6 hours": "6 tuntia", - "24 hours": "24 tuntia", - "7 days": "7 päivää", - "14 days": "14 päivää", - "21 days": "21 päivää", - "28 days": "28 päivää" - }; - const __vite_glob_0_16$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$25 - }, Symbol.toStringTag, { value: "Module" })); - const translations$24 = { - "Add date window": "Lisää aikaikkuna" - }; - const __vite_glob_0_17$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$24 - }, Symbol.toStringTag, { value: "Module" })); - const translations$23 = { - "A date window saves a named date range as a tab, so you can quickly preview it against the selected range or jump the chart back to it later.": "Aikaikkuna tallentaa nimetyn päivävälin välilehteksi, jotta voit nopeasti esikatsella sitä suhteessa valittuun alueeseen tai palata kaavion kyseiseen ajanjaksoon.", - Name: "Nimi", - "e.g. Heating season start": "esim. Lämmityskausi alkaa", - "Date range": "Päiväväli", - Start: "Alku", - End: "Loppu", - "Use previous range": "Käytä edellistä aluetta", - "Use next range": "Käytä seuraavaa aluetta", - "Delete date window": "Poista aikaikkuna", - Cancel: "Peruuta" - }; - const __vite_glob_0_18$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$23 - }, Symbol.toStringTag, { value: "Module" })); - const translations$22 = { - Datapoints: "Datapisteet", - "Choose which annotation datapoints appear on the chart.": "Valitse, mitkä huomautusten datapisteet näkyvät kaaviossa.", - "Linked to selected targets": "Linkitetty valittuihin kohteisiin", - "All datapoints": "Kaikki datapisteet", - "Hide datapoints": "Piilota datapisteet" - }; - const __vite_glob_0_19$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$22 - }, Symbol.toStringTag, { value: "Module" })); - const translations$21 = {}; - const __vite_glob_0_20$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$21 - }, Symbol.toStringTag, { value: "Module" })); - const translations$20 = { - "Analysis configured": "Analyysi määritetty", - "Configure analysis": "Määritä analyysi", - "Stepped series": "Porrastettu sarja", - "Hide source series": "Piilota lähdesarja", - "All targets already have the same settings": "Kaikilla kohteilla on jo samat asetukset", - "Copy these analysis settings to all targets": "Kopioi nämä analyysiasetus kaikille kohteille", - "Copy to all targets": "Kopioi kaikille kohteille" - }; - const __vite_glob_0_21$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$20 - }, Symbol.toStringTag, { value: "Module" })); - const translations$1$ = { - Targets: "Kohteet", - "Each row controls one chart series.": "Jokainen rivi ohjaa yhtä kaaviosarjaa.", - "Add target": "Lisää kohde", - "Chart preferences": "Kaavioasetukset" - }; - const __vite_glob_0_22$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1$ - }, Symbol.toStringTag, { value: "Module" })); - const translations$1_ = { - "Loading Datapoints…": "Ladataan Datapoints…", - Datapoints: "Datapoints", - "Page options": "Sivun asetukset", - "Download spreadsheet": "Lataa taulukko", - "Save page state": "Tallenna sivun tila", - "Restore saved page": "Palauta tallennettu sivu", - "Clear saved page": "Tyhjennä tallennettu sivu", - "Expand targets sidebar": "Laajenna kohteiden sivupalkki", - "Collapse targets sidebar": "Kutista kohteiden sivupalkki" - }; - const __vite_glob_0_23$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1_ - }, Symbol.toStringTag, { value: "Module" })); - const translations$1Z = { - "Toggle sidebar": "Vaihda sivupalkki", - Start: "Alku", - End: "Loppu", - "Select date range": "Valitse aikaväli", - "Timeline options": "Aikajanan asetukset", - "Zoom level": "Zoomaustaso", - "Date snapping": "Päivän kohdistus", - Auto: "Automaattinen", - Hour: "Tunti", - Day: "Päivä", - Week: "Viikko", - Month: "Kuukausi", - Minute: "Minuutti", - Second: "Sekunti", - Quarterly: "Neljännesvuosi", - "Month Compressed": "Kuukausi (tiivis)", - "Month Short": "Kuukausi (lyhyt)", - "Month Expanded": "Kuukausi (laaja)", - "Week Compressed": "Viikko (tiivis)", - "Week Expanded": "Viikko (laaja)" - }; - const __vite_glob_0_24$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1Z - }, Symbol.toStringTag, { value: "Module" })); - const modules$5 = /* @__PURE__ */ Object.assign({ - "../../../atoms/interactive/range-timeline/i18n/fi.ts": __vite_glob_0_0$5, - "../../../cards/action/i18n/fi.ts": __vite_glob_0_1$5, - "../../../cards/history/history-chart/i18n/fi.ts": __vite_glob_0_2$5, - "../../../cards/history/i18n/fi.ts": __vite_glob_0_3$5, - "../../../cards/list/i18n/fi.ts": __vite_glob_0_4$5, - "../../../cards/quick/i18n/fi.ts": __vite_glob_0_5$5, - "../../../cards/sensor/i18n/fi.ts": __vite_glob_0_6$5, - "../../chart/i18n/fi.ts": __vite_glob_0_7$5, - "../../ha/i18n/fi.ts": __vite_glob_0_8$5, - "../../timeline/i18n/fi.ts": __vite_glob_0_9$5, - "../../../molecules/analysis-anomaly-group/i18n/fi.ts": __vite_glob_0_10$5, - "../../../molecules/analysis-delta-group/i18n/fi.ts": __vite_glob_0_11$5, - "../../../molecules/analysis-rate-group/i18n/fi.ts": __vite_glob_0_12$5, - "../../../molecules/analysis-sample-group/i18n/fi.ts": __vite_glob_0_13$5, - "../../../molecules/analysis-summary-group/i18n/fi.ts": __vite_glob_0_14$5, - "../../../molecules/analysis-threshold-group/i18n/fi.ts": __vite_glob_0_15$5, - "../../../molecules/analysis-trend-group/i18n/fi.ts": __vite_glob_0_16$5, - "../../../molecules/comparison-tab-rail/i18n/fi.ts": __vite_glob_0_17$5, - "../../../molecules/date-window-dialog/i18n/fi.ts": __vite_glob_0_18$5, - "../../../molecules/sidebar-options/sections/i18n/fi.ts": __vite_glob_0_19$5, - "../../../molecules/target-row-list/i18n/fi.ts": __vite_glob_0_20$5, - "../../../molecules/target-row/i18n/fi.ts": __vite_glob_0_21$5, - "../../../panels/datapoints/components/history-targets/i18n/fi.ts": __vite_glob_0_22$5, - "../../../panels/datapoints/components/panel-shell/i18n/fi.ts": __vite_glob_0_23$5, - "../../../panels/datapoints/components/range-toolbar/i18n/fi.ts": __vite_glob_0_24$5 - }); - const merged$5 = {}; - for (const mod of Object.values(modules$5)) { - Object.assign(merged$5, mod.translations); - } - const templates$5 = merged$5; - const fi = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - templates: templates$5 - }, Symbol.toStringTag, { value: "Module" })); - const translations$1Y = { - "Updates with new data": "Se met à jour avec de nouvelles données", - "Scroll to selected range": "Faire défiler jusqu’à la plage sélectionnée", - "Start date and time": "Date et heure de début", - "End date and time": "Date et heure de fin", - Select: "Sélectionner" - }; - const __vite_glob_0_0$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1Y - }, Symbol.toStringTag, { value: "Module" })); - const translations$1X = { - General: "Général", - "Related items": "Éléments liés", - "Datapoint Appearance": "Apparence du point de données", - "Form fields": "Champs du formulaire", - "Card title (optional)": "Titre de la carte (facultatif)", - "Pre-fill entities, devices, areas or labels that are always linked to recordings from this card.": "Préremplissez les entités, appareils, zones ou étiquettes toujours liés aux enregistrements créés avec cette carte.", - "Show always included targets on card": "Afficher sur la carte les cibles toujours incluses", - "Allow user to add more related items": "Autoriser l’utilisateur à ajouter d’autres éléments liés", - "Default icon": "Icône par défaut", - "Default colour": "Couleur par défaut", - "Show date & time field": "Afficher le champ date et heure", - "Show annotation field": "Afficher le champ d’annotation" - }; - const __vite_glob_0_1$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1X - }, Symbol.toStringTag, { value: "Module" })); - const translations$1W = { - "Date window:": "Fenêtre de dates :", - "Actual:": "Réel :" - }; - const __vite_glob_0_2$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1W - }, Symbol.toStringTag, { value: "Module" })); - const translations$1V = { - General: "Général", - Entity: "Entité", - "Multiple entities": "Plusieurs entités", - Display: "Affichage", - "Card title (optional)": "Titre de la carte (facultatif)", - "Hours to show": "Heures à afficher", - "Single entity": "Entité unique", - "Show data gaps": "Afficher les lacunes de données", - "Highlight missing data ranges with dashed lines and boundary markers": "Mettre en évidence les plages de données manquantes avec des lignes en pointillés et des marqueurs de bord" - }; - const __vite_glob_0_3$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1V - }, Symbol.toStringTag, { value: "Module" })); - const translations$1U = { - "Search datapoints…": "Rechercher des points de données…", - "Delete record": "Supprimer l’enregistrement", - Delete: "Supprimer", - "Show annotation": "Afficher l’annotation", - "Open related data point history": "Ouvrir l’historique du point de données lié", - "Edit record": "Modifier l’enregistrement", - "Show chart marker": "Afficher le marqueur du graphique", - "Hide chart marker": "Masquer le marqueur du graphique", - "Choose colour": "Choisir une couleur", - Save: "Enregistrer", - Cancel: "Annuler", - Message: "Message", - "Annotation / full message": "Annotation / message complet" - }; - const __vite_glob_0_4$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1U - }, Symbol.toStringTag, { value: "Module" })); - const translations$1T = { - General: "Général", - "Icon & colour": "Icône et couleur", - "Related items": "Éléments liés", - "Multiple entities": "Plusieurs entités", - "Form fields": "Champs du formulaire", - "Card title (optional)": "Titre de la carte (facultatif)", - "Input placeholder text": "Texte indicatif du champ", - Icon: "Icône", - Colour: "Couleur", - "These items will be linked to every record made with this card.": "Ces éléments seront liés à chaque enregistrement créé avec cette carte.", - "Single entity (optional)": "Entité unique (facultatif)", - "Add related items": "Ajouter des éléments liés", - "Show annotation field": "Afficher le champ d’annotation" - }; - const __vite_glob_0_5$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1T - }, Symbol.toStringTag, { value: "Module" })); - const translations$1S = { - Entity: "Entité", - Display: "Affichage", - "Records list": "Liste des enregistrements", - "Sensor entity *": "Entité capteur *", - "Override display name (optional)": "Remplacer le nom affiché (facultatif)", - "Hours to show": "Heures à afficher", - "Graph colour": "Couleur du graphique", - "Annotation style": "Style d’annotation", - "Circle on line": "Cercle sur la ligne", - "Dotted vertical line": "Ligne verticale pointillée", - "Show records list below graph": "Afficher la liste des enregistrements sous le graphique", - "Records per page (blank = show all)": "Enregistrements par page (vide = tout afficher)", - "Max records to show (blank = all)": "Nombre maximal d’enregistrements à afficher (vide = tous)", - "Show full message": "Afficher le message complet", - "User will be able to expand the row if hidden": "L’utilisateur pourra développer la ligne si elle est masquée" - }; - const __vite_glob_0_6$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1S - }, Symbol.toStringTag, { value: "Module" })); - const translations$1R = { - "⚠️ Anomaly Insight": "⚠️ Analyse d’anomalie", - "⚠️ Multi-method Anomaly": "⚠️ Anomalie multi-méthodes", - "Click the highlighted circle to add an annotation.": "Cliquez sur le cercle en surbrillance pour ajouter une annotation.", - "Alert:": "Alerte :", - "Confirmed by": "Confirmé par", - "methods:": "méthodes :", - "Trend deviation": "Écart de tendance", - "Sudden change": "Changement brusque", - "Statistical outlier (IQR)": "Valeur aberrante statistique (IQR)", - "Rolling Z-score": "Z-score glissant", - "Flat-line / stuck": "Stable / bloqué", - "Comparison window": "Fenêtre de comparaison", - "{0} deviates from its expected trend between {1} and {2}.": "{0} s’écarte de sa tendance attendue entre {1} et {2}.", - "{0} shows an unusual rate of change between {1} and {2}.": "{0} présente un taux de variation inhabituel entre {1} et {2}.", - "{0} contains statistical outliers between {1} and {2}.": "{0} contient des valeurs aberrantes statistiques entre {1} et {2}.", - "{0} shows statistically unusual values between {1} and {2}.": "{0} présente des valeurs statistiquement inhabituelles entre {1} et {2}.", - "{0} appears stuck or flat between {1} and {2}{3}.": "{0} semble bloqué ou stable entre {1} et {2}{3}.", - "{0} deviates significantly from the comparison window between {1} and {2}.": "{0} s’écarte significativement de la fenêtre de comparaison entre {1} et {2}.", - "Peak deviation: {0} from a baseline of {1} at {2}.": "Écart maximal : {0} par rapport à une base de {1} à {2}.", - "Peak rate deviation: {0} from a typical rate of {1} at {2}.": "Écart maximal de vitesse : {0} par rapport à une vitesse typique de {1} à {2}.", - "Peak value: {0}, deviating {1} from the median at {2}.": "Valeur maximale : {0}, s’écarte de {1} par rapport à la médiane à {2}.", - "Peak deviation: {0} from a rolling mean of {1} at {2}.": "Écart maximal : {0} par rapport à une moyenne mobile de {1} à {2}.", - "Value remained near {0} for an unusually long period.": "La valeur est restée proche de {0} pendant une durée inhabituellement longue.", - "Peak deviation from comparison: {0} at {1}.": "Écart maximal par rapport à la comparaison : {0} à {1}.", - " (range: {0})": " (plage : {0})", - "Date window": "Fenêtre de dates", - Trend: "Tendance", - Rate: "Vitesse", - Delta: "Delta", - Threshold: "Seuil" - }; - const __vite_glob_0_7$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1R - }, Symbol.toStringTag, { value: "Module" })); - const translations$1Q = { - "Confirm delete": "Confirmer la suppression", - "Are you sure you want to delete this item?": "Voulez-vous vraiment supprimer cet élément ?", - Cancel: "Annuler", - Delete: "Supprimer", - "Delete date window": "Supprimer la fenêtre de dates", - "this date window": "cette fenêtre de dates", - "Edit date window": "Modifier la fenêtre de dates", - "Add date window": "Ajouter une fenêtre de dates", - "Save date window": "Enregistrer la fenêtre de dates", - "Create date window": "Créer une fenêtre de dates" - }; - const __vite_glob_0_8$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1Q - }, Symbol.toStringTag, { value: "Module" })); - const translations$1P = { - Wk: "Sem.", - "Week of": "Semaine du" - }; - const __vite_glob_0_9$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1P - }, Symbol.toStringTag, { value: "Module" })); - const translations$1O = { - "Show anomalies": "Afficher les anomalies", - Sensitivity: "Sensibilité", - "Use downsampled data for detection": "Utiliser les données rééchantillonnées pour la détection", - "Rate window": "Fenêtre de variation", - "Rolling window": "Fenêtre glissante", - "Min flat duration": "Durée minimale de stabilité", - "Compare to window": "Comparer à la fenêtre", - "— select window —": "— sélectionner une fenêtre —", - "When methods overlap": "Lorsque les méthodes se chevauchent", - Low: "Faible", - Medium: "Moyenne", - High: "Élevée", - "Trend deviation": "Écart de tendance", - "Sudden change": "Changement brusque", - "Statistical outlier (IQR)": "Valeur aberrante statistique (IQR)", - "Rolling Z-score": "Z-score glissant", - "Flat-line / stuck value": "Valeur stable / bloquée", - "Comparison window deviation": "Écart par rapport à la fenêtre de comparaison", - "Flags points that deviate significantly from a fitted trend line. Good for catching gradual drift or sudden jumps away from a steady baseline.": "Signale les points qui s’écartent fortement d’une ligne de tendance ajustée. Idéal pour repérer une dérive progressive ou des sauts soudains loin d’une base stable.", - "Flags unusually fast rises or drops compared to the typical rate of change. Best for detecting spikes, crashes, or rapid transitions.": "Signale les hausses ou baisses inhabituellement rapides par rapport au taux de variation habituel. Idéal pour détecter des pics, des chutes ou des transitions rapides.", - "Uses the interquartile range to flag values far outside the normal spread of data. Robust against outliers that skew averages.": "Utilise l’intervalle interquartile pour signaler les valeurs très en dehors de la dispersion normale des données. Robuste face aux valeurs extrêmes qui faussent les moyennes.", - "Compares each value to a rolling mean and standard deviation. Catches unusual readings relative to recent context rather than the whole series.": "Compare chaque valeur à une moyenne mobile et à un écart-type. Détecte les lectures inhabituelles par rapport au contexte récent plutôt qu’à l’ensemble de la série.", - "Flags when a sensor reports nearly the same value for an unusually long time. Useful for detecting stuck sensors or frozen readings.": "Signale lorsqu’un capteur rapporte presque la même valeur pendant une durée inhabituellement longue. Utile pour détecter des capteurs bloqués ou des lectures figées.", - "Compares the current period to a reference date window. Highlights differences from an expected historical pattern, such as last week or the same day last year.": "Compare la période actuelle à une fenêtre de dates de référence. Met en évidence les différences par rapport à un modèle historique attendu, comme la semaine dernière ou le même jour l’année précédente.", - "Show all anomalies": "Afficher toutes les anomalies", - "Overlaps only": "Chevauchements uniquement", - "Computing…": "Calcul…", - "1 hour": "1 heure", - "3 hours": "3 heures", - "6 hours": "6 heures", - "12 hours": "12 heures", - "24 hours": "24 heures", - "7 days": "7 jours", - "30 minutes": "30 minutes" - }; - const __vite_glob_0_10$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1O - }, Symbol.toStringTag, { value: "Module" })); - const translations$1N = { - "Show delta vs selected date window": "Afficher le delta par rapport à la fenêtre de dates sélectionnée", - "Select a date window tab to enable delta analysis.": "Sélectionnez un onglet de fenêtre de dates pour activer l’analyse delta.", - "Show delta in tooltip": "Afficher le delta dans l’infobulle", - "Show delta lines": "Afficher les lignes delta" - }; - const __vite_glob_0_11$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1N - }, Symbol.toStringTag, { value: "Module" })); - const translations$1M = { - "Show rate of change": "Afficher le taux de variation", - "Show rate of change crosshairs": "Afficher les repères du taux de variation", - "Rate window": "Fenêtre de variation", - "Point to point": "Point à point", - "1 hour": "1 heure", - "6 hours": "6 heures", - "24 hours": "24 heures" - }; - const __vite_glob_0_12$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1M - }, Symbol.toStringTag, { value: "Module" })); - const translations$1L = { - Downsampling: "Rééchantillonnage", - Interval: "Intervalle", - Aggregate: "Agrégat", - "Raw (no sampling)": "Brut (sans échantillonnage)", - "5 seconds": "5 secondes", - "10 seconds": "10 secondes", - "15 seconds": "15 secondes", - "30 seconds": "30 secondes", - "1 minute": "1 minute", - "2 minutes": "2 minutes", - "5 minutes": "5 minutes", - "10 minutes": "10 minutes", - "15 minutes": "15 minutes", - "30 minutes": "30 minutes", - "1 hour": "1 heure", - "2 hours": "2 heures", - "3 hours": "3 heures", - "4 hours": "4 heures", - "6 hours": "6 heures", - "12 hours": "12 heures", - "24 hours": "24 heures", - "Mean (average)": "Moyenne", - Min: "Min", - Max: "Max", - Median: "Médiane", - First: "Premier", - Last: "Dernier" - }; - const __vite_glob_0_13$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1L - }, Symbol.toStringTag, { value: "Module" })); - const translations$1K = { - "Show min / max / mean": "Afficher min / max / moyenne", - "Show range shading": "Afficher l’ombrage de la plage" - }; - const __vite_glob_0_14$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1K - }, Symbol.toStringTag, { value: "Module" })); - const translations$1J = { - "Show threshold analysis": "Afficher l’analyse de seuil", - "Shade threshold area": "Ombrer la zone du seuil", - Threshold: "Seuil", - "Shade area": "Ombrer la zone", - "Shade above": "Ombrer au-dessus", - "Shade below": "Ombrer en dessous" - }; - const __vite_glob_0_15$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1J - }, Symbol.toStringTag, { value: "Module" })); - const translations$1I = { - "Show trend lines": "Afficher les lignes de tendance", - "Show trend crosshairs": "Afficher les repères de tendance", - "Trend method": "Méthode de tendance", - "Trend window": "Fenêtre de tendance", - "Rolling average": "Moyenne mobile", - "Linear trend": "Tendance linéaire", - "1 hour": "1 heure", - "6 hours": "6 heures", - "24 hours": "24 heures", - "7 days": "7 jours", - "14 days": "14 jours", - "21 days": "21 jours", - "28 days": "28 jours" - }; - const __vite_glob_0_16$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1I - }, Symbol.toStringTag, { value: "Module" })); - const translations$1H = { - "Add date window": "Ajouter une fenêtre de dates" - }; - const __vite_glob_0_17$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1H - }, Symbol.toStringTag, { value: "Module" })); - const translations$1G = { - "A date window saves a named date range as a tab, so you can quickly preview it against the selected range or jump the chart back to it later.": "Une fenêtre de dates enregistre une plage de dates nommée comme onglet afin que vous puissiez la prévisualiser rapidement par rapport à la plage sélectionnée ou y revenir plus tard dans le graphique.", - Name: "Nom", - "e.g. Heating season start": "ex. Début de la saison de chauffe", - "Date range": "Plage de dates", - Start: "Début", - End: "Fin", - "Use previous range": "Utiliser la plage précédente", - "Use next range": "Utiliser la plage suivante", - "Delete date window": "Supprimer la fenêtre de dates", - Cancel: "Annuler" - }; - const __vite_glob_0_18$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1G - }, Symbol.toStringTag, { value: "Module" })); - const translations$1F = { - Datapoints: "Points de données", - "Choose which annotation datapoints appear on the chart.": "Choisissez quels points de données d’annotation apparaissent sur le graphique.", - "Linked to selected targets": "Liés aux cibles sélectionnées", - "All datapoints": "Tous les points de données", - "Hide datapoints": "Masquer les points de données" - }; - const __vite_glob_0_19$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1F - }, Symbol.toStringTag, { value: "Module" })); - const translations$1E = {}; - const __vite_glob_0_20$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1E - }, Symbol.toStringTag, { value: "Module" })); - const translations$1D = { - "Analysis configured": "Analyse configurée", - "Configure analysis": "Configurer l’analyse", - "Stepped series": "Série en escalier", - "Hide source series": "Masquer la série source", - "All targets already have the same settings": "Toutes les cibles ont déjà les mêmes paramètres", - "Copy these analysis settings to all targets": "Copier ces paramètres d’analyse vers toutes les cibles", - "Copy to all targets": "Copier vers toutes les cibles" - }; - const __vite_glob_0_21$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1D - }, Symbol.toStringTag, { value: "Module" })); - const translations$1C = { - Targets: "Cibles", - "Each row controls one chart series.": "Chaque ligne contrôle une série du graphique.", - "Add target": "Ajouter une cible", - "Chart preferences": "Préférences du graphique" - }; - const __vite_glob_0_22$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1C - }, Symbol.toStringTag, { value: "Module" })); - const translations$1B = { - "Loading Datapoints…": "Chargement des points de données…", - Datapoints: "Points de données", - "Page options": "Options de la page", - "Download spreadsheet": "Télécharger la feuille de calcul", - "Save page state": "Enregistrer l’état de la page", - "Restore saved page": "Restaurer la page enregistrée", - "Clear saved page": "Effacer la page enregistrée", - "Expand targets sidebar": "Développer la barre latérale des cibles", - "Collapse targets sidebar": "Réduire la barre latérale des cibles" - }; - const __vite_glob_0_23$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1B - }, Symbol.toStringTag, { value: "Module" })); - const translations$1A = { - "Toggle sidebar": "Basculer la barre latérale", - Start: "Début", - End: "Fin", - "Select date range": "Sélectionner une plage de dates", - "Timeline options": "Options de la chronologie", - "Zoom level": "Niveau de zoom", - "Date snapping": "Ajustement des dates", - Auto: "Auto", - Hour: "Heure", - Day: "Jour", - Week: "Semaine", - Month: "Mois", - Minute: "Minute", - Second: "Seconde", - Quarterly: "Trimestriel", - "Month Compressed": "Mois compact", - "Month Short": "Mois abrégé", - "Month Expanded": "Mois développé", - "Week Compressed": "Semaine compacte", - "Week Expanded": "Semaine développée" - }; - const __vite_glob_0_24$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1A - }, Symbol.toStringTag, { value: "Module" })); - const modules$4 = /* @__PURE__ */ Object.assign({ - "../../../atoms/interactive/range-timeline/i18n/fr.ts": __vite_glob_0_0$4, - "../../../cards/action/i18n/fr.ts": __vite_glob_0_1$4, - "../../../cards/history/history-chart/i18n/fr.ts": __vite_glob_0_2$4, - "../../../cards/history/i18n/fr.ts": __vite_glob_0_3$4, - "../../../cards/list/i18n/fr.ts": __vite_glob_0_4$4, - "../../../cards/quick/i18n/fr.ts": __vite_glob_0_5$4, - "../../../cards/sensor/i18n/fr.ts": __vite_glob_0_6$4, - "../../chart/i18n/fr.ts": __vite_glob_0_7$4, - "../../ha/i18n/fr.ts": __vite_glob_0_8$4, - "../../timeline/i18n/fr.ts": __vite_glob_0_9$4, - "../../../molecules/analysis-anomaly-group/i18n/fr.ts": __vite_glob_0_10$4, - "../../../molecules/analysis-delta-group/i18n/fr.ts": __vite_glob_0_11$4, - "../../../molecules/analysis-rate-group/i18n/fr.ts": __vite_glob_0_12$4, - "../../../molecules/analysis-sample-group/i18n/fr.ts": __vite_glob_0_13$4, - "../../../molecules/analysis-summary-group/i18n/fr.ts": __vite_glob_0_14$4, - "../../../molecules/analysis-threshold-group/i18n/fr.ts": __vite_glob_0_15$4, - "../../../molecules/analysis-trend-group/i18n/fr.ts": __vite_glob_0_16$4, - "../../../molecules/comparison-tab-rail/i18n/fr.ts": __vite_glob_0_17$4, - "../../../molecules/date-window-dialog/i18n/fr.ts": __vite_glob_0_18$4, - "../../../molecules/sidebar-options/sections/i18n/fr.ts": __vite_glob_0_19$4, - "../../../molecules/target-row-list/i18n/fr.ts": __vite_glob_0_20$4, - "../../../molecules/target-row/i18n/fr.ts": __vite_glob_0_21$4, - "../../../panels/datapoints/components/history-targets/i18n/fr.ts": __vite_glob_0_22$4, - "../../../panels/datapoints/components/panel-shell/i18n/fr.ts": __vite_glob_0_23$4, - "../../../panels/datapoints/components/range-toolbar/i18n/fr.ts": __vite_glob_0_24$4 - }); - const merged$4 = {}; - for (const mod of Object.values(modules$4)) { - Object.assign(merged$4, mod.translations); - } - const templates$4 = merged$4; - const fr = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - templates: templates$4 - }, Symbol.toStringTag, { value: "Module" })); - const translations$1z = { - "Updates with new data": "Aktualisiert sich mit neuen Daten", - "Scroll to selected range": "Zum ausgewählten Bereich scrollen", - "Start date and time": "Startdatum und -zeit", - "End date and time": "Enddatum und -zeit", - Select: "Auswählen" - }; - const __vite_glob_0_0$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1z - }, Symbol.toStringTag, { value: "Module" })); - const translations$1y = { - General: "Allgemein", - "Related items": "Verknüpfte Elemente", - "Datapoint Appearance": "Darstellung des Datenpunkts", - "Form fields": "Formularfelder", - "Card title (optional)": "Kartentitel (optional)", - "Pre-fill entities, devices, areas or labels that are always linked to recordings from this card.": "Entitäten, Geräte, Bereiche oder Labels vorab ausfüllen, die immer mit Aufzeichnungen dieser Karte verknüpft sind.", - "Show always included targets on card": "Immer enthaltene Ziele auf der Karte anzeigen", - "Allow user to add more related items": "Benutzer dürfen weitere verknüpfte Elemente hinzufügen", - "Default icon": "Standardsymbol", - "Default colour": "Standardfarbe", - "Show date & time field": "Datums- und Uhrzeitfeld anzeigen", - "Show annotation field": "Anmerkungsfeld anzeigen" - }; - const __vite_glob_0_1$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1y - }, Symbol.toStringTag, { value: "Module" })); - const translations$1x = { - "Date window:": "Datumsfenster:", - "Actual:": "Tatsächlich:" - }; - const __vite_glob_0_2$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1x - }, Symbol.toStringTag, { value: "Module" })); - const translations$1w = { - General: "Allgemein", - Entity: "Entität", - "Multiple entities": "Mehrere Entitäten", - Display: "Anzeige", - "Card title (optional)": "Kartentitel (optional)", - "Hours to show": "Anzuzeigende Stunden", - "Single entity": "Einzelne Entität", - "Show data gaps": "Datenlücken anzeigen", - "Highlight missing data ranges with dashed lines and boundary markers": "Fehlende Datenbereiche mit gestrichelten Linien und Randmarkierungen hervorheben" - }; - const __vite_glob_0_3$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1w - }, Symbol.toStringTag, { value: "Module" })); - const translations$1v = { - "Search datapoints…": "Datenpunkte suchen…", - "Delete record": "Eintrag löschen", - Delete: "Löschen", - "Show annotation": "Anmerkung anzeigen", - "Open related data point history": "Verlauf des verknüpften Datenpunkts öffnen", - "Edit record": "Eintrag bearbeiten", - "Show chart marker": "Diagrammmarkierung anzeigen", - "Hide chart marker": "Diagrammmarkierung ausblenden", - "Choose colour": "Farbe auswählen", - Save: "Speichern", - Cancel: "Abbrechen", - Message: "Nachricht", - "Annotation / full message": "Anmerkung / vollständige Nachricht" - }; - const __vite_glob_0_4$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1v - }, Symbol.toStringTag, { value: "Module" })); - const translations$1u = { - General: "Allgemein", - "Icon & colour": "Symbol und Farbe", - "Related items": "Verknüpfte Elemente", - "Multiple entities": "Mehrere Entitäten", - "Form fields": "Formularfelder", - "Card title (optional)": "Kartentitel (optional)", - "Input placeholder text": "Platzhaltertext", - Icon: "Symbol", - Colour: "Farbe", - "These items will be linked to every record made with this card.": "Diese Elemente werden mit jedem Eintrag verknüpft, der mit dieser Karte erstellt wird.", - "Single entity (optional)": "Einzelne Entität (optional)", - "Add related items": "Verknüpfte Elemente hinzufügen", - "Show annotation field": "Anmerkungsfeld anzeigen" - }; - const __vite_glob_0_5$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1u - }, Symbol.toStringTag, { value: "Module" })); - const translations$1t = { - Entity: "Entität", - Display: "Anzeige", - "Records list": "Eintragsliste", - "Sensor entity *": "Sensor-Entität *", - "Override display name (optional)": "Anzeigenamen überschreiben (optional)", - "Hours to show": "Anzuzeigende Stunden", - "Graph colour": "Diagrammfarbe", - "Annotation style": "Anmerkungsstil", - "Circle on line": "Kreis auf der Linie", - "Dotted vertical line": "Gepunktete vertikale Linie", - "Show records list below graph": "Eintragsliste unter dem Diagramm anzeigen", - "Records per page (blank = show all)": "Einträge pro Seite (leer = alle anzeigen)", - "Max records to show (blank = all)": "Maximal anzuzeigende Einträge (leer = alle)", - "Show full message": "Vollständige Nachricht anzeigen", - "User will be able to expand the row if hidden": "Benutzer können die Zeile erweitern, wenn sie ausgeblendet ist" - }; - const __vite_glob_0_6$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1t - }, Symbol.toStringTag, { value: "Module" })); - const translations$1s = { - "⚠️ Anomaly Insight": "⚠️ Anomalie-Einblick", - "⚠️ Multi-method Anomaly": "⚠️ Anomalie mit mehreren Methoden", - "Click the highlighted circle to add an annotation.": "Klicken Sie auf den hervorgehobenen Kreis, um eine Anmerkung hinzuzufügen.", - "Alert:": "Warnung:", - "Confirmed by": "Bestätigt durch", - "methods:": "Methoden:", - "Trend deviation": "Trendabweichung", - "Sudden change": "Plötzliche Änderung", - "Statistical outlier (IQR)": "Statistischer Ausreißer (IQR)", - "Rolling Z-score": "Gleitender Z-Score", - "Flat-line / stuck": "Flach / festhängend", - "Comparison window": "Vergleichsfenster", - "{0} deviates from its expected trend between {1} and {2}.": "{0} weicht zwischen {1} und {2} von seinem erwarteten Trend ab.", - "{0} shows an unusual rate of change between {1} and {2}.": "{0} zeigt zwischen {1} und {2} eine ungewöhnliche Änderungsrate.", - "{0} contains statistical outliers between {1} and {2}.": "{0} enthält statistische Ausreißer zwischen {1} und {2}.", - "{0} shows statistically unusual values between {1} and {2}.": "{0} zeigt zwischen {1} und {2} statistisch ungewöhnliche Werte.", - "{0} appears stuck or flat between {1} and {2}{3}.": "{0} scheint zwischen {1} und {2}{3} festzuhängen oder flach zu sein.", - "{0} deviates significantly from the comparison window between {1} and {2}.": "{0} weicht zwischen {1} und {2} deutlich vom Vergleichsfenster ab.", - "Peak deviation: {0} from a baseline of {1} at {2}.": "Maximale Abweichung: {0} von einer Basis von {1} um {2}.", - "Peak rate deviation: {0} from a typical rate of {1} at {2}.": "Maximale Ratenabweichung: {0} von einer typischen Rate von {1} um {2}.", - "Peak value: {0}, deviating {1} from the median at {2}.": "Spitzenwert: {0}, weicht um {1} vom Median bei {2} ab.", - "Peak deviation: {0} from a rolling mean of {1} at {2}.": "Maximale Abweichung: {0} von einem gleitenden Mittelwert von {1} um {2}.", - "Value remained near {0} for an unusually long period.": "Der Wert blieb ungewöhnlich lange nahe bei {0}.", - "Peak deviation from comparison: {0} at {1}.": "Maximale Abweichung vom Vergleich: {0} um {1}.", - " (range: {0})": " (Bereich: {0})", - "Date window": "Datumsfenster", - Trend: "Trend", - Rate: "Rate", - Delta: "Delta", - Threshold: "Schwellenwert" - }; - const __vite_glob_0_7$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1s - }, Symbol.toStringTag, { value: "Module" })); - const translations$1r = { - "Confirm delete": "Löschen bestätigen", - "Are you sure you want to delete this item?": "Möchten Sie dieses Element wirklich löschen?", - Cancel: "Abbrechen", - Delete: "Löschen", - "Delete date window": "Datumsfenster löschen", - "this date window": "dieses Datumsfenster", - "Edit date window": "Datumsfenster bearbeiten", - "Add date window": "Datumsfenster hinzufügen", - "Save date window": "Datumsfenster speichern", - "Create date window": "Datumsfenster erstellen" - }; - const __vite_glob_0_8$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1r - }, Symbol.toStringTag, { value: "Module" })); - const translations$1q = { - Wk: "KW", - "Week of": "Woche von" - }; - const __vite_glob_0_9$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1q - }, Symbol.toStringTag, { value: "Module" })); - const translations$1p = { - "Show anomalies": "Anomalien anzeigen", - Sensitivity: "Empfindlichkeit", - "Use downsampled data for detection": "Heruntergesampelte Daten für die Erkennung verwenden", - "Rate window": "Ratenfenster", - "Rolling window": "Gleitendes Fenster", - "Min flat duration": "Minimale Flachdauer", - "Compare to window": "Mit Fenster vergleichen", - "— select window —": "— Fenster auswählen —", - "When methods overlap": "Wenn sich Methoden überschneiden", - Low: "Niedrig", - Medium: "Mittel", - High: "Hoch", - "Trend deviation": "Trendabweichung", - "Sudden change": "Plötzliche Änderung", - "Statistical outlier (IQR)": "Statistischer Ausreißer (IQR)", - "Rolling Z-score": "Gleitender Z-Score", - "Flat-line / stuck value": "Flacher / festhängender Wert", - "Comparison window deviation": "Abweichung vom Vergleichsfenster", - "Flags points that deviate significantly from a fitted trend line. Good for catching gradual drift or sudden jumps away from a steady baseline.": "Markiert Punkte, die deutlich von einer angepassten Trendlinie abweichen. Gut geeignet, um schleichende Drift oder plötzliche Sprünge von einer stabilen Basis zu erkennen.", - "Flags unusually fast rises or drops compared to the typical rate of change. Best for detecting spikes, crashes, or rapid transitions.": "Markiert ungewöhnlich schnelle Anstiege oder Abfälle im Vergleich zur typischen Änderungsrate. Ideal zum Erkennen von Spitzen, Einbrüchen oder schnellen Übergängen.", - "Uses the interquartile range to flag values far outside the normal spread of data. Robust against outliers that skew averages.": "Verwendet den Interquartilsabstand, um Werte zu markieren, die weit außerhalb der normalen Datenstreuung liegen. Robust gegenüber Ausreißern, die Mittelwerte verzerren.", - "Compares each value to a rolling mean and standard deviation. Catches unusual readings relative to recent context rather than the whole series.": "Vergleicht jeden Wert mit einem gleitenden Mittelwert und einer Standardabweichung. Erkennt ungewöhnliche Werte relativ zum jüngsten Kontext statt zur gesamten Serie.", - "Flags when a sensor reports nearly the same value for an unusually long time. Useful for detecting stuck sensors or frozen readings.": "Markiert, wenn ein Sensor ungewöhnlich lange nahezu denselben Wert meldet. Nützlich zum Erkennen festhängender Sensoren oder eingefrorener Messwerte.", - "Compares the current period to a reference date window. Highlights differences from an expected historical pattern, such as last week or the same day last year.": "Vergleicht den aktuellen Zeitraum mit einem Referenz-Datumsfenster. Hebt Unterschiede zu einem erwarteten historischen Muster hervor, etwa zur letzten Woche oder zum gleichen Tag im Vorjahr.", - "Show all anomalies": "Alle Anomalien anzeigen", - "Overlaps only": "Nur Überlappungen", - "Computing…": "Wird berechnet…", - "1 hour": "1 Stunde", - "3 hours": "3 Stunden", - "6 hours": "6 Stunden", - "12 hours": "12 Stunden", - "24 hours": "24 Stunden", - "7 days": "7 Tage", - "30 minutes": "30 Minuten" - }; - const __vite_glob_0_10$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1p - }, Symbol.toStringTag, { value: "Module" })); - const translations$1o = { - "Show delta vs selected date window": "Delta gegenüber dem ausgewählten Datumsfenster anzeigen", - "Select a date window tab to enable delta analysis.": "Wählen Sie einen Tab für ein Datumsfenster, um die Delta-Analyse zu aktivieren.", - "Show delta in tooltip": "Delta im Tooltip anzeigen", - "Show delta lines": "Delta-Linien anzeigen" - }; - const __vite_glob_0_11$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1o - }, Symbol.toStringTag, { value: "Module" })); - const translations$1n = { - "Show rate of change": "Änderungsrate anzeigen", - "Show rate of change crosshairs": "Fadenkreuz für Änderungsrate anzeigen", - "Rate window": "Ratenfenster", - "Point to point": "Punkt zu Punkt", - "1 hour": "1 Stunde", - "6 hours": "6 Stunden", - "24 hours": "24 Stunden" - }; - const __vite_glob_0_12$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1n - }, Symbol.toStringTag, { value: "Module" })); - const translations$1m = { - Downsampling: "Downsampling", - Interval: "Intervall", - Aggregate: "Aggregation", - "Raw (no sampling)": "Roh (ohne Sampling)", - "5 seconds": "5 Sekunden", - "10 seconds": "10 Sekunden", - "15 seconds": "15 Sekunden", - "30 seconds": "30 Sekunden", - "1 minute": "1 Minute", - "2 minutes": "2 Minuten", - "5 minutes": "5 Minuten", - "10 minutes": "10 Minuten", - "15 minutes": "15 Minuten", - "30 minutes": "30 Minuten", - "1 hour": "1 Stunde", - "2 hours": "2 Stunden", - "3 hours": "3 Stunden", - "4 hours": "4 Stunden", - "6 hours": "6 Stunden", - "12 hours": "12 Stunden", - "24 hours": "24 Stunden", - "Mean (average)": "Mittelwert", - Min: "Min.", - Max: "Max.", - Median: "Median", - First: "Erster", - Last: "Letzter" - }; - const __vite_glob_0_13$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1m - }, Symbol.toStringTag, { value: "Module" })); - const translations$1l = { - "Show min / max / mean": "Min / Max / Mittelwert anzeigen", - "Show range shading": "Bereichsschattierung anzeigen" - }; - const __vite_glob_0_14$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1l - }, Symbol.toStringTag, { value: "Module" })); - const translations$1k = { - "Show threshold analysis": "Schwellwertanalyse anzeigen", - "Shade threshold area": "Schwellwertbereich schattieren", - Threshold: "Schwellenwert", - "Shade area": "Bereich schattieren", - "Shade above": "Oberhalb schattieren", - "Shade below": "Unterhalb schattieren" - }; - const __vite_glob_0_15$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1k - }, Symbol.toStringTag, { value: "Module" })); - const translations$1j = { - "Show trend lines": "Trendlinien anzeigen", - "Show trend crosshairs": "Fadenkreuz für Trend anzeigen", - "Trend method": "Trendmethode", - "Trend window": "Trendfenster", - "Rolling average": "Gleitender Durchschnitt", - "Linear trend": "Linearer Trend", - "1 hour": "1 Stunde", - "6 hours": "6 Stunden", - "24 hours": "24 Stunden", - "7 days": "7 Tage", - "14 days": "14 Tage", - "21 days": "21 Tage", - "28 days": "28 Tage" - }; - const __vite_glob_0_16$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1j - }, Symbol.toStringTag, { value: "Module" })); - const translations$1i = { - "Add date window": "Datumsfenster hinzufügen" - }; - const __vite_glob_0_17$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1i - }, Symbol.toStringTag, { value: "Module" })); - const translations$1h = { - "A date window saves a named date range as a tab, so you can quickly preview it against the selected range or jump the chart back to it later.": "Ein Datumsfenster speichert einen benannten Datumsbereich als Registerkarte, damit Sie ihn schnell mit dem ausgewählten Bereich vergleichen oder später im Diagramm wieder dorthin springen können.", - Name: "Name", - "e.g. Heating season start": "z. B. Beginn der Heizsaison", - "Date range": "Datumsbereich", - Start: "Start", - End: "Ende", - "Use previous range": "Vorherigen Bereich verwenden", - "Use next range": "Nächsten Bereich verwenden", - "Delete date window": "Datumsfenster löschen", - Cancel: "Abbrechen" - }; - const __vite_glob_0_18$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1h - }, Symbol.toStringTag, { value: "Module" })); - const translations$1g = { - Datapoints: "Datenpunkte", - "Choose which annotation datapoints appear on the chart.": "Wählen Sie aus, welche Anmerkungs-Datenpunkte im Diagramm angezeigt werden.", - "Linked to selected targets": "Mit ausgewählten Zielen verknüpft", - "All datapoints": "Alle Datenpunkte", - "Hide datapoints": "Datenpunkte ausblenden" - }; - const __vite_glob_0_19$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1g - }, Symbol.toStringTag, { value: "Module" })); - const translations$1f = {}; - const __vite_glob_0_20$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1f - }, Symbol.toStringTag, { value: "Module" })); - const translations$1e = { - "Analysis configured": "Analyse konfiguriert", - "Configure analysis": "Analyse konfigurieren", - "Stepped series": "Stufenserie", - "Hide source series": "Quellserie ausblenden", - "All targets already have the same settings": "Alle Ziele haben bereits dieselben Einstellungen", - "Copy these analysis settings to all targets": "Diese Analyseeinstellungen auf alle Ziele kopieren", - "Copy to all targets": "Auf alle Ziele kopieren" - }; - const __vite_glob_0_21$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1e - }, Symbol.toStringTag, { value: "Module" })); - const translations$1d = { - Targets: "Ziele", - "Each row controls one chart series.": "Jede Zeile steuert eine Diagrammserie.", - "Add target": "Ziel hinzufügen", - "Chart preferences": "Diagrammeinstellungen" - }; - const __vite_glob_0_22$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1d - }, Symbol.toStringTag, { value: "Module" })); - const translations$1c = { - "Loading Datapoints…": "Datenpunkte werden geladen…", - Datapoints: "Datenpunkte", - "Page options": "Seitenoptionen", - "Download spreadsheet": "Tabellenblatt herunterladen", - "Save page state": "Seitenstatus speichern", - "Restore saved page": "Gespeicherte Seite wiederherstellen", - "Clear saved page": "Gespeicherte Seite löschen", - "Expand targets sidebar": "Ziel-Seitenleiste ausklappen", - "Collapse targets sidebar": "Ziel-Seitenleiste einklappen" - }; - const __vite_glob_0_23$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1c - }, Symbol.toStringTag, { value: "Module" })); - const translations$1b = { - "Toggle sidebar": "Seitenleiste umschalten", - Start: "Start", - End: "Ende", - "Select date range": "Datumsbereich auswählen", - "Timeline options": "Zeitachsenoptionen", - "Zoom level": "Zoomstufe", - "Date snapping": "Datumsraster", - Auto: "Auto", - Hour: "Stunde", - Day: "Tag", - Week: "Woche", - Month: "Monat", - Minute: "Minute", - Second: "Sekunde", - Quarterly: "Quartalsweise", - "Month Compressed": "Monat kompakt", - "Month Short": "Monat kurz", - "Month Expanded": "Monat erweitert", - "Week Compressed": "Woche kompakt", - "Week Expanded": "Woche erweitert" - }; - const __vite_glob_0_24$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1b - }, Symbol.toStringTag, { value: "Module" })); - const modules$3 = /* @__PURE__ */ Object.assign({ - "../../../atoms/interactive/range-timeline/i18n/de.ts": __vite_glob_0_0$3, - "../../../cards/action/i18n/de.ts": __vite_glob_0_1$3, - "../../../cards/history/history-chart/i18n/de.ts": __vite_glob_0_2$3, - "../../../cards/history/i18n/de.ts": __vite_glob_0_3$3, - "../../../cards/list/i18n/de.ts": __vite_glob_0_4$3, - "../../../cards/quick/i18n/de.ts": __vite_glob_0_5$3, - "../../../cards/sensor/i18n/de.ts": __vite_glob_0_6$3, - "../../chart/i18n/de.ts": __vite_glob_0_7$3, - "../../ha/i18n/de.ts": __vite_glob_0_8$3, - "../../timeline/i18n/de.ts": __vite_glob_0_9$3, - "../../../molecules/analysis-anomaly-group/i18n/de.ts": __vite_glob_0_10$3, - "../../../molecules/analysis-delta-group/i18n/de.ts": __vite_glob_0_11$3, - "../../../molecules/analysis-rate-group/i18n/de.ts": __vite_glob_0_12$3, - "../../../molecules/analysis-sample-group/i18n/de.ts": __vite_glob_0_13$3, - "../../../molecules/analysis-summary-group/i18n/de.ts": __vite_glob_0_14$3, - "../../../molecules/analysis-threshold-group/i18n/de.ts": __vite_glob_0_15$3, - "../../../molecules/analysis-trend-group/i18n/de.ts": __vite_glob_0_16$3, - "../../../molecules/comparison-tab-rail/i18n/de.ts": __vite_glob_0_17$3, - "../../../molecules/date-window-dialog/i18n/de.ts": __vite_glob_0_18$3, - "../../../molecules/sidebar-options/sections/i18n/de.ts": __vite_glob_0_19$3, - "../../../molecules/target-row-list/i18n/de.ts": __vite_glob_0_20$3, - "../../../molecules/target-row/i18n/de.ts": __vite_glob_0_21$3, - "../../../panels/datapoints/components/history-targets/i18n/de.ts": __vite_glob_0_22$3, - "../../../panels/datapoints/components/panel-shell/i18n/de.ts": __vite_glob_0_23$3, - "../../../panels/datapoints/components/range-toolbar/i18n/de.ts": __vite_glob_0_24$3 - }); - const merged$3 = {}; - for (const mod of Object.values(modules$3)) { - Object.assign(merged$3, mod.translations); - } - const templates$3 = merged$3; - const de = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - templates: templates$3 - }, Symbol.toStringTag, { value: "Module" })); - const translations$1a = { - "Updates with new data": "Se actualiza con datos nuevos", - "Scroll to selected range": "Desplazarse al rango seleccionado", - "Start date and time": "Fecha y hora de inicio", - "End date and time": "Fecha y hora de fin", - Select: "Seleccionar" - }; - const __vite_glob_0_0$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1a - }, Symbol.toStringTag, { value: "Module" })); - const translations$19 = { - General: "General", - "Related items": "Elementos relacionados", - "Datapoint Appearance": "Apariencia del punto de datos", - "Form fields": "Campos del formulario", - "Card title (optional)": "Título de la tarjeta (opcional)", - "Pre-fill entities, devices, areas or labels that are always linked to recordings from this card.": "Rellena previamente entidades, dispositivos, áreas o etiquetas que siempre estarán vinculados a los registros creados con esta tarjeta.", - "Show always included targets on card": "Mostrar en la tarjeta los objetivos siempre incluidos", - "Allow user to add more related items": "Permitir que el usuario añada más elementos relacionados", - "Default icon": "Icono predeterminado", - "Default colour": "Color predeterminado", - "Show date & time field": "Mostrar campo de fecha y hora", - "Show annotation field": "Mostrar campo de anotación" - }; - const __vite_glob_0_1$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$19 - }, Symbol.toStringTag, { value: "Module" })); - const translations$18 = { - "Date window:": "Ventana de fechas:", - "Actual:": "Real:" - }; - const __vite_glob_0_2$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$18 - }, Symbol.toStringTag, { value: "Module" })); - const translations$17 = { - General: "General", - Entity: "Entidad", - "Multiple entities": "Varias entidades", - Display: "Visualización", - "Card title (optional)": "Título de la tarjeta (opcional)", - "Hours to show": "Horas a mostrar", - "Single entity": "Entidad única", - "Show data gaps": "Mostrar huecos de datos", - "Highlight missing data ranges with dashed lines and boundary markers": "Resaltar los rangos de datos faltantes con líneas discontinuas y marcadores de límite" - }; - const __vite_glob_0_3$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$17 - }, Symbol.toStringTag, { value: "Module" })); - const translations$16 = { - "Search datapoints…": "Buscar puntos de datos…", - "Delete record": "Eliminar registro", - Delete: "Eliminar", - "Show annotation": "Mostrar anotación", - "Open related data point history": "Abrir el historial del punto de datos relacionado", - "Edit record": "Editar registro", - "Show chart marker": "Mostrar marcador del gráfico", - "Hide chart marker": "Ocultar marcador del gráfico", - "Choose colour": "Elegir color", - Save: "Guardar", - Cancel: "Cancelar", - Message: "Mensaje", - "Annotation / full message": "Anotación / mensaje completo" - }; - const __vite_glob_0_4$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$16 - }, Symbol.toStringTag, { value: "Module" })); - const translations$15 = { - General: "General", - "Icon & colour": "Icono y color", - "Related items": "Elementos relacionados", - "Multiple entities": "Varias entidades", - "Form fields": "Campos del formulario", - "Card title (optional)": "Título de la tarjeta (opcional)", - "Input placeholder text": "Texto de marcador de posición", - Icon: "Icono", - Colour: "Color", - "These items will be linked to every record made with this card.": "Estos elementos se vincularán a cada registro creado con esta tarjeta.", - "Single entity (optional)": "Entidad única (opcional)", - "Add related items": "Añadir elementos relacionados", - "Show annotation field": "Mostrar campo de anotación" - }; - const __vite_glob_0_5$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$15 - }, Symbol.toStringTag, { value: "Module" })); - const translations$14 = { - Entity: "Entidad", - Display: "Visualización", - "Records list": "Lista de registros", - "Sensor entity *": "Entidad de sensor *", - "Override display name (optional)": "Sobrescribir nombre visible (opcional)", - "Hours to show": "Horas a mostrar", - "Graph colour": "Color del gráfico", - "Annotation style": "Estilo de anotación", - "Circle on line": "Círculo sobre la línea", - "Dotted vertical line": "Línea vertical punteada", - "Show records list below graph": "Mostrar lista de registros debajo del gráfico", - "Records per page (blank = show all)": "Registros por página (en blanco = mostrar todos)", - "Max records to show (blank = all)": "Máximo de registros a mostrar (en blanco = todos)", - "Show full message": "Mostrar mensaje completo", - "User will be able to expand the row if hidden": "El usuario podrá ampliar la fila si está oculta" - }; - const __vite_glob_0_6$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$14 - }, Symbol.toStringTag, { value: "Module" })); - const translations$13 = { - "⚠️ Anomaly Insight": "⚠️ Información de anomalía", - "⚠️ Multi-method Anomaly": "⚠️ Anomalía multimétodo", - "Click the highlighted circle to add an annotation.": "Haz clic en el círculo resaltado para añadir una anotación.", - "Alert:": "Alerta:", - "Confirmed by": "Confirmado por", - "methods:": "métodos:", - "Trend deviation": "Desviación de tendencia", - "Sudden change": "Cambio repentino", - "Statistical outlier (IQR)": "Valor atípico estadístico (IQR)", - "Rolling Z-score": "Puntuación Z móvil", - "Flat-line / stuck": "Plano / atascado", - "Comparison window": "Ventana de comparación", - "{0} deviates from its expected trend between {1} and {2}.": "{0} se desvía de su tendencia esperada entre {1} y {2}.", - "{0} shows an unusual rate of change between {1} and {2}.": "{0} muestra una tasa de cambio inusual entre {1} y {2}.", - "{0} contains statistical outliers between {1} and {2}.": "{0} contiene valores atípicos estadísticos entre {1} y {2}.", - "{0} shows statistically unusual values between {1} and {2}.": "{0} muestra valores estadísticamente inusuales entre {1} y {2}.", - "{0} appears stuck or flat between {1} and {2}{3}.": "{0} parece atascado o plano entre {1} y {2}{3}.", - "{0} deviates significantly from the comparison window between {1} and {2}.": "{0} se desvía significativamente de la ventana de comparación entre {1} y {2}.", - "Peak deviation: {0} from a baseline of {1} at {2}.": "Desviación máxima: {0} respecto a una base de {1} en {2}.", - "Peak rate deviation: {0} from a typical rate of {1} at {2}.": "Desviación máxima de la tasa: {0} respecto a una tasa típica de {1} en {2}.", - "Peak value: {0}, deviating {1} from the median at {2}.": "Valor máximo: {0}, con una desviación de {1} respecto a la mediana en {2}.", - "Peak deviation: {0} from a rolling mean of {1} at {2}.": "Desviación máxima: {0} respecto a una media móvil de {1} en {2}.", - "Value remained near {0} for an unusually long period.": "El valor permaneció cerca de {0} durante un período inusualmente largo.", - "Peak deviation from comparison: {0} at {1}.": "Desviación máxima respecto a la comparación: {0} en {1}.", - " (range: {0})": " (rango: {0})", - "Date window": "Ventana de fechas", - Trend: "Tendencia", - Rate: "Tasa", - Delta: "Delta", - Threshold: "Umbral" - }; - const __vite_glob_0_7$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$13 - }, Symbol.toStringTag, { value: "Module" })); - const translations$12 = { - "Confirm delete": "Confirmar eliminación", - "Are you sure you want to delete this item?": "¿Seguro que quieres eliminar este elemento?", - Cancel: "Cancelar", - Delete: "Eliminar", - "Delete date window": "Eliminar ventana de fechas", - "this date window": "esta ventana de fechas", - "Edit date window": "Editar ventana de fechas", - "Add date window": "Añadir ventana de fechas", - "Save date window": "Guardar ventana de fechas", - "Create date window": "Crear ventana de fechas" - }; - const __vite_glob_0_8$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$12 - }, Symbol.toStringTag, { value: "Module" })); - const translations$11 = { - Wk: "Sem.", - "Week of": "Semana del" - }; - const __vite_glob_0_9$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$11 - }, Symbol.toStringTag, { value: "Module" })); - const translations$10 = { - "Show anomalies": "Mostrar anomalías", - Sensitivity: "Sensibilidad", - "Use downsampled data for detection": "Usar datos submuestreados para la detección", - "Rate window": "Ventana de tasa", - "Rolling window": "Ventana móvil", - "Min flat duration": "Duración mínima plana", - "Compare to window": "Comparar con la ventana", - "— select window —": "— seleccionar ventana —", - "When methods overlap": "Cuando los métodos se superponen", - Low: "Baja", - Medium: "Media", - High: "Alta", - "Trend deviation": "Desviación de tendencia", - "Sudden change": "Cambio repentino", - "Statistical outlier (IQR)": "Valor atípico estadístico (IQR)", - "Rolling Z-score": "Puntuación Z móvil", - "Flat-line / stuck value": "Valor plano / atascado", - "Comparison window deviation": "Desviación respecto a la ventana de comparación", - "Flags points that deviate significantly from a fitted trend line. Good for catching gradual drift or sudden jumps away from a steady baseline.": "Marca puntos que se desvían significativamente de una línea de tendencia ajustada. Es útil para detectar una deriva gradual o saltos bruscos desde una base estable.", - "Flags unusually fast rises or drops compared to the typical rate of change. Best for detecting spikes, crashes, or rapid transitions.": "Marca subidas o bajadas inusualmente rápidas en comparación con la tasa de cambio típica. Es ideal para detectar picos, caídas o transiciones rápidas.", - "Uses the interquartile range to flag values far outside the normal spread of data. Robust against outliers that skew averages.": "Usa el rango intercuartílico para marcar valores muy alejados de la dispersión normal de los datos. Es robusto frente a valores atípicos que sesgan las medias.", - "Compares each value to a rolling mean and standard deviation. Catches unusual readings relative to recent context rather than the whole series.": "Compara cada valor con una media móvil y una desviación estándar. Detecta lecturas inusuales en relación con el contexto reciente, no con toda la serie.", - "Flags when a sensor reports nearly the same value for an unusually long time. Useful for detecting stuck sensors or frozen readings.": "Marca cuando un sensor informa casi el mismo valor durante un tiempo inusualmente largo. Es útil para detectar sensores atascados o lecturas congeladas.", - "Compares the current period to a reference date window. Highlights differences from an expected historical pattern, such as last week or the same day last year.": "Compara el periodo actual con una ventana de fechas de referencia. Destaca las diferencias respecto a un patrón histórico esperado, como la semana pasada o el mismo día del año anterior.", - "Show all anomalies": "Mostrar todas las anomalías", - "Overlaps only": "Solo solapamientos", - "Computing…": "Calculando…", - "1 hour": "1 hora", - "3 hours": "3 horas", - "6 hours": "6 horas", - "12 hours": "12 horas", - "24 hours": "24 horas", - "7 days": "7 días", - "30 minutes": "30 minutos" - }; - const __vite_glob_0_10$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$10 - }, Symbol.toStringTag, { value: "Module" })); - const translations$$ = { - "Show delta vs selected date window": "Mostrar delta frente a la ventana de fechas seleccionada", - "Select a date window tab to enable delta analysis.": "Selecciona una pestaña de ventana de fechas para habilitar el análisis delta.", - "Show delta in tooltip": "Mostrar delta en la información sobre herramientas", - "Show delta lines": "Mostrar líneas delta" - }; - const __vite_glob_0_11$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$$ - }, Symbol.toStringTag, { value: "Module" })); - const translations$_ = { - "Show rate of change": "Mostrar tasa de cambio", - "Show rate of change crosshairs": "Mostrar guías de la tasa de cambio", - "Rate window": "Ventana de tasa", - "Point to point": "Punto a punto", - "1 hour": "1 hora", - "6 hours": "6 horas", - "24 hours": "24 horas" - }; - const __vite_glob_0_12$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$_ - }, Symbol.toStringTag, { value: "Module" })); - const translations$Z = { - Downsampling: "Submuestreo", - Interval: "Intervalo", - Aggregate: "Agregado", - "Raw (no sampling)": "Sin procesar (sin muestreo)", - "5 seconds": "5 segundos", - "10 seconds": "10 segundos", - "15 seconds": "15 segundos", - "30 seconds": "30 segundos", - "1 minute": "1 minuto", - "2 minutes": "2 minutos", - "5 minutes": "5 minutos", - "10 minutes": "10 minutos", - "15 minutes": "15 minutos", - "30 minutes": "30 minutos", - "1 hour": "1 hora", - "2 hours": "2 horas", - "3 hours": "3 horas", - "4 hours": "4 horas", - "6 hours": "6 horas", - "12 hours": "12 horas", - "24 hours": "24 horas", - "Mean (average)": "Media", - Min: "Mín.", - Max: "Máx.", - Median: "Mediana", - First: "Primero", - Last: "Último" - }; - const __vite_glob_0_13$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$Z - }, Symbol.toStringTag, { value: "Module" })); - const translations$Y = { - "Show min / max / mean": "Mostrar mín. / máx. / media", - "Show range shading": "Mostrar sombreado del rango" - }; - const __vite_glob_0_14$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$Y - }, Symbol.toStringTag, { value: "Module" })); - const translations$X = { - "Show threshold analysis": "Mostrar análisis de umbral", - "Shade threshold area": "Sombrear área del umbral", - Threshold: "Umbral", - "Shade area": "Sombrear área", - "Shade above": "Sombrear arriba", - "Shade below": "Sombrear abajo" - }; - const __vite_glob_0_15$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$X - }, Symbol.toStringTag, { value: "Module" })); - const translations$W = { - "Show trend lines": "Mostrar líneas de tendencia", - "Show trend crosshairs": "Mostrar guías de tendencia", - "Trend method": "Método de tendencia", - "Trend window": "Ventana de tendencia", - "Rolling average": "Media móvil", - "Linear trend": "Tendencia lineal", - "1 hour": "1 hora", - "6 hours": "6 horas", - "24 hours": "24 horas", - "7 days": "7 días", - "14 days": "14 días", - "21 days": "21 días", - "28 days": "28 días" - }; - const __vite_glob_0_16$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$W - }, Symbol.toStringTag, { value: "Module" })); - const translations$V = { - "Add date window": "Añadir ventana de fechas" - }; - const __vite_glob_0_17$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$V - }, Symbol.toStringTag, { value: "Module" })); - const translations$U = { - "A date window saves a named date range as a tab, so you can quickly preview it against the selected range or jump the chart back to it later.": "Una ventana de fechas guarda un rango de fechas con nombre como pestaña, para que puedas previsualizarlo rápidamente frente al rango seleccionado o volver más tarde a él en el gráfico.", - Name: "Nombre", - "e.g. Heating season start": "p. ej., inicio de la temporada de calefacción", - "Date range": "Rango de fechas", - Start: "Inicio", - End: "Fin", - "Use previous range": "Usar rango anterior", - "Use next range": "Usar rango siguiente", - "Delete date window": "Eliminar ventana de fechas", - Cancel: "Cancelar" - }; - const __vite_glob_0_18$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$U - }, Symbol.toStringTag, { value: "Module" })); - const translations$T = { - Datapoints: "Puntos de datos", - "Choose which annotation datapoints appear on the chart.": "Elige qué puntos de datos de anotación aparecen en el gráfico.", - "Linked to selected targets": "Vinculados a los objetivos seleccionados", - "All datapoints": "Todos los puntos de datos", - "Hide datapoints": "Ocultar puntos de datos" - }; - const __vite_glob_0_19$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$T - }, Symbol.toStringTag, { value: "Module" })); - const translations$S = {}; - const __vite_glob_0_20$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$S - }, Symbol.toStringTag, { value: "Module" })); - const translations$R = { - "Analysis configured": "Análisis configurado", - "Configure analysis": "Configurar análisis", - "Stepped series": "Serie escalonada", - "Hide source series": "Ocultar serie de origen", - "All targets already have the same settings": "Todos los objetivos ya tienen la misma configuración", - "Copy these analysis settings to all targets": "Copiar estos ajustes de análisis a todos los objetivos", - "Copy to all targets": "Copiar a todos los objetivos" - }; - const __vite_glob_0_21$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$R - }, Symbol.toStringTag, { value: "Module" })); - const translations$Q = { - Targets: "Objetivos", - "Each row controls one chart series.": "Cada fila controla una serie del gráfico.", - "Add target": "Añadir objetivo", - "Chart preferences": "Preferencias del gráfico" - }; - const __vite_glob_0_22$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$Q - }, Symbol.toStringTag, { value: "Module" })); - const translations$P = { - "Loading Datapoints…": "Cargando puntos de datos…", - Datapoints: "Puntos de datos", - "Page options": "Opciones de la página", - "Download spreadsheet": "Descargar hoja de cálculo", - "Save page state": "Guardar estado de la página", - "Restore saved page": "Restaurar página guardada", - "Clear saved page": "Borrar página guardada", - "Expand targets sidebar": "Expandir la barra lateral de objetivos", - "Collapse targets sidebar": "Contraer la barra lateral de objetivos" - }; - const __vite_glob_0_23$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$P - }, Symbol.toStringTag, { value: "Module" })); - const translations$O = { - "Toggle sidebar": "Alternar barra lateral", - Start: "Inicio", - End: "Fin", - "Select date range": "Seleccionar rango de fechas", - "Timeline options": "Opciones de la línea temporal", - "Zoom level": "Nivel de zoom", - "Date snapping": "Ajuste de fechas", - Auto: "Auto", - Hour: "Hora", - Day: "Día", - Week: "Semana", - Month: "Mes", - Minute: "Minuto", - Second: "Segundo", - Quarterly: "Trimestral", - "Month Compressed": "Mes comprimido", - "Month Short": "Mes corto", - "Month Expanded": "Mes expandido", - "Week Compressed": "Semana comprimida", - "Week Expanded": "Semana expandida" - }; - const __vite_glob_0_24$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$O - }, Symbol.toStringTag, { value: "Module" })); - const modules$2 = /* @__PURE__ */ Object.assign({ - "../../../atoms/interactive/range-timeline/i18n/es.ts": __vite_glob_0_0$2, - "../../../cards/action/i18n/es.ts": __vite_glob_0_1$2, - "../../../cards/history/history-chart/i18n/es.ts": __vite_glob_0_2$2, - "../../../cards/history/i18n/es.ts": __vite_glob_0_3$2, - "../../../cards/list/i18n/es.ts": __vite_glob_0_4$2, - "../../../cards/quick/i18n/es.ts": __vite_glob_0_5$2, - "../../../cards/sensor/i18n/es.ts": __vite_glob_0_6$2, - "../../chart/i18n/es.ts": __vite_glob_0_7$2, - "../../ha/i18n/es.ts": __vite_glob_0_8$2, - "../../timeline/i18n/es.ts": __vite_glob_0_9$2, - "../../../molecules/analysis-anomaly-group/i18n/es.ts": __vite_glob_0_10$2, - "../../../molecules/analysis-delta-group/i18n/es.ts": __vite_glob_0_11$2, - "../../../molecules/analysis-rate-group/i18n/es.ts": __vite_glob_0_12$2, - "../../../molecules/analysis-sample-group/i18n/es.ts": __vite_glob_0_13$2, - "../../../molecules/analysis-summary-group/i18n/es.ts": __vite_glob_0_14$2, - "../../../molecules/analysis-threshold-group/i18n/es.ts": __vite_glob_0_15$2, - "../../../molecules/analysis-trend-group/i18n/es.ts": __vite_glob_0_16$2, - "../../../molecules/comparison-tab-rail/i18n/es.ts": __vite_glob_0_17$2, - "../../../molecules/date-window-dialog/i18n/es.ts": __vite_glob_0_18$2, - "../../../molecules/sidebar-options/sections/i18n/es.ts": __vite_glob_0_19$2, - "../../../molecules/target-row-list/i18n/es.ts": __vite_glob_0_20$2, - "../../../molecules/target-row/i18n/es.ts": __vite_glob_0_21$2, - "../../../panels/datapoints/components/history-targets/i18n/es.ts": __vite_glob_0_22$2, - "../../../panels/datapoints/components/panel-shell/i18n/es.ts": __vite_glob_0_23$2, - "../../../panels/datapoints/components/range-toolbar/i18n/es.ts": __vite_glob_0_24$2 - }); - const merged$2 = {}; - for (const mod of Object.values(modules$2)) { - Object.assign(merged$2, mod.translations); - } - const templates$2 = merged$2; - const es = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - templates: templates$2 - }, Symbol.toStringTag, { value: "Module" })); - const translations$N = { - "Updates with new data": "Atualiza com novos dados", - "Scroll to selected range": "Deslocar para o intervalo selecionado", - "Start date and time": "Data e hora de início", - "End date and time": "Data e hora de fim", - Select: "Selecionar" - }; - const __vite_glob_0_0$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$N - }, Symbol.toStringTag, { value: "Module" })); - const translations$M = { - General: "Geral", - "Related items": "Itens relacionados", - "Datapoint Appearance": "Aspeto do ponto de dados", - "Form fields": "Campos do formulário", - "Card title (optional)": "Título do cartão (opcional)", - "Pre-fill entities, devices, areas or labels that are always linked to recordings from this card.": "Pré-preencha entidades, dispositivos, áreas ou etiquetas que estejam sempre ligados aos registos feitos com este cartão.", - "Show always included targets on card": "Mostrar no cartão os alvos sempre incluídos", - "Allow user to add more related items": "Permitir que o utilizador adicione mais itens relacionados", - "Default icon": "Ícone predefinido", - "Default colour": "Cor predefinida", - "Show date & time field": "Mostrar campo de data e hora", - "Show annotation field": "Mostrar campo de anotação" - }; - const __vite_glob_0_1$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$M - }, Symbol.toStringTag, { value: "Module" })); - const translations$L = { - "Date window:": "Janela de datas:", - "Actual:": "Real:" - }; - const __vite_glob_0_2$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$L - }, Symbol.toStringTag, { value: "Module" })); - const translations$K = { - General: "Geral", - Entity: "Entidade", - "Multiple entities": "Múltiplas entidades", - Display: "Visualização", - "Card title (optional)": "Título do cartão (opcional)", - "Hours to show": "Horas a mostrar", - "Single entity": "Entidade única", - "Show data gaps": "Mostrar lacunas de dados", - "Highlight missing data ranges with dashed lines and boundary markers": "Realçar intervalos de dados em falta com linhas tracejadas e marcadores de limite" - }; - const __vite_glob_0_3$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$K - }, Symbol.toStringTag, { value: "Module" })); - const translations$J = { - "Search datapoints…": "Pesquisar pontos de dados…", - "Delete record": "Eliminar registo", - Delete: "Eliminar", - "Show annotation": "Mostrar anotação", - "Open related data point history": "Abrir histórico do ponto de dados relacionado", - "Edit record": "Editar registo", - "Show chart marker": "Mostrar marcador do gráfico", - "Hide chart marker": "Ocultar marcador do gráfico", - "Choose colour": "Escolher cor", - Save: "Guardar", - Cancel: "Cancelar", - Message: "Mensagem", - "Annotation / full message": "Anotação / mensagem completa" - }; - const __vite_glob_0_4$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$J - }, Symbol.toStringTag, { value: "Module" })); - const translations$I = { - General: "Geral", - "Icon & colour": "Ícone e cor", - "Related items": "Itens relacionados", - "Multiple entities": "Múltiplas entidades", - "Form fields": "Campos do formulário", - "Card title (optional)": "Título do cartão (opcional)", - "Input placeholder text": "Texto do marcador de posição", - Icon: "Ícone", - Colour: "Cor", - "These items will be linked to every record made with this card.": "Estes itens serão ligados a cada registo feito com este cartão.", - "Single entity (optional)": "Entidade única (opcional)", - "Add related items": "Adicionar itens relacionados", - "Show annotation field": "Mostrar campo de anotação" - }; - const __vite_glob_0_5$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$I - }, Symbol.toStringTag, { value: "Module" })); - const translations$H = { - Entity: "Entidade", - Display: "Visualização", - "Records list": "Lista de registos", - "Sensor entity *": "Entidade do sensor *", - "Override display name (optional)": "Substituir nome apresentado (opcional)", - "Hours to show": "Horas a mostrar", - "Graph colour": "Cor do gráfico", - "Annotation style": "Estilo da anotação", - "Circle on line": "Círculo na linha", - "Dotted vertical line": "Linha vertical pontilhada", - "Show records list below graph": "Mostrar lista de registos abaixo do gráfico", - "Records per page (blank = show all)": "Registos por página (em branco = mostrar todos)", - "Max records to show (blank = all)": "Máximo de registos a mostrar (em branco = todos)", - "Show full message": "Mostrar mensagem completa", - "User will be able to expand the row if hidden": "O utilizador poderá expandir a linha se estiver oculta" - }; - const __vite_glob_0_6$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$H - }, Symbol.toStringTag, { value: "Module" })); - const translations$G = { - "⚠️ Anomaly Insight": "⚠️ Informação de anomalia", - "⚠️ Multi-method Anomaly": "⚠️ Anomalia multimétodo", - "Click the highlighted circle to add an annotation.": "Clique no círculo destacado para adicionar uma anotação.", - "Alert:": "Alerta:", - "Confirmed by": "Confirmado por", - "methods:": "métodos:", - "Trend deviation": "Desvio de tendência", - "Sudden change": "Mudança súbita", - "Statistical outlier (IQR)": "Valor atípico estatístico (IQR)", - "Rolling Z-score": "Z-score móvel", - "Flat-line / stuck": "Plano / bloqueado", - "Comparison window": "Janela de comparação", - "{0} deviates from its expected trend between {1} and {2}.": "{0} desvia-se da tendência esperada entre {1} e {2}.", - "{0} shows an unusual rate of change between {1} and {2}.": "{0} mostra uma taxa de variação invulgar entre {1} e {2}.", - "{0} contains statistical outliers between {1} and {2}.": "{0} contém valores atípicos estatísticos entre {1} e {2}.", - "{0} shows statistically unusual values between {1} and {2}.": "{0} mostra valores estatisticamente invulgares entre {1} e {2}.", - "{0} appears stuck or flat between {1} and {2}{3}.": "{0} parece bloqueado ou plano entre {1} e {2}{3}.", - "{0} deviates significantly from the comparison window between {1} and {2}.": "{0} desvia-se significativamente da janela de comparação entre {1} e {2}.", - "Peak deviation: {0} from a baseline of {1} at {2}.": "Desvio máximo: {0} em relação a uma base de {1} em {2}.", - "Peak rate deviation: {0} from a typical rate of {1} at {2}.": "Desvio máximo da taxa: {0} em relação a uma taxa típica de {1} em {2}.", - "Peak value: {0}, deviating {1} from the median at {2}.": "Valor máximo: {0}, desviando-se {1} da mediana em {2}.", - "Peak deviation: {0} from a rolling mean of {1} at {2}.": "Desvio máximo: {0} em relação a uma média móvel de {1} em {2}.", - "Value remained near {0} for an unusually long period.": "O valor manteve-se perto de {0} durante um período invulgarmente longo.", - "Peak deviation from comparison: {0} at {1}.": "Desvio máximo da comparação: {0} em {1}.", - " (range: {0})": " (intervalo: {0})", - "Date window": "Janela de datas", - Trend: "Tendência", - Rate: "Taxa", - Delta: "Delta", - Threshold: "Limiar" - }; - const __vite_glob_0_7$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$G - }, Symbol.toStringTag, { value: "Module" })); - const translations$F = { - "Confirm delete": "Confirmar eliminação", - "Are you sure you want to delete this item?": "Tem a certeza de que pretende eliminar este item?", - Cancel: "Cancelar", - Delete: "Eliminar", - "Delete date window": "Eliminar janela de datas", - "this date window": "esta janela de datas", - "Edit date window": "Editar janela de datas", - "Add date window": "Adicionar janela de datas", - "Save date window": "Guardar janela de datas", - "Create date window": "Criar janela de datas" - }; - const __vite_glob_0_8$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$F - }, Symbol.toStringTag, { value: "Module" })); - const translations$E = { - Wk: "Sem.", - "Week of": "Semana de" - }; - const __vite_glob_0_9$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$E - }, Symbol.toStringTag, { value: "Module" })); - const translations$D = { - "Show anomalies": "Mostrar anomalias", - Sensitivity: "Sensibilidade", - "Use downsampled data for detection": "Usar dados reamostrados para deteção", - "Rate window": "Janela da taxa", - "Rolling window": "Janela móvel", - "Min flat duration": "Duração mínima plana", - "Compare to window": "Comparar com a janela", - "— select window —": "— selecionar janela —", - "When methods overlap": "Quando os métodos se sobrepõem", - Low: "Baixa", - Medium: "Média", - High: "Alta", - "Trend deviation": "Desvio de tendência", - "Sudden change": "Mudança súbita", - "Statistical outlier (IQR)": "Valor atípico estatístico (IQR)", - "Rolling Z-score": "Z-score móvel", - "Flat-line / stuck value": "Valor plano / bloqueado", - "Comparison window deviation": "Desvio da janela de comparação", - "Flags points that deviate significantly from a fitted trend line. Good for catching gradual drift or sudden jumps away from a steady baseline.": "Assinala pontos que se desviam significativamente de uma linha de tendência ajustada. É útil para detetar deriva gradual ou saltos súbitos a partir de uma base estável.", - "Flags unusually fast rises or drops compared to the typical rate of change. Best for detecting spikes, crashes, or rapid transitions.": "Assinala subidas ou descidas invulgarmente rápidas em comparação com a taxa de variação típica. É ideal para detetar picos, quedas ou transições rápidas.", - "Uses the interquartile range to flag values far outside the normal spread of data. Robust against outliers that skew averages.": "Usa o intervalo interquartil para assinalar valores muito fora da dispersão normal dos dados. É robusto contra valores atípicos que distorcem as médias.", - "Compares each value to a rolling mean and standard deviation. Catches unusual readings relative to recent context rather than the whole series.": "Compara cada valor com uma média móvel e um desvio padrão. Deteta leituras invulgares em relação ao contexto recente, e não à série completa.", - "Flags when a sensor reports nearly the same value for an unusually long time. Useful for detecting stuck sensors or frozen readings.": "Assinala quando um sensor reporta quase o mesmo valor durante um período invulgarmente longo. É útil para detetar sensores bloqueados ou leituras congeladas.", - "Compares the current period to a reference date window. Highlights differences from an expected historical pattern, such as last week or the same day last year.": "Compara o período atual com uma janela de datas de referência. Destaca diferenças face a um padrão histórico esperado, como a semana passada ou o mesmo dia do ano anterior.", - "Show all anomalies": "Mostrar todas as anomalias", - "Overlaps only": "Apenas sobreposições", - "Computing…": "A calcular…", - "1 hour": "1 hora", - "3 hours": "3 horas", - "6 hours": "6 horas", - "12 hours": "12 horas", - "24 hours": "24 horas", - "7 days": "7 dias", - "30 minutes": "30 minutos" - }; - const __vite_glob_0_10$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$D - }, Symbol.toStringTag, { value: "Module" })); - const translations$C = { - "Show delta vs selected date window": "Mostrar delta face à janela de datas selecionada", - "Select a date window tab to enable delta analysis.": "Selecione um separador de janela de datas para ativar a análise delta.", - "Show delta in tooltip": "Mostrar delta na dica", - "Show delta lines": "Mostrar linhas delta" - }; - const __vite_glob_0_11$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$C - }, Symbol.toStringTag, { value: "Module" })); - const translations$B = { - "Show rate of change": "Mostrar taxa de variação", - "Show rate of change crosshairs": "Mostrar guias da taxa de variação", - "Rate window": "Janela da taxa", - "Point to point": "Ponto a ponto", - "1 hour": "1 hora", - "6 hours": "6 horas", - "24 hours": "24 horas" - }; - const __vite_glob_0_12$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$B - }, Symbol.toStringTag, { value: "Module" })); - const translations$A = { - Downsampling: "Reamostragem", - Interval: "Intervalo", - Aggregate: "Agregado", - "Raw (no sampling)": "Em bruto (sem amostragem)", - "5 seconds": "5 segundos", - "10 seconds": "10 segundos", - "15 seconds": "15 segundos", - "30 seconds": "30 segundos", - "1 minute": "1 minuto", - "2 minutes": "2 minutos", - "5 minutes": "5 minutos", - "10 minutes": "10 minutos", - "15 minutes": "15 minutos", - "30 minutes": "30 minutos", - "1 hour": "1 hora", - "2 hours": "2 horas", - "3 hours": "3 horas", - "4 hours": "4 horas", - "6 hours": "6 horas", - "12 hours": "12 horas", - "24 hours": "24 horas", - "Mean (average)": "Média", - Min: "Mín.", - Max: "Máx.", - Median: "Mediana", - First: "Primeiro", - Last: "Último" - }; - const __vite_glob_0_13$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$A - }, Symbol.toStringTag, { value: "Module" })); - const translations$z = { - "Show min / max / mean": "Mostrar mín. / máx. / média", - "Show range shading": "Mostrar sombreamento do intervalo" - }; - const __vite_glob_0_14$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$z - }, Symbol.toStringTag, { value: "Module" })); - const translations$y = { - "Show threshold analysis": "Mostrar análise de limiar", - "Shade threshold area": "Sombrear área do limiar", - Threshold: "Limiar", - "Shade area": "Sombrear área", - "Shade above": "Sombrear acima", - "Shade below": "Sombrear abaixo" - }; - const __vite_glob_0_15$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$y - }, Symbol.toStringTag, { value: "Module" })); - const translations$x = { - "Show trend lines": "Mostrar linhas de tendência", - "Show trend crosshairs": "Mostrar guias da tendência", - "Trend method": "Método de tendência", - "Trend window": "Janela de tendência", - "Rolling average": "Média móvel", - "Linear trend": "Tendência linear", - "1 hour": "1 hora", - "6 hours": "6 horas", - "24 hours": "24 horas", - "7 days": "7 dias", - "14 days": "14 dias", - "21 days": "21 dias", - "28 days": "28 dias" - }; - const __vite_glob_0_16$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$x - }, Symbol.toStringTag, { value: "Module" })); - const translations$w = { - "Add date window": "Adicionar janela de datas" - }; - const __vite_glob_0_17$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$w - }, Symbol.toStringTag, { value: "Module" })); - const translations$v = { - "A date window saves a named date range as a tab, so you can quickly preview it against the selected range or jump the chart back to it later.": "Uma janela de datas guarda um intervalo de datas com nome como separador, para que possa pré-visualizá-lo rapidamente face ao intervalo selecionado ou regressar mais tarde a ele no gráfico.", - Name: "Nome", - "e.g. Heating season start": "ex.: início da época de aquecimento", - "Date range": "Intervalo de datas", - Start: "Início", - End: "Fim", - "Use previous range": "Usar intervalo anterior", - "Use next range": "Usar intervalo seguinte", - "Delete date window": "Eliminar janela de datas", - Cancel: "Cancelar" - }; - const __vite_glob_0_18$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$v - }, Symbol.toStringTag, { value: "Module" })); - const translations$u = { - Datapoints: "Pontos de dados", - "Choose which annotation datapoints appear on the chart.": "Escolha quais os pontos de dados de anotação que aparecem no gráfico.", - "Linked to selected targets": "Ligados aos alvos selecionados", - "All datapoints": "Todos os pontos de dados", - "Hide datapoints": "Ocultar pontos de dados" - }; - const __vite_glob_0_19$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$u - }, Symbol.toStringTag, { value: "Module" })); - const translations$t = {}; - const __vite_glob_0_20$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$t - }, Symbol.toStringTag, { value: "Module" })); - const translations$s = { - "Analysis configured": "Análise configurada", - "Configure analysis": "Configurar análise", - "Stepped series": "Série em degraus", - "Hide source series": "Ocultar série de origem", - "All targets already have the same settings": "Todos os alvos já têm as mesmas definições", - "Copy these analysis settings to all targets": "Copiar estas definições de análise para todos os alvos", - "Copy to all targets": "Copiar para todos os alvos" - }; - const __vite_glob_0_21$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$s - }, Symbol.toStringTag, { value: "Module" })); - const translations$r = { - Targets: "Alvos", - "Each row controls one chart series.": "Cada linha controla uma série do gráfico.", - "Add target": "Adicionar alvo", - "Chart preferences": "Preferências do gráfico" - }; - const __vite_glob_0_22$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$r - }, Symbol.toStringTag, { value: "Module" })); - const translations$q = { - "Loading Datapoints…": "A carregar pontos de dados…", - Datapoints: "Pontos de dados", - "Page options": "Opções da página", - "Download spreadsheet": "Transferir folha de cálculo", - "Save page state": "Guardar estado da página", - "Restore saved page": "Restaurar página guardada", - "Clear saved page": "Limpar página guardada", - "Expand targets sidebar": "Expandir barra lateral dos alvos", - "Collapse targets sidebar": "Recolher barra lateral dos alvos" - }; - const __vite_glob_0_23$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$q - }, Symbol.toStringTag, { value: "Module" })); - const translations$p = { - "Toggle sidebar": "Alternar barra lateral", - Start: "Início", - End: "Fim", - "Select date range": "Selecionar intervalo de datas", - "Timeline options": "Opções da linha temporal", - "Zoom level": "Nível de zoom", - "Date snapping": "Ajuste de datas", - Auto: "Auto", - Hour: "Hora", - Day: "Dia", - Week: "Semana", - Month: "Mês", - Minute: "Minuto", - Second: "Segundo", - Quarterly: "Trimestral", - "Month Compressed": "Mês comprimido", - "Month Short": "Mês curto", - "Month Expanded": "Mês expandido", - "Week Compressed": "Semana comprimida", - "Week Expanded": "Semana expandida" - }; - const __vite_glob_0_24$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$p - }, Symbol.toStringTag, { value: "Module" })); - const modules$1 = /* @__PURE__ */ Object.assign({ - "../../../atoms/interactive/range-timeline/i18n/pt.ts": __vite_glob_0_0$1, - "../../../cards/action/i18n/pt.ts": __vite_glob_0_1$1, - "../../../cards/history/history-chart/i18n/pt.ts": __vite_glob_0_2$1, - "../../../cards/history/i18n/pt.ts": __vite_glob_0_3$1, - "../../../cards/list/i18n/pt.ts": __vite_glob_0_4$1, - "../../../cards/quick/i18n/pt.ts": __vite_glob_0_5$1, - "../../../cards/sensor/i18n/pt.ts": __vite_glob_0_6$1, - "../../chart/i18n/pt.ts": __vite_glob_0_7$1, - "../../ha/i18n/pt.ts": __vite_glob_0_8$1, - "../../timeline/i18n/pt.ts": __vite_glob_0_9$1, - "../../../molecules/analysis-anomaly-group/i18n/pt.ts": __vite_glob_0_10$1, - "../../../molecules/analysis-delta-group/i18n/pt.ts": __vite_glob_0_11$1, - "../../../molecules/analysis-rate-group/i18n/pt.ts": __vite_glob_0_12$1, - "../../../molecules/analysis-sample-group/i18n/pt.ts": __vite_glob_0_13$1, - "../../../molecules/analysis-summary-group/i18n/pt.ts": __vite_glob_0_14$1, - "../../../molecules/analysis-threshold-group/i18n/pt.ts": __vite_glob_0_15$1, - "../../../molecules/analysis-trend-group/i18n/pt.ts": __vite_glob_0_16$1, - "../../../molecules/comparison-tab-rail/i18n/pt.ts": __vite_glob_0_17$1, - "../../../molecules/date-window-dialog/i18n/pt.ts": __vite_glob_0_18$1, - "../../../molecules/sidebar-options/sections/i18n/pt.ts": __vite_glob_0_19$1, - "../../../molecules/target-row-list/i18n/pt.ts": __vite_glob_0_20$1, - "../../../molecules/target-row/i18n/pt.ts": __vite_glob_0_21$1, - "../../../panels/datapoints/components/history-targets/i18n/pt.ts": __vite_glob_0_22$1, - "../../../panels/datapoints/components/panel-shell/i18n/pt.ts": __vite_glob_0_23$1, - "../../../panels/datapoints/components/range-toolbar/i18n/pt.ts": __vite_glob_0_24$1 - }); - const merged$1 = {}; - for (const mod of Object.values(modules$1)) { - Object.assign(merged$1, mod.translations); - } - const templates$1 = merged$1; - const pt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - templates: templates$1 - }, Symbol.toStringTag, { value: "Module" })); - const translations$o = { - "Updates with new data": "使用新数据更新", - "Scroll to selected range": "滚动到选定范围", - "Start date and time": "开始日期和时间", - "End date and time": "结束日期和时间", - Select: "选择" - }; - const __vite_glob_0_0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$o - }, Symbol.toStringTag, { value: "Module" })); - const translations$n = { - General: "常规", - "Related items": "关联项", - "Datapoint Appearance": "数据点外观", - "Form fields": "表单字段", - "Card title (optional)": "卡片标题(可选)", - "Pre-fill entities, devices, areas or labels that are always linked to recordings from this card.": "预先填写始终与此卡片创建的记录关联的实体、设备、区域或标签。", - "Show always included targets on card": "在卡片上显示始终包含的目标", - "Allow user to add more related items": "允许用户添加更多关联项", - "Default icon": "默认图标", - "Default colour": "默认颜色", - "Show date & time field": "显示日期和时间字段", - "Show annotation field": "显示注释字段" - }; - const __vite_glob_0_1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$n - }, Symbol.toStringTag, { value: "Module" })); - const translations$m = { - "Date window:": "日期窗口:", - "Actual:": "实际:" - }; - const __vite_glob_0_2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$m - }, Symbol.toStringTag, { value: "Module" })); - const translations$l = { - General: "常规", - Entity: "实体", - "Multiple entities": "多个实体", - Display: "显示", - "Card title (optional)": "卡片标题(可选)", - "Hours to show": "显示小时数", - "Single entity": "单个实体", - "Show data gaps": "显示数据缺口", - "Highlight missing data ranges with dashed lines and boundary markers": "用虚线和边界标记突出显示缺失数据范围" - }; - const __vite_glob_0_3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$l - }, Symbol.toStringTag, { value: "Module" })); - const translations$k = { - "Search datapoints…": "搜索数据点…", - "Delete record": "删除记录", - Delete: "删除", - "Show annotation": "显示注释", - "Open related data point history": "打开相关数据点历史", - "Edit record": "编辑记录", - "Show chart marker": "显示图表标记", - "Hide chart marker": "隐藏图表标记", - "Choose colour": "选择颜色", - Save: "保存", - Cancel: "取消", - Message: "消息", - "Annotation / full message": "注释 / 完整消息" - }; - const __vite_glob_0_4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$k - }, Symbol.toStringTag, { value: "Module" })); - const translations$j = { - General: "常规", - "Icon & colour": "图标和颜色", - "Related items": "关联项", - "Multiple entities": "多个实体", - "Form fields": "表单字段", - "Card title (optional)": "卡片标题(可选)", - "Input placeholder text": "输入占位文本", - Icon: "图标", - Colour: "颜色", - "These items will be linked to every record made with this card.": "这些项目将关联到使用此卡片创建的每一条记录。", - "Single entity (optional)": "单个实体(可选)", - "Add related items": "添加关联项", - "Show annotation field": "显示注释字段" - }; - const __vite_glob_0_5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$j - }, Symbol.toStringTag, { value: "Module" })); - const translations$i = { - Entity: "实体", - Display: "显示", - "Records list": "记录列表", - "Sensor entity *": "传感器实体 *", - "Override display name (optional)": "覆盖显示名称(可选)", - "Hours to show": "显示小时数", - "Graph colour": "图表颜色", - "Annotation style": "注释样式", - "Circle on line": "线上的圆点", - "Dotted vertical line": "点状垂直线", - "Show records list below graph": "在图表下方显示记录列表", - "Records per page (blank = show all)": "每页记录数(留空 = 显示全部)", - "Max records to show (blank = all)": "显示的最大记录数(留空 = 全部)", - "Show full message": "显示完整消息", - "User will be able to expand the row if hidden": "如果该行被隐藏,用户仍可展开它" - }; - const __vite_glob_0_6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$i - }, Symbol.toStringTag, { value: "Module" })); - const translations$h = { - "⚠️ Anomaly Insight": "⚠️ 异常洞察", - "⚠️ Multi-method Anomaly": "⚠️ 多方法异常", - "Click the highlighted circle to add an annotation.": "点击高亮圆圈以添加注释。", - "Alert:": "警报:", - "Confirmed by": "确认方式", - "methods:": "方法:", - "Trend deviation": "趋势偏差", - "Sudden change": "突变", - "Statistical outlier (IQR)": "统计离群值(IQR)", - "Rolling Z-score": "滚动 Z 分数", - "Flat-line / stuck": "平直 / 卡住", - "Comparison window": "比较窗口", - "{0} deviates from its expected trend between {1} and {2}.": "{0} 在 {1} 到 {2} 之间偏离了预期趋势。", - "{0} shows an unusual rate of change between {1} and {2}.": "{0} 在 {1} 到 {2} 之间表现出异常的变化率。", - "{0} contains statistical outliers between {1} and {2}.": "{0} 在 {1} 到 {2} 之间包含统计离群值。", - "{0} shows statistically unusual values between {1} and {2}.": "{0} 在 {1} 到 {2} 之间显示出统计上异常的值。", - "{0} appears stuck or flat between {1} and {2}{3}.": "{0} 在 {1} 到 {2}{3} 之间似乎卡住或保持平直。", - "{0} deviates significantly from the comparison window between {1} and {2}.": "{0} 在 {1} 到 {2} 之间显著偏离比较窗口。", - "Peak deviation: {0} from a baseline of {1} at {2}.": "峰值偏差:{2} 时相对基线 {1} 偏差 {0}。", - "Peak rate deviation: {0} from a typical rate of {1} at {2}.": "峰值速率偏差:{2} 时相对典型速率 {1} 偏差 {0}。", - "Peak value: {0}, deviating {1} from the median at {2}.": "峰值:{0},在 {2} 时偏离中位数 {1}。", - "Peak deviation: {0} from a rolling mean of {1} at {2}.": "峰值偏差:{2} 时相对滚动平均值 {1} 偏差 {0}。", - "Value remained near {0} for an unusually long period.": "该值在异常长的时间内一直接近 {0}。", - "Peak deviation from comparison: {0} at {1}.": "与比较值的峰值偏差:{1} 时为 {0}。", - " (range: {0})": "(范围:{0})", - "Date window": "日期窗口", - Trend: "趋势", - Rate: "变化率", - Delta: "差值", - Threshold: "阈值" - }; - const __vite_glob_0_7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$h - }, Symbol.toStringTag, { value: "Module" })); - const translations$g = { - "Confirm delete": "确认删除", - "Are you sure you want to delete this item?": "确定要删除此项目吗?", - Cancel: "取消", - Delete: "删除", - "Delete date window": "删除日期窗口", - "this date window": "此日期窗口", - "Edit date window": "编辑日期窗口", - "Add date window": "添加日期窗口", - "Save date window": "保存日期窗口", - "Create date window": "创建日期窗口" - }; - const __vite_glob_0_8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$g - }, Symbol.toStringTag, { value: "Module" })); - const translations$f = { - Wk: "周", - "Week of": "所在周" - }; - const __vite_glob_0_9 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$f - }, Symbol.toStringTag, { value: "Module" })); - const translations$e = { - "Show anomalies": "显示异常", - Sensitivity: "灵敏度", - "Use downsampled data for detection": "检测时使用降采样数据", - "Rate window": "变化率窗口", - "Rolling window": "滚动窗口", - "Min flat duration": "最小平稳持续时间", - "Compare to window": "与窗口比较", - "— select window —": "— 选择窗口 —", - "When methods overlap": "当方法重叠时", - Low: "低", - Medium: "中", - High: "高", - "Trend deviation": "趋势偏差", - "Sudden change": "突变", - "Statistical outlier (IQR)": "统计离群值(IQR)", - "Rolling Z-score": "滚动 Z 分数", - "Flat-line / stuck value": "平直 / 卡住值", - "Comparison window deviation": "比较窗口偏差", - "Flags points that deviate significantly from a fitted trend line. Good for catching gradual drift or sudden jumps away from a steady baseline.": "标记显著偏离拟合趋势线的点。适合捕捉从稳定基线逐渐漂移或突然跳变的情况。", - "Flags unusually fast rises or drops compared to the typical rate of change. Best for detecting spikes, crashes, or rapid transitions.": "标记相对于典型变化率异常快速的上升或下降。最适合检测尖峰、崩跌或快速变化。", - "Uses the interquartile range to flag values far outside the normal spread of data. Robust against outliers that skew averages.": "使用四分位距来标记远超正常数据分布范围的值。对会扭曲平均值的离群值具有较强鲁棒性。", - "Compares each value to a rolling mean and standard deviation. Catches unusual readings relative to recent context rather than the whole series.": "将每个值与滚动平均值和标准差进行比较。它根据近期上下文而不是整条序列来捕捉异常读数。", - "Flags when a sensor reports nearly the same value for an unusually long time. Useful for detecting stuck sensors or frozen readings.": "当传感器在异常长时间内报告几乎相同的值时进行标记。适用于检测卡住的传感器或冻结的读数。", - "Compares the current period to a reference date window. Highlights differences from an expected historical pattern, such as last week or the same day last year.": "将当前时间段与参考日期窗口进行比较。突出显示与预期历史模式的差异,例如上周或去年的同一天。", - "Show all anomalies": "显示所有异常", - "Overlaps only": "仅重叠项", - "Computing…": "计算中…", - "1 hour": "1小时", - "3 hours": "3小时", - "6 hours": "6小时", - "12 hours": "12小时", - "24 hours": "24小时", - "7 days": "7天", - "30 minutes": "30分钟" - }; - const __vite_glob_0_10 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$e - }, Symbol.toStringTag, { value: "Module" })); - const translations$d = { - "Show delta vs selected date window": "显示相对于所选日期窗口的差值", - "Select a date window tab to enable delta analysis.": "选择一个日期窗口标签以启用差值分析。", - "Show delta in tooltip": "在提示中显示差值", - "Show delta lines": "显示差值线" - }; - const __vite_glob_0_11 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$d - }, Symbol.toStringTag, { value: "Module" })); - const translations$c = { - "Show rate of change": "显示变化率", - "Show rate of change crosshairs": "显示变化率准星", - "Rate window": "变化率窗口", - "Point to point": "点对点", - "1 hour": "1小时", - "6 hours": "6小时", - "24 hours": "24小时" - }; - const __vite_glob_0_12 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$c - }, Symbol.toStringTag, { value: "Module" })); - const translations$b = { - Downsampling: "降采样", - Interval: "间隔", - Aggregate: "聚合", - "Raw (no sampling)": "原始(无采样)", - "5 seconds": "5秒", - "10 seconds": "10秒", - "15 seconds": "15秒", - "30 seconds": "30秒", - "1 minute": "1分钟", - "2 minutes": "2分钟", - "5 minutes": "5分钟", - "10 minutes": "10分钟", - "15 minutes": "15分钟", - "30 minutes": "30分钟", - "1 hour": "1小时", - "2 hours": "2小时", - "3 hours": "3小时", - "4 hours": "4小时", - "6 hours": "6小时", - "12 hours": "12小时", - "24 hours": "24小时", - "Mean (average)": "平均值", - Min: "最小", - Max: "最大", - Median: "中位数", - First: "首个", - Last: "最后一个" - }; - const __vite_glob_0_13 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$b - }, Symbol.toStringTag, { value: "Module" })); - const translations$a = { - "Show min / max / mean": "显示最小 / 最大 / 平均值", - "Show range shading": "显示范围阴影" - }; - const __vite_glob_0_14 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$a - }, Symbol.toStringTag, { value: "Module" })); - const translations$9 = { - "Show threshold analysis": "显示阈值分析", - "Shade threshold area": "为阈值区域着色", - Threshold: "阈值", - "Shade area": "着色区域", - "Shade above": "对上方区域着色", - "Shade below": "对下方区域着色" - }; - const __vite_glob_0_15 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$9 - }, Symbol.toStringTag, { value: "Module" })); - const translations$8 = { - "Show trend lines": "显示趋势线", - "Show trend crosshairs": "显示趋势准星", - "Trend method": "趋势方法", - "Trend window": "趋势窗口", - "Rolling average": "滚动平均", - "Linear trend": "线性趋势", - "1 hour": "1小时", - "6 hours": "6小时", - "24 hours": "24小时", - "7 days": "7天", - "14 days": "14天", - "21 days": "21天", - "28 days": "28天" - }; - const __vite_glob_0_16 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$8 - }, Symbol.toStringTag, { value: "Module" })); - const translations$7 = { - "Add date window": "添加日期窗口" - }; - const __vite_glob_0_17 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$7 - }, Symbol.toStringTag, { value: "Module" })); - const translations$6 = { - "A date window saves a named date range as a tab, so you can quickly preview it against the selected range or jump the chart back to it later.": "日期窗口会将一个命名的日期范围保存为标签页,这样你就可以快速将其与所选范围进行预览对比,或稍后在图表中跳回该范围。", - Name: "名称", - "e.g. Heating season start": "例如:供暖季开始", - "Date range": "日期范围", - Start: "开始", - End: "结束", - "Use previous range": "使用上一个范围", - "Use next range": "使用下一个范围", - "Delete date window": "删除日期窗口", - Cancel: "取消" - }; - const __vite_glob_0_18 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$6 - }, Symbol.toStringTag, { value: "Module" })); - const translations$5 = { - Datapoints: "数据点", - "Choose which annotation datapoints appear on the chart.": "选择哪些注释数据点显示在图表上。", - "Linked to selected targets": "关联到所选目标", - "All datapoints": "所有数据点", - "Hide datapoints": "隐藏数据点" - }; - const __vite_glob_0_19 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$5 - }, Symbol.toStringTag, { value: "Module" })); - const translations$4 = {}; - const __vite_glob_0_20 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$4 - }, Symbol.toStringTag, { value: "Module" })); - const translations$3 = { - "Analysis configured": "分析已配置", - "Configure analysis": "配置分析", - "Stepped series": "阶梯序列", - "Hide source series": "隐藏源序列", - "All targets already have the same settings": "所有目标已经具有相同设置", - "Copy these analysis settings to all targets": "将这些分析设置复制到所有目标", - "Copy to all targets": "复制到所有目标" - }; - const __vite_glob_0_21 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$3 - }, Symbol.toStringTag, { value: "Module" })); - const translations$2 = { - Targets: "目标", - "Each row controls one chart series.": "每一行控制一条图表序列。", - "Add target": "添加目标", - "Chart preferences": "图表偏好设置" - }; - const __vite_glob_0_22 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$2 - }, Symbol.toStringTag, { value: "Module" })); - const translations$1 = { - "Loading Datapoints…": "正在加载数据点…", - Datapoints: "数据点", - "Page options": "页面选项", - "Download spreadsheet": "下载电子表格", - "Save page state": "保存页面状态", - "Restore saved page": "恢复已保存页面", - "Clear saved page": "清除已保存页面", - "Expand targets sidebar": "展开目标侧边栏", - "Collapse targets sidebar": "折叠目标侧边栏" - }; - const __vite_glob_0_23 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations: translations$1 - }, Symbol.toStringTag, { value: "Module" })); - const translations = { - "Toggle sidebar": "切换侧边栏", - Start: "开始", - End: "结束", - "Select date range": "选择日期范围", - "Timeline options": "时间线选项", - "Zoom level": "缩放级别", - "Date snapping": "日期对齐", - Auto: "自动", - Hour: "小时", - Day: "日", - Week: "周", - Month: "月", - Minute: "分钟", - Second: "秒", - Quarterly: "按季度", - "Month Compressed": "月份紧凑", - "Month Short": "月份简写", - "Month Expanded": "月份展开", - "Week Compressed": "周紧凑", - "Week Expanded": "周展开" - }; - const __vite_glob_0_24 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - translations - }, Symbol.toStringTag, { value: "Module" })); - const modules = /* @__PURE__ */ Object.assign({ - "../../../atoms/interactive/range-timeline/i18n/zh-hans.ts": __vite_glob_0_0, - "../../../cards/action/i18n/zh-hans.ts": __vite_glob_0_1, - "../../../cards/history/history-chart/i18n/zh-hans.ts": __vite_glob_0_2, - "../../../cards/history/i18n/zh-hans.ts": __vite_glob_0_3, - "../../../cards/list/i18n/zh-hans.ts": __vite_glob_0_4, - "../../../cards/quick/i18n/zh-hans.ts": __vite_glob_0_5, - "../../../cards/sensor/i18n/zh-hans.ts": __vite_glob_0_6, - "../../chart/i18n/zh-hans.ts": __vite_glob_0_7, - "../../ha/i18n/zh-hans.ts": __vite_glob_0_8, - "../../timeline/i18n/zh-hans.ts": __vite_glob_0_9, - "../../../molecules/analysis-anomaly-group/i18n/zh-hans.ts": __vite_glob_0_10, - "../../../molecules/analysis-delta-group/i18n/zh-hans.ts": __vite_glob_0_11, - "../../../molecules/analysis-rate-group/i18n/zh-hans.ts": __vite_glob_0_12, - "../../../molecules/analysis-sample-group/i18n/zh-hans.ts": __vite_glob_0_13, - "../../../molecules/analysis-summary-group/i18n/zh-hans.ts": __vite_glob_0_14, - "../../../molecules/analysis-threshold-group/i18n/zh-hans.ts": __vite_glob_0_15, - "../../../molecules/analysis-trend-group/i18n/zh-hans.ts": __vite_glob_0_16, - "../../../molecules/comparison-tab-rail/i18n/zh-hans.ts": __vite_glob_0_17, - "../../../molecules/date-window-dialog/i18n/zh-hans.ts": __vite_glob_0_18, - "../../../molecules/sidebar-options/sections/i18n/zh-hans.ts": __vite_glob_0_19, - "../../../molecules/target-row-list/i18n/zh-hans.ts": __vite_glob_0_20, - "../../../molecules/target-row/i18n/zh-hans.ts": __vite_glob_0_21, - "../../../panels/datapoints/components/history-targets/i18n/zh-hans.ts": __vite_glob_0_22, - "../../../panels/datapoints/components/panel-shell/i18n/zh-hans.ts": __vite_glob_0_23, - "../../../panels/datapoints/components/range-toolbar/i18n/zh-hans.ts": __vite_glob_0_24 - }); - const merged = {}; - for (const mod of Object.values(modules)) { - Object.assign(merged, mod.translations); - } - const templates = merged; - const zhHans = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ - __proto__: null, - templates - }, Symbol.toStringTag, { value: "Module" })); + } + }; + _defineProperty(HassRecordsSensorCardEditor, "styles", [EditorBase.styles, styles$2]); + //#endregion + //#region custom_components/hass_datapoints/src/register.ts + /** + * Register all custom elements and advertise them to the Lovelace card picker. + */ + if (!customElements.get("hass-datapoints-action-card")) customElements.define("hass-datapoints-action-card", HassRecordsActionCard); + if (!customElements.get("hass-datapoints-quick-card")) customElements.define("hass-datapoints-quick-card", HassRecordsQuickCard); + if (!customElements.get("hass-datapoints-history-card")) customElements.define("hass-datapoints-history-card", HassRecordsHistoryCard); + if (!customElements.get("hass-datapoints-sensor-card")) customElements.define("hass-datapoints-sensor-card", HassRecordsSensorCard); + if (!customElements.get("hass-datapoints-list-card")) customElements.define("hass-datapoints-list-card", HassRecordsListCard); + if (!customElements.get("hass-datapoints-history-panel")) customElements.define("hass-datapoints-history-panel", HassRecordsHistoryPanel); + if (!customElements.get("hass-datapoints-dev-tool-card")) customElements.define("hass-datapoints-dev-tool-card", HassRecordsDevToolCard); + if (!customElements.get("hass-datapoints-action-card-editor")) customElements.define("hass-datapoints-action-card-editor", HassRecordsActionCardEditor); + if (!customElements.get("hass-datapoints-quick-card-editor")) customElements.define("hass-datapoints-quick-card-editor", HassRecordsQuickCardEditor); + if (!customElements.get("hass-datapoints-history-card-editor")) customElements.define("hass-datapoints-history-card-editor", HassRecordsHistoryCardEditor); + if (!customElements.get("hass-datapoints-sensor-card-editor")) customElements.define("hass-datapoints-sensor-card-editor", HassRecordsSensorCardEditor); + if (!customElements.get("hass-datapoints-list-card-editor")) customElements.define("hass-datapoints-list-card-editor", HassRecordsListCardEditor); + if (!customElements.get("hass-datapoints-dev-tool-card-editor")) customElements.define("hass-datapoints-dev-tool-card-editor", HassRecordsDevToolCardEditor); + window.customCards = window.customCards || []; + var registeredTypes = new Set(window.customCards.map((card) => card.type)); + [ + { + type: "hass-datapoints-action-card", + name: "Hass Records – Action Card", + description: "Full form to record a custom event with message, annotation, icon, colour and entity association.", + preview: false + }, + { + type: "hass-datapoints-quick-card", + name: "Hass Records – Quick Card", + description: "Simple one-field card to quickly record a note with a bookmark icon.", + preview: false + }, + { + type: "hass-datapoints-history-card", + name: "Hass Records – History Card", + description: "History line chart with coloured annotation markers for recorded events.", + preview: false + }, + { + type: "hass-datapoints-sensor-card", + name: "Hass Records – Sensor Card", + description: "Sensor card with line chart — annotations shown as icons on the data line.", + preview: false + }, + { + type: "hass-datapoints-list-card", + name: "Hass Records – List Card", + description: "Activity-style datagrid to browse, search, edit and delete all recorded events.", + preview: false + }, + { + type: "hass-datapoints-dev-tool-card", + name: "Hass Records – Dev Tool", + description: "Generate demo datapoints from HA history and bulk-delete dev-flagged events.", + preview: false + } + ].forEach((card) => { + if (!registeredTypes.has(card.type)) window.customCards?.push(card); + }); + console.groupCollapsed("%c hass-datapoints %c v0.4.1 loaded ", "color:#fff;background:#03a9f4;font-weight:bold;padding:2px 6px;border-radius:3px 0 0 3px", "color:#03a9f4;background:#fff;font-weight:bold;padding:2px 6px;border:1px solid #03a9f4;border-radius:0 3px 3px 0"); + console.log("Enable debug logging by setting %cwindow.__HASS_DATAPOINTS_DEV__ = true", "color:#333;background:#eee;border:1px solid #777;padding:2px 6px;border-radius:5px; font-family: Courier"); + console.groupEnd(); + //#endregion })(); diff --git a/custom_components/hass_datapoints/src/atoms/interactive/range-timeline/range-timeline.ts b/custom_components/hass_datapoints/src/atoms/interactive/range-timeline/range-timeline.ts index 113c35c6..996e056b 100644 --- a/custom_components/hass_datapoints/src/atoms/interactive/range-timeline/range-timeline.ts +++ b/custom_components/hass_datapoints/src/atoms/interactive/range-timeline/range-timeline.ts @@ -182,7 +182,6 @@ export class RangeTimeline extends LitElement { } disconnectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); if (this._rangeScrollViewportEl) { this._rangeScrollViewportEl.removeEventListener( diff --git a/custom_components/hass_datapoints/src/atoms/interactive/resizable-panes/resizable-panes.ts b/custom_components/hass_datapoints/src/atoms/interactive/resizable-panes/resizable-panes.ts index 4daef681..33faa58e 100644 --- a/custom_components/hass_datapoints/src/atoms/interactive/resizable-panes/resizable-panes.ts +++ b/custom_components/hass_datapoints/src/atoms/interactive/resizable-panes/resizable-panes.ts @@ -51,7 +51,6 @@ export class ResizablePanes extends LitElement { } disconnectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); // Clean up global pointer listeners if disconnected during an active drag. if (this._pointerId != null) { diff --git a/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool-windows/dev-tool-windows.ts b/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool-windows/dev-tool-windows.ts index 6b72ae70..5199e16f 100644 --- a/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool-windows/dev-tool-windows.ts +++ b/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool-windows/dev-tool-windows.ts @@ -12,7 +12,6 @@ export class CardDevToolWindows extends LitElement { @state() accessor _nextWindowId = 1; connectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.connectedCallback(); if (this.windows.length === 0) { this.windows = [this._createWindow()]; diff --git a/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool.ts b/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool.ts index 3d2a550a..d3a83919 100644 --- a/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool.ts +++ b/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool.ts @@ -273,9 +273,9 @@ export class HassRecordsDevToolCard extends HTMLElement { ? new Date(timestampRaw * 1000).toISOString() : new Date().toISOString(); - let message: Nullable = null; - let icon = "mdi:bookmark"; - let color = "#03a9f4"; + let message: Nullable; + let icon: string; + let color: string; if (domain === "binary_sensor" || domain === "input_boolean") { message = `${friendlyName}: ${this._binaryLabel(deviceClass, currentValue)}`; diff --git a/custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.ts b/custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.ts index 3deee680..764f2da0 100644 --- a/custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.ts +++ b/custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.ts @@ -83,7 +83,7 @@ import { buildRollingAverageTrend, buildSummaryStats, getTrendWindowMs, -} from "../analysis/index"; +} from "../analysis"; import { navigateToDataPointsHistory } from "@/lib/ha/navigation"; import { styles } from "./history-chart.styles"; diff --git a/custom_components/hass_datapoints/src/cards/list/list.ts b/custom_components/hass_datapoints/src/cards/list/list.ts index 25a9c056..afa5cfd8 100644 --- a/custom_components/hass_datapoints/src/cards/list/list.ts +++ b/custom_components/hass_datapoints/src/cards/list/list.ts @@ -95,7 +95,6 @@ export class HassRecordsListCard extends LitElement { } connectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.connectedCallback(); this._windowListener = () => this._load(); window.addEventListener( @@ -105,7 +104,6 @@ export class HassRecordsListCard extends LitElement { } disconnectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); if (this._unsubscribe) { this._unsubscribe(); diff --git a/custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.ts b/custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.ts index cfc5d2cc..51c87d4f 100644 --- a/custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.ts +++ b/custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.ts @@ -117,13 +117,11 @@ export class SensorChart extends LitElement { } connectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.connectedCallback(); this._setupResizeObserver(); } disconnectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); if (this._resizeObserver) { this._resizeObserver.disconnect(); diff --git a/custom_components/hass_datapoints/src/cards/sensor/sensor-records/sensor-records.ts b/custom_components/hass_datapoints/src/cards/sensor/sensor-records/sensor-records.ts index 9d0422b2..713796e2 100644 --- a/custom_components/hass_datapoints/src/cards/sensor/sensor-records/sensor-records.ts +++ b/custom_components/hass_datapoints/src/cards/sensor/sensor-records/sensor-records.ts @@ -66,7 +66,6 @@ export class SensorRecords extends LitElement { } disconnectedCallback(): void { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); if (this._paginationNotifyRaf !== null) { window.cancelAnimationFrame(this._paginationNotifyRaf); diff --git a/custom_components/hass_datapoints/src/cards/sensor/sensor.ts b/custom_components/hass_datapoints/src/cards/sensor/sensor.ts index 535d20d6..56c7a7d4 100644 --- a/custom_components/hass_datapoints/src/cards/sensor/sensor.ts +++ b/custom_components/hass_datapoints/src/cards/sensor/sensor.ts @@ -92,14 +92,12 @@ export class HassRecordsSensorCard extends LitElement { } connectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.connectedCallback(); this._setupResizeObserver(); if (this._initialized && this._hass) this._load(); } disconnectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); if (this._unsubscribe) { this._unsubscribe(); diff --git a/custom_components/hass_datapoints/src/charts/base/chart-card-base.ts b/custom_components/hass_datapoints/src/charts/base/chart-card-base.ts index 487ff165..8009992c 100644 --- a/custom_components/hass_datapoints/src/charts/base/chart-card-base.ts +++ b/custom_components/hass_datapoints/src/charts/base/chart-card-base.ts @@ -85,7 +85,6 @@ export abstract class ChartCardBase extends LitElement { // ── Lifecycle ───────────────────────────────────────────────────────────── connectedCallback(): void { - // eslint-disable-next-line wc/guard-super-call super.connectedCallback(); if (this._initialized) { if (!this._unsubscribe || !this._windowListener) { @@ -111,7 +110,6 @@ export abstract class ChartCardBase extends LitElement { } disconnectedCallback(): void { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); this._cleanup(); } diff --git a/custom_components/hass_datapoints/src/molecules/collapsed-options-menu/collapsed-options-menu.ts b/custom_components/hass_datapoints/src/molecules/collapsed-options-menu/collapsed-options-menu.ts index db1d0a34..4e4675e9 100644 --- a/custom_components/hass_datapoints/src/molecules/collapsed-options-menu/collapsed-options-menu.ts +++ b/custom_components/hass_datapoints/src/molecules/collapsed-options-menu/collapsed-options-menu.ts @@ -56,7 +56,6 @@ export class CollapsedOptionsMenu extends LitElement { private _closeTimer: Nullable> = null; disconnectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); if (this._closeTimer !== null) { clearTimeout(this._closeTimer); diff --git a/custom_components/hass_datapoints/src/molecules/comparison-tab-rail/comparison-tab-rail.ts b/custom_components/hass_datapoints/src/molecules/comparison-tab-rail/comparison-tab-rail.ts index 22ef9ef4..5af6a13a 100644 --- a/custom_components/hass_datapoints/src/molecules/comparison-tab-rail/comparison-tab-rail.ts +++ b/custom_components/hass_datapoints/src/molecules/comparison-tab-rail/comparison-tab-rail.ts @@ -47,7 +47,6 @@ export class ComparisonTabRail extends LitElement { private _resizeObserver?: ResizeObserver; connectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.connectedCallback(); if (this._resizeObserver) { return; @@ -67,7 +66,6 @@ export class ComparisonTabRail extends LitElement { } disconnectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); this._resizeObserver?.disconnect(); this._resizeObserver = undefined; diff --git a/custom_components/hass_datapoints/src/molecules/floating-menu/floating-menu.ts b/custom_components/hass_datapoints/src/molecules/floating-menu/floating-menu.ts index f3c46011..c791ebe2 100644 --- a/custom_components/hass_datapoints/src/molecules/floating-menu/floating-menu.ts +++ b/custom_components/hass_datapoints/src/molecules/floating-menu/floating-menu.ts @@ -21,14 +21,12 @@ export class FloatingMenu extends LitElement { @property({ type: Boolean, reflect: true }) accessor open: boolean = false; connectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.connectedCallback(); this._onPointerDown = this._onPointerDown.bind(this); window.addEventListener("pointerdown", this._onPointerDown, true); } disconnectedCallback() { - // eslint-disable-next-line wc/guard-super-call super.disconnectedCallback(); window.removeEventListener("pointerdown", this._onPointerDown, true); } diff --git a/custom_components/hass_datapoints/src/panels/datapoints/datapoints.ts b/custom_components/hass_datapoints/src/panels/datapoints/datapoints.ts index ec675db2..08b2e71d 100644 --- a/custom_components/hass_datapoints/src/panels/datapoints/datapoints.ts +++ b/custom_components/hass_datapoints/src/panels/datapoints/datapoints.ts @@ -2859,8 +2859,7 @@ export class HassRecordsHistoryPanel extends HTMLElement { targetControl.addEventListener( "value-changed", (ev: DetailEvent<{ value?: unknown }>) => { - const hasValue = - ev.detail && Object.prototype.hasOwnProperty.call(ev.detail, "value"); + const hasValue = ev.detail && Object.hasOwn(ev.detail, "value"); if (!hasValue) { return; } diff --git a/eslint.config.mjs b/eslint.config.mjs index 2ce54fef..dd7a3d2f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,10 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; +import { includeIgnoreFile } from "@eslint/compat"; import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; import eslintConfigPrettier from "eslint-config-prettier"; +import { configs, plugins } from "eslint-config-airbnb-extended"; import litPlugin from "eslint-plugin-lit"; import litA11yPlugin from "eslint-plugin-lit-a11y"; import unusedImports from "eslint-plugin-unused-imports"; @@ -13,10 +14,7 @@ import tseslint from "typescript-eslint"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); - -const compat = new FlatCompat({ - baseDirectory: __dirname, -}); +const gitignorePath = path.resolve(__dirname, ".gitignore"); export default [ { @@ -31,8 +29,17 @@ export default [ ".idea/**", ], }, + // Ignore files and folders listed in .gitignore + includeIgnoreFile(gitignorePath), + // ESLint recommended js.configs.recommended, - ...compat.extends("airbnb-base"), + // Airbnb base rules (replaces legacy compat.extends("airbnb-base")). + // configs.base.recommended uses @stylistic and import-x but does not bundle + // their plugin registrations — include the plugin objects first. + plugins.stylistic, + plugins.importX, + ...configs.base.recommended, + // TypeScript recommended (non-type-aware — no parserOptions.project needed) ...tseslint.configs.recommended.map((config) => ({ ...config, files: ["custom_components/hass_datapoints/src/**/*.{js,ts}"], @@ -57,10 +64,11 @@ export default [ "class-methods-use-this": "off", "consistent-return": "off", curly: ["error", "all"], - "import/extensions": "off", - "import/no-mutable-exports": "off", - "import/no-unresolved": "off", - "import/prefer-default-export": "off", + // airbnb-extended uses import-x plugin (successor to eslint-plugin-import) + "import-x/extensions": "off", + "import-x/no-mutable-exports": "off", + "import-x/no-unresolved": "off", + "import-x/prefer-default-export": "off", "no-console": "warn", "no-continue": "off", "no-param-reassign": "off", @@ -107,7 +115,7 @@ export default [ ], rules: { "@typescript-eslint/no-explicit-any": "off", - "import/no-extraneous-dependencies": "off", + "import-x/no-extraneous-dependencies": "off", }, }, eslintConfigPrettier, diff --git a/package.json b/package.json index 98bb6ad3..cef7ddc7 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "Build and development entrypoints for the hass-datapoints Home Assistant integration.", "packageManager": "pnpm@10.33.0", "engines": { - "node": ">=18.18.0" + "node": ">=24.0.0" }, "scripts": { "build": "bash scripts/build.sh", @@ -18,51 +18,52 @@ "lint:eslint:cmd": "eslint \"custom_components/hass_datapoints/src/**/*.{js,ts}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache", "lint:eslint": "pnpm lint:eslint:cmd --max-warnings=0", "format:eslint": "pnpm lint:eslint:cmd --fix", + "lint:package-json": "npmPkgJsonLint .", "lint:prettier": "prettier . --cache --check", "format:prettier": "prettier . --cache --write", "lint:types": "tsc --noEmit", - "lint": "pnpm run lint:eslint && pnpm run lint:prettier && pnpm run lint:types", + "lint": "pnpm run lint:eslint && pnpm run lint:package-json && pnpm run lint:prettier && pnpm run lint:types", "format": "pnpm run format:eslint && pnpm run format:prettier", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" }, "devDependencies": { - "@chromatic-com/storybook": "^5.1.1", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.39.1", - "@mdi/js": "^7.4.47", - "@storybook/addon-a11y": "^10.3.3", - "@storybook/addon-docs": "^10.3.3", - "@storybook/addon-mcp": "^0.5.0", - "@storybook/addon-vitest": "^10.3.3", - "@storybook/test": "^8.6.15", - "@storybook/web-components-vite": "^10.3.3", + "@chromatic-com/storybook": "5.1.1", + "@eslint/compat": "2.0.5", + "@eslint/js": "10.0.1", + "@mdi/js": "7.4.47", + "@storybook/addon-a11y": "10.3.3", + "@storybook/addon-docs": "10.3.3", + "@storybook/addon-mcp": "0.5.0", + "@storybook/addon-vitest": "10.3.3", + "@storybook/test": "8.6.15", + "@storybook/web-components-vite": "10.3.3", "@vitest/browser": "3.2.4", "@vitest/coverage-v8": "3.2.4", "@vitest/ui": "3.2.4", - "chromatic": "^16.0.0", - "eslint": "^9.39.1", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-import": "^2.32.0", - "eslint-plugin-lit": "^2.1.1", - "eslint-plugin-lit-a11y": "^5.1.1", - "eslint-plugin-unused-imports": "^4.3.0", - "eslint-plugin-wc": "^2.0.3", - "globals": "^16.4.0", - "happy-dom": "^20.8.9", - "playwright": "^1.58.2", - "prettier": "^3.6.2", - "storybook": "^10.3.3", - "typescript": "^5.9.3", - "typescript-eslint": "^8.46.2", - "vite": "^7.1.11", - "vitest": "^3.2.4" + "chromatic": "16.0.0", + "eslint": "10.2.0", + "eslint-config-airbnb-extended": "3.1.0", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-lit": "2.2.1", + "eslint-plugin-lit-a11y": "5.1.1", + "eslint-plugin-unused-imports": "4.4.1", + "eslint-plugin-wc": "3.1.0", + "globals": "17.4.0", + "happy-dom": "20.8.9", + "npm-package-json-lint": "10.2.0", + "playwright": "1.58.2", + "prettier": "3.8.1", + "storybook": "10.3.3", + "typescript": "5.9.3", + "typescript-eslint": "8.58.1", + "vite": "8.0.5", + "vitest": "3.2.4" }, "dependencies": { - "@kipk/load-ha-components": "^1.0.3", - "@lit/context": "^1.1.6", - "@lit/localize": "^0.12.2", - "lit": "^3.3.2" + "@kipk/load-ha-components": "1.0.3", + "@lit/context": "1.1.6", + "@lit/localize": "0.12.2", + "lit": "3.3.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba77b2dc..25a03bef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,51 +8,51 @@ importers: .: dependencies: "@kipk/load-ha-components": - specifier: ^1.0.3 + specifier: 1.0.3 version: 1.0.3 "@lit/context": - specifier: ^1.1.6 + specifier: 1.1.6 version: 1.1.6 "@lit/localize": - specifier: ^0.12.2 + specifier: 0.12.2 version: 0.12.2 lit: - specifier: ^3.3.2 + specifier: 3.3.2 version: 3.3.2 devDependencies: "@chromatic-com/storybook": - specifier: ^5.1.1 + specifier: 5.1.1 version: 5.1.1(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) - "@eslint/eslintrc": - specifier: ^3.3.1 - version: 3.3.5 + "@eslint/compat": + specifier: 2.0.5 + version: 2.0.5(eslint@10.2.0) "@eslint/js": - specifier: ^9.39.1 - version: 9.39.4 + specifier: 10.0.1 + version: 10.0.1(eslint@10.2.0) "@mdi/js": - specifier: ^7.4.47 + specifier: 7.4.47 version: 7.4.47 "@storybook/addon-a11y": - specifier: ^10.3.3 + specifier: 10.3.3 version: 10.3.3(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) "@storybook/addon-docs": - specifier: ^10.3.3 - version: 10.3.3(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0)) + specifier: 10.3.3 + version: 10.3.3(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4)) "@storybook/addon-mcp": - specifier: ^0.5.0 + specifier: 0.5.0 version: 0.5.0(@storybook/addon-vitest@10.3.3(@vitest/browser@3.2.4)(@vitest/runner@3.2.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vitest@3.2.4))(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) "@storybook/addon-vitest": - specifier: ^10.3.3 + specifier: 10.3.3 version: 10.3.3(@vitest/browser@3.2.4)(@vitest/runner@3.2.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vitest@3.2.4) "@storybook/test": - specifier: ^8.6.15 + specifier: 8.6.15 version: 8.6.15(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) "@storybook/web-components-vite": - specifier: ^10.3.3 - version: 10.3.3(esbuild@0.27.4)(lit@3.3.2)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0)) + specifier: 10.3.3 + version: 10.3.3(esbuild@0.27.4)(lit@3.3.2)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4)) "@vitest/browser": specifier: 3.2.4 - version: 3.2.4(playwright@1.58.2)(vite@7.3.1(@types/node@25.5.0))(vitest@3.2.4) + version: 3.2.4(playwright@1.58.2)(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))(vitest@3.2.4) "@vitest/coverage-v8": specifier: 3.2.4 version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4) @@ -60,59 +60,59 @@ importers: specifier: 3.2.4 version: 3.2.4(vitest@3.2.4) chromatic: - specifier: ^16.0.0 + specifier: 16.0.0 version: 16.0.0 eslint: - specifier: ^9.39.1 - version: 9.39.4 - eslint-config-airbnb-base: - specifier: ^15.0.0 - version: 15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4) + specifier: 10.2.0 + version: 10.2.0 + eslint-config-airbnb-extended: + specifier: 3.1.0 + version: 3.1.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0)(typescript@5.9.3) eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.4) - eslint-plugin-import: - specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4) + specifier: 10.1.8 + version: 10.1.8(eslint@10.2.0) eslint-plugin-lit: - specifier: ^2.1.1 - version: 2.2.1(eslint@9.39.4) + specifier: 2.2.1 + version: 2.2.1(eslint@10.2.0) eslint-plugin-lit-a11y: - specifier: ^5.1.1 - version: 5.1.1(eslint@9.39.4) + specifier: 5.1.1 + version: 5.1.1(eslint@10.2.0) eslint-plugin-unused-imports: - specifier: ^4.3.0 - version: 4.4.1(@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4) + specifier: 4.4.1 + version: 4.4.1(@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint@10.2.0)(typescript@5.9.3))(eslint@10.2.0) eslint-plugin-wc: - specifier: ^2.0.3 - version: 2.2.1(eslint@9.39.4) + specifier: 3.1.0 + version: 3.1.0(eslint@10.2.0) globals: - specifier: ^16.4.0 - version: 16.5.0 + specifier: 17.4.0 + version: 17.4.0 happy-dom: - specifier: ^20.8.9 + specifier: 20.8.9 version: 20.8.9 + npm-package-json-lint: + specifier: 10.2.0 + version: 10.2.0(typescript@5.9.3) playwright: - specifier: ^1.58.2 + specifier: 1.58.2 version: 1.58.2 prettier: - specifier: ^3.6.2 + specifier: 3.8.1 version: 3.8.1 storybook: - specifier: ^10.3.3 + specifier: 10.3.3 version: 10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) typescript: - specifier: ^5.9.3 + specifier: 5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.46.2 - version: 8.57.2(eslint@9.39.4)(typescript@5.9.3) + specifier: 8.58.1 + version: 8.58.1(eslint@10.2.0)(typescript@5.9.3) vite: - specifier: ^7.1.11 - version: 7.3.1(@types/node@25.5.0) + specifier: 8.0.5 + version: 8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4) vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9) + specifier: 3.2.4 + version: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9)(lightningcss@1.32.0) packages: "@adobe/css-tools@4.4.4": @@ -135,6 +135,57 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/compat-data@7.29.0": + resolution: + { + integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==, + } + engines: { node: ">=6.9.0" } + + "@babel/core@7.29.0": + resolution: + { + integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==, + } + engines: { node: ">=6.9.0" } + + "@babel/generator@7.29.1": + resolution: + { + integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-compilation-targets@7.28.6": + resolution: + { + integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-globals@7.28.0": + resolution: + { + integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-imports@7.28.6": + resolution: + { + integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-transforms@7.28.6": + resolution: + { + integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + "@babel/helper-string-parser@7.27.1": resolution: { @@ -149,6 +200,20 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/helper-validator-option@7.27.1": + resolution: + { + integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, + } + engines: { node: ">=6.9.0" } + + "@babel/helpers@7.29.2": + resolution: + { + integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==, + } + engines: { node: ">=6.9.0" } + "@babel/parser@7.29.2": resolution: { @@ -164,6 +229,20 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/template@7.28.6": + resolution: + { + integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==, + } + engines: { node: ">=6.9.0" } + + "@babel/traverse@7.29.0": + resolution: + { + integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==, + } + engines: { node: ">=6.9.0" } + "@babel/types@7.29.0": resolution: { @@ -187,6 +266,24 @@ packages: peerDependencies: storybook: ^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 + "@emnapi/core@1.9.2": + resolution: + { + integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==, + } + + "@emnapi/runtime@1.9.2": + resolution: + { + integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==, + } + + "@emnapi/wasi-threads@1.2.1": + resolution: + { + integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==, + } + "@esbuild/aix-ppc64@0.27.4": resolution: { @@ -437,54 +534,64 @@ packages: } engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - "@eslint/config-array@0.21.2": + "@eslint/compat@2.0.5": resolution: { - integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==, + integrity: sha512-IbHDbHJfkVNv6xjlET8AIVo/K1NQt7YT4Rp6ok/clyBGcpRx1l6gv0Rq3vBvYfPJIZt6ODf66Zq08FJNDpnzgg==, } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + peerDependencies: + eslint: ^8.40 || 9 || 10 + peerDependenciesMeta: + eslint: + optional: true - "@eslint/config-helpers@0.4.2": + "@eslint/config-array@0.23.5": resolution: { - integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==, + integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==, } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - "@eslint/core@0.17.0": + "@eslint/config-helpers@0.5.5": resolution: { - integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==, + integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==, } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - "@eslint/eslintrc@3.3.5": + "@eslint/core@1.2.1": resolution: { - integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==, + integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==, } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - "@eslint/js@9.39.4": + "@eslint/js@10.0.1": resolution: { - integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==, + integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==, } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true - "@eslint/object-schema@2.1.7": + "@eslint/object-schema@3.0.5": resolution: { - integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==, + integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==, } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - "@eslint/plugin-kit@0.4.1": + "@eslint/plugin-kit@0.7.1": resolution: { - integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==, + integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==, } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } "@humanfs/core@0.19.1": resolution: @@ -604,12 +711,66 @@ packages: "@types/react": ">=16" react: ">=16" + "@napi-rs/wasm-runtime@0.2.12": + resolution: + { + integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==, + } + + "@napi-rs/wasm-runtime@1.1.3": + resolution: + { + integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==, + } + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + "@neoconfetti/react@1.0.0": resolution: { integrity: sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==, } + "@next/eslint-plugin-next@16.2.2": + resolution: + { + integrity: sha512-IOPbWzDQ+76AtjZioaCjpIY72xNSDMnarZ2GMQ4wjNLvnJEJHqxQwGFhgnIWLV9klb4g/+amg88Tk5OXVpyLTw==, + } + + "@nodelib/fs.scandir@2.1.5": + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, + } + engines: { node: ">= 8" } + + "@nodelib/fs.stat@2.0.5": + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, + } + engines: { node: ">= 8" } + + "@nodelib/fs.walk@1.2.8": + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, + } + engines: { node: ">= 8" } + + "@oxc-project/types@0.122.0": + resolution: + { + integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==, + } + + "@package-json/types@0.0.12": + resolution: + { + integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==, + } + "@pkgjs/parseargs@0.11.0": resolution: { @@ -623,6 +784,152 @@ packages: integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==, } + "@rolldown/binding-android-arm64@1.0.0-rc.12": + resolution: + { + integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [android] + + "@rolldown/binding-darwin-arm64@1.0.0-rc.12": + resolution: + { + integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [darwin] + + "@rolldown/binding-darwin-x64@1.0.0-rc.12": + resolution: + { + integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [darwin] + + "@rolldown/binding-freebsd-x64@1.0.0-rc.12": + resolution: + { + integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [freebsd] + + "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12": + resolution: + { + integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm] + os: [linux] + + "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12": + resolution: + { + integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [linux] + libc: [glibc] + + "@rolldown/binding-linux-arm64-musl@1.0.0-rc.12": + resolution: + { + integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [linux] + libc: [musl] + + "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12": + resolution: + { + integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [ppc64] + os: [linux] + libc: [glibc] + + "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12": + resolution: + { + integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [s390x] + os: [linux] + libc: [glibc] + + "@rolldown/binding-linux-x64-gnu@1.0.0-rc.12": + resolution: + { + integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [linux] + libc: [glibc] + + "@rolldown/binding-linux-x64-musl@1.0.0-rc.12": + resolution: + { + integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [linux] + libc: [musl] + + "@rolldown/binding-openharmony-arm64@1.0.0-rc.12": + resolution: + { + integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [openharmony] + + "@rolldown/binding-wasm32-wasi@1.0.0-rc.12": + resolution: + { + integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==, + } + engines: { node: ">=14.0.0" } + cpu: [wasm32] + + "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12": + resolution: + { + integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [arm64] + os: [win32] + + "@rolldown/binding-win32-x64-msvc@1.0.0-rc.12": + resolution: + { + integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + cpu: [x64] + os: [win32] + + "@rolldown/pluginutils@1.0.0-rc.12": + resolution: + { + integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==, + } + "@rollup/rollup-android-arm-eabi@4.60.1": resolution: { @@ -991,6 +1298,15 @@ packages: lit: ^2.0.0 || ^3.0.0 storybook: ^10.3.3 + "@stylistic/eslint-plugin@5.10.0": + resolution: + { + integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + "@testing-library/dom@10.4.0": resolution: { @@ -1065,6 +1381,12 @@ packages: "@tmcp/auth": optional: true + "@tybys/wasm-util@0.10.1": + resolution: + { + integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==, + } + "@types/aria-query@5.0.4": resolution: { @@ -1083,6 +1405,12 @@ packages: integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, } + "@types/esrecurse@4.3.1": + resolution: + { + integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==, + } + "@types/estree@1.0.8": resolution: { @@ -1107,12 +1435,24 @@ packages: integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==, } - "@types/node@25.5.0": + "@types/minimist@1.2.5": + resolution: + { + integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==, + } + + "@types/node@25.5.0": resolution: { integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==, } + "@types/normalize-package-data@2.4.4": + resolution: + { + integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==, + } + "@types/parse5@2.2.34": resolution: { @@ -1143,61 +1483,61 @@ packages: integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==, } - "@typescript-eslint/eslint-plugin@8.57.2": + "@typescript-eslint/eslint-plugin@8.58.1": resolution: { - integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==, + integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - "@typescript-eslint/parser": ^8.57.2 + "@typescript-eslint/parser": ^8.58.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" + typescript: ">=4.8.4 <6.1.0" - "@typescript-eslint/parser@8.57.2": + "@typescript-eslint/parser@8.58.1": resolution: { - integrity: sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==, + integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" + typescript: ">=4.8.4 <6.1.0" - "@typescript-eslint/project-service@8.57.2": + "@typescript-eslint/project-service@8.58.1": resolution: { - integrity: sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==, + integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: ">=4.8.4 <6.0.0" + typescript: ">=4.8.4 <6.1.0" - "@typescript-eslint/scope-manager@8.57.2": + "@typescript-eslint/scope-manager@8.58.1": resolution: { - integrity: sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==, + integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@typescript-eslint/tsconfig-utils@8.57.2": + "@typescript-eslint/tsconfig-utils@8.58.1": resolution: { - integrity: sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==, + integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: ">=4.8.4 <6.0.0" + typescript: ">=4.8.4 <6.1.0" - "@typescript-eslint/type-utils@8.57.2": + "@typescript-eslint/type-utils@8.58.1": resolution: { - integrity: sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==, + integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" + typescript: ">=4.8.4 <6.1.0" "@typescript-eslint/types@8.57.2": resolution: @@ -1206,32 +1546,199 @@ packages: } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@typescript-eslint/typescript-estree@8.57.2": + "@typescript-eslint/types@8.58.1": + resolution: + { + integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/typescript-estree@8.58.1": resolution: { - integrity: sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==, + integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: - typescript: ">=4.8.4 <6.0.0" + typescript: ">=4.8.4 <6.1.0" - "@typescript-eslint/utils@8.57.2": + "@typescript-eslint/utils@8.58.1": resolution: { - integrity: sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==, + integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" + typescript: ">=4.8.4 <6.1.0" - "@typescript-eslint/visitor-keys@8.57.2": + "@typescript-eslint/visitor-keys@8.58.1": resolution: { - integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==, + integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@unrs/resolver-binding-android-arm-eabi@1.11.1": + resolution: + { + integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==, + } + cpu: [arm] + os: [android] + + "@unrs/resolver-binding-android-arm64@1.11.1": + resolution: + { + integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==, + } + cpu: [arm64] + os: [android] + + "@unrs/resolver-binding-darwin-arm64@1.11.1": + resolution: + { + integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==, + } + cpu: [arm64] + os: [darwin] + + "@unrs/resolver-binding-darwin-x64@1.11.1": + resolution: + { + integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==, + } + cpu: [x64] + os: [darwin] + + "@unrs/resolver-binding-freebsd-x64@1.11.1": + resolution: + { + integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==, + } + cpu: [x64] + os: [freebsd] + + "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": + resolution: + { + integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==, + } + cpu: [arm] + os: [linux] + + "@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": + resolution: + { + integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==, + } + cpu: [arm] + os: [linux] + + "@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + resolution: + { + integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==, + } + cpu: [arm64] + os: [linux] + libc: [glibc] + + "@unrs/resolver-binding-linux-arm64-musl@1.11.1": + resolution: + { + integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==, + } + cpu: [arm64] + os: [linux] + libc: [musl] + + "@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + resolution: + { + integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==, + } + cpu: [ppc64] + os: [linux] + libc: [glibc] + + "@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + resolution: + { + integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==, + } + cpu: [riscv64] + os: [linux] + libc: [glibc] + + "@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + resolution: + { + integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==, + } + cpu: [riscv64] + os: [linux] + libc: [musl] + + "@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + resolution: + { + integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==, + } + cpu: [s390x] + os: [linux] + libc: [glibc] + + "@unrs/resolver-binding-linux-x64-gnu@1.11.1": + resolution: + { + integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==, + } + cpu: [x64] + os: [linux] + libc: [glibc] + + "@unrs/resolver-binding-linux-x64-musl@1.11.1": + resolution: + { + integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==, + } + cpu: [x64] + os: [linux] + libc: [musl] + + "@unrs/resolver-binding-wasm32-wasi@1.11.1": + resolution: + { + integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==, + } + engines: { node: ">=14.0.0" } + cpu: [wasm32] + + "@unrs/resolver-binding-win32-arm64-msvc@1.11.1": + resolution: + { + integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==, + } + cpu: [arm64] + os: [win32] + + "@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + resolution: + { + integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==, + } + cpu: [ia32] + os: [win32] + + "@unrs/resolver-binding-win32-x64-msvc@1.11.1": + resolution: + { + integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==, + } + cpu: [x64] + os: [win32] + "@valibot/to-json-schema@1.6.0": resolution: { @@ -1380,6 +1887,20 @@ packages: engines: { node: ">=0.4.0" } hasBin: true + ajv-errors@1.0.1: + resolution: + { + integrity: sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==, + } + peerDependencies: + ajv: ">=5.0.0" + + ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, + } + ajv@6.14.0: resolution: { @@ -1454,6 +1975,20 @@ packages: } engines: { node: ">= 0.4" } + array-union@2.1.0: + resolution: + { + integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, + } + engines: { node: ">=8" } + + array.prototype.findlast@1.2.5: + resolution: + { + integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==, + } + engines: { node: ">= 0.4" } + array.prototype.findlastindex@1.2.6: resolution: { @@ -1475,6 +2010,13 @@ packages: } engines: { node: ">= 0.4" } + array.prototype.tosorted@1.1.4: + resolution: + { + integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==, + } + engines: { node: ">= 0.4" } + arraybuffer.prototype.slice@1.0.4: resolution: { @@ -1482,6 +2024,13 @@ packages: } engines: { node: ">= 0.4" } + arrify@1.0.1: + resolution: + { + integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==, + } + engines: { node: ">=0.10.0" } + assertion-error@2.0.1: resolution: { @@ -1489,6 +2038,12 @@ packages: } engines: { node: ">=12" } + ast-types-flow@0.0.8: + resolution: + { + integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==, + } + ast-types@0.16.1: resolution: { @@ -1523,6 +2078,13 @@ packages: } engines: { node: ">=4" } + axobject-query@4.1.0: + resolution: + { + integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==, + } + engines: { node: ">= 0.4" } + balanced-match@1.0.2: resolution: { @@ -1536,6 +2098,14 @@ packages: } engines: { node: 18 || 20 || >=22 } + baseline-browser-mapping@2.10.16: + resolution: + { + integrity: sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==, + } + engines: { node: ">=6.0.0" } + hasBin: true + brace-expansion@1.1.13: resolution: { @@ -1555,6 +2125,21 @@ packages: } engines: { node: 18 || 20 || >=22 } + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: ">=8" } + + browserslist@4.28.2: + resolution: + { + integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==, + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + bundle-name@4.1.0: resolution: { @@ -1597,6 +2182,26 @@ packages: } engines: { node: ">=6" } + camelcase-keys@6.2.2: + resolution: + { + integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==, + } + engines: { node: ">=8" } + + camelcase@5.3.1: + resolution: + { + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, + } + engines: { node: ">=6" } + + caniuse-lite@1.0.30001787: + resolution: + { + integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==, + } + chai@5.3.3: resolution: { @@ -1675,6 +2280,13 @@ packages: integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, } + comment-parser@1.4.6: + resolution: + { + integrity: sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==, + } + engines: { node: ">= 12.0.0" } + concat-map@0.0.1: resolution: { @@ -1687,6 +2299,24 @@ packages: integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==, } + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, + } + + cosmiconfig@8.3.6: + resolution: + { + integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==, + } + engines: { node: ">=14" } + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + cross-spawn@7.0.6: resolution: { @@ -1706,6 +2336,12 @@ packages: integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==, } + damerau-levenshtein@1.0.8: + resolution: + { + integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==, + } + data-view-buffer@1.0.2: resolution: { @@ -1750,6 +2386,20 @@ packages: supports-color: optional: true + decamelize-keys@1.1.1: + resolution: + { + integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==, + } + engines: { node: ">=0.10.0" } + + decamelize@1.2.0: + resolution: + { + integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==, + } + engines: { node: ">=0.10.0" } + deep-eql@5.0.2: resolution: { @@ -1805,6 +2455,20 @@ packages: } engines: { node: ">=6" } + detect-libc@2.1.2: + resolution: + { + integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, + } + engines: { node: ">=8" } + + dir-glob@3.0.1: + resolution: + { + integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, + } + engines: { node: ">=8" } + doctrine@2.1.0: resolution: { @@ -1843,6 +2507,12 @@ packages: integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, } + electron-to-chromium@1.5.334: + resolution: + { + integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==, + } + emoji-regex@10.6.0: resolution: { @@ -1861,6 +2531,13 @@ packages: integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, } + enhanced-resolve@5.20.1: + resolution: + { + integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==, + } + engines: { node: ">=10.13.0" } + entities@6.0.1: resolution: { @@ -1875,12 +2552,18 @@ packages: } engines: { node: ">=0.12" } - es-abstract@1.24.1: + error-ex@1.3.4: resolution: { - integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==, + integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==, } - engines: { node: ">= 0.4" } + + es-abstract@1.24.1: + resolution: + { + integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==, + } + engines: { node: ">= 0.4" } es-define-property@1.0.1: resolution: @@ -1896,6 +2579,13 @@ packages: } engines: { node: ">= 0.4" } + es-iterator-helpers@1.3.1: + resolution: + { + integrity: sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==, + } + engines: { node: ">= 0.4" } + es-module-lexer@1.7.0: resolution: { @@ -1938,6 +2628,13 @@ packages: engines: { node: ">=18" } hasBin: true + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, + } + engines: { node: ">=6" } + escape-string-regexp@4.0.0: resolution: { @@ -1945,15 +2642,23 @@ packages: } engines: { node: ">=10" } - eslint-config-airbnb-base@15.0.0: + eslint-compat-utils@0.5.1: resolution: { - integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==, + integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==, } - engines: { node: ^10.12.0 || >=12.0.0 } + engines: { node: ">=12" } peerDependencies: - eslint: ^7.32.0 || ^8.2.0 - eslint-plugin-import: ^2.25.2 + eslint: ">=6.0.0" + + eslint-config-airbnb-extended@3.1.0: + resolution: + { + integrity: sha512-uUE5+8gQ9h+QqHOI0OIBIQW0+/bA4l/GE1i5fPAGk64Y5lBKxyZRNDcQUGSaovOzX7yMa0Q+3MS1lj5bq/BC/Q==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^9.0.0 eslint-config-prettier@10.1.8: resolution: @@ -1964,12 +2669,40 @@ packages: peerDependencies: eslint: ">=7.0.0" + eslint-import-context@0.1.9: + resolution: + { + integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + peerDependencies: + unrs-resolver: ^1.0.0 + peerDependenciesMeta: + unrs-resolver: + optional: true + eslint-import-resolver-node@0.3.9: resolution: { integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==, } + eslint-import-resolver-typescript@4.4.4: + resolution: + { + integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==, + } + engines: { node: ^16.17.0 || >=18.6.0 } + peerDependencies: + eslint: "*" + eslint-plugin-import: "*" + eslint-plugin-import-x: "*" + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + eslint-module-utils@2.12.1: resolution: { @@ -1994,6 +2727,31 @@ packages: eslint-import-resolver-webpack: optional: true + eslint-plugin-es-x@7.8.0: + resolution: + { + integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + eslint: ">=8" + + eslint-plugin-import-x@4.16.2: + resolution: + { + integrity: sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + "@typescript-eslint/utils": ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + eslint-import-resolver-node: "*" + peerDependenciesMeta: + "@typescript-eslint/utils": + optional: true + eslint-import-resolver-node: + optional: true + eslint-plugin-import@2.32.0: resolution: { @@ -2007,6 +2765,15 @@ packages: "@typescript-eslint/parser": optional: true + eslint-plugin-jsx-a11y@6.10.2: + resolution: + { + integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==, + } + engines: { node: ">=4.0" } + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + eslint-plugin-lit-a11y@5.1.1: resolution: { @@ -2024,6 +2791,33 @@ packages: peerDependencies: eslint: ">= 8" + eslint-plugin-n@17.24.0: + resolution: + { + integrity: sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ">=8.23.0" + + eslint-plugin-react-hooks@7.0.1: + resolution: + { + integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==, + } + engines: { node: ">=18" } + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: + { + integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==, + } + engines: { node: ">=4" } + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-unused-imports@4.4.1: resolution: { @@ -2036,10 +2830,10 @@ packages: "@typescript-eslint/eslint-plugin": optional: true - eslint-plugin-wc@2.2.1: + eslint-plugin-wc@3.1.0: resolution: { - integrity: sha512-KstLqGmyQz088DvFlDYHg0sHih+w2QeulreCi1D1ftr357klO2zqHdG/bbnNMmuQdVFDuNkopNIyNhmG0XCT/g==, + integrity: sha512-spvXHD2/GTTgYXxFB3xlMThnXGUeNJaiCwWuPGzjDOLXnVGLcQpDt0fyiN6yiLoaLs/yhsj+7G1FpBZKeigCSA==, } peerDependencies: eslint: ">=8.40.0" @@ -2051,12 +2845,12 @@ packages: } engines: { node: ">=10" } - eslint-scope@8.4.0: + eslint-scope@9.1.2: resolution: { - integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, + integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==, } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } eslint-visitor-keys@3.4.3: resolution: @@ -2079,12 +2873,12 @@ packages: } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } - eslint@9.39.4: + eslint@10.2.0: resolution: { - integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==, + integrity: sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==, } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } hasBin: true peerDependencies: jiti: "*" @@ -2105,6 +2899,13 @@ packages: } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + espree@11.2.0: + resolution: + { + integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + esprima@4.0.1: resolution: { @@ -2160,6 +2961,20 @@ packages: integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, } + fast-glob@3.3.1: + resolution: + { + integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==, + } + engines: { node: ">=8.6.0" } + + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, + } + engines: { node: ">=8.6.0" } + fast-json-stable-stringify@2.1.0: resolution: { @@ -2172,6 +2987,12 @@ packages: integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, } + fastq@1.20.1: + resolution: + { + integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==, + } + fdir@6.5.0: resolution: { @@ -2204,6 +3025,20 @@ packages: } engines: { node: ">= 10.4.0" } + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: ">=8" } + + find-up@4.1.0: + resolution: + { + integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, + } + engines: { node: ">=8" } + find-up@5.0.0: resolution: { @@ -2280,6 +3115,13 @@ packages: } engines: { node: ">= 0.4" } + gensync@1.0.0-beta.2: + resolution: + { + integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, + } + engines: { node: ">=6.9.0" } + get-intrinsic@1.3.0: resolution: { @@ -2301,6 +3143,19 @@ packages: } engines: { node: ">= 0.4" } + get-tsconfig@4.13.7: + resolution: + { + integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==, + } + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, + } + engines: { node: ">= 6" } + glob-parent@6.0.2: resolution: { @@ -2316,17 +3171,17 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - globals@14.0.0: + globals@15.15.0: resolution: { - integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, + integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==, } engines: { node: ">=18" } - globals@16.5.0: + globals@17.4.0: resolution: { - integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==, + integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==, } engines: { node: ">=18" } @@ -2337,6 +3192,19 @@ packages: } engines: { node: ">= 0.4" } + globby@11.1.0: + resolution: + { + integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==, + } + engines: { node: ">=10" } + + globrex@0.1.2: + resolution: + { + integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==, + } + gopd@1.2.0: resolution: { @@ -2357,6 +3225,13 @@ packages: } engines: { node: ">=20.0.0" } + hard-rejection@2.1.0: + resolution: + { + integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==, + } + engines: { node: ">=6" } + has-bigints@1.1.0: resolution: { @@ -2405,6 +3280,31 @@ packages: } engines: { node: ">= 0.4" } + hermes-estree@0.25.1: + resolution: + { + integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==, + } + + hermes-parser@0.25.1: + resolution: + { + integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==, + } + + hosted-git-info@2.8.9: + resolution: + { + integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==, + } + + hosted-git-info@4.1.0: + resolution: + { + integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==, + } + engines: { node: ">=10" } + html-escaper@2.0.2: resolution: { @@ -2453,6 +3353,13 @@ packages: } engines: { node: ">= 0.4" } + irregular-plurals@3.5.0: + resolution: + { + integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==, + } + engines: { node: ">=8" } + is-array-buffer@3.0.5: resolution: { @@ -2460,6 +3367,12 @@ packages: } engines: { node: ">= 0.4" } + is-arrayish@0.2.1: + resolution: + { + integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, + } + is-async-function@2.1.1: resolution: { @@ -2481,6 +3394,12 @@ packages: } engines: { node: ">= 0.4" } + is-bun-module@2.0.0: + resolution: + { + integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==, + } + is-callable@1.2.7: resolution: { @@ -2581,6 +3500,27 @@ packages: } engines: { node: ">= 0.4" } + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, + } + engines: { node: ">=0.12.0" } + + is-plain-obj@1.1.0: + resolution: + { + integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==, + } + engines: { node: ">=0.10.0" } + + is-plain-obj@3.0.0: + resolution: + { + integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==, + } + engines: { node: ">=10" } + is-potential-custom-element-name@1.0.1: resolution: { @@ -2629,6 +3569,13 @@ packages: } engines: { node: ">= 0.4" } + is-unicode-supported@0.1.0: + resolution: + { + integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==, + } + engines: { node: ">=10" } + is-valid-element-name@1.0.0: resolution: { @@ -2703,16 +3650,23 @@ packages: } engines: { node: ">=8" } + iterator.prototype@1.1.5: + resolution: + { + integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==, + } + engines: { node: ">= 0.4" } + jackspeak@3.4.3: resolution: { integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, } - js-levenshtein-esm@1.2.0: + js-levenshtein-esm@2.0.0: resolution: { - integrity: sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==, + integrity: sha512-1n4LEPOL4wRXY8rOQcuA7Iuaphe5xCMayvufCzlLAi+hRsnBRDbSS6XPuV58CBVJxj5D9ApFLyjQ7KzFToyHBw==, } js-tokens@10.0.0: @@ -2740,12 +3694,26 @@ packages: } hasBin: true + jsesc@3.1.0: + resolution: + { + integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, + } + engines: { node: ">=6" } + hasBin: true + json-buffer@3.0.1: resolution: { integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, } + json-parse-even-better-errors@2.3.1: + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, + } + json-rpc-2.0@1.7.1: resolution: { @@ -2771,18 +3739,46 @@ packages: } hasBin: true + json5@2.2.3: + resolution: + { + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, + } + engines: { node: ">=6" } + hasBin: true + + jsonc-parser@3.3.1: + resolution: + { + integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==, + } + jsonfile@6.2.0: resolution: { integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==, } + jsx-ast-utils@3.3.5: + resolution: + { + integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==, + } + engines: { node: ">=4.0" } + keyv@4.5.4: resolution: { integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, } + kind-of@6.0.3: + resolution: + { + integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==, + } + engines: { node: ">=0.10.0" } + language-subtag-registry@0.3.23: resolution: { @@ -2803,6 +3799,122 @@ packages: } engines: { node: ">= 0.8.0" } + lightningcss-android-arm64@1.32.0: + resolution: + { + integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: + { + integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: + { + integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: + { + integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: + { + integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: + { + integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: + { + integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: + { + integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: + { + integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: + { + integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==, + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: + { + integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==, + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: + { + integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==, + } + engines: { node: ">= 12.0.0" } + + lines-and-columns@1.2.4: + resolution: + { + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, + } + lit-element@4.2.2: resolution: { @@ -2821,6 +3933,13 @@ packages: integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==, } + locate-path@5.0.0: + resolution: + { + integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, + } + engines: { node: ">=8" } + locate-path@6.0.0: resolution: { @@ -2828,17 +3947,25 @@ packages: } engines: { node: ">=10" } - lodash.merge@4.6.2: + lodash@4.17.23: resolution: { - integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, + integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==, } - lodash@4.17.23: + log-symbols@4.1.0: resolution: { - integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==, + integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==, + } + engines: { node: ">=10" } + + loose-envify@1.4.0: + resolution: + { + integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, } + hasBin: true loupe@3.2.1: resolution: @@ -2852,6 +3979,19 @@ packages: integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, } + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, + } + + lru-cache@6.0.0: + resolution: + { + integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, + } + engines: { node: ">=10" } + lz-string@1.5.0: resolution: { @@ -2878,6 +4018,20 @@ packages: } engines: { node: ">=10" } + map-obj@1.0.1: + resolution: + { + integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==, + } + engines: { node: ">=0.10.0" } + + map-obj@4.3.0: + resolution: + { + integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==, + } + engines: { node: ">=8" } + math-intrinsics@1.1.0: resolution: { @@ -2885,6 +4039,27 @@ packages: } engines: { node: ">= 0.4" } + meow@9.0.0: + resolution: + { + integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==, + } + engines: { node: ">=10" } + + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, + } + engines: { node: ">= 8" } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: ">=8.6" } + min-indent@1.0.1: resolution: { @@ -2912,6 +4087,13 @@ packages: } engines: { node: ">=16 || 14 >=14.17" } + minimist-options@4.1.0: + resolution: + { + integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==, + } + engines: { node: ">= 6" } + minimist@1.2.8: resolution: { @@ -2946,12 +4128,61 @@ packages: engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } hasBin: true + napi-postinstall@0.3.4: + resolution: + { + integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + hasBin: true + natural-compare@1.4.0: resolution: { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, } + node-exports-info@1.6.0: + resolution: + { + integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==, + } + engines: { node: ">= 0.4" } + + node-releases@2.0.37: + resolution: + { + integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==, + } + + normalize-package-data@2.5.0: + resolution: + { + integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==, + } + + normalize-package-data@3.0.3: + resolution: + { + integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==, + } + engines: { node: ">=10" } + + npm-package-json-lint@10.2.0: + resolution: + { + integrity: sha512-p0fKe65OyXsJslOxGIcnqSo3tyMddH0n3X8AI7Ab0BSxVPc2eZ0ey1z6/L9Rh59k+BW9mHJNK9LKr4k+pA8Ojg==, + } + engines: { node: ">=22.0.0", npm: ">=10.0.0" } + hasBin: true + + object-assign@4.1.1: + resolution: + { + integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, + } + engines: { node: ">=0.10.0" } + object-inspect@1.13.4: resolution: { @@ -3022,6 +4253,13 @@ packages: } engines: { node: ">= 0.4" } + p-limit@2.3.0: + resolution: + { + integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, + } + engines: { node: ">=6" } + p-limit@3.1.0: resolution: { @@ -3029,6 +4267,13 @@ packages: } engines: { node: ">=10" } + p-locate@4.1.0: + resolution: + { + integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, + } + engines: { node: ">=8" } + p-locate@5.0.0: resolution: { @@ -3036,6 +4281,13 @@ packages: } engines: { node: ">=10" } + p-try@2.2.0: + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, + } + engines: { node: ">=6" } + package-json-from-dist@1.0.1: resolution: { @@ -3049,6 +4301,13 @@ packages: } engines: { node: ">=6" } + parse-json@5.2.0: + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, + } + engines: { node: ">=8" } + parse5-htmlparser2-tree-adapter@6.0.1: resolution: { @@ -3100,6 +4359,13 @@ packages: } engines: { node: ">=16 || 14 >=14.18" } + path-type@4.0.0: + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, + } + engines: { node: ">=8" } + pathe@2.0.3: resolution: { @@ -3119,6 +4385,13 @@ packages: integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, } + picomatch@2.3.2: + resolution: + { + integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==, + } + engines: { node: ">=8.6" } + picomatch@4.0.4: resolution: { @@ -3148,6 +4421,13 @@ packages: engines: { node: ">=18" } hasBin: true + plur@4.0.0: + resolution: + { + integrity: sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==, + } + engines: { node: ">=10" } + possible-typed-array-names@1.1.0: resolution: { @@ -3184,6 +4464,12 @@ packages: } engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } + prop-types@15.8.1: + resolution: + { + integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, + } + punycode@2.3.1: resolution: { @@ -3191,6 +4477,19 @@ packages: } engines: { node: ">=6" } + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } + + quick-lru@4.0.1: + resolution: + { + integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==, + } + engines: { node: ">=8" } + react-dom@19.2.4: resolution: { @@ -3199,6 +4498,12 @@ packages: peerDependencies: react: ^19.2.4 + react-is@16.13.1: + resolution: + { + integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, + } + react-is@17.0.2: resolution: { @@ -3212,6 +4517,20 @@ packages: } engines: { node: ">=0.10.0" } + read-pkg-up@7.0.1: + resolution: + { + integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==, + } + engines: { node: ">=8" } + + read-pkg@5.2.0: + resolution: + { + integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==, + } + engines: { node: ">=8" } + recast@0.23.11: resolution: { @@ -3243,16 +4562,45 @@ packages: resolve-from@4.0.0: resolution: { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: { node: ">=4" } + + resolve-pkg-maps@1.0.0: + resolution: + { + integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, + } + + resolve@1.22.11: + resolution: + { + integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==, + } + engines: { node: ">= 0.4" } + hasBin: true + + resolve@2.0.0-next.6: + resolution: + { + integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==, + } + engines: { node: ">= 0.4" } + hasBin: true + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, } - engines: { node: ">=4" } + engines: { iojs: ">=1.0.0", node: ">=0.10.0" } - resolve@1.22.11: + rolldown@1.0.0-rc.12: resolution: { - integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==, + integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==, } - engines: { node: ">= 0.4" } + engines: { node: ^20.19.0 || >=22.12.0 } hasBin: true rollup@4.60.1: @@ -3270,6 +4618,12 @@ packages: } engines: { node: ">=18" } + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } + safe-array-concat@1.1.3: resolution: { @@ -3297,6 +4651,13 @@ packages: integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==, } + semver@5.7.2: + resolution: + { + integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==, + } + hasBin: true + semver@6.3.1: resolution: { @@ -3395,6 +4756,13 @@ packages: } engines: { node: ">=18" } + slash@3.0.0: + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, + } + engines: { node: ">=8" } + source-map-js@1.2.1: resolution: { @@ -3409,12 +4777,43 @@ packages: } engines: { node: ">=0.10.0" } + spdx-correct@3.2.0: + resolution: + { + integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==, + } + + spdx-exceptions@2.5.0: + resolution: + { + integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==, + } + + spdx-expression-parse@3.0.1: + resolution: + { + integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==, + } + + spdx-license-ids@3.0.23: + resolution: + { + integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==, + } + sqids@0.3.0: resolution: { integrity: sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw==, } + stable-hash-x@0.2.0: + resolution: + { + integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==, + } + engines: { node: ">=12.0.0" } + stackback@0.0.2: resolution: { @@ -3460,6 +4859,26 @@ packages: } engines: { node: ">=12" } + string.prototype.includes@2.0.1: + resolution: + { + integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==, + } + engines: { node: ">= 0.4" } + + string.prototype.matchall@4.0.12: + resolution: + { + integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==, + } + engines: { node: ">= 0.4" } + + string.prototype.repeat@1.0.0: + resolution: + { + integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==, + } + string.prototype.trim@1.2.10: resolution: { @@ -3536,6 +4955,20 @@ packages: } engines: { node: ">= 0.4" } + tagged-tag@1.0.0: + resolution: + { + integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==, + } + engines: { node: ">=20" } + + tapable@2.3.2: + resolution: + { + integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==, + } + engines: { node: ">=6" } + test-exclude@7.0.2: resolution: { @@ -3609,6 +5042,13 @@ packages: integrity: sha512-plz/TLKNFrdfQN32LjCTN6ULy6pynfGPgHcU7KGCI5dBrxQ9Mub99SmcYuzxEkLjJooQuOD3gosSwZEl1htOtw==, } + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: ">=8.0" } + totalist@3.0.1: resolution: { @@ -3616,6 +5056,13 @@ packages: } engines: { node: ">=6" } + trim-newlines@3.0.1: + resolution: + { + integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==, + } + engines: { node: ">=8" } + ts-api-utils@2.5.0: resolution: { @@ -3625,6 +5072,14 @@ packages: peerDependencies: typescript: ">=4.8.4" + ts-declaration-location@1.0.7: + resolution: + { + integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==, + } + peerDependencies: + typescript: ">=4.0.0" + ts-dedent@2.2.0: resolution: { @@ -3651,6 +5106,34 @@ packages: } engines: { node: ">= 0.8.0" } + type-fest@0.18.1: + resolution: + { + integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==, + } + engines: { node: ">=10" } + + type-fest@0.6.0: + resolution: + { + integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==, + } + engines: { node: ">=8" } + + type-fest@0.8.1: + resolution: + { + integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==, + } + engines: { node: ">=8" } + + type-fest@5.3.1: + resolution: + { + integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==, + } + engines: { node: ">=20" } + typed-array-buffer@1.0.3: resolution: { @@ -3679,15 +5162,15 @@ packages: } engines: { node: ">= 0.4" } - typescript-eslint@8.57.2: + typescript-eslint@8.58.1: resolution: { - integrity: sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==, + integrity: sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==, } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.0.0" + typescript: ">=4.8.4 <6.1.0" typescript@5.9.3: resolution: @@ -3724,6 +5207,21 @@ packages: } engines: { node: ">=18.12.0" } + unrs-resolver@1.11.1: + resolution: + { + integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==, + } + + update-browserslist-db@1.2.3: + resolution: + { + integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==, + } + hasBin: true + peerDependencies: + browserslist: ">= 4.21.0" + uri-js@4.4.1: resolution: { @@ -3755,6 +5253,19 @@ packages: typescript: optional: true + validate-npm-package-license@3.0.4: + resolution: + { + integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==, + } + + validate-npm-package-name@6.0.0: + resolution: + { + integrity: sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg==, + } + engines: { node: ^18.17.0 || >=20.5.0 } + vite-node@3.2.4: resolution: { @@ -3806,6 +5317,52 @@ packages: yaml: optional: true + vite@8.0.5: + resolution: + { + integrity: sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitest@3.2.4: resolution: { @@ -3937,6 +5494,25 @@ packages: } engines: { node: ">=18" } + yallist@3.1.1: + resolution: + { + integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, + } + + yallist@4.0.0: + resolution: + { + integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, + } + + yargs-parser@20.2.9: + resolution: + { + integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==, + } + engines: { node: ">=10" } + yocto-queue@0.1.0: resolution: { @@ -3944,6 +5520,21 @@ packages: } engines: { node: ">=10" } + zod-validation-error@4.0.2: + resolution: + { + integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==, + } + engines: { node: ">=18.0.0" } + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: + { + integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==, + } + snapshots: "@adobe/css-tools@4.4.4": {} @@ -3958,16 +5549,97 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + "@babel/compat-data@7.29.0": {} + + "@babel/core@7.29.0": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/generator": 7.29.1 + "@babel/helper-compilation-targets": 7.28.6 + "@babel/helper-module-transforms": 7.28.6(@babel/core@7.29.0) + "@babel/helpers": 7.29.2 + "@babel/parser": 7.29.2 + "@babel/template": 7.28.6 + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + "@jridgewell/remapping": 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + "@babel/generator@7.29.1": + dependencies: + "@babel/parser": 7.29.2 + "@babel/types": 7.29.0 + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 + jsesc: 3.1.0 + + "@babel/helper-compilation-targets@7.28.6": + dependencies: + "@babel/compat-data": 7.29.0 + "@babel/helper-validator-option": 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + "@babel/helper-globals@7.28.0": {} + + "@babel/helper-module-imports@7.28.6": + dependencies: + "@babel/traverse": 7.29.0 + "@babel/types": 7.29.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)": + dependencies: + "@babel/core": 7.29.0 + "@babel/helper-module-imports": 7.28.6 + "@babel/helper-validator-identifier": 7.28.5 + "@babel/traverse": 7.29.0 + transitivePeerDependencies: + - supports-color + "@babel/helper-string-parser@7.27.1": {} "@babel/helper-validator-identifier@7.28.5": {} + "@babel/helper-validator-option@7.27.1": {} + + "@babel/helpers@7.29.2": + dependencies: + "@babel/template": 7.28.6 + "@babel/types": 7.29.0 + "@babel/parser@7.29.2": dependencies: "@babel/types": 7.29.0 "@babel/runtime@7.29.2": {} + "@babel/template@7.28.6": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/parser": 7.29.2 + "@babel/types": 7.29.0 + + "@babel/traverse@7.29.0": + dependencies: + "@babel/code-frame": 7.29.0 + "@babel/generator": 7.29.1 + "@babel/helper-globals": 7.28.0 + "@babel/parser": 7.29.2 + "@babel/template": 7.28.6 + "@babel/types": 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + "@babel/types@7.29.0": dependencies: "@babel/helper-string-parser": 7.27.1 @@ -3987,6 +5659,22 @@ snapshots: - "@chromatic-com/cypress" - "@chromatic-com/playwright" + "@emnapi/core@1.9.2": + dependencies: + "@emnapi/wasi-threads": 1.2.1 + tslib: 2.8.1 + optional: true + + "@emnapi/runtime@1.9.2": + dependencies: + tslib: 2.8.1 + optional: true + + "@emnapi/wasi-threads@1.2.1": + dependencies: + tslib: 2.8.1 + optional: true + "@esbuild/aix-ppc64@0.27.4": optional: true @@ -4065,50 +5753,44 @@ snapshots: "@esbuild/win32-x64@0.27.4": optional: true - "@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)": + "@eslint-community/eslint-utils@4.9.1(eslint@10.2.0)": dependencies: - eslint: 9.39.4 + eslint: 10.2.0 eslint-visitor-keys: 3.4.3 "@eslint-community/regexpp@4.12.2": {} - "@eslint/config-array@0.21.2": + "@eslint/compat@2.0.5(eslint@10.2.0)": + dependencies: + "@eslint/core": 1.2.1 + optionalDependencies: + eslint: 10.2.0 + + "@eslint/config-array@0.23.5": dependencies: - "@eslint/object-schema": 2.1.7 + "@eslint/object-schema": 3.0.5 debug: 4.4.3 - minimatch: 3.1.5 + minimatch: 10.2.4 transitivePeerDependencies: - supports-color - "@eslint/config-helpers@0.4.2": - dependencies: - "@eslint/core": 0.17.0 - - "@eslint/core@0.17.0": + "@eslint/config-helpers@0.5.5": dependencies: - "@types/json-schema": 7.0.15 + "@eslint/core": 1.2.1 - "@eslint/eslintrc@3.3.5": + "@eslint/core@1.2.1": dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color + "@types/json-schema": 7.0.15 - "@eslint/js@9.39.4": {} + "@eslint/js@10.0.1(eslint@10.2.0)": + optionalDependencies: + eslint: 10.2.0 - "@eslint/object-schema@2.1.7": {} + "@eslint/object-schema@3.0.5": {} - "@eslint/plugin-kit@0.4.1": + "@eslint/plugin-kit@0.7.1": dependencies: - "@eslint/core": 0.17.0 + "@eslint/core": 1.2.1 levn: 0.4.1 "@humanfs/core@0.19.1": {} @@ -4176,13 +5858,99 @@ snapshots: "@types/react": 19.2.14 react: 19.2.4 + "@napi-rs/wasm-runtime@0.2.12": + dependencies: + "@emnapi/core": 1.9.2 + "@emnapi/runtime": 1.9.2 + "@tybys/wasm-util": 0.10.1 + optional: true + + "@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)": + dependencies: + "@emnapi/core": 1.9.2 + "@emnapi/runtime": 1.9.2 + "@tybys/wasm-util": 0.10.1 + optional: true + "@neoconfetti/react@1.0.0": {} + "@next/eslint-plugin-next@16.2.2": + dependencies: + fast-glob: 3.3.1 + + "@nodelib/fs.scandir@2.1.5": + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: 1.2.0 + + "@nodelib/fs.stat@2.0.5": {} + + "@nodelib/fs.walk@1.2.8": + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: 1.20.1 + + "@oxc-project/types@0.122.0": {} + + "@package-json/types@0.0.12": {} + "@pkgjs/parseargs@0.11.0": optional: true "@polka/url@1.0.0-next.29": {} + "@rolldown/binding-android-arm64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-darwin-arm64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-darwin-x64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-freebsd-x64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-arm64-musl@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-x64-gnu@1.0.0-rc.12": + optional: true + + "@rolldown/binding-linux-x64-musl@1.0.0-rc.12": + optional: true + + "@rolldown/binding-openharmony-arm64@1.0.0-rc.12": + optional: true + + "@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)": + dependencies: + "@napi-rs/wasm-runtime": 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + transitivePeerDependencies: + - "@emnapi/core" + - "@emnapi/runtime" + optional: true + + "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12": + optional: true + + "@rolldown/binding-win32-x64-msvc@1.0.0-rc.12": + optional: true + + "@rolldown/pluginutils@1.0.0-rc.12": {} + "@rollup/rollup-android-arm-eabi@4.60.1": optional: true @@ -4268,10 +6036,10 @@ snapshots: axe-core: 4.11.1 storybook: 10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - "@storybook/addon-docs@10.3.3(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0))": + "@storybook/addon-docs@10.3.3(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))": dependencies: "@mdx-js/react": 3.1.1(@types/react@19.2.14)(react@19.2.4) - "@storybook/csf-plugin": 10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0)) + "@storybook/csf-plugin": 10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4)) "@storybook/icons": 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) "@storybook/react-dom-shim": 10.3.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) react: 19.2.4 @@ -4306,32 +6074,32 @@ snapshots: "@storybook/icons": 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) storybook: 10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) optionalDependencies: - "@vitest/browser": 3.2.4(playwright@1.58.2)(vite@7.3.1(@types/node@25.5.0))(vitest@3.2.4) + "@vitest/browser": 3.2.4(playwright@1.58.2)(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))(vitest@3.2.4) "@vitest/runner": 3.2.4 - vitest: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9) + vitest: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9)(lightningcss@1.32.0) transitivePeerDependencies: - react - react-dom - "@storybook/builder-vite@10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0))": + "@storybook/builder-vite@10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))": dependencies: - "@storybook/csf-plugin": 10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0)) + "@storybook/csf-plugin": 10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4)) storybook: 10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: 7.3.1(@types/node@25.5.0) + vite: 8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4) transitivePeerDependencies: - esbuild - rollup - webpack - "@storybook/csf-plugin@10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0))": + "@storybook/csf-plugin@10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))": dependencies: storybook: 10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.4 rollup: 4.60.1 - vite: 7.3.1(@types/node@25.5.0) + vite: 8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4) "@storybook/global@5.0.0": {} @@ -4373,9 +6141,9 @@ snapshots: "@vitest/spy": 2.0.5 storybook: 10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - "@storybook/web-components-vite@10.3.3(esbuild@0.27.4)(lit@3.3.2)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0))": + "@storybook/web-components-vite@10.3.3(esbuild@0.27.4)(lit@3.3.2)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))": dependencies: - "@storybook/builder-vite": 10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.5.0)) + "@storybook/builder-vite": 10.3.3(esbuild@0.27.4)(rollup@4.60.1)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4)) "@storybook/web-components": 10.3.3(lit@3.3.2)(storybook@10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) storybook: 10.3.3(@testing-library/dom@10.4.0)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) transitivePeerDependencies: @@ -4393,6 +6161,16 @@ snapshots: tiny-invariant: 1.3.3 ts-dedent: 2.2.0 + "@stylistic/eslint-plugin@5.10.0(eslint@10.2.0)": + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@10.2.0) + "@typescript-eslint/types": 8.57.2 + eslint: 10.2.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + estraverse: 5.3.0 + picomatch: 4.0.4 + "@testing-library/dom@10.4.0": dependencies: "@babel/code-frame": 7.29.0 @@ -4452,6 +6230,11 @@ snapshots: esm-env: 1.2.2 tmcp: 1.19.3(typescript@5.9.3) + "@tybys/wasm-util@0.10.1": + dependencies: + tslib: 2.8.1 + optional: true + "@types/aria-query@5.0.4": {} "@types/chai@5.2.3": @@ -4461,6 +6244,8 @@ snapshots: "@types/deep-eql@4.0.2": {} + "@types/esrecurse@4.3.1": {} + "@types/estree@1.0.8": {} "@types/json-schema@7.0.15": {} @@ -4469,10 +6254,14 @@ snapshots: "@types/mdx@2.0.13": {} + "@types/minimist@1.2.5": {} + "@types/node@25.5.0": dependencies: undici-types: 7.18.2 + "@types/normalize-package-data@2.4.4": {} + "@types/parse5@2.2.34": dependencies: "@types/node": 25.5.0 @@ -4489,15 +6278,15 @@ snapshots: dependencies: "@types/node": 25.5.0 - "@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)": + "@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint@10.2.0)(typescript@5.9.3)": dependencies: "@eslint-community/regexpp": 4.12.2 - "@typescript-eslint/parser": 8.57.2(eslint@9.39.4)(typescript@5.9.3) - "@typescript-eslint/scope-manager": 8.57.2 - "@typescript-eslint/type-utils": 8.57.2(eslint@9.39.4)(typescript@5.9.3) - "@typescript-eslint/utils": 8.57.2(eslint@9.39.4)(typescript@5.9.3) - "@typescript-eslint/visitor-keys": 8.57.2 - eslint: 9.39.4 + "@typescript-eslint/parser": 8.58.1(eslint@10.2.0)(typescript@5.9.3) + "@typescript-eslint/scope-manager": 8.58.1 + "@typescript-eslint/type-utils": 8.58.1(eslint@10.2.0)(typescript@5.9.3) + "@typescript-eslint/utils": 8.58.1(eslint@10.2.0)(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.58.1 + eslint: 10.2.0 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -4505,43 +6294,43 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3)": + "@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3)": dependencies: - "@typescript-eslint/scope-manager": 8.57.2 - "@typescript-eslint/types": 8.57.2 - "@typescript-eslint/typescript-estree": 8.57.2(typescript@5.9.3) - "@typescript-eslint/visitor-keys": 8.57.2 + "@typescript-eslint/scope-manager": 8.58.1 + "@typescript-eslint/types": 8.58.1 + "@typescript-eslint/typescript-estree": 8.58.1(typescript@5.9.3) + "@typescript-eslint/visitor-keys": 8.58.1 debug: 4.4.3 - eslint: 9.39.4 + eslint: 10.2.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/project-service@8.57.2(typescript@5.9.3)": + "@typescript-eslint/project-service@8.58.1(typescript@5.9.3)": dependencies: - "@typescript-eslint/tsconfig-utils": 8.57.2(typescript@5.9.3) - "@typescript-eslint/types": 8.57.2 + "@typescript-eslint/tsconfig-utils": 8.58.1(typescript@5.9.3) + "@typescript-eslint/types": 8.58.1 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/scope-manager@8.57.2": + "@typescript-eslint/scope-manager@8.58.1": dependencies: - "@typescript-eslint/types": 8.57.2 - "@typescript-eslint/visitor-keys": 8.57.2 + "@typescript-eslint/types": 8.58.1 + "@typescript-eslint/visitor-keys": 8.58.1 - "@typescript-eslint/tsconfig-utils@8.57.2(typescript@5.9.3)": + "@typescript-eslint/tsconfig-utils@8.58.1(typescript@5.9.3)": dependencies: typescript: 5.9.3 - "@typescript-eslint/type-utils@8.57.2(eslint@9.39.4)(typescript@5.9.3)": + "@typescript-eslint/type-utils@8.58.1(eslint@10.2.0)(typescript@5.9.3)": dependencies: - "@typescript-eslint/types": 8.57.2 - "@typescript-eslint/typescript-estree": 8.57.2(typescript@5.9.3) - "@typescript-eslint/utils": 8.57.2(eslint@9.39.4)(typescript@5.9.3) + "@typescript-eslint/types": 8.58.1 + "@typescript-eslint/typescript-estree": 8.58.1(typescript@5.9.3) + "@typescript-eslint/utils": 8.58.1(eslint@10.2.0)(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.4 + eslint: 10.2.0 ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -4549,12 +6338,14 @@ snapshots: "@typescript-eslint/types@8.57.2": {} - "@typescript-eslint/typescript-estree@8.57.2(typescript@5.9.3)": + "@typescript-eslint/types@8.58.1": {} + + "@typescript-eslint/typescript-estree@8.58.1(typescript@5.9.3)": dependencies: - "@typescript-eslint/project-service": 8.57.2(typescript@5.9.3) - "@typescript-eslint/tsconfig-utils": 8.57.2(typescript@5.9.3) - "@typescript-eslint/types": 8.57.2 - "@typescript-eslint/visitor-keys": 8.57.2 + "@typescript-eslint/project-service": 8.58.1(typescript@5.9.3) + "@typescript-eslint/tsconfig-utils": 8.58.1(typescript@5.9.3) + "@typescript-eslint/types": 8.58.1 + "@typescript-eslint/visitor-keys": 8.58.1 debug: 4.4.3 minimatch: 10.2.4 semver: 7.7.4 @@ -4564,36 +6355,95 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/utils@8.57.2(eslint@9.39.4)(typescript@5.9.3)": + "@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@5.9.3)": dependencies: - "@eslint-community/eslint-utils": 4.9.1(eslint@9.39.4) - "@typescript-eslint/scope-manager": 8.57.2 - "@typescript-eslint/types": 8.57.2 - "@typescript-eslint/typescript-estree": 8.57.2(typescript@5.9.3) - eslint: 9.39.4 + "@eslint-community/eslint-utils": 4.9.1(eslint@10.2.0) + "@typescript-eslint/scope-manager": 8.58.1 + "@typescript-eslint/types": 8.58.1 + "@typescript-eslint/typescript-estree": 8.58.1(typescript@5.9.3) + eslint: 10.2.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/visitor-keys@8.57.2": + "@typescript-eslint/visitor-keys@8.58.1": dependencies: - "@typescript-eslint/types": 8.57.2 + "@typescript-eslint/types": 8.58.1 eslint-visitor-keys: 5.0.1 + "@unrs/resolver-binding-android-arm-eabi@1.11.1": + optional: true + + "@unrs/resolver-binding-android-arm64@1.11.1": + optional: true + + "@unrs/resolver-binding-darwin-arm64@1.11.1": + optional: true + + "@unrs/resolver-binding-darwin-x64@1.11.1": + optional: true + + "@unrs/resolver-binding-freebsd-x64@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-arm64-musl@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-x64-gnu@1.11.1": + optional: true + + "@unrs/resolver-binding-linux-x64-musl@1.11.1": + optional: true + + "@unrs/resolver-binding-wasm32-wasi@1.11.1": + dependencies: + "@napi-rs/wasm-runtime": 0.2.12 + optional: true + + "@unrs/resolver-binding-win32-arm64-msvc@1.11.1": + optional: true + + "@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + optional: true + + "@unrs/resolver-binding-win32-x64-msvc@1.11.1": + optional: true + "@valibot/to-json-schema@1.6.0(valibot@1.2.0(typescript@5.9.3))": dependencies: valibot: 1.2.0(typescript@5.9.3) - "@vitest/browser@3.2.4(playwright@1.58.2)(vite@7.3.1(@types/node@25.5.0))(vitest@3.2.4)": + "@vitest/browser@3.2.4(playwright@1.58.2)(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))(vitest@3.2.4)": dependencies: "@testing-library/dom": 10.4.0 "@testing-library/user-event": 14.6.1(@testing-library/dom@10.4.0) - "@vitest/mocker": 3.2.4(vite@7.3.1(@types/node@25.5.0)) + "@vitest/mocker": 3.2.4(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4)) "@vitest/utils": 3.2.4 magic-string: 0.30.21 sirv: 3.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9) + vitest: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9)(lightningcss@1.32.0) ws: 8.20.0 optionalDependencies: playwright: 1.58.2 @@ -4618,9 +6468,9 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9) + vitest: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9)(lightningcss@1.32.0) optionalDependencies: - "@vitest/browser": 3.2.4(playwright@1.58.2)(vite@7.3.1(@types/node@25.5.0))(vitest@3.2.4) + "@vitest/browser": 3.2.4(playwright@1.58.2)(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))(vitest@3.2.4) transitivePeerDependencies: - supports-color @@ -4639,13 +6489,21 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - "@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.5.0))": + "@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.5.0)(lightningcss@1.32.0))": + dependencies: + "@vitest/spy": 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.5.0)(lightningcss@1.32.0) + + "@vitest/mocker@3.2.4(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))": dependencies: "@vitest/spy": 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.5.0) + vite: 8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4) "@vitest/pretty-format@2.0.5": dependencies: @@ -4688,7 +6546,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9) + vitest: 3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9)(lightningcss@1.32.0) "@vitest/utils@2.0.5": dependencies: @@ -4715,6 +6573,17 @@ snapshots: acorn@8.16.0: {} + ajv-errors@1.0.1(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -4758,6 +6627,17 @@ snapshots: is-string: 1.1.1 math-intrinsics: 1.1.0 + array-union@2.1.0: {} + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + array.prototype.findlastindex@1.2.6: dependencies: call-bind: 1.0.8 @@ -4782,6 +6662,14 @@ snapshots: es-abstract: 1.24.1 es-shim-unscopables: 1.1.0 + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 @@ -4792,8 +6680,12 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + arrify@1.0.1: {} + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} + ast-types@0.16.1: dependencies: tslib: 2.8.1 @@ -4812,10 +6704,14 @@ snapshots: axe-core@4.11.1: {} + axobject-query@4.1.0: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} + baseline-browser-mapping@2.10.16: {} + brace-expansion@1.1.13: dependencies: balanced-match: 1.0.2 @@ -4829,6 +6725,18 @@ snapshots: dependencies: balanced-match: 4.0.4 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.16 + caniuse-lite: 1.0.30001787 + electron-to-chromium: 1.5.334 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -4854,6 +6762,16 @@ snapshots: callsites@3.1.0: {} + camelcase-keys@6.2.2: + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + + camelcase@5.3.1: {} + + caniuse-lite@1.0.30001787: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -4886,10 +6804,23 @@ snapshots: color-name@1.1.4: {} + comment-parser@1.4.6: {} + concat-map@0.0.1: {} confusing-browser-globals@1.0.11: {} + convert-source-map@2.0.0: {} + + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4900,6 +6831,8 @@ snapshots: csstype@3.2.3: {} + damerau-levenshtein@1.0.8: {} + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -4926,6 +6859,13 @@ snapshots: dependencies: ms: 2.1.3 + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -4953,6 +6893,12 @@ snapshots: dequal@2.0.3: {} + detect-libc@2.1.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -4975,16 +6921,27 @@ snapshots: eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.334: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.2 + entities@6.0.1: {} entities@7.0.1: {} + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 @@ -5046,6 +7003,26 @@ snapshots: es-errors@1.3.0: {} + es-iterator-helpers@1.3.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + math-intrinsics: 1.1.0 + safe-array-concat: 1.1.3 + es-module-lexer@1.7.0: {} es-object-atoms@1.1.1: @@ -5098,20 +7075,48 @@ snapshots: "@esbuild/win32-ia32": 0.27.4 "@esbuild/win32-x64": 0.27.4 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4): + eslint-compat-utils@0.5.1(eslint@10.2.0): + dependencies: + eslint: 10.2.0 + semver: 7.7.4 + + eslint-config-airbnb-extended@3.1.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0)(typescript@5.9.3): dependencies: + "@next/eslint-plugin-next": 16.2.2 + "@stylistic/eslint-plugin": 5.10.0(eslint@10.2.0) confusing-browser-globals: 1.0.11 - eslint: 9.39.4 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4) - object.assign: 4.1.7 - object.entries: 1.1.9 - semver: 6.3.1 + eslint: 10.2.0 + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0))(eslint-plugin-import@2.32.0)(eslint@10.2.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.2.0) + eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0) + eslint-plugin-jsx-a11y: 6.10.2(eslint@10.2.0) + eslint-plugin-n: 17.24.0(eslint@10.2.0)(typescript@5.9.3) + eslint-plugin-react: 7.37.5(eslint@10.2.0) + eslint-plugin-react-hooks: 7.0.1(eslint@10.2.0) + globals: 17.4.0 + typescript-eslint: 8.58.1(eslint@10.2.0)(typescript@5.9.3) + transitivePeerDependencies: + - "@typescript-eslint/parser" + - "@typescript-eslint/utils" + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + - typescript + + eslint-config-prettier@10.1.8(eslint@10.2.0): + dependencies: + eslint: 10.2.0 - eslint-config-prettier@10.1.8(eslint@9.39.4): + eslint-import-context@0.1.9(unrs-resolver@1.11.1): dependencies: - eslint: 9.39.4 + get-tsconfig: 4.13.7 + stable-hash-x: 0.2.0 + optionalDependencies: + unrs-resolver: 1.11.1 eslint-import-resolver-node@0.3.9: dependencies: @@ -5121,17 +7126,60 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.4): + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0))(eslint-plugin-import@2.32.0)(eslint@10.2.0): + dependencies: + debug: 4.4.3 + eslint: 10.2.0 + eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + get-tsconfig: 4.13.7 + is-bun-module: 2.0.0 + stable-hash-x: 0.2.0 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.2.0) + eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@10.2.0): dependencies: debug: 3.2.7 optionalDependencies: - "@typescript-eslint/parser": 8.57.2(eslint@9.39.4)(typescript@5.9.3) - eslint: 9.39.4 + "@typescript-eslint/parser": 8.58.1(eslint@10.2.0)(typescript@5.9.3) + eslint: 10.2.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0))(eslint-plugin-import@2.32.0)(eslint@10.2.0) + transitivePeerDependencies: + - supports-color + + eslint-plugin-es-x@7.8.0(eslint@10.2.0): + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@10.2.0) + "@eslint-community/regexpp": 4.12.2 + eslint: 10.2.0 + eslint-compat-utils: 0.5.1(eslint@10.2.0) + + eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@10.2.0): + dependencies: + "@package-json/types": 0.0.12 + "@typescript-eslint/types": 8.57.2 + comment-parser: 1.4.6 + debug: 4.4.3 + eslint: 10.2.0 + eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + is-glob: 4.0.3 + minimatch: 10.2.4 + semver: 7.7.4 + stable-hash-x: 0.2.0 + unrs-resolver: 1.11.1 + optionalDependencies: + "@typescript-eslint/utils": 8.58.1(eslint@10.2.0)(typescript@5.9.3) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.2.0): dependencies: "@rtsao/scc": 1.1.0 array-includes: 3.1.9 @@ -5140,9 +7188,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.4 + eslint: 10.2.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.4) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@10.2.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -5154,48 +7202,117 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - "@typescript-eslint/parser": 8.57.2(eslint@9.39.4)(typescript@5.9.3) + "@typescript-eslint/parser": 8.58.1(eslint@10.2.0)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-lit-a11y@5.1.1(eslint@9.39.4): + eslint-plugin-jsx-a11y@6.10.2(eslint@10.2.0): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.1 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 10.2.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-lit-a11y@5.1.1(eslint@10.2.0): dependencies: "@thepassle/axobject-query": 4.0.0 aria-query: 5.3.2 axe-core: 4.11.1 dom5: 3.0.1 emoji-regex: 10.6.0 - eslint: 9.39.4 - eslint-plugin-lit: 2.2.1(eslint@9.39.4) + eslint: 10.2.0 + eslint-plugin-lit: 2.2.1(eslint@10.2.0) eslint-rule-extender: 0.0.1 language-tags: 1.0.9 parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 6.0.1 - eslint-plugin-lit@2.2.1(eslint@9.39.4): + eslint-plugin-lit@2.2.1(eslint@10.2.0): dependencies: - eslint: 9.39.4 + eslint: 10.2.0 parse5: 6.0.1 parse5-htmlparser2-tree-adapter: 6.0.1 - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4): + eslint-plugin-n@17.24.0(eslint@10.2.0)(typescript@5.9.3): + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@10.2.0) + enhanced-resolve: 5.20.1 + eslint: 10.2.0 + eslint-plugin-es-x: 7.8.0(eslint@10.2.0) + get-tsconfig: 4.13.7 + globals: 15.15.0 + globrex: 0.1.2 + ignore: 5.3.2 + semver: 7.7.4 + ts-declaration-location: 1.0.7(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + eslint-plugin-react-hooks@7.0.1(eslint@10.2.0): + dependencies: + "@babel/core": 7.29.0 + "@babel/parser": 7.29.2 + eslint: 10.2.0 + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react@7.37.5(eslint@10.2.0): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.3.1 + eslint: 10.2.0 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.5 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.6 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint@10.2.0)(typescript@5.9.3))(eslint@10.2.0): dependencies: - eslint: 9.39.4 + eslint: 10.2.0 optionalDependencies: - "@typescript-eslint/eslint-plugin": 8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + "@typescript-eslint/eslint-plugin": 8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint@10.2.0)(typescript@5.9.3) - eslint-plugin-wc@2.2.1(eslint@9.39.4): + eslint-plugin-wc@3.1.0(eslint@10.2.0): dependencies: - eslint: 9.39.4 + eslint: 10.2.0 is-valid-element-name: 1.0.0 - js-levenshtein-esm: 1.2.0 + js-levenshtein-esm: 2.0.0 eslint-rule-extender@0.0.1: {} - eslint-scope@8.4.0: + eslint-scope@9.1.2: dependencies: + "@types/esrecurse": 4.3.1 + "@types/estree": 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -5205,28 +7322,25 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.4: + eslint@10.2.0: dependencies: - "@eslint-community/eslint-utils": 4.9.1(eslint@9.39.4) + "@eslint-community/eslint-utils": 4.9.1(eslint@10.2.0) "@eslint-community/regexpp": 4.12.2 - "@eslint/config-array": 0.21.2 - "@eslint/config-helpers": 0.4.2 - "@eslint/core": 0.17.0 - "@eslint/eslintrc": 3.3.5 - "@eslint/js": 9.39.4 - "@eslint/plugin-kit": 0.4.1 + "@eslint/config-array": 0.23.5 + "@eslint/config-helpers": 0.5.5 + "@eslint/core": 1.2.1 + "@eslint/plugin-kit": 0.7.1 "@humanfs/node": 0.16.7 "@humanwhocodes/module-importer": 1.0.1 "@humanwhocodes/retry": 0.4.3 "@types/estree": 1.0.8 ajv: 6.14.0 - chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -5237,8 +7351,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 + minimatch: 10.2.4 natural-compare: 1.4.0 optionator: 0.9.4 transitivePeerDependencies: @@ -5252,6 +7365,12 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 4.2.1 + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + esprima@4.0.1: {} esquery@1.7.0: @@ -5274,10 +7393,30 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-glob@3.3.1: + dependencies: + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-glob@3.3.3: + dependencies: + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -5290,6 +7429,15 @@ snapshots: filesize@10.1.6: {} + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -5332,6 +7480,8 @@ snapshots: generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5356,6 +7506,14 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-tsconfig@4.13.7: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -5369,19 +7527,29 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - globals@14.0.0: {} + globals@15.15.0: {} - globals@16.5.0: {} + globals@17.4.0: {} globalthis@1.0.4: dependencies: define-properties: 1.2.1 gopd: 1.2.0 + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globrex@0.1.2: {} + gopd@1.2.0: {} - graceful-fs@4.2.11: - optional: true + graceful-fs@4.2.11: {} happy-dom@20.8.9: dependencies: @@ -5395,6 +7563,8 @@ snapshots: - bufferutil - utf-8-validate + hard-rejection@2.1.0: {} + has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -5417,6 +7587,18 @@ snapshots: dependencies: function-bind: 1.1.2 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hosted-git-info@2.8.9: {} + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + html-escaper@2.0.2: {} ignore@5.3.2: {} @@ -5438,12 +7620,16 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + irregular-plurals@3.5.0: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-arrayish@0.2.1: {} + is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -5461,6 +7647,10 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-bun-module@2.0.0: + dependencies: + semver: 7.7.4 + is-callable@1.2.7: {} is-core-module@2.16.1: @@ -5513,6 +7703,12 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-number@7.0.0: {} + + is-plain-obj@1.1.0: {} + + is-plain-obj@3.0.0: {} + is-potential-custom-element-name@1.0.1: {} is-regex@1.2.1: @@ -5543,6 +7739,8 @@ snapshots: dependencies: which-typed-array: 1.1.20 + is-unicode-supported@0.1.0: {} + is-valid-element-name@1.0.0: dependencies: is-potential-custom-element-name: 1.0.1 @@ -5587,13 +7785,22 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + jackspeak@3.4.3: dependencies: "@isaacs/cliui": 8.0.2 optionalDependencies: "@pkgjs/parseargs": 0.11.0 - js-levenshtein-esm@1.2.0: {} + js-levenshtein-esm@2.0.0: {} js-tokens@10.0.0: {} @@ -5605,8 +7812,12 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-rpc-2.0@1.7.1: {} json-schema-traverse@0.4.1: {} @@ -5617,16 +7828,29 @@ snapshots: dependencies: minimist: 1.2.8 + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + jsonfile@6.2.0: dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + kind-of@6.0.3: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -5638,6 +7862,57 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lines-and-columns@1.2.4: {} + lit-element@4.2.2: dependencies: "@lit-labs/ssr-dom-shim": 1.5.1 @@ -5654,18 +7929,37 @@ snapshots: lit-element: 4.2.2 lit-html: 3.3.2 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 - lodash.merge@4.6.2: {} - lodash@4.17.23: {} + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + loupe@3.2.1: {} lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lz-string@1.5.0: {} magic-string@0.30.21: @@ -5682,8 +7976,34 @@ snapshots: dependencies: semver: 7.7.4 + map-obj@1.0.1: {} + + map-obj@4.3.0: {} + math-intrinsics@1.1.0: {} + meow@9.0.0: + dependencies: + "@types/minimist": 1.2.5 + camelcase-keys: 6.2.2 + decamelize: 1.2.0 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + min-indent@1.0.1: {} minimatch@10.2.4: @@ -5698,6 +8018,12 @@ snapshots: dependencies: brace-expansion: 2.0.3 + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + minimist@1.2.8: {} minipass@7.1.3: {} @@ -5708,8 +8034,58 @@ snapshots: nanoid@3.3.11: {} + napi-postinstall@0.3.4: {} + natural-compare@1.4.0: {} + node-exports-info@1.6.0: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + + node-releases@2.0.37: {} + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.11 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + normalize-package-data@3.0.3: + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.16.1 + semver: 7.7.4 + validate-npm-package-license: 3.0.4 + + npm-package-json-lint@10.2.0(typescript@5.9.3): + dependencies: + ajv: 6.12.6 + ajv-errors: 1.0.1(ajv@6.12.6) + chalk: 4.1.2 + cosmiconfig: 8.3.6(typescript@5.9.3) + debug: 4.4.3 + globby: 11.1.0 + ignore: 5.3.2 + is-plain-obj: 3.0.0 + jsonc-parser: 3.3.1 + log-symbols: 4.1.0 + meow: 9.0.0 + plur: 4.0.0 + semver: 7.7.4 + slash: 3.0.0 + strip-json-comments: 3.1.1 + type-fest: 5.3.1 + validate-npm-package-name: 6.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + object-assign@4.1.1: {} + object-inspect@1.13.4: {} object-keys@1.1.1: {} @@ -5772,20 +8148,37 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-json@5.2.0: + dependencies: + "@babel/code-frame": 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + parse5-htmlparser2-tree-adapter@6.0.1: dependencies: parse5: 6.0.1 @@ -5809,12 +8202,16 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 + path-type@4.0.0: {} + pathe@2.0.3: {} pathval@2.0.1: {} picocolors@1.1.1: {} + picomatch@2.3.2: {} + picomatch@4.0.4: {} picoquery@2.5.0: {} @@ -5827,6 +8224,10 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + plur@4.0.0: + dependencies: + irregular-plurals: 3.5.0 + possible-typed-array-names@1.1.0: {} postcss@8.5.8: @@ -5845,17 +8246,42 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + punycode@2.3.1: {} + queue-microtask@1.2.3: {} + + quick-lru@4.0.1: {} + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 scheduler: 0.27.0 + react-is@16.13.1: {} + react-is@17.0.2: {} react@19.2.4: {} + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + read-pkg@5.2.0: + dependencies: + "@types/normalize-package-data": 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + recast@0.23.11: dependencies: ast-types: 0.16.1 @@ -5891,12 +8317,49 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@2.0.0-next.6: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.1 + node-exports-info: 1.6.0 + object-keys: 1.1.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rolldown@1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2): + dependencies: + "@oxc-project/types": 0.122.0 + "@rolldown/pluginutils": 1.0.0-rc.12 + optionalDependencies: + "@rolldown/binding-android-arm64": 1.0.0-rc.12 + "@rolldown/binding-darwin-arm64": 1.0.0-rc.12 + "@rolldown/binding-darwin-x64": 1.0.0-rc.12 + "@rolldown/binding-freebsd-x64": 1.0.0-rc.12 + "@rolldown/binding-linux-arm-gnueabihf": 1.0.0-rc.12 + "@rolldown/binding-linux-arm64-gnu": 1.0.0-rc.12 + "@rolldown/binding-linux-arm64-musl": 1.0.0-rc.12 + "@rolldown/binding-linux-ppc64-gnu": 1.0.0-rc.12 + "@rolldown/binding-linux-s390x-gnu": 1.0.0-rc.12 + "@rolldown/binding-linux-x64-gnu": 1.0.0-rc.12 + "@rolldown/binding-linux-x64-musl": 1.0.0-rc.12 + "@rolldown/binding-openharmony-arm64": 1.0.0-rc.12 + "@rolldown/binding-wasm32-wasi": 1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + "@rolldown/binding-win32-arm64-msvc": 1.0.0-rc.12 + "@rolldown/binding-win32-x64-msvc": 1.0.0-rc.12 + transitivePeerDependencies: + - "@emnapi/core" + - "@emnapi/runtime" + rollup@4.60.1: dependencies: "@types/estree": 1.0.8 @@ -5930,6 +8393,10 @@ snapshots: run-applescript@7.1.0: {} + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -5951,6 +8418,8 @@ snapshots: scheduler@0.27.0: {} + semver@5.7.2: {} + semver@6.3.1: {} semver@7.7.4: {} @@ -6021,12 +8490,30 @@ snapshots: mrmime: 2.0.1 totalist: 3.0.1 + slash@3.0.0: {} + source-map-js@1.2.1: {} source-map@0.6.1: {} + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.23 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 + + spdx-license-ids@3.0.23: {} + sqids@0.3.0: {} + stable-hash-x@0.2.0: {} + stackback@0.0.2: {} std-env@3.10.0: {} @@ -6071,6 +8558,33 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.2.0 + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.1 + string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 @@ -6120,6 +8634,10 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tagged-tag@1.0.0: {} + + tapable@2.3.2: {} + test-exclude@7.0.2: dependencies: "@istanbuljs/schema": 0.1.3 @@ -6157,12 +8675,23 @@ snapshots: transitivePeerDependencies: - typescript + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + totalist@3.0.1: {} + trim-newlines@3.0.1: {} + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 + ts-declaration-location@1.0.7(typescript@5.9.3): + dependencies: + picomatch: 4.0.4 + typescript: 5.9.3 + ts-dedent@2.2.0: {} tsconfig-paths@3.15.0: @@ -6178,6 +8707,16 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@0.18.1: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + type-fest@5.3.1: + dependencies: + tagged-tag: 1.0.0 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -6211,13 +8750,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.57.2(eslint@9.39.4)(typescript@5.9.3): + typescript-eslint@8.58.1(eslint@10.2.0)(typescript@5.9.3): dependencies: - "@typescript-eslint/eslint-plugin": 8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) - "@typescript-eslint/parser": 8.57.2(eslint@9.39.4)(typescript@5.9.3) - "@typescript-eslint/typescript-estree": 8.57.2(typescript@5.9.3) - "@typescript-eslint/utils": 8.57.2(eslint@9.39.4)(typescript@5.9.3) - eslint: 9.39.4 + "@typescript-eslint/eslint-plugin": 8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0)(typescript@5.9.3))(eslint@10.2.0)(typescript@5.9.3) + "@typescript-eslint/parser": 8.58.1(eslint@10.2.0)(typescript@5.9.3) + "@typescript-eslint/typescript-estree": 8.58.1(typescript@5.9.3) + "@typescript-eslint/utils": 8.58.1(eslint@10.2.0)(typescript@5.9.3) + eslint: 10.2.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6242,6 +8781,36 @@ snapshots: picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi": 1.11.1 + "@unrs/resolver-binding-android-arm64": 1.11.1 + "@unrs/resolver-binding-darwin-arm64": 1.11.1 + "@unrs/resolver-binding-darwin-x64": 1.11.1 + "@unrs/resolver-binding-freebsd-x64": 1.11.1 + "@unrs/resolver-binding-linux-arm-gnueabihf": 1.11.1 + "@unrs/resolver-binding-linux-arm-musleabihf": 1.11.1 + "@unrs/resolver-binding-linux-arm64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-arm64-musl": 1.11.1 + "@unrs/resolver-binding-linux-ppc64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-riscv64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-riscv64-musl": 1.11.1 + "@unrs/resolver-binding-linux-s390x-gnu": 1.11.1 + "@unrs/resolver-binding-linux-x64-gnu": 1.11.1 + "@unrs/resolver-binding-linux-x64-musl": 1.11.1 + "@unrs/resolver-binding-wasm32-wasi": 1.11.1 + "@unrs/resolver-binding-win32-arm64-msvc": 1.11.1 + "@unrs/resolver-binding-win32-ia32-msvc": 1.11.1 + "@unrs/resolver-binding-win32-x64-msvc": 1.11.1 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -6256,13 +8825,20 @@ snapshots: optionalDependencies: typescript: 5.9.3 - vite-node@3.2.4(@types/node@25.5.0): + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + validate-npm-package-name@6.0.0: {} + + vite-node@3.2.4(@types/node@25.5.0)(lightningcss@1.32.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.1(@types/node@25.5.0) + vite: 7.3.1(@types/node@25.5.0)(lightningcss@1.32.0) transitivePeerDependencies: - "@types/node" - jiti @@ -6277,7 +8853,7 @@ snapshots: - tsx - yaml - vite@7.3.1(@types/node@25.5.0): + vite@7.3.1(@types/node@25.5.0)(lightningcss@1.32.0): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) @@ -6288,12 +8864,28 @@ snapshots: optionalDependencies: "@types/node": 25.5.0 fsevents: 2.3.3 + lightningcss: 1.32.0 - vitest@3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9): + vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.8 + rolldown: 1.0.0-rc.12(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + tinyglobby: 0.2.15 + optionalDependencies: + "@types/node": 25.5.0 + esbuild: 0.27.4 + fsevents: 2.3.3 + transitivePeerDependencies: + - "@emnapi/core" + - "@emnapi/runtime" + + vitest@3.2.4(@types/node@25.5.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@20.8.9)(lightningcss@1.32.0): dependencies: "@types/chai": 5.2.3 "@vitest/expect": 3.2.4 - "@vitest/mocker": 3.2.4(vite@7.3.1(@types/node@25.5.0)) + "@vitest/mocker": 3.2.4(vite@7.3.1(@types/node@25.5.0)(lightningcss@1.32.0)) "@vitest/pretty-format": 3.2.4 "@vitest/runner": 3.2.4 "@vitest/snapshot": 3.2.4 @@ -6311,12 +8903,12 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@25.5.0) - vite-node: 3.2.4(@types/node@25.5.0) + vite: 7.3.1(@types/node@25.5.0)(lightningcss@1.32.0) + vite-node: 3.2.4(@types/node@25.5.0)(lightningcss@1.32.0) why-is-node-running: 2.3.0 optionalDependencies: "@types/node": 25.5.0 - "@vitest/browser": 3.2.4(playwright@1.58.2)(vite@7.3.1(@types/node@25.5.0))(vitest@3.2.4) + "@vitest/browser": 3.2.4(playwright@1.58.2)(vite@8.0.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(esbuild@0.27.4))(vitest@3.2.4) "@vitest/ui": 3.2.4(vitest@3.2.4) happy-dom: 20.8.9 transitivePeerDependencies: @@ -6407,4 +8999,16 @@ snapshots: dependencies: is-wsl: 3.1.1 + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yargs-parser@20.2.9: {} + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} From e25be69a83c6cbee41456c629e8fee9b0e7a7e22 Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 15:43:44 +0300 Subject: [PATCH 04/14] fix(build): enable experimentalDecorators to prevent accessor syntax in bundle After Typescript updates the build broke. Without experimentalDecorators:true, TypeScript emits TC39 standard decorator syntax including the accessor keyword, which esbuild passes through untransformed into the bundle. The accessor keyword is only supported in Chrome 112+ / Firefox 119+ / Safari 16.4+ and causes: Uncaught SyntaxError: Invalid or unexpected token in older or embedded browser environments (e.g. some HA companion builds). With experimentalDecorators:true TypeScript compiles accessor fields to getter/setter pairs and decorators to __decorate() calls, producing output that is fully compatible with the es2021 build target already set in vite.config.mjs. Lit 3 supports both modes transparently. --- .../hass_datapoints/hass-datapoints-cards.js | 3824 +++++++++++++---- tsconfig.json | 1 + 2 files changed, 3069 insertions(+), 756 deletions(-) diff --git a/custom_components/hass_datapoints/hass-datapoints-cards.js b/custom_components/hass_datapoints/hass-datapoints-cards.js index 0f317f09..64c6028a 100644 --- a/custom_components/hass_datapoints/hass-datapoints-cards.js +++ b/custom_components/hass_datapoints/hass-datapoints-cards.js @@ -798,6 +798,40 @@ pointer-events: none; } `; + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/decorate.js + function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; + } + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/checkPrivateRedeclaration.js + function _checkPrivateRedeclaration(e, t) { + if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); + } + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldInitSpec.js + function _classPrivateFieldInitSpec(e, t, a) { + _checkPrivateRedeclaration(e, t), t.set(e, a); + } + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/assertClassBrand.js + function _assertClassBrand(e, t, n) { + if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; + throw new TypeError("Private element is not present on this object"); + } + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldGet2.js + function _classPrivateFieldGet2(s, a) { + return s.get(_assertClassBrand(s, a)); + } + //#endregion + //#region \0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldSet2.js + function _classPrivateFieldSet2(s, a, r) { + return s.set(_assertClassBrand(s, a), r), r; + } //#endregion //#region \0@oxc-project+runtime@0.122.0/helpers/typeof.js function _typeof(o) { @@ -838,9 +872,26 @@ } //#endregion //#region custom_components/hass_datapoints/src/atoms/display/color-swatch/color-swatch.ts + var _color_accessor_storage$2 = /* @__PURE__ */ new WeakMap(); + var _label_accessor_storage$14 = /* @__PURE__ */ new WeakMap(); var ColorSwatch = class extends i$2 { - @n$1({ type: String }) accessor color = "#ff9800"; - @n$1({ type: String }) accessor label = ""; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _color_accessor_storage$2, "#ff9800"); + _classPrivateFieldInitSpec(this, _label_accessor_storage$14, ""); + } + get color() { + return _classPrivateFieldGet2(_color_accessor_storage$2, this); + } + set color(value) { + _classPrivateFieldSet2(_color_accessor_storage$2, this, value); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$14, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$14, this, value); + } _onInput(e) { const newColor = e.target.value; this.dispatchEvent(new CustomEvent("dp-color-change", { @@ -865,6 +916,8 @@ } }; _defineProperty(ColorSwatch, "styles", styles$69); + __decorate([n$1({ type: String })], ColorSwatch.prototype, "color", null); + __decorate([n$1({ type: String })], ColorSwatch.prototype, "label", null); customElements.define("color-swatch", ColorSwatch); //#endregion //#region custom_components/hass_datapoints/src/atoms/display/feedback-banner/feedback-banner.styles.ts @@ -897,11 +950,42 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/display/feedback-banner/feedback-banner.ts + var _kind_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _text_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _visible_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _variant_accessor_storage = /* @__PURE__ */ new WeakMap(); var FeedbackBanner = class extends i$2 { - @n$1({ type: String }) accessor kind = ""; - @n$1({ type: String }) accessor text = ""; - @n$1({ type: Boolean }) accessor visible = false; - @n$1({ type: String }) accessor variant = "default"; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _kind_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _text_accessor_storage$1, ""); + _classPrivateFieldInitSpec(this, _visible_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _variant_accessor_storage, "default"); + } + get kind() { + return _classPrivateFieldGet2(_kind_accessor_storage, this); + } + set kind(value) { + _classPrivateFieldSet2(_kind_accessor_storage, this, value); + } + get text() { + return _classPrivateFieldGet2(_text_accessor_storage$1, this); + } + set text(value) { + _classPrivateFieldSet2(_text_accessor_storage$1, this, value); + } + get visible() { + return _classPrivateFieldGet2(_visible_accessor_storage$1, this); + } + set visible(value) { + _classPrivateFieldSet2(_visible_accessor_storage$1, this, value); + } + get variant() { + return _classPrivateFieldGet2(_variant_accessor_storage, this); + } + set variant(value) { + _classPrivateFieldSet2(_variant_accessor_storage, this, value); + } render() { if (!this.text) return A; return b` @@ -915,6 +999,10 @@ } }; _defineProperty(FeedbackBanner, "styles", styles$68); + __decorate([n$1({ type: String })], FeedbackBanner.prototype, "kind", null); + __decorate([n$1({ type: String })], FeedbackBanner.prototype, "text", null); + __decorate([n$1({ type: Boolean })], FeedbackBanner.prototype, "visible", null); + __decorate([n$1({ type: String })], FeedbackBanner.prototype, "variant", null); customElements.define("feedback-banner", FeedbackBanner); //#endregion //#region custom_components/hass_datapoints/src/cards/action/action-targets/action-targets.styles.ts @@ -977,14 +1065,42 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/form/entity-chip/entity-chip.ts + var _type_accessor_storage$2 = /* @__PURE__ */ new WeakMap(); + var _itemId_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _hass_accessor_storage$14 = /* @__PURE__ */ new WeakMap(); + var _removable_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); var EntityChip = class extends i$2 { - @n$1({ type: String }) accessor type = "entity"; - @n$1({ - type: String, - attribute: "item-id" - }) accessor itemId = ""; - @n$1({ type: Object }) accessor hass = null; - @n$1({ type: Boolean }) accessor removable = false; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _type_accessor_storage$2, "entity"); + _classPrivateFieldInitSpec(this, _itemId_accessor_storage$1, ""); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$14, null); + _classPrivateFieldInitSpec(this, _removable_accessor_storage$1, false); + } + get type() { + return _classPrivateFieldGet2(_type_accessor_storage$2, this); + } + set type(value) { + _classPrivateFieldSet2(_type_accessor_storage$2, this, value); + } + get itemId() { + return _classPrivateFieldGet2(_itemId_accessor_storage$1, this); + } + set itemId(value) { + _classPrivateFieldSet2(_itemId_accessor_storage$1, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$14, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$14, this, value); + } + get removable() { + return _classPrivateFieldGet2(_removable_accessor_storage$1, this); + } + set removable(value) { + _classPrivateFieldSet2(_removable_accessor_storage$1, this, value); + } _getName() { if (!this.hass || !this.itemId) return this.itemId || ""; switch (this.type) { @@ -1019,14 +1135,52 @@ } }; _defineProperty(EntityChip, "styles", styles$65); + __decorate([n$1({ type: String })], EntityChip.prototype, "type", null); + __decorate([n$1({ + type: String, + attribute: "item-id" + })], EntityChip.prototype, "itemId", null); + __decorate([n$1({ type: Object })], EntityChip.prototype, "hass", null); + __decorate([n$1({ type: Boolean })], EntityChip.prototype, "removable", null); customElements.define("entity-chip", EntityChip); //#endregion //#region custom_components/hass_datapoints/src/molecules/chip-group/chip-group.ts + var _items_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _hass_accessor_storage$13 = /* @__PURE__ */ new WeakMap(); + var _removable_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _label_accessor_storage$13 = /* @__PURE__ */ new WeakMap(); var ChipGroup = class extends i$2 { - @n$1({ type: Array }) accessor items = []; - @n$1({ type: Object }) accessor hass = null; - @n$1({ type: Boolean }) accessor removable = false; - @n$1({ type: String }) accessor label = ""; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _items_accessor_storage$1, []); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$13, null); + _classPrivateFieldInitSpec(this, _removable_accessor_storage, false); + _classPrivateFieldInitSpec(this, _label_accessor_storage$13, ""); + } + get items() { + return _classPrivateFieldGet2(_items_accessor_storage$1, this); + } + set items(value) { + _classPrivateFieldSet2(_items_accessor_storage$1, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$13, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$13, this, value); + } + get removable() { + return _classPrivateFieldGet2(_removable_accessor_storage, this); + } + set removable(value) { + _classPrivateFieldSet2(_removable_accessor_storage, this, value); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$13, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$13, this, value); + } _onRemove(e) { const { type, itemId } = e.detail; const next = this.items.filter((item) => !(item.type === type && item.id === itemId)); @@ -1054,21 +1208,57 @@ } }; _defineProperty(ChipGroup, "styles", styles$66); + __decorate([n$1({ type: Array })], ChipGroup.prototype, "items", null); + __decorate([n$1({ type: Object })], ChipGroup.prototype, "hass", null); + __decorate([n$1({ type: Boolean })], ChipGroup.prototype, "removable", null); + __decorate([n$1({ type: String })], ChipGroup.prototype, "label", null); customElements.define("chip-group", ChipGroup); //#endregion //#region custom_components/hass_datapoints/src/cards/action/action-targets/action-targets.ts + var _hass_accessor_storage$12 = /* @__PURE__ */ new WeakMap(); + var _showConfigTargets_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _showTargetPicker_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _configChips_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _targetValue_accessor_storage = /* @__PURE__ */ new WeakMap(); var CardActionTargets = class extends i$2 { - @n$1({ attribute: false }) accessor hass = null; - @n$1({ - type: Boolean, - attribute: "show-config-targets" - }) accessor showConfigTargets = true; - @n$1({ - type: Boolean, - attribute: "show-target-picker" - }) accessor showTargetPicker = true; - @n$1({ attribute: false }) accessor configChips = []; - @r$1() accessor _targetValue = {}; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$12, null); + _classPrivateFieldInitSpec(this, _showConfigTargets_accessor_storage, true); + _classPrivateFieldInitSpec(this, _showTargetPicker_accessor_storage, true); + _classPrivateFieldInitSpec(this, _configChips_accessor_storage, []); + _classPrivateFieldInitSpec(this, _targetValue_accessor_storage, {}); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$12, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$12, this, value); + } + get showConfigTargets() { + return _classPrivateFieldGet2(_showConfigTargets_accessor_storage, this); + } + set showConfigTargets(value) { + _classPrivateFieldSet2(_showConfigTargets_accessor_storage, this, value); + } + get showTargetPicker() { + return _classPrivateFieldGet2(_showTargetPicker_accessor_storage, this); + } + set showTargetPicker(value) { + _classPrivateFieldSet2(_showTargetPicker_accessor_storage, this, value); + } + get configChips() { + return _classPrivateFieldGet2(_configChips_accessor_storage, this); + } + set configChips(value) { + _classPrivateFieldSet2(_configChips_accessor_storage, this, value); + } + get _targetValue() { + return _classPrivateFieldGet2(_targetValue_accessor_storage, this); + } + set _targetValue(value) { + _classPrivateFieldSet2(_targetValue_accessor_storage, this, value); + } resetSelection() { this._targetValue = {}; } @@ -1105,6 +1295,17 @@ } }; _defineProperty(CardActionTargets, "styles", styles$67); + __decorate([n$1({ attribute: false })], CardActionTargets.prototype, "hass", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-config-targets" + })], CardActionTargets.prototype, "showConfigTargets", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-target-picker" + })], CardActionTargets.prototype, "showTargetPicker", null); + __decorate([n$1({ attribute: false })], CardActionTargets.prototype, "configChips", null); + __decorate([r$1()], CardActionTargets.prototype, "_targetValue", null); customElements.define("action-targets", CardActionTargets); //#endregion //#region custom_components/hass_datapoints/src/lib/logger.ts @@ -4988,9 +5189,25 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/editor-base/editor-base.ts - var EditorBase = @localized() class extends i$2 { - @r$1() accessor _config = {}; - @n$1({ type: Object }) accessor hass = null; + var _EditorBase, _config_accessor_storage, _hass_accessor_storage$11; + var EditorBase = (_config_accessor_storage = /* @__PURE__ */ new WeakMap(), _hass_accessor_storage$11 = /* @__PURE__ */ new WeakMap(), _EditorBase = class EditorBase extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _config_accessor_storage, {}); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$11, null); + } + get _config() { + return _classPrivateFieldGet2(_config_accessor_storage, this); + } + set _config(value) { + _classPrivateFieldSet2(_config_accessor_storage, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$11, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$11, this, value); + } setConfig(config) { this._config = { ...config }; } @@ -5008,8 +5225,10 @@ this._config = cfg; this._fire(cfg); } - }; - _defineProperty(EditorBase, "styles", styles$63); + }, _defineProperty(_EditorBase, "styles", styles$63), _EditorBase); + __decorate([r$1()], EditorBase.prototype, "_config", null); + __decorate([n$1({ type: Object })], EditorBase.prototype, "hass", null); + EditorBase = __decorate([localized()], EditorBase); customElements.define("editor-base", EditorBase); //#endregion //#region custom_components/hass_datapoints/src/lib/domain/target-selection.ts @@ -5095,13 +5314,24 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/display/section-heading/section-heading.ts + var _text_accessor_storage = /* @__PURE__ */ new WeakMap(); var SectionHeading = class extends i$2 { - @n$1({ type: String }) accessor text = ""; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _text_accessor_storage, ""); + } + get text() { + return _classPrivateFieldGet2(_text_accessor_storage, this); + } + set text(value) { + _classPrivateFieldSet2(_text_accessor_storage, this, value); + } render() { return b`
${this.text}
`; } }; _defineProperty(SectionHeading, "styles", styles$62); + __decorate([n$1({ type: String })], SectionHeading.prototype, "text", null); customElements.define("section-heading", SectionHeading); //#endregion //#region custom_components/hass_datapoints/src/atoms/form/editor-text-field/editor-text-field.styles.ts @@ -5116,12 +5346,50 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/form/editor-text-field/editor-text-field.ts + var _label_accessor_storage$12 = /* @__PURE__ */ new WeakMap(); + var _value_accessor_storage$7 = /* @__PURE__ */ new WeakMap(); + var _type_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _placeholder_accessor_storage$2 = /* @__PURE__ */ new WeakMap(); + var _suffix_accessor_storage = /* @__PURE__ */ new WeakMap(); var EditorTextField = class extends i$2 { - @n$1({ type: String }) accessor label = ""; - @n$1({ type: String }) accessor value = ""; - @n$1({ type: String }) accessor type = "text"; - @n$1({ type: String }) accessor placeholder = ""; - @n$1({ type: String }) accessor suffix = ""; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _label_accessor_storage$12, ""); + _classPrivateFieldInitSpec(this, _value_accessor_storage$7, ""); + _classPrivateFieldInitSpec(this, _type_accessor_storage$1, "text"); + _classPrivateFieldInitSpec(this, _placeholder_accessor_storage$2, ""); + _classPrivateFieldInitSpec(this, _suffix_accessor_storage, ""); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$12, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$12, this, value); + } + get value() { + return _classPrivateFieldGet2(_value_accessor_storage$7, this); + } + set value(value) { + _classPrivateFieldSet2(_value_accessor_storage$7, this, value); + } + get type() { + return _classPrivateFieldGet2(_type_accessor_storage$1, this); + } + set type(value) { + _classPrivateFieldSet2(_type_accessor_storage$1, this, value); + } + get placeholder() { + return _classPrivateFieldGet2(_placeholder_accessor_storage$2, this); + } + set placeholder(value) { + _classPrivateFieldSet2(_placeholder_accessor_storage$2, this, value); + } + get suffix() { + return _classPrivateFieldGet2(_suffix_accessor_storage, this); + } + set suffix(value) { + _classPrivateFieldSet2(_suffix_accessor_storage, this, value); + } firstUpdated() { const field = this.shadowRoot.querySelector("ha-textfield"); if (field) { @@ -5152,6 +5420,11 @@ } }; _defineProperty(EditorTextField, "styles", styles$61); + __decorate([n$1({ type: String })], EditorTextField.prototype, "label", null); + __decorate([n$1({ type: String })], EditorTextField.prototype, "value", null); + __decorate([n$1({ type: String })], EditorTextField.prototype, "type", null); + __decorate([n$1({ type: String })], EditorTextField.prototype, "placeholder", null); + __decorate([n$1({ type: String })], EditorTextField.prototype, "suffix", null); customElements.define("editor-text-field", EditorTextField); //#endregion //#region custom_components/hass_datapoints/src/atoms/form/editor-switch/editor-switch.styles.ts @@ -5196,10 +5469,34 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/form/editor-switch/editor-switch.ts + var _label_accessor_storage$11 = /* @__PURE__ */ new WeakMap(); + var _checked_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _tooltip_accessor_storage = /* @__PURE__ */ new WeakMap(); var EditorSwitch = class extends i$2 { - @n$1({ type: String }) accessor label = ""; - @n$1({ type: Boolean }) accessor checked = false; - @n$1({ type: String }) accessor tooltip = ""; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _label_accessor_storage$11, ""); + _classPrivateFieldInitSpec(this, _checked_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _tooltip_accessor_storage, ""); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$11, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$11, this, value); + } + get checked() { + return _classPrivateFieldGet2(_checked_accessor_storage$1, this); + } + set checked(value) { + _classPrivateFieldSet2(_checked_accessor_storage$1, this, value); + } + get tooltip() { + return _classPrivateFieldGet2(_tooltip_accessor_storage, this); + } + set tooltip(value) { + _classPrivateFieldSet2(_tooltip_accessor_storage, this, value); + } firstUpdated() { const ff = this.shadowRoot.querySelector("ha-formfield"); if (ff) ff.label = this.label; @@ -5240,6 +5537,9 @@ } }; _defineProperty(EditorSwitch, "styles", styles$60); + __decorate([n$1({ type: String })], EditorSwitch.prototype, "label", null); + __decorate([n$1({ type: Boolean })], EditorSwitch.prototype, "checked", null); + __decorate([n$1({ type: String })], EditorSwitch.prototype, "tooltip", null); customElements.define("editor-switch", EditorSwitch); //#endregion //#region custom_components/hass_datapoints/src/atoms/form/editor-icon-picker/editor-icon-picker.styles.ts @@ -5254,10 +5554,34 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/form/editor-icon-picker/editor-icon-picker.ts + var _label_accessor_storage$10 = /* @__PURE__ */ new WeakMap(); + var _value_accessor_storage$6 = /* @__PURE__ */ new WeakMap(); + var _hass_accessor_storage$10 = /* @__PURE__ */ new WeakMap(); var EditorIconPicker = class extends i$2 { - @n$1({ type: String }) accessor label = ""; - @n$1({ type: String }) accessor value = "mdi:bookmark"; - @n$1({ type: Object }) accessor hass = null; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _label_accessor_storage$10, ""); + _classPrivateFieldInitSpec(this, _value_accessor_storage$6, "mdi:bookmark"); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$10, null); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$10, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$10, this, value); + } + get value() { + return _classPrivateFieldGet2(_value_accessor_storage$6, this); + } + set value(value) { + _classPrivateFieldSet2(_value_accessor_storage$6, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$10, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$10, this, value); + } firstUpdated() { const el = this.shadowRoot.querySelector("ha-icon-picker"); if (el) { @@ -5286,6 +5610,9 @@ } }; _defineProperty(EditorIconPicker, "styles", styles$59); + __decorate([n$1({ type: String })], EditorIconPicker.prototype, "label", null); + __decorate([n$1({ type: String })], EditorIconPicker.prototype, "value", null); + __decorate([n$1({ type: Object })], EditorIconPicker.prototype, "hass", null); customElements.define("editor-icon-picker", EditorIconPicker); //#endregion //#region custom_components/hass_datapoints/src/cards/action/editor.ts @@ -5848,13 +6175,58 @@ `; //#endregion //#region custom_components/hass_datapoints/src/cards/dev-tool/dev-tool-results/dev-tool-results.ts + var _results_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _isAdmin_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _statusKind_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _statusText_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _statusVisible_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _collapsedWindowIds_accessor_storage = /* @__PURE__ */ new WeakMap(); var CardDevToolResults = class extends i$2 { - @n$1({ attribute: false }) accessor results = []; - @n$1({ type: Boolean }) accessor isAdmin = false; - @n$1({ type: String }) accessor statusKind = ""; - @n$1({ type: String }) accessor statusText = ""; - @n$1({ type: Boolean }) accessor statusVisible = false; - @r$1() accessor _collapsedWindowIds = []; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _results_accessor_storage, []); + _classPrivateFieldInitSpec(this, _isAdmin_accessor_storage, false); + _classPrivateFieldInitSpec(this, _statusKind_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _statusText_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _statusVisible_accessor_storage, false); + _classPrivateFieldInitSpec(this, _collapsedWindowIds_accessor_storage, []); + } + get results() { + return _classPrivateFieldGet2(_results_accessor_storage, this); + } + set results(value) { + _classPrivateFieldSet2(_results_accessor_storage, this, value); + } + get isAdmin() { + return _classPrivateFieldGet2(_isAdmin_accessor_storage, this); + } + set isAdmin(value) { + _classPrivateFieldSet2(_isAdmin_accessor_storage, this, value); + } + get statusKind() { + return _classPrivateFieldGet2(_statusKind_accessor_storage, this); + } + set statusKind(value) { + _classPrivateFieldSet2(_statusKind_accessor_storage, this, value); + } + get statusText() { + return _classPrivateFieldGet2(_statusText_accessor_storage, this); + } + set statusText(value) { + _classPrivateFieldSet2(_statusText_accessor_storage, this, value); + } + get statusVisible() { + return _classPrivateFieldGet2(_statusVisible_accessor_storage, this); + } + set statusVisible(value) { + _classPrivateFieldSet2(_statusVisible_accessor_storage, this, value); + } + get _collapsedWindowIds() { + return _classPrivateFieldGet2(_collapsedWindowIds_accessor_storage, this); + } + set _collapsedWindowIds(value) { + _classPrivateFieldSet2(_collapsedWindowIds_accessor_storage, this, value); + } _emitSelection() { this.dispatchEvent(new CustomEvent("dp-results-selection-change", { detail: { results: this.results.map((result) => ({ @@ -6012,6 +6384,12 @@ } }; _defineProperty(CardDevToolResults, "styles", styles$57); + __decorate([n$1({ attribute: false })], CardDevToolResults.prototype, "results", null); + __decorate([n$1({ type: Boolean })], CardDevToolResults.prototype, "isAdmin", null); + __decorate([n$1({ type: String })], CardDevToolResults.prototype, "statusKind", null); + __decorate([n$1({ type: String })], CardDevToolResults.prototype, "statusText", null); + __decorate([n$1({ type: Boolean })], CardDevToolResults.prototype, "statusVisible", null); + __decorate([r$1()], CardDevToolResults.prototype, "_collapsedWindowIds", null); customElements.define("dev-tool-results", CardDevToolResults); //#endregion //#region custom_components/hass_datapoints/src/cards/dev-tool/dev-tool-windows/dev-tool-windows.styles.ts @@ -6133,9 +6511,26 @@ `; //#endregion //#region custom_components/hass_datapoints/src/cards/dev-tool/dev-tool-windows/dev-tool-windows.ts + var _windows_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _nextWindowId_accessor_storage = /* @__PURE__ */ new WeakMap(); var CardDevToolWindows = class extends i$2 { - @n$1({ attribute: false }) accessor windows = []; - @r$1() accessor _nextWindowId = 1; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _windows_accessor_storage, []); + _classPrivateFieldInitSpec(this, _nextWindowId_accessor_storage, 1); + } + get windows() { + return _classPrivateFieldGet2(_windows_accessor_storage, this); + } + set windows(value) { + _classPrivateFieldSet2(_windows_accessor_storage, this, value); + } + get _nextWindowId() { + return _classPrivateFieldGet2(_nextWindowId_accessor_storage, this); + } + set _nextWindowId(value) { + _classPrivateFieldSet2(_nextWindowId_accessor_storage, this, value); + } connectedCallback() { super.connectedCallback(); if (this.windows.length === 0) this.windows = [this._createWindow()]; @@ -6242,6 +6637,8 @@ } }; _defineProperty(CardDevToolWindows, "styles", styles$56); + __decorate([n$1({ attribute: false })], CardDevToolWindows.prototype, "windows", null); + __decorate([r$1()], CardDevToolWindows.prototype, "_nextWindowId", null); customElements.define("dev-tool-windows", CardDevToolWindows); //#endregion //#region custom_components/hass_datapoints/src/cards/dev-tool/dev-tool.ts @@ -14326,26 +14723,66 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/annotation-chip/annotation-chip.ts + var _type_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _itemId_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _icon_accessor_storage$2 = /* @__PURE__ */ new WeakMap(); + var _name_accessor_storage$3 = /* @__PURE__ */ new WeakMap(); + var _secondaryText_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _stateObj_accessor_storage$2 = /* @__PURE__ */ new WeakMap(); + var _hass_accessor_storage$9 = /* @__PURE__ */ new WeakMap(); var AnnotationChip = class extends i$2 { - @n$1({ type: String }) accessor type = ""; - @n$1({ - type: String, - attribute: "item-id" - }) accessor itemId = ""; - @n$1({ type: String }) accessor icon = ""; - @n$1({ type: String }) accessor name = ""; - @n$1({ - type: String, - attribute: "secondary-text" - }) accessor secondaryText = ""; - @n$1({ - type: Object, - attribute: false - }) accessor stateObj = null; - @n$1({ - type: Object, - attribute: false - }) accessor hass = null; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _type_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _itemId_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _icon_accessor_storage$2, ""); + _classPrivateFieldInitSpec(this, _name_accessor_storage$3, ""); + _classPrivateFieldInitSpec(this, _secondaryText_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _stateObj_accessor_storage$2, null); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$9, null); + } + get type() { + return _classPrivateFieldGet2(_type_accessor_storage, this); + } + set type(value) { + _classPrivateFieldSet2(_type_accessor_storage, this, value); + } + get itemId() { + return _classPrivateFieldGet2(_itemId_accessor_storage, this); + } + set itemId(value) { + _classPrivateFieldSet2(_itemId_accessor_storage, this, value); + } + get icon() { + return _classPrivateFieldGet2(_icon_accessor_storage$2, this); + } + set icon(value) { + _classPrivateFieldSet2(_icon_accessor_storage$2, this, value); + } + get name() { + return _classPrivateFieldGet2(_name_accessor_storage$3, this); + } + set name(value) { + _classPrivateFieldSet2(_name_accessor_storage$3, this, value); + } + get secondaryText() { + return _classPrivateFieldGet2(_secondaryText_accessor_storage, this); + } + set secondaryText(value) { + _classPrivateFieldSet2(_secondaryText_accessor_storage, this, value); + } + get stateObj() { + return _classPrivateFieldGet2(_stateObj_accessor_storage$2, this); + } + set stateObj(value) { + _classPrivateFieldSet2(_stateObj_accessor_storage$2, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$9, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$9, this, value); + } _onRemove() { this.dispatchEvent(new CustomEvent("dp-chip-remove", { detail: { @@ -14382,9 +14819,33 @@ } }; _defineProperty(AnnotationChip, "styles", styles$52); + __decorate([n$1({ type: String })], AnnotationChip.prototype, "type", null); + __decorate([n$1({ + type: String, + attribute: "item-id" + })], AnnotationChip.prototype, "itemId", null); + __decorate([n$1({ type: String })], AnnotationChip.prototype, "icon", null); + __decorate([n$1({ type: String })], AnnotationChip.prototype, "name", null); + __decorate([n$1({ + type: String, + attribute: "secondary-text" + })], AnnotationChip.prototype, "secondaryText", null); + __decorate([n$1({ + type: Object, + attribute: false + })], AnnotationChip.prototype, "stateObj", null); + __decorate([n$1({ + type: Object, + attribute: false + })], AnnotationChip.prototype, "hass", null); customElements.define("annotation-chip", AnnotationChip); //#endregion //#region custom_components/hass_datapoints/src/molecules/annotation-chip-row/annotation-chip-row.ts + var _chips_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _hass_accessor_storage$8 = /* @__PURE__ */ new WeakMap(); + var _label_accessor_storage$9 = /* @__PURE__ */ new WeakMap(); + var _helpText_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _emptyText_accessor_storage = /* @__PURE__ */ new WeakMap(); /** * `annotation-chip-row` renders a labelled group of removable annotation * target chips. When the list is empty it shows an empty-state help text instead. @@ -14393,25 +14854,44 @@ * remove button is clicked. */ var AnnotationChipRow = class extends i$2 { - /** Array of pre-resolved chip items to display. */ - @n$1({ type: Array }) accessor chips = []; - /** HA hass object forwarded to annotation-chip when rendering entity icons. */ - @n$1({ - type: Object, - attribute: false - }) accessor hass = null; - /** Section label shown above the chips. */ - @n$1({ type: String }) accessor label = "Linked targets"; - /** Help text shown below the label when chips are present. */ - @n$1({ - type: String, - attribute: "help-text" - }) accessor helpText = "These targets will be associated with the new data point by default. Remove any that should not be linked."; - /** Help text shown below the label when there are no chips. */ - @n$1({ - type: String, - attribute: "empty-text" - }) accessor emptyText = "No linked targets will be associated with this data point."; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _chips_accessor_storage, []); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$8, null); + _classPrivateFieldInitSpec(this, _label_accessor_storage$9, "Linked targets"); + _classPrivateFieldInitSpec(this, _helpText_accessor_storage, "These targets will be associated with the new data point by default. Remove any that should not be linked."); + _classPrivateFieldInitSpec(this, _emptyText_accessor_storage, "No linked targets will be associated with this data point."); + } + get chips() { + return _classPrivateFieldGet2(_chips_accessor_storage, this); + } + set chips(value) { + _classPrivateFieldSet2(_chips_accessor_storage, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$8, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$8, this, value); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$9, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$9, this, value); + } + get helpText() { + return _classPrivateFieldGet2(_helpText_accessor_storage, this); + } + set helpText(value) { + _classPrivateFieldSet2(_helpText_accessor_storage, this, value); + } + get emptyText() { + return _classPrivateFieldGet2(_emptyText_accessor_storage, this); + } + set emptyText(value) { + _classPrivateFieldSet2(_emptyText_accessor_storage, this, value); + } _onChipRemove(ev) { const detail = ev.detail; ev.stopPropagation(); @@ -14454,6 +14934,20 @@ } }; _defineProperty(AnnotationChipRow, "styles", styles$53); + __decorate([n$1({ type: Array })], AnnotationChipRow.prototype, "chips", null); + __decorate([n$1({ + type: Object, + attribute: false + })], AnnotationChipRow.prototype, "hass", null); + __decorate([n$1({ type: String })], AnnotationChipRow.prototype, "label", null); + __decorate([n$1({ + type: String, + attribute: "help-text" + })], AnnotationChipRow.prototype, "helpText", null); + __decorate([n$1({ + type: String, + attribute: "empty-text" + })], AnnotationChipRow.prototype, "emptyText", null); customElements.define("annotation-chip-row", AnnotationChipRow); //#endregion //#region custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts @@ -15976,14 +16470,42 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/analysis/analysis-group/analysis-group.ts + var _label_accessor_storage$8 = /* @__PURE__ */ new WeakMap(); + var _checked_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _disabled_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _alignTop_accessor_storage = /* @__PURE__ */ new WeakMap(); var AnalysisGroup = class extends i$2 { - @n$1({ type: String }) accessor label = ""; - @n$1({ type: Boolean }) accessor checked = false; - @n$1({ type: Boolean }) accessor disabled = false; - @n$1({ - type: Boolean, - attribute: "align-top" - }) accessor alignTop = false; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _label_accessor_storage$8, ""); + _classPrivateFieldInitSpec(this, _checked_accessor_storage, false); + _classPrivateFieldInitSpec(this, _disabled_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _alignTop_accessor_storage, false); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$8, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$8, this, value); + } + get checked() { + return _classPrivateFieldGet2(_checked_accessor_storage, this); + } + set checked(value) { + _classPrivateFieldSet2(_checked_accessor_storage, this, value); + } + get disabled() { + return _classPrivateFieldGet2(_disabled_accessor_storage$1, this); + } + set disabled(value) { + _classPrivateFieldSet2(_disabled_accessor_storage$1, this, value); + } + get alignTop() { + return _classPrivateFieldGet2(_alignTop_accessor_storage, this); + } + set alignTop(value) { + _classPrivateFieldSet2(_alignTop_accessor_storage, this, value); + } _onChange(e) { const checked = e.target.checked; this.checked = checked; @@ -16021,9 +16543,17 @@ } }; _defineProperty(AnalysisGroup, "styles", styles$49); + __decorate([n$1({ type: String })], AnalysisGroup.prototype, "label", null); + __decorate([n$1({ type: Boolean })], AnalysisGroup.prototype, "checked", null); + __decorate([n$1({ type: Boolean })], AnalysisGroup.prototype, "disabled", null); + __decorate([n$1({ + type: Boolean, + attribute: "align-top" + })], AnalysisGroup.prototype, "alignTop", null); customElements.define("analysis-group", AnalysisGroup); //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-sample-group/analysis-sample-group.ts + var _AnalysisSampleGroup, _analysis_accessor_storage$7, _entityId_accessor_storage$7; var SAMPLE_INTERVAL_OPTIONS = [ { value: "raw", @@ -16124,12 +16654,24 @@ label: "Last" } ]; - var AnalysisSampleGroup = @localized() class extends i$2 { - @n$1({ type: Object }) accessor analysis = {}; - @n$1({ - type: String, - attribute: "entity-id" - }) accessor entityId = ""; + var AnalysisSampleGroup = (_analysis_accessor_storage$7 = /* @__PURE__ */ new WeakMap(), _entityId_accessor_storage$7 = /* @__PURE__ */ new WeakMap(), _AnalysisSampleGroup = class AnalysisSampleGroup extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _analysis_accessor_storage$7, {}); + _classPrivateFieldInitSpec(this, _entityId_accessor_storage$7, ""); + } + get analysis() { + return _classPrivateFieldGet2(_analysis_accessor_storage$7, this); + } + set analysis(value) { + _classPrivateFieldSet2(_analysis_accessor_storage$7, this, value); + } + get entityId() { + return _classPrivateFieldGet2(_entityId_accessor_storage$7, this); + } + set entityId(value) { + _classPrivateFieldSet2(_entityId_accessor_storage$7, this, value); + } _emit(key, value) { this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { detail: { @@ -16186,14 +16728,20 @@ `; } - }; - _defineProperty(AnalysisSampleGroup, "styles", [sharedStyles, styles$50]); + }, _defineProperty(_AnalysisSampleGroup, "styles", [sharedStyles, styles$50]), _AnalysisSampleGroup); + __decorate([n$1({ type: Object })], AnalysisSampleGroup.prototype, "analysis", null); + __decorate([n$1({ + type: String, + attribute: "entity-id" + })], AnalysisSampleGroup.prototype, "entityId", null); + AnalysisSampleGroup = __decorate([localized()], AnalysisSampleGroup); customElements.define("analysis-sample-group", AnalysisSampleGroup); //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-trend-group/analysis-trend-group.styles.ts var styles$48 = i$5``; //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-trend-group/analysis-trend-group.ts + var _AnalysisTrendGroup, _analysis_accessor_storage$6, _entityId_accessor_storage$6; var ANALYSIS_TREND_METHOD_OPTIONS = [{ value: "rolling_average", label: "Rolling average" @@ -16231,12 +16779,24 @@ label: "28 days" } ]; - var AnalysisTrendGroup = @localized() class extends i$2 { - @n$1({ type: Object }) accessor analysis = {}; - @n$1({ - type: String, - attribute: "entity-id" - }) accessor entityId = ""; + var AnalysisTrendGroup = (_analysis_accessor_storage$6 = /* @__PURE__ */ new WeakMap(), _entityId_accessor_storage$6 = /* @__PURE__ */ new WeakMap(), _AnalysisTrendGroup = class AnalysisTrendGroup extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _analysis_accessor_storage$6, {}); + _classPrivateFieldInitSpec(this, _entityId_accessor_storage$6, ""); + } + get analysis() { + return _classPrivateFieldGet2(_analysis_accessor_storage$6, this); + } + set analysis(value) { + _classPrivateFieldSet2(_analysis_accessor_storage$6, this, value); + } + get entityId() { + return _classPrivateFieldGet2(_entityId_accessor_storage$6, this); + } + set entityId(value) { + _classPrivateFieldSet2(_entityId_accessor_storage$6, this, value); + } _emit(key, value) { this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { detail: { @@ -16301,20 +16861,38 @@ `; } - }; - _defineProperty(AnalysisTrendGroup, "styles", [sharedStyles, styles$48]); + }, _defineProperty(_AnalysisTrendGroup, "styles", [sharedStyles, styles$48]), _AnalysisTrendGroup); + __decorate([n$1({ type: Object })], AnalysisTrendGroup.prototype, "analysis", null); + __decorate([n$1({ + type: String, + attribute: "entity-id" + })], AnalysisTrendGroup.prototype, "entityId", null); + AnalysisTrendGroup = __decorate([localized()], AnalysisTrendGroup); customElements.define("analysis-trend-group", AnalysisTrendGroup); //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-summary-group/analysis-summary-group.styles.ts var styles$47 = i$5``; //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-summary-group/analysis-summary-group.ts - var AnalysisSummaryGroup = @localized() class extends i$2 { - @n$1({ type: Object }) accessor analysis = {}; - @n$1({ - type: String, - attribute: "entity-id" - }) accessor entityId = ""; + var _AnalysisSummaryGroup, _analysis_accessor_storage$5, _entityId_accessor_storage$5; + var AnalysisSummaryGroup = (_analysis_accessor_storage$5 = /* @__PURE__ */ new WeakMap(), _entityId_accessor_storage$5 = /* @__PURE__ */ new WeakMap(), _AnalysisSummaryGroup = class AnalysisSummaryGroup extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _analysis_accessor_storage$5, {}); + _classPrivateFieldInitSpec(this, _entityId_accessor_storage$5, ""); + } + get analysis() { + return _classPrivateFieldGet2(_analysis_accessor_storage$5, this); + } + set analysis(value) { + _classPrivateFieldSet2(_analysis_accessor_storage$5, this, value); + } + get entityId() { + return _classPrivateFieldGet2(_entityId_accessor_storage$5, this); + } + set entityId(value) { + _classPrivateFieldSet2(_entityId_accessor_storage$5, this, value); + } _emit(key, value) { this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { detail: { @@ -16351,14 +16929,20 @@ `; } - }; - _defineProperty(AnalysisSummaryGroup, "styles", [sharedStyles, styles$47]); + }, _defineProperty(_AnalysisSummaryGroup, "styles", [sharedStyles, styles$47]), _AnalysisSummaryGroup); + __decorate([n$1({ type: Object })], AnalysisSummaryGroup.prototype, "analysis", null); + __decorate([n$1({ + type: String, + attribute: "entity-id" + })], AnalysisSummaryGroup.prototype, "entityId", null); + AnalysisSummaryGroup = __decorate([localized()], AnalysisSummaryGroup); customElements.define("analysis-summary-group", AnalysisSummaryGroup); //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-rate-group/analysis-rate-group.styles.ts var styles$46 = i$5``; //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-rate-group/analysis-rate-group.ts + var _AnalysisRateGroup, _analysis_accessor_storage$4, _entityId_accessor_storage$4; var ANALYSIS_RATE_WINDOW_OPTIONS = [ { value: "point_to_point", @@ -16377,12 +16961,24 @@ label: "24 hours" } ]; - var AnalysisRateGroup = @localized() class extends i$2 { - @n$1({ type: Object }) accessor analysis = {}; - @n$1({ - type: String, - attribute: "entity-id" - }) accessor entityId = ""; + var AnalysisRateGroup = (_analysis_accessor_storage$4 = /* @__PURE__ */ new WeakMap(), _entityId_accessor_storage$4 = /* @__PURE__ */ new WeakMap(), _AnalysisRateGroup = class AnalysisRateGroup extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _analysis_accessor_storage$4, {}); + _classPrivateFieldInitSpec(this, _entityId_accessor_storage$4, ""); + } + get analysis() { + return _classPrivateFieldGet2(_analysis_accessor_storage$4, this); + } + set analysis(value) { + _classPrivateFieldSet2(_analysis_accessor_storage$4, this, value); + } + get entityId() { + return _classPrivateFieldGet2(_entityId_accessor_storage$4, this); + } + set entityId(value) { + _classPrivateFieldSet2(_entityId_accessor_storage$4, this, value); + } _emit(key, value) { this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { detail: { @@ -16441,21 +17037,45 @@ `; } - }; - _defineProperty(AnalysisRateGroup, "styles", [sharedStyles, styles$46]); + }, _defineProperty(_AnalysisRateGroup, "styles", [sharedStyles, styles$46]), _AnalysisRateGroup); + __decorate([n$1({ type: Object })], AnalysisRateGroup.prototype, "analysis", null); + __decorate([n$1({ + type: String, + attribute: "entity-id" + })], AnalysisRateGroup.prototype, "entityId", null); + AnalysisRateGroup = __decorate([localized()], AnalysisRateGroup); customElements.define("analysis-rate-group", AnalysisRateGroup); //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-threshold-group/analysis-threshold-group.styles.ts var styles$45 = i$5``; //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-threshold-group/analysis-threshold-group.ts - var AnalysisThresholdGroup = @localized() class extends i$2 { - @n$1({ type: Object }) accessor analysis = {}; - @n$1({ - type: String, - attribute: "entity-id" - }) accessor entityId = ""; - @n$1({ type: String }) accessor unit = ""; + var _AnalysisThresholdGroup, _analysis_accessor_storage$3, _entityId_accessor_storage$3, _unit_accessor_storage$1; + var AnalysisThresholdGroup = (_analysis_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _entityId_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _unit_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _AnalysisThresholdGroup = class AnalysisThresholdGroup extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _analysis_accessor_storage$3, {}); + _classPrivateFieldInitSpec(this, _entityId_accessor_storage$3, ""); + _classPrivateFieldInitSpec(this, _unit_accessor_storage$1, ""); + } + get analysis() { + return _classPrivateFieldGet2(_analysis_accessor_storage$3, this); + } + set analysis(value) { + _classPrivateFieldSet2(_analysis_accessor_storage$3, this, value); + } + get entityId() { + return _classPrivateFieldGet2(_entityId_accessor_storage$3, this); + } + set entityId(value) { + _classPrivateFieldSet2(_entityId_accessor_storage$3, this, value); + } + get unit() { + return _classPrivateFieldGet2(_unit_accessor_storage$1, this); + } + set unit(value) { + _classPrivateFieldSet2(_unit_accessor_storage$1, this, value); + } _emit(key, value) { this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { detail: { @@ -16540,8 +17160,14 @@ `; } - }; - _defineProperty(AnalysisThresholdGroup, "styles", [sharedStyles, styles$45]); + }, _defineProperty(_AnalysisThresholdGroup, "styles", [sharedStyles, styles$45]), _AnalysisThresholdGroup); + __decorate([n$1({ type: Object })], AnalysisThresholdGroup.prototype, "analysis", null); + __decorate([n$1({ + type: String, + attribute: "entity-id" + })], AnalysisThresholdGroup.prototype, "entityId", null); + __decorate([n$1({ type: String })], AnalysisThresholdGroup.prototype, "unit", null); + AnalysisThresholdGroup = __decorate([localized()], AnalysisThresholdGroup); customElements.define("analysis-threshold-group", AnalysisThresholdGroup); //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-anomaly-group/analysis-anomaly-group.styles.ts @@ -16650,6 +17276,7 @@ customElements.define("analysis-method-subopts", AnalysisMethodSubopts); //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-anomaly-group/analysis-anomaly-group.ts + var _AnalysisAnomalyGroup, _analysis_accessor_storage$2, _entityId_accessor_storage$2, _comparisonWindows_accessor_storage$3, _computing_accessor_storage$1, _computingProgress_accessor_storage$1, _computingMethods_accessor_storage$1; var ANALYSIS_ANOMALY_SENSITIVITY_OPTIONS = [ { value: "low", @@ -16761,31 +17388,52 @@ value: "only", label: "Overlaps only" }]; - var AnalysisAnomalyGroup = @localized() class extends i$2 { - @n$1({ type: Object }) accessor analysis = {}; - @n$1({ - type: String, - attribute: "entity-id" - }) accessor entityId = ""; - @n$1({ - type: Array, - attribute: "comparison-windows" - }) accessor comparisonWindows = []; - /** Whether analysis is currently being computed in the worker for this entity. */ - @n$1({ - type: Boolean, - attribute: false - }) accessor computing = false; - /** Overall analysis computation progress (0–100) shown in the group header. */ - @n$1({ - type: Number, - attribute: false - }) accessor computingProgress = 0; - /** Set of anomaly method names still in-flight in the worker. Each method shows its own spinner. */ - @n$1({ - type: Object, - attribute: false - }) accessor computingMethods = /* @__PURE__ */ new Set(); + var AnalysisAnomalyGroup = (_analysis_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _entityId_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _comparisonWindows_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _computing_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _computingProgress_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _computingMethods_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _AnalysisAnomalyGroup = class AnalysisAnomalyGroup extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _analysis_accessor_storage$2, {}); + _classPrivateFieldInitSpec(this, _entityId_accessor_storage$2, ""); + _classPrivateFieldInitSpec(this, _comparisonWindows_accessor_storage$3, []); + _classPrivateFieldInitSpec(this, _computing_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _computingProgress_accessor_storage$1, 0); + _classPrivateFieldInitSpec(this, _computingMethods_accessor_storage$1, /* @__PURE__ */ new Set()); + } + get analysis() { + return _classPrivateFieldGet2(_analysis_accessor_storage$2, this); + } + set analysis(value) { + _classPrivateFieldSet2(_analysis_accessor_storage$2, this, value); + } + get entityId() { + return _classPrivateFieldGet2(_entityId_accessor_storage$2, this); + } + set entityId(value) { + _classPrivateFieldSet2(_entityId_accessor_storage$2, this, value); + } + get comparisonWindows() { + return _classPrivateFieldGet2(_comparisonWindows_accessor_storage$3, this); + } + set comparisonWindows(value) { + _classPrivateFieldSet2(_comparisonWindows_accessor_storage$3, this, value); + } + get computing() { + return _classPrivateFieldGet2(_computing_accessor_storage$1, this); + } + set computing(value) { + _classPrivateFieldSet2(_computing_accessor_storage$1, this, value); + } + get computingProgress() { + return _classPrivateFieldGet2(_computingProgress_accessor_storage$1, this); + } + set computingProgress(value) { + _classPrivateFieldSet2(_computingProgress_accessor_storage$1, this, value); + } + get computingMethods() { + return _classPrivateFieldGet2(_computingMethods_accessor_storage$1, this); + } + set computingMethods(value) { + _classPrivateFieldSet2(_computingMethods_accessor_storage$1, this, value); + } _emit(key, value) { this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { detail: { @@ -16954,8 +17602,29 @@ `; } - }; - _defineProperty(AnalysisAnomalyGroup, "styles", [sharedStyles, styles$44]); + }, _defineProperty(_AnalysisAnomalyGroup, "styles", [sharedStyles, styles$44]), _AnalysisAnomalyGroup); + __decorate([n$1({ type: Object })], AnalysisAnomalyGroup.prototype, "analysis", null); + __decorate([n$1({ + type: String, + attribute: "entity-id" + })], AnalysisAnomalyGroup.prototype, "entityId", null); + __decorate([n$1({ + type: Array, + attribute: "comparison-windows" + })], AnalysisAnomalyGroup.prototype, "comparisonWindows", null); + __decorate([n$1({ + type: Boolean, + attribute: false + })], AnalysisAnomalyGroup.prototype, "computing", null); + __decorate([n$1({ + type: Number, + attribute: false + })], AnalysisAnomalyGroup.prototype, "computingProgress", null); + __decorate([n$1({ + type: Object, + attribute: false + })], AnalysisAnomalyGroup.prototype, "computingMethods", null); + AnalysisAnomalyGroup = __decorate([localized()], AnalysisAnomalyGroup); customElements.define("analysis-anomaly-group", AnalysisAnomalyGroup); //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-delta-group/analysis-delta-group.styles.ts @@ -16969,16 +17638,32 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/analysis-delta-group/analysis-delta-group.ts - var AnalysisDeltaGroup = @localized() class extends i$2 { - @n$1({ type: Object }) accessor analysis = {}; - @n$1({ - type: String, - attribute: "entity-id" - }) accessor entityId = ""; - @n$1({ - type: Boolean, - attribute: "can-show-delta-analysis" - }) accessor canShowDeltaAnalysis = false; + var _AnalysisDeltaGroup, _analysis_accessor_storage$1, _entityId_accessor_storage$1, _canShowDeltaAnalysis_accessor_storage$3; + var AnalysisDeltaGroup = (_analysis_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _entityId_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _canShowDeltaAnalysis_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _AnalysisDeltaGroup = class AnalysisDeltaGroup extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _analysis_accessor_storage$1, {}); + _classPrivateFieldInitSpec(this, _entityId_accessor_storage$1, ""); + _classPrivateFieldInitSpec(this, _canShowDeltaAnalysis_accessor_storage$3, false); + } + get analysis() { + return _classPrivateFieldGet2(_analysis_accessor_storage$1, this); + } + set analysis(value) { + _classPrivateFieldSet2(_analysis_accessor_storage$1, this, value); + } + get entityId() { + return _classPrivateFieldGet2(_entityId_accessor_storage$1, this); + } + set entityId(value) { + _classPrivateFieldSet2(_entityId_accessor_storage$1, this, value); + } + get canShowDeltaAnalysis() { + return _classPrivateFieldGet2(_canShowDeltaAnalysis_accessor_storage$3, this); + } + set canShowDeltaAnalysis(value) { + _classPrivateFieldSet2(_canShowDeltaAnalysis_accessor_storage$3, this, value); + } _emit(key, value) { this.dispatchEvent(new CustomEvent("dp-group-analysis-change", { detail: { @@ -17033,8 +17718,17 @@ `; } - }; - _defineProperty(AnalysisDeltaGroup, "styles", [sharedStyles, styles$42]); + }, _defineProperty(_AnalysisDeltaGroup, "styles", [sharedStyles, styles$42]), _AnalysisDeltaGroup); + __decorate([n$1({ type: Object })], AnalysisDeltaGroup.prototype, "analysis", null); + __decorate([n$1({ + type: String, + attribute: "entity-id" + })], AnalysisDeltaGroup.prototype, "entityId", null); + __decorate([n$1({ + type: Boolean, + attribute: "can-show-delta-analysis" + })], AnalysisDeltaGroup.prototype, "canShowDeltaAnalysis", null); + AnalysisDeltaGroup = __decorate([localized()], AnalysisDeltaGroup); customElements.define("analysis-delta-group", AnalysisDeltaGroup); //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sections/sidebar-datapoints-section.styles.ts @@ -17119,11 +17813,42 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/display/sidebar-section-header/sidebar-section-header.ts + var _title_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _subtitle_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _collapsible_accessor_storage$5 = /* @__PURE__ */ new WeakMap(); + var _open_accessor_storage$7 = /* @__PURE__ */ new WeakMap(); var SidebarSectionHeader = class extends i$2 { - @n$1({ type: String }) accessor title = ""; - @n$1({ type: String }) accessor subtitle = ""; - @n$1({ type: Boolean }) accessor collapsible = false; - @n$1({ type: Boolean }) accessor open = true; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _title_accessor_storage$1, ""); + _classPrivateFieldInitSpec(this, _subtitle_accessor_storage$1, ""); + _classPrivateFieldInitSpec(this, _collapsible_accessor_storage$5, false); + _classPrivateFieldInitSpec(this, _open_accessor_storage$7, true); + } + get title() { + return _classPrivateFieldGet2(_title_accessor_storage$1, this); + } + set title(value) { + _classPrivateFieldSet2(_title_accessor_storage$1, this, value); + } + get subtitle() { + return _classPrivateFieldGet2(_subtitle_accessor_storage$1, this); + } + set subtitle(value) { + _classPrivateFieldSet2(_subtitle_accessor_storage$1, this, value); + } + get collapsible() { + return _classPrivateFieldGet2(_collapsible_accessor_storage$5, this); + } + set collapsible(value) { + _classPrivateFieldSet2(_collapsible_accessor_storage$5, this, value); + } + get open() { + return _classPrivateFieldGet2(_open_accessor_storage$7, this); + } + set open(value) { + _classPrivateFieldSet2(_open_accessor_storage$7, this, value); + } _emitToggle() { this.dispatchEvent(new CustomEvent("dp-section-toggle", { bubbles: true, @@ -17174,14 +17899,49 @@ } }; _defineProperty(SidebarSectionHeader, "styles", styles$39); + __decorate([n$1({ type: String })], SidebarSectionHeader.prototype, "title", null); + __decorate([n$1({ type: String })], SidebarSectionHeader.prototype, "subtitle", null); + __decorate([n$1({ type: Boolean })], SidebarSectionHeader.prototype, "collapsible", null); + __decorate([n$1({ type: Boolean })], SidebarSectionHeader.prototype, "open", null); customElements.define("sidebar-section-header", SidebarSectionHeader); //#endregion //#region custom_components/hass_datapoints/src/atoms/display/sidebar-options-section/sidebar-options-section.ts + var _title_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _subtitle_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _collapsible_accessor_storage$4 = /* @__PURE__ */ new WeakMap(); + var _open_accessor_storage$6 = /* @__PURE__ */ new WeakMap(); var SidebarOptionsSection = class extends i$2 { - @n$1({ type: String }) accessor title = ""; - @n$1({ type: String }) accessor subtitle = ""; - @n$1({ type: Boolean }) accessor collapsible = false; - @n$1({ type: Boolean }) accessor open = true; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _title_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _subtitle_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _collapsible_accessor_storage$4, false); + _classPrivateFieldInitSpec(this, _open_accessor_storage$6, true); + } + get title() { + return _classPrivateFieldGet2(_title_accessor_storage, this); + } + set title(value) { + _classPrivateFieldSet2(_title_accessor_storage, this, value); + } + get subtitle() { + return _classPrivateFieldGet2(_subtitle_accessor_storage, this); + } + set subtitle(value) { + _classPrivateFieldSet2(_subtitle_accessor_storage, this, value); + } + get collapsible() { + return _classPrivateFieldGet2(_collapsible_accessor_storage$4, this); + } + set collapsible(value) { + _classPrivateFieldSet2(_collapsible_accessor_storage$4, this, value); + } + get open() { + return _classPrivateFieldGet2(_open_accessor_storage$6, this); + } + set open(value) { + _classPrivateFieldSet2(_open_accessor_storage$6, this, value); + } _onToggle(e) { e.stopPropagation(); this.open = !this.open; @@ -17207,6 +17967,10 @@ } }; _defineProperty(SidebarOptionsSection, "styles", styles$40); + __decorate([n$1({ type: String })], SidebarOptionsSection.prototype, "title", null); + __decorate([n$1({ type: String })], SidebarOptionsSection.prototype, "subtitle", null); + __decorate([n$1({ type: Boolean })], SidebarOptionsSection.prototype, "collapsible", null); + __decorate([n$1({ type: Boolean })], SidebarOptionsSection.prototype, "open", null); customElements.define("sidebar-options-section", SidebarOptionsSection); //#endregion //#region custom_components/hass_datapoints/src/atoms/form/radio-group/radio-group.styles.ts @@ -17237,10 +18001,34 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/form/radio-group/radio-group.ts + var _name_accessor_storage$2 = /* @__PURE__ */ new WeakMap(); + var _value_accessor_storage$5 = /* @__PURE__ */ new WeakMap(); + var _options_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); var RadioGroup = class extends i$2 { - @n$1({ type: String }) accessor name = ""; - @n$1({ type: String }) accessor value = ""; - @n$1({ type: Array }) accessor options = []; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _name_accessor_storage$2, ""); + _classPrivateFieldInitSpec(this, _value_accessor_storage$5, ""); + _classPrivateFieldInitSpec(this, _options_accessor_storage$1, []); + } + get name() { + return _classPrivateFieldGet2(_name_accessor_storage$2, this); + } + set name(value) { + _classPrivateFieldSet2(_name_accessor_storage$2, this, value); + } + get value() { + return _classPrivateFieldGet2(_value_accessor_storage$5, this); + } + set value(value) { + _classPrivateFieldSet2(_value_accessor_storage$5, this, value); + } + get options() { + return _classPrivateFieldGet2(_options_accessor_storage$1, this); + } + set options(value) { + _classPrivateFieldSet2(_options_accessor_storage$1, this, value); + } _onChange(e) { const input = e.target; this.dispatchEvent(new CustomEvent("dp-radio-change", { @@ -17271,9 +18059,13 @@ } }; _defineProperty(RadioGroup, "styles", styles$38); + __decorate([n$1({ type: String })], RadioGroup.prototype, "name", null); + __decorate([n$1({ type: String })], RadioGroup.prototype, "value", null); + __decorate([n$1({ type: Array })], RadioGroup.prototype, "options", null); customElements.define("radio-group", RadioGroup); //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sections/sidebar-datapoints-section.ts + var _SidebarDatapointsSection, _datapointScope_accessor_storage$2, _collapsible_accessor_storage$3, _open_accessor_storage$5; var DATAPOINT_SCOPE_OPTIONS = [ { value: "linked", @@ -17288,13 +18080,31 @@ label: "Hide datapoints" } ]; - var SidebarDatapointsSection = @localized() class extends i$2 { - @n$1({ - type: String, - attribute: "datapoint-scope" - }) accessor datapointScope = "linked"; - @n$1({ type: Boolean }) accessor collapsible = false; - @n$1({ type: Boolean }) accessor open = true; + var SidebarDatapointsSection = (_datapointScope_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _collapsible_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _open_accessor_storage$5 = /* @__PURE__ */ new WeakMap(), _SidebarDatapointsSection = class SidebarDatapointsSection extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _datapointScope_accessor_storage$2, "linked"); + _classPrivateFieldInitSpec(this, _collapsible_accessor_storage$3, false); + _classPrivateFieldInitSpec(this, _open_accessor_storage$5, true); + } + get datapointScope() { + return _classPrivateFieldGet2(_datapointScope_accessor_storage$2, this); + } + set datapointScope(value) { + _classPrivateFieldSet2(_datapointScope_accessor_storage$2, this, value); + } + get collapsible() { + return _classPrivateFieldGet2(_collapsible_accessor_storage$3, this); + } + set collapsible(value) { + _classPrivateFieldSet2(_collapsible_accessor_storage$3, this, value); + } + get open() { + return _classPrivateFieldGet2(_open_accessor_storage$5, this); + } + set open(value) { + _classPrivateFieldSet2(_open_accessor_storage$5, this, value); + } _onScopeChange(e) { this.dispatchEvent(new CustomEvent("dp-scope-change", { detail: { value: e.detail.value }, @@ -17325,8 +18135,14 @@ `; } - }; - _defineProperty(SidebarDatapointsSection, "styles", styles$41); + }, _defineProperty(_SidebarDatapointsSection, "styles", styles$41), _SidebarDatapointsSection); + __decorate([n$1({ + type: String, + attribute: "datapoint-scope" + })], SidebarDatapointsSection.prototype, "datapointScope", null); + __decorate([n$1({ type: Boolean })], SidebarDatapointsSection.prototype, "collapsible", null); + __decorate([n$1({ type: Boolean })], SidebarDatapointsSection.prototype, "open", null); + SidebarDatapointsSection = __decorate([localized()], SidebarDatapointsSection); customElements.define("sidebar-datapoints-section", SidebarDatapointsSection); //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sections/sidebar-datapoint-display-section.styles.ts @@ -17359,8 +18175,18 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/form/checkbox-list/checkbox-list.ts + var _items_accessor_storage = /* @__PURE__ */ new WeakMap(); var CheckboxList = class extends i$2 { - @n$1({ type: Array }) accessor items = []; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _items_accessor_storage, []); + } + get items() { + return _classPrivateFieldGet2(_items_accessor_storage, this); + } + set items(value) { + _classPrivateFieldSet2(_items_accessor_storage, this, value); + } _onChange(e) { const input = e.target; this.dispatchEvent(new CustomEvent("dp-item-change", { @@ -17391,20 +18217,43 @@ } }; _defineProperty(CheckboxList, "styles", styles$36); + __decorate([n$1({ type: Array })], CheckboxList.prototype, "items", null); customElements.define("checkbox-list", CheckboxList); //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sections/sidebar-datapoint-display-section.ts - var SidebarDatapointDisplaySection = @localized() class extends i$2 { - @n$1({ - type: Boolean, - attribute: "show-icons" - }) accessor showIcons = true; - @n$1({ - type: Boolean, - attribute: "show-lines" - }) accessor showLines = true; - @n$1({ type: Boolean }) accessor collapsible = false; - @n$1({ type: Boolean }) accessor open = true; + var _SidebarDatapointDisplaySection, _showIcons_accessor_storage$2, _showLines_accessor_storage$2, _collapsible_accessor_storage$2, _open_accessor_storage$4; + var SidebarDatapointDisplaySection = (_showIcons_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _showLines_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _collapsible_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _open_accessor_storage$4 = /* @__PURE__ */ new WeakMap(), _SidebarDatapointDisplaySection = class SidebarDatapointDisplaySection extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _showIcons_accessor_storage$2, true); + _classPrivateFieldInitSpec(this, _showLines_accessor_storage$2, true); + _classPrivateFieldInitSpec(this, _collapsible_accessor_storage$2, false); + _classPrivateFieldInitSpec(this, _open_accessor_storage$4, true); + } + get showIcons() { + return _classPrivateFieldGet2(_showIcons_accessor_storage$2, this); + } + set showIcons(value) { + _classPrivateFieldSet2(_showIcons_accessor_storage$2, this, value); + } + get showLines() { + return _classPrivateFieldGet2(_showLines_accessor_storage$2, this); + } + set showLines(value) { + _classPrivateFieldSet2(_showLines_accessor_storage$2, this, value); + } + get collapsible() { + return _classPrivateFieldGet2(_collapsible_accessor_storage$2, this); + } + set collapsible(value) { + _classPrivateFieldSet2(_collapsible_accessor_storage$2, this, value); + } + get open() { + return _classPrivateFieldGet2(_open_accessor_storage$4, this); + } + set open(value) { + _classPrivateFieldSet2(_open_accessor_storage$4, this, value); + } _onCheckboxChange(e) { const { name, checked } = e.detail; this.dispatchEvent(new CustomEvent("dp-display-change", { @@ -17439,8 +18288,18 @@ `; } - }; - _defineProperty(SidebarDatapointDisplaySection, "styles", styles$37); + }, _defineProperty(_SidebarDatapointDisplaySection, "styles", styles$37), _SidebarDatapointDisplaySection); + __decorate([n$1({ + type: Boolean, + attribute: "show-icons" + })], SidebarDatapointDisplaySection.prototype, "showIcons", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-lines" + })], SidebarDatapointDisplaySection.prototype, "showLines", null); + __decorate([n$1({ type: Boolean })], SidebarDatapointDisplaySection.prototype, "collapsible", null); + __decorate([n$1({ type: Boolean })], SidebarDatapointDisplaySection.prototype, "open", null); + SidebarDatapointDisplaySection = __decorate([localized()], SidebarDatapointDisplaySection); customElements.define("sidebar-datapoint-display-section", SidebarDatapointDisplaySection); //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sections/sidebar-analysis-section.styles.ts @@ -17472,6 +18331,7 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sections/sidebar-analysis-section.ts + var _SidebarAnalysisSection, _anomalyOverlapMode_accessor_storage$2, _showCorrelatedAnomal_accessor_storage$2, _anyAnomaliesEnabled_accessor_storage$2, _collapsible_accessor_storage$1, _open_accessor_storage$3; var ANALYSIS_ANOMALY_OVERLAP_MODE_OPTIONS$1 = [{ value: "all", label: "Show all anomalies" @@ -17479,22 +18339,45 @@ value: "only", label: "Overlaps only" }]; - var SidebarAnalysisSection = @localized() class extends i$2 { - @n$1({ - type: String, - attribute: "anomaly-overlap-mode" - }) accessor anomalyOverlapMode = "all"; - @n$1({ - type: Boolean, - attribute: "show-correlated-anomalies" - }) accessor showCorrelatedAnomalies = false; - /** True when at least one target has "Show anomalies" enabled. */ - @n$1({ - type: Boolean, - attribute: false - }) accessor anyAnomaliesEnabled = false; - @n$1({ type: Boolean }) accessor collapsible = false; - @n$1({ type: Boolean }) accessor open = true; + var SidebarAnalysisSection = (_anomalyOverlapMode_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _showCorrelatedAnomal_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _anyAnomaliesEnabled_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _collapsible_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _open_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _SidebarAnalysisSection = class SidebarAnalysisSection extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _anomalyOverlapMode_accessor_storage$2, "all"); + _classPrivateFieldInitSpec(this, _showCorrelatedAnomal_accessor_storage$2, false); + _classPrivateFieldInitSpec(this, _anyAnomaliesEnabled_accessor_storage$2, false); + _classPrivateFieldInitSpec(this, _collapsible_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _open_accessor_storage$3, true); + } + get anomalyOverlapMode() { + return _classPrivateFieldGet2(_anomalyOverlapMode_accessor_storage$2, this); + } + set anomalyOverlapMode(value) { + _classPrivateFieldSet2(_anomalyOverlapMode_accessor_storage$2, this, value); + } + get showCorrelatedAnomalies() { + return _classPrivateFieldGet2(_showCorrelatedAnomal_accessor_storage$2, this); + } + set showCorrelatedAnomalies(value) { + _classPrivateFieldSet2(_showCorrelatedAnomal_accessor_storage$2, this, value); + } + get anyAnomaliesEnabled() { + return _classPrivateFieldGet2(_anyAnomaliesEnabled_accessor_storage$2, this); + } + set anyAnomaliesEnabled(value) { + _classPrivateFieldSet2(_anyAnomaliesEnabled_accessor_storage$2, this, value); + } + get collapsible() { + return _classPrivateFieldGet2(_collapsible_accessor_storage$1, this); + } + set collapsible(value) { + _classPrivateFieldSet2(_collapsible_accessor_storage$1, this, value); + } + get open() { + return _classPrivateFieldGet2(_open_accessor_storage$3, this); + } + set open(value) { + _classPrivateFieldSet2(_open_accessor_storage$3, this, value); + } _localizedOptions(options) { return options.map((opt) => ({ ...opt, @@ -17556,8 +18439,22 @@ `; } - }; - _defineProperty(SidebarAnalysisSection, "styles", styles$35); + }, _defineProperty(_SidebarAnalysisSection, "styles", styles$35), _SidebarAnalysisSection); + __decorate([n$1({ + type: String, + attribute: "anomaly-overlap-mode" + })], SidebarAnalysisSection.prototype, "anomalyOverlapMode", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-correlated-anomalies" + })], SidebarAnalysisSection.prototype, "showCorrelatedAnomalies", null); + __decorate([n$1({ + type: Boolean, + attribute: false + })], SidebarAnalysisSection.prototype, "anyAnomaliesEnabled", null); + __decorate([n$1({ type: Boolean })], SidebarAnalysisSection.prototype, "collapsible", null); + __decorate([n$1({ type: Boolean })], SidebarAnalysisSection.prototype, "open", null); + SidebarAnalysisSection = __decorate([localized()], SidebarAnalysisSection); customElements.define("sidebar-analysis-section", SidebarAnalysisSection); //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sections/sidebar-chart-display-section.styles.ts @@ -17605,6 +18502,7 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sections/sidebar-chart-display-section.ts + var _SidebarChartDisplaySection, _showTooltips_accessor_storage$2, _showHoverGuides_accessor_storage$2, _showDataGaps_accessor_storage$2, _dataGapThreshold_accessor_storage$2, _yAxisMode_accessor_storage$2, _hoverSnapMode_accessor_storage$2, _collapsible_accessor_storage, _open_accessor_storage$2; var DATA_GAP_THRESHOLD_OPTIONS$1 = [ { value: "auto", @@ -17664,33 +18562,66 @@ value: "snap_to_data_points", label: "Snap to data points" }]; - var SidebarChartDisplaySection = @localized() class extends i$2 { - @n$1({ - type: Boolean, - attribute: "show-tooltips" - }) accessor showTooltips = true; - @n$1({ - type: Boolean, - attribute: "show-hover-guides" - }) accessor showHoverGuides = false; - @n$1({ - type: Boolean, - attribute: "show-data-gaps" - }) accessor showDataGaps = true; - @n$1({ - type: String, - attribute: "data-gap-threshold" - }) accessor dataGapThreshold = "2h"; - @n$1({ - type: String, - attribute: "y-axis-mode" - }) accessor yAxisMode = "combined"; - @n$1({ - type: String, - attribute: "hover-snap-mode" - }) accessor hoverSnapMode = "follow_series"; - @n$1({ type: Boolean }) accessor collapsible = false; - @n$1({ type: Boolean }) accessor open = true; + var SidebarChartDisplaySection = (_showTooltips_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _showHoverGuides_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _showDataGaps_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _dataGapThreshold_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _yAxisMode_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _hoverSnapMode_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _collapsible_accessor_storage = /* @__PURE__ */ new WeakMap(), _open_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _SidebarChartDisplaySection = class SidebarChartDisplaySection extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _showTooltips_accessor_storage$2, true); + _classPrivateFieldInitSpec(this, _showHoverGuides_accessor_storage$2, false); + _classPrivateFieldInitSpec(this, _showDataGaps_accessor_storage$2, true); + _classPrivateFieldInitSpec(this, _dataGapThreshold_accessor_storage$2, "2h"); + _classPrivateFieldInitSpec(this, _yAxisMode_accessor_storage$2, "combined"); + _classPrivateFieldInitSpec(this, _hoverSnapMode_accessor_storage$2, "follow_series"); + _classPrivateFieldInitSpec(this, _collapsible_accessor_storage, false); + _classPrivateFieldInitSpec(this, _open_accessor_storage$2, true); + } + get showTooltips() { + return _classPrivateFieldGet2(_showTooltips_accessor_storage$2, this); + } + set showTooltips(value) { + _classPrivateFieldSet2(_showTooltips_accessor_storage$2, this, value); + } + get showHoverGuides() { + return _classPrivateFieldGet2(_showHoverGuides_accessor_storage$2, this); + } + set showHoverGuides(value) { + _classPrivateFieldSet2(_showHoverGuides_accessor_storage$2, this, value); + } + get showDataGaps() { + return _classPrivateFieldGet2(_showDataGaps_accessor_storage$2, this); + } + set showDataGaps(value) { + _classPrivateFieldSet2(_showDataGaps_accessor_storage$2, this, value); + } + get dataGapThreshold() { + return _classPrivateFieldGet2(_dataGapThreshold_accessor_storage$2, this); + } + set dataGapThreshold(value) { + _classPrivateFieldSet2(_dataGapThreshold_accessor_storage$2, this, value); + } + get yAxisMode() { + return _classPrivateFieldGet2(_yAxisMode_accessor_storage$2, this); + } + set yAxisMode(value) { + _classPrivateFieldSet2(_yAxisMode_accessor_storage$2, this, value); + } + get hoverSnapMode() { + return _classPrivateFieldGet2(_hoverSnapMode_accessor_storage$2, this); + } + set hoverSnapMode(value) { + _classPrivateFieldSet2(_hoverSnapMode_accessor_storage$2, this, value); + } + get collapsible() { + return _classPrivateFieldGet2(_collapsible_accessor_storage, this); + } + set collapsible(value) { + _classPrivateFieldSet2(_collapsible_accessor_storage, this, value); + } + get open() { + return _classPrivateFieldGet2(_open_accessor_storage$2, this); + } + set open(value) { + _classPrivateFieldSet2(_open_accessor_storage$2, this, value); + } _localizedOptions(options) { return options.map((opt) => ({ ...opt, @@ -17784,8 +18715,34 @@ `; } - }; - _defineProperty(SidebarChartDisplaySection, "styles", styles$34); + }, _defineProperty(_SidebarChartDisplaySection, "styles", styles$34), _SidebarChartDisplaySection); + __decorate([n$1({ + type: Boolean, + attribute: "show-tooltips" + })], SidebarChartDisplaySection.prototype, "showTooltips", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-hover-guides" + })], SidebarChartDisplaySection.prototype, "showHoverGuides", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-data-gaps" + })], SidebarChartDisplaySection.prototype, "showDataGaps", null); + __decorate([n$1({ + type: String, + attribute: "data-gap-threshold" + })], SidebarChartDisplaySection.prototype, "dataGapThreshold", null); + __decorate([n$1({ + type: String, + attribute: "y-axis-mode" + })], SidebarChartDisplaySection.prototype, "yAxisMode", null); + __decorate([n$1({ + type: String, + attribute: "hover-snap-mode" + })], SidebarChartDisplaySection.prototype, "hoverSnapMode", null); + __decorate([n$1({ type: Boolean })], SidebarChartDisplaySection.prototype, "collapsible", null); + __decorate([n$1({ type: Boolean })], SidebarChartDisplaySection.prototype, "open", null); + SidebarChartDisplaySection = __decorate([localized()], SidebarChartDisplaySection); customElements.define("sidebar-chart-display-section", SidebarChartDisplaySection); //#endregion //#region custom_components/hass_datapoints/src/cards/history/editor.ts @@ -19022,6 +19979,7 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/target-row/target-row.ts + var _TargetRow, _color_accessor_storage$1, _visible_accessor_storage, _analysis_accessor_storage, _index_accessor_storage, _entityId_accessor_storage, _canShowDeltaAnalysis_accessor_storage$2, _stateObj_accessor_storage$1, _hass_accessor_storage$7, _comparisonWindows_accessor_storage$2, _computing_accessor_storage, _computingProgress_accessor_storage, _computingMethods_accessor_storage, _rowCount_accessor_storage, _allAnalysisSame_accessor_storage, _hideDragHandle_accessor_storage; function deriveSwatchIconColor(color) { const hex = String(color || "").trim(); const normalizedHex = /^#([0-9a-f]{6})$/i.test(hex) ? hex : null; @@ -19045,64 +20003,115 @@ function _hasActiveAnalysis(a, hasComparisonWindow) { return a.show_trend_lines || a.show_summary_stats || a.show_rate_of_change || a.show_threshold_analysis || a.show_anomalies || a.show_delta_analysis && hasComparisonWindow; } - var TargetRow = @localized() class extends i$2 { - @n$1({ type: String }) accessor color = "#03a9f4"; - @n$1({ type: Boolean }) accessor visible = true; - @n$1({ type: Object }) accessor analysis = {}; - @n$1({ type: Number }) accessor index = 0; - /** Config entity ID — always available, used as fallback before HA state loads. */ - @n$1({ - type: String, - attribute: "entity-id" - }) accessor entityId = ""; - @n$1({ - type: Boolean, - attribute: "can-show-delta-analysis" - }) accessor canShowDeltaAnalysis = false; - /** HA entity state object. Provides entity_id, display name, unit, and icon for the row. */ - @n$1({ - type: Object, - attribute: false - }) accessor stateObj = null; - /** HA hass object. Required by ha-state-icon to resolve entity icons correctly. */ - @n$1({ - type: Object, - attribute: false - }) accessor hass = null; - @n$1({ - type: Array, - attribute: "comparison-windows" - }) accessor comparisonWindows = []; - /** Whether this entity's analysis is currently being computed in the worker. */ - @n$1({ - type: Boolean, - attribute: false - }) accessor computing = false; - /** Analysis computation progress (0–100), shown alongside the spinner. */ - @n$1({ - type: Number, - attribute: false - }) accessor computingProgress = 0; - /** Set of anomaly method names still in-flight in the worker for this entity. */ - @n$1({ - type: Object, - attribute: false - }) accessor computingMethods = /* @__PURE__ */ new Set(); - /** Total number of rows in the list — used to hide "Copy to all" when there is only one target. */ - @n$1({ - type: Number, - attribute: false - }) accessor rowCount = 1; - /** True when all rows share identical analysis settings — used to disable the "Copy to all" button. */ - @n$1({ - type: Boolean, - attribute: false - }) accessor allAnalysisSame = false; - /** When true, the drag handle button is hidden (e.g. when rendered inside a tooltip popup). */ - @n$1({ - type: Boolean, - attribute: "hide-drag-handle" - }) accessor hideDragHandle = false; + var TargetRow = (_color_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _visible_accessor_storage = /* @__PURE__ */ new WeakMap(), _analysis_accessor_storage = /* @__PURE__ */ new WeakMap(), _index_accessor_storage = /* @__PURE__ */ new WeakMap(), _entityId_accessor_storage = /* @__PURE__ */ new WeakMap(), _canShowDeltaAnalysis_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _stateObj_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _hass_accessor_storage$7 = /* @__PURE__ */ new WeakMap(), _comparisonWindows_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _computing_accessor_storage = /* @__PURE__ */ new WeakMap(), _computingProgress_accessor_storage = /* @__PURE__ */ new WeakMap(), _computingMethods_accessor_storage = /* @__PURE__ */ new WeakMap(), _rowCount_accessor_storage = /* @__PURE__ */ new WeakMap(), _allAnalysisSame_accessor_storage = /* @__PURE__ */ new WeakMap(), _hideDragHandle_accessor_storage = /* @__PURE__ */ new WeakMap(), _TargetRow = class TargetRow extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _color_accessor_storage$1, "#03a9f4"); + _classPrivateFieldInitSpec(this, _visible_accessor_storage, true); + _classPrivateFieldInitSpec(this, _analysis_accessor_storage, {}); + _classPrivateFieldInitSpec(this, _index_accessor_storage, 0); + _classPrivateFieldInitSpec(this, _entityId_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _canShowDeltaAnalysis_accessor_storage$2, false); + _classPrivateFieldInitSpec(this, _stateObj_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$7, null); + _classPrivateFieldInitSpec(this, _comparisonWindows_accessor_storage$2, []); + _classPrivateFieldInitSpec(this, _computing_accessor_storage, false); + _classPrivateFieldInitSpec(this, _computingProgress_accessor_storage, 0); + _classPrivateFieldInitSpec(this, _computingMethods_accessor_storage, /* @__PURE__ */ new Set()); + _classPrivateFieldInitSpec(this, _rowCount_accessor_storage, 1); + _classPrivateFieldInitSpec(this, _allAnalysisSame_accessor_storage, false); + _classPrivateFieldInitSpec(this, _hideDragHandle_accessor_storage, false); + } + get color() { + return _classPrivateFieldGet2(_color_accessor_storage$1, this); + } + set color(value) { + _classPrivateFieldSet2(_color_accessor_storage$1, this, value); + } + get visible() { + return _classPrivateFieldGet2(_visible_accessor_storage, this); + } + set visible(value) { + _classPrivateFieldSet2(_visible_accessor_storage, this, value); + } + get analysis() { + return _classPrivateFieldGet2(_analysis_accessor_storage, this); + } + set analysis(value) { + _classPrivateFieldSet2(_analysis_accessor_storage, this, value); + } + get index() { + return _classPrivateFieldGet2(_index_accessor_storage, this); + } + set index(value) { + _classPrivateFieldSet2(_index_accessor_storage, this, value); + } + get entityId() { + return _classPrivateFieldGet2(_entityId_accessor_storage, this); + } + set entityId(value) { + _classPrivateFieldSet2(_entityId_accessor_storage, this, value); + } + get canShowDeltaAnalysis() { + return _classPrivateFieldGet2(_canShowDeltaAnalysis_accessor_storage$2, this); + } + set canShowDeltaAnalysis(value) { + _classPrivateFieldSet2(_canShowDeltaAnalysis_accessor_storage$2, this, value); + } + get stateObj() { + return _classPrivateFieldGet2(_stateObj_accessor_storage$1, this); + } + set stateObj(value) { + _classPrivateFieldSet2(_stateObj_accessor_storage$1, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$7, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$7, this, value); + } + get comparisonWindows() { + return _classPrivateFieldGet2(_comparisonWindows_accessor_storage$2, this); + } + set comparisonWindows(value) { + _classPrivateFieldSet2(_comparisonWindows_accessor_storage$2, this, value); + } + get computing() { + return _classPrivateFieldGet2(_computing_accessor_storage, this); + } + set computing(value) { + _classPrivateFieldSet2(_computing_accessor_storage, this, value); + } + get computingProgress() { + return _classPrivateFieldGet2(_computingProgress_accessor_storage, this); + } + set computingProgress(value) { + _classPrivateFieldSet2(_computingProgress_accessor_storage, this, value); + } + get computingMethods() { + return _classPrivateFieldGet2(_computingMethods_accessor_storage, this); + } + set computingMethods(value) { + _classPrivateFieldSet2(_computingMethods_accessor_storage, this, value); + } + get rowCount() { + return _classPrivateFieldGet2(_rowCount_accessor_storage, this); + } + set rowCount(value) { + _classPrivateFieldSet2(_rowCount_accessor_storage, this, value); + } + get allAnalysisSame() { + return _classPrivateFieldGet2(_allAnalysisSame_accessor_storage, this); + } + set allAnalysisSame(value) { + _classPrivateFieldSet2(_allAnalysisSame_accessor_storage, this, value); + } + get hideDragHandle() { + return _classPrivateFieldGet2(_hideDragHandle_accessor_storage, this); + } + set hideDragHandle(value) { + _classPrivateFieldSet2(_hideDragHandle_accessor_storage, this, value); + } /** Entity ID — from HA state when available, else from the config prop. */ get _entityId() { return this.stateObj?.entity_id ?? this.entityId ?? ""; @@ -19336,8 +20345,56 @@
`; } - }; - _defineProperty(TargetRow, "styles", styles$33); + }, _defineProperty(_TargetRow, "styles", styles$33), _TargetRow); + __decorate([n$1({ type: String })], TargetRow.prototype, "color", null); + __decorate([n$1({ type: Boolean })], TargetRow.prototype, "visible", null); + __decorate([n$1({ type: Object })], TargetRow.prototype, "analysis", null); + __decorate([n$1({ type: Number })], TargetRow.prototype, "index", null); + __decorate([n$1({ + type: String, + attribute: "entity-id" + })], TargetRow.prototype, "entityId", null); + __decorate([n$1({ + type: Boolean, + attribute: "can-show-delta-analysis" + })], TargetRow.prototype, "canShowDeltaAnalysis", null); + __decorate([n$1({ + type: Object, + attribute: false + })], TargetRow.prototype, "stateObj", null); + __decorate([n$1({ + type: Object, + attribute: false + })], TargetRow.prototype, "hass", null); + __decorate([n$1({ + type: Array, + attribute: "comparison-windows" + })], TargetRow.prototype, "comparisonWindows", null); + __decorate([n$1({ + type: Boolean, + attribute: false + })], TargetRow.prototype, "computing", null); + __decorate([n$1({ + type: Number, + attribute: false + })], TargetRow.prototype, "computingProgress", null); + __decorate([n$1({ + type: Object, + attribute: false + })], TargetRow.prototype, "computingMethods", null); + __decorate([n$1({ + type: Number, + attribute: false + })], TargetRow.prototype, "rowCount", null); + __decorate([n$1({ + type: Boolean, + attribute: false + })], TargetRow.prototype, "allAnalysisSame", null); + __decorate([n$1({ + type: Boolean, + attribute: "hide-drag-handle" + })], TargetRow.prototype, "hideDragHandle", null); + TargetRow = __decorate([localized()], TargetRow); customElements.define("target-row", TargetRow); //#endregion //#region custom_components/hass_datapoints/src/molecules/target-row-list/target-row-list.styles.ts @@ -19415,6 +20472,7 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/target-row-list/target-row-list.ts + var _TargetRowList, _rows_accessor_storage$1, _states_accessor_storage$1, _hass_accessor_storage$6, _canShowDeltaAnalysis_accessor_storage$1, _comparisonWindows_accessor_storage$1, _computingEntityIds_accessor_storage, _analysisProgress_accessor_storage, _computingMethodsByEn_accessor_storage; var _DURATION_SECONDS = { raw: 0, "5s": 5, @@ -19488,9 +20546,17 @@ } return updates; } - var TargetRowList = @localized() class extends i$2 { + var TargetRowList = (_rows_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _states_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _hass_accessor_storage$6 = /* @__PURE__ */ new WeakMap(), _canShowDeltaAnalysis_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _comparisonWindows_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _computingEntityIds_accessor_storage = /* @__PURE__ */ new WeakMap(), _analysisProgress_accessor_storage = /* @__PURE__ */ new WeakMap(), _computingMethodsByEn_accessor_storage = /* @__PURE__ */ new WeakMap(), _TargetRowList = class TargetRowList extends i$2 { constructor(..._args) { super(..._args); + _classPrivateFieldInitSpec(this, _rows_accessor_storage$1, []); + _classPrivateFieldInitSpec(this, _states_accessor_storage$1, {}); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$6, null); + _classPrivateFieldInitSpec(this, _canShowDeltaAnalysis_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _comparisonWindows_accessor_storage$1, []); + _classPrivateFieldInitSpec(this, _computingEntityIds_accessor_storage, /* @__PURE__ */ new Set()); + _classPrivateFieldInitSpec(this, _analysisProgress_accessor_storage, 0); + _classPrivateFieldInitSpec(this, _computingMethodsByEn_accessor_storage, /* @__PURE__ */ new Map()); _defineProperty( this, /** Index of the row currently being dragged, or null when not dragging. */ @@ -19570,40 +20636,54 @@ this._removeDragCursorStyle(); }); } - @n$1({ type: Array }) accessor rows = []; - /** HA entity states map (entity_id → state object). Passed directly to each `target-row`. */ - @n$1({ - type: Object, - attribute: false - }) accessor states = {}; - /** HA hass object. Required by ha-state-icon inside target-row to resolve entity icons. */ - @n$1({ - type: Object, - attribute: false - }) accessor hass = null; - @n$1({ - type: Boolean, - attribute: "can-show-delta-analysis" - }) accessor canShowDeltaAnalysis = false; - @n$1({ - type: Array, - attribute: false - }) accessor comparisonWindows = []; - /** Set of entity IDs currently being analysed in the worker. */ - @n$1({ - type: Object, - attribute: false - }) accessor computingEntityIds = /* @__PURE__ */ new Set(); - /** Current analysis progress percentage (0–100) shared across all computing rows. */ - @n$1({ - type: Number, - attribute: false - }) accessor analysisProgress = 0; - /** Map of entityId → Set of anomaly method names that are still in-flight in the worker. */ - @n$1({ - type: Object, - attribute: false - }) accessor computingMethodsByEntity = /* @__PURE__ */ new Map(); + get rows() { + return _classPrivateFieldGet2(_rows_accessor_storage$1, this); + } + set rows(value) { + _classPrivateFieldSet2(_rows_accessor_storage$1, this, value); + } + get states() { + return _classPrivateFieldGet2(_states_accessor_storage$1, this); + } + set states(value) { + _classPrivateFieldSet2(_states_accessor_storage$1, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$6, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$6, this, value); + } + get canShowDeltaAnalysis() { + return _classPrivateFieldGet2(_canShowDeltaAnalysis_accessor_storage$1, this); + } + set canShowDeltaAnalysis(value) { + _classPrivateFieldSet2(_canShowDeltaAnalysis_accessor_storage$1, this, value); + } + get comparisonWindows() { + return _classPrivateFieldGet2(_comparisonWindows_accessor_storage$1, this); + } + set comparisonWindows(value) { + _classPrivateFieldSet2(_comparisonWindows_accessor_storage$1, this, value); + } + get computingEntityIds() { + return _classPrivateFieldGet2(_computingEntityIds_accessor_storage, this); + } + set computingEntityIds(value) { + _classPrivateFieldSet2(_computingEntityIds_accessor_storage, this, value); + } + get analysisProgress() { + return _classPrivateFieldGet2(_analysisProgress_accessor_storage, this); + } + set analysisProgress(value) { + _classPrivateFieldSet2(_analysisProgress_accessor_storage, this, value); + } + get computingMethodsByEntity() { + return _classPrivateFieldGet2(_computingMethodsByEn_accessor_storage, this); + } + set computingMethodsByEntity(value) { + _classPrivateFieldSet2(_computingMethodsByEn_accessor_storage, this, value); + } render() { if (!this.rows.length) return b``; const firstAnalysis = JSON.stringify(this.rows[0]?.analysis ?? {}); @@ -19716,8 +20796,37 @@ r.classList.remove("is-drag-over-before", "is-drag-over-after"); }); } - }; - _defineProperty(TargetRowList, "styles", styles$32); + }, _defineProperty(_TargetRowList, "styles", styles$32), _TargetRowList); + __decorate([n$1({ type: Array })], TargetRowList.prototype, "rows", null); + __decorate([n$1({ + type: Object, + attribute: false + })], TargetRowList.prototype, "states", null); + __decorate([n$1({ + type: Object, + attribute: false + })], TargetRowList.prototype, "hass", null); + __decorate([n$1({ + type: Boolean, + attribute: "can-show-delta-analysis" + })], TargetRowList.prototype, "canShowDeltaAnalysis", null); + __decorate([n$1({ + type: Array, + attribute: false + })], TargetRowList.prototype, "comparisonWindows", null); + __decorate([n$1({ + type: Object, + attribute: false + })], TargetRowList.prototype, "computingEntityIds", null); + __decorate([n$1({ + type: Number, + attribute: false + })], TargetRowList.prototype, "analysisProgress", null); + __decorate([n$1({ + type: Object, + attribute: false + })], TargetRowList.prototype, "computingMethodsByEntity", null); + TargetRowList = __decorate([localized()], TargetRowList); customElements.define("target-row-list", TargetRowList); //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sidebar-options.styles.ts @@ -19734,68 +20843,138 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/sidebar-options/sidebar-options.ts + var _datapointScope_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _showIcons_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _showLines_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _showTooltips_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _showHoverGuides_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _showCorrelatedAnomal_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _showDataGaps_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _dataGapThreshold_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _yAxisMode_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _hoverSnapMode_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _anomalyOverlapMode_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _anyAnomaliesEnabled_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _targetsOpen_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _datapointsOpen_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _analysisOpen_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _chartOpen_accessor_storage = /* @__PURE__ */ new WeakMap(); var SidebarOptions = class extends i$2 { - @n$1({ - type: String, - attribute: "datapoint-scope" - }) accessor datapointScope = "linked"; - @n$1({ - type: Boolean, - attribute: "show-icons" - }) accessor showIcons = true; - @n$1({ - type: Boolean, - attribute: "show-lines" - }) accessor showLines = true; - @n$1({ - type: Boolean, - attribute: "show-tooltips" - }) accessor showTooltips = true; - @n$1({ - type: Boolean, - attribute: "show-hover-guides" - }) accessor showHoverGuides = false; - @n$1({ - type: Boolean, - attribute: "show-correlated-anomalies" - }) accessor showCorrelatedAnomalies = false; - @n$1({ - type: Boolean, - attribute: "show-data-gaps" - }) accessor showDataGaps = true; - @n$1({ - type: String, - attribute: "data-gap-threshold" - }) accessor dataGapThreshold = "2h"; - @n$1({ - type: String, - attribute: "y-axis-mode" - }) accessor yAxisMode = "combined"; - @n$1({ - type: String, - attribute: "hover-snap-mode" - }) accessor hoverSnapMode = "follow_series"; - @n$1({ - type: String, - attribute: "anomaly-overlap-mode" - }) accessor anomalyOverlapMode = "all"; - @n$1({ - type: Boolean, - attribute: false - }) accessor anyAnomaliesEnabled = false; - @r$1() accessor targetsOpen = true; - @n$1({ - type: Boolean, - attribute: "datapoints-open" - }) accessor datapointsOpen = true; - @n$1({ - type: Boolean, - attribute: "analysis-open" - }) accessor analysisOpen = true; - @n$1({ - type: Boolean, - attribute: "chart-open" - }) accessor chartOpen = true; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _datapointScope_accessor_storage$1, "linked"); + _classPrivateFieldInitSpec(this, _showIcons_accessor_storage$1, true); + _classPrivateFieldInitSpec(this, _showLines_accessor_storage$1, true); + _classPrivateFieldInitSpec(this, _showTooltips_accessor_storage$1, true); + _classPrivateFieldInitSpec(this, _showHoverGuides_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _showCorrelatedAnomal_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _showDataGaps_accessor_storage$1, true); + _classPrivateFieldInitSpec(this, _dataGapThreshold_accessor_storage$1, "2h"); + _classPrivateFieldInitSpec(this, _yAxisMode_accessor_storage$1, "combined"); + _classPrivateFieldInitSpec(this, _hoverSnapMode_accessor_storage$1, "follow_series"); + _classPrivateFieldInitSpec(this, _anomalyOverlapMode_accessor_storage$1, "all"); + _classPrivateFieldInitSpec(this, _anyAnomaliesEnabled_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _targetsOpen_accessor_storage, true); + _classPrivateFieldInitSpec(this, _datapointsOpen_accessor_storage, true); + _classPrivateFieldInitSpec(this, _analysisOpen_accessor_storage, true); + _classPrivateFieldInitSpec(this, _chartOpen_accessor_storage, true); + } + get datapointScope() { + return _classPrivateFieldGet2(_datapointScope_accessor_storage$1, this); + } + set datapointScope(value) { + _classPrivateFieldSet2(_datapointScope_accessor_storage$1, this, value); + } + get showIcons() { + return _classPrivateFieldGet2(_showIcons_accessor_storage$1, this); + } + set showIcons(value) { + _classPrivateFieldSet2(_showIcons_accessor_storage$1, this, value); + } + get showLines() { + return _classPrivateFieldGet2(_showLines_accessor_storage$1, this); + } + set showLines(value) { + _classPrivateFieldSet2(_showLines_accessor_storage$1, this, value); + } + get showTooltips() { + return _classPrivateFieldGet2(_showTooltips_accessor_storage$1, this); + } + set showTooltips(value) { + _classPrivateFieldSet2(_showTooltips_accessor_storage$1, this, value); + } + get showHoverGuides() { + return _classPrivateFieldGet2(_showHoverGuides_accessor_storage$1, this); + } + set showHoverGuides(value) { + _classPrivateFieldSet2(_showHoverGuides_accessor_storage$1, this, value); + } + get showCorrelatedAnomalies() { + return _classPrivateFieldGet2(_showCorrelatedAnomal_accessor_storage$1, this); + } + set showCorrelatedAnomalies(value) { + _classPrivateFieldSet2(_showCorrelatedAnomal_accessor_storage$1, this, value); + } + get showDataGaps() { + return _classPrivateFieldGet2(_showDataGaps_accessor_storage$1, this); + } + set showDataGaps(value) { + _classPrivateFieldSet2(_showDataGaps_accessor_storage$1, this, value); + } + get dataGapThreshold() { + return _classPrivateFieldGet2(_dataGapThreshold_accessor_storage$1, this); + } + set dataGapThreshold(value) { + _classPrivateFieldSet2(_dataGapThreshold_accessor_storage$1, this, value); + } + get yAxisMode() { + return _classPrivateFieldGet2(_yAxisMode_accessor_storage$1, this); + } + set yAxisMode(value) { + _classPrivateFieldSet2(_yAxisMode_accessor_storage$1, this, value); + } + get hoverSnapMode() { + return _classPrivateFieldGet2(_hoverSnapMode_accessor_storage$1, this); + } + set hoverSnapMode(value) { + _classPrivateFieldSet2(_hoverSnapMode_accessor_storage$1, this, value); + } + get anomalyOverlapMode() { + return _classPrivateFieldGet2(_anomalyOverlapMode_accessor_storage$1, this); + } + set anomalyOverlapMode(value) { + _classPrivateFieldSet2(_anomalyOverlapMode_accessor_storage$1, this, value); + } + get anyAnomaliesEnabled() { + return _classPrivateFieldGet2(_anyAnomaliesEnabled_accessor_storage$1, this); + } + set anyAnomaliesEnabled(value) { + _classPrivateFieldSet2(_anyAnomaliesEnabled_accessor_storage$1, this, value); + } + get targetsOpen() { + return _classPrivateFieldGet2(_targetsOpen_accessor_storage, this); + } + set targetsOpen(value) { + _classPrivateFieldSet2(_targetsOpen_accessor_storage, this, value); + } + get datapointsOpen() { + return _classPrivateFieldGet2(_datapointsOpen_accessor_storage, this); + } + set datapointsOpen(value) { + _classPrivateFieldSet2(_datapointsOpen_accessor_storage, this, value); + } + get analysisOpen() { + return _classPrivateFieldGet2(_analysisOpen_accessor_storage, this); + } + set analysisOpen(value) { + _classPrivateFieldSet2(_analysisOpen_accessor_storage, this, value); + } + get chartOpen() { + return _classPrivateFieldGet2(_chartOpen_accessor_storage, this); + } + set chartOpen(value) { + _classPrivateFieldSet2(_chartOpen_accessor_storage, this, value); + } _onTargetsToggle(e) { this.targetsOpen = e.detail.open; this._emitAccordionChange(); @@ -19864,6 +21043,67 @@ } }; _defineProperty(SidebarOptions, "styles", styles$31); + __decorate([n$1({ + type: String, + attribute: "datapoint-scope" + })], SidebarOptions.prototype, "datapointScope", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-icons" + })], SidebarOptions.prototype, "showIcons", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-lines" + })], SidebarOptions.prototype, "showLines", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-tooltips" + })], SidebarOptions.prototype, "showTooltips", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-hover-guides" + })], SidebarOptions.prototype, "showHoverGuides", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-correlated-anomalies" + })], SidebarOptions.prototype, "showCorrelatedAnomalies", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-data-gaps" + })], SidebarOptions.prototype, "showDataGaps", null); + __decorate([n$1({ + type: String, + attribute: "data-gap-threshold" + })], SidebarOptions.prototype, "dataGapThreshold", null); + __decorate([n$1({ + type: String, + attribute: "y-axis-mode" + })], SidebarOptions.prototype, "yAxisMode", null); + __decorate([n$1({ + type: String, + attribute: "hover-snap-mode" + })], SidebarOptions.prototype, "hoverSnapMode", null); + __decorate([n$1({ + type: String, + attribute: "anomaly-overlap-mode" + })], SidebarOptions.prototype, "anomalyOverlapMode", null); + __decorate([n$1({ + type: Boolean, + attribute: false + })], SidebarOptions.prototype, "anyAnomaliesEnabled", null); + __decorate([r$1()], SidebarOptions.prototype, "targetsOpen", null); + __decorate([n$1({ + type: Boolean, + attribute: "datapoints-open" + })], SidebarOptions.prototype, "datapointsOpen", null); + __decorate([n$1({ + type: Boolean, + attribute: "analysis-open" + })], SidebarOptions.prototype, "analysisOpen", null); + __decorate([n$1({ + type: Boolean, + attribute: "chart-open" + })], SidebarOptions.prototype, "chartOpen", null); customElements.define("sidebar-options", SidebarOptions); //#endregion //#region custom_components/hass_datapoints/src/molecules/collapsed-options-menu/collapsed-options-menu.styles.ts @@ -19977,6 +21217,19 @@ label: "Chart Display" } ]; + var _datapointScope_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _showIcons_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _showLines_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _showTooltips_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _showHoverGuides_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _showCorrelatedAnomal_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _showDataGaps_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _dataGapThreshold_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _yAxisMode_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _hoverSnapMode_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _anomalyOverlapMode_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _anyAnomaliesEnabled_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _activeSection_accessor_storage = /* @__PURE__ */ new WeakMap(); /** * `collapsed-options-menu` renders a two-panel nested options menu for use * in the collapsed sidebar. Level 1 shows section group names; hovering or @@ -19989,21 +21242,99 @@ var CollapsedOptionsMenu = class extends i$2 { constructor(..._args) { super(..._args); + _classPrivateFieldInitSpec(this, _datapointScope_accessor_storage, "linked"); + _classPrivateFieldInitSpec(this, _showIcons_accessor_storage, true); + _classPrivateFieldInitSpec(this, _showLines_accessor_storage, true); + _classPrivateFieldInitSpec(this, _showTooltips_accessor_storage, true); + _classPrivateFieldInitSpec(this, _showHoverGuides_accessor_storage, false); + _classPrivateFieldInitSpec(this, _showCorrelatedAnomal_accessor_storage, false); + _classPrivateFieldInitSpec(this, _showDataGaps_accessor_storage, true); + _classPrivateFieldInitSpec(this, _dataGapThreshold_accessor_storage, "2h"); + _classPrivateFieldInitSpec(this, _yAxisMode_accessor_storage, "combined"); + _classPrivateFieldInitSpec(this, _hoverSnapMode_accessor_storage, "follow_series"); + _classPrivateFieldInitSpec(this, _anomalyOverlapMode_accessor_storage, "all"); + _classPrivateFieldInitSpec(this, _anyAnomaliesEnabled_accessor_storage, false); + _classPrivateFieldInitSpec(this, _activeSection_accessor_storage, null); _defineProperty(this, "_closeTimer", null); } - @n$1({ type: String }) accessor datapointScope = "linked"; - @n$1({ type: Boolean }) accessor showIcons = true; - @n$1({ type: Boolean }) accessor showLines = true; - @n$1({ type: Boolean }) accessor showTooltips = true; - @n$1({ type: Boolean }) accessor showHoverGuides = false; - @n$1({ type: Boolean }) accessor showCorrelatedAnomalies = false; - @n$1({ type: Boolean }) accessor showDataGaps = true; - @n$1({ type: String }) accessor dataGapThreshold = "2h"; - @n$1({ type: String }) accessor yAxisMode = "combined"; - @n$1({ type: String }) accessor hoverSnapMode = "follow_series"; - @n$1({ type: String }) accessor anomalyOverlapMode = "all"; - @n$1({ type: Boolean }) accessor anyAnomaliesEnabled = false; - @r$1() accessor activeSection = null; + get datapointScope() { + return _classPrivateFieldGet2(_datapointScope_accessor_storage, this); + } + set datapointScope(value) { + _classPrivateFieldSet2(_datapointScope_accessor_storage, this, value); + } + get showIcons() { + return _classPrivateFieldGet2(_showIcons_accessor_storage, this); + } + set showIcons(value) { + _classPrivateFieldSet2(_showIcons_accessor_storage, this, value); + } + get showLines() { + return _classPrivateFieldGet2(_showLines_accessor_storage, this); + } + set showLines(value) { + _classPrivateFieldSet2(_showLines_accessor_storage, this, value); + } + get showTooltips() { + return _classPrivateFieldGet2(_showTooltips_accessor_storage, this); + } + set showTooltips(value) { + _classPrivateFieldSet2(_showTooltips_accessor_storage, this, value); + } + get showHoverGuides() { + return _classPrivateFieldGet2(_showHoverGuides_accessor_storage, this); + } + set showHoverGuides(value) { + _classPrivateFieldSet2(_showHoverGuides_accessor_storage, this, value); + } + get showCorrelatedAnomalies() { + return _classPrivateFieldGet2(_showCorrelatedAnomal_accessor_storage, this); + } + set showCorrelatedAnomalies(value) { + _classPrivateFieldSet2(_showCorrelatedAnomal_accessor_storage, this, value); + } + get showDataGaps() { + return _classPrivateFieldGet2(_showDataGaps_accessor_storage, this); + } + set showDataGaps(value) { + _classPrivateFieldSet2(_showDataGaps_accessor_storage, this, value); + } + get dataGapThreshold() { + return _classPrivateFieldGet2(_dataGapThreshold_accessor_storage, this); + } + set dataGapThreshold(value) { + _classPrivateFieldSet2(_dataGapThreshold_accessor_storage, this, value); + } + get yAxisMode() { + return _classPrivateFieldGet2(_yAxisMode_accessor_storage, this); + } + set yAxisMode(value) { + _classPrivateFieldSet2(_yAxisMode_accessor_storage, this, value); + } + get hoverSnapMode() { + return _classPrivateFieldGet2(_hoverSnapMode_accessor_storage, this); + } + set hoverSnapMode(value) { + _classPrivateFieldSet2(_hoverSnapMode_accessor_storage, this, value); + } + get anomalyOverlapMode() { + return _classPrivateFieldGet2(_anomalyOverlapMode_accessor_storage, this); + } + set anomalyOverlapMode(value) { + _classPrivateFieldSet2(_anomalyOverlapMode_accessor_storage, this, value); + } + get anyAnomaliesEnabled() { + return _classPrivateFieldGet2(_anyAnomaliesEnabled_accessor_storage, this); + } + set anyAnomaliesEnabled(value) { + _classPrivateFieldSet2(_anyAnomaliesEnabled_accessor_storage, this, value); + } + get activeSection() { + return _classPrivateFieldGet2(_activeSection_accessor_storage, this); + } + set activeSection(value) { + _classPrivateFieldSet2(_activeSection_accessor_storage, this, value); + } disconnectedCallback() { super.disconnectedCallback(); if (this._closeTimer !== null) { @@ -20101,6 +21432,19 @@ } }; _defineProperty(CollapsedOptionsMenu, "styles", styles$30); + __decorate([n$1({ type: String })], CollapsedOptionsMenu.prototype, "datapointScope", null); + __decorate([n$1({ type: Boolean })], CollapsedOptionsMenu.prototype, "showIcons", null); + __decorate([n$1({ type: Boolean })], CollapsedOptionsMenu.prototype, "showLines", null); + __decorate([n$1({ type: Boolean })], CollapsedOptionsMenu.prototype, "showTooltips", null); + __decorate([n$1({ type: Boolean })], CollapsedOptionsMenu.prototype, "showHoverGuides", null); + __decorate([n$1({ type: Boolean })], CollapsedOptionsMenu.prototype, "showCorrelatedAnomalies", null); + __decorate([n$1({ type: Boolean })], CollapsedOptionsMenu.prototype, "showDataGaps", null); + __decorate([n$1({ type: String })], CollapsedOptionsMenu.prototype, "dataGapThreshold", null); + __decorate([n$1({ type: String })], CollapsedOptionsMenu.prototype, "yAxisMode", null); + __decorate([n$1({ type: String })], CollapsedOptionsMenu.prototype, "hoverSnapMode", null); + __decorate([n$1({ type: String })], CollapsedOptionsMenu.prototype, "anomalyOverlapMode", null); + __decorate([n$1({ type: Boolean })], CollapsedOptionsMenu.prototype, "anyAnomaliesEnabled", null); + __decorate([r$1()], CollapsedOptionsMenu.prototype, "activeSection", null); customElements.define("collapsed-options-menu", CollapsedOptionsMenu); //#endregion //#region node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/class-map.js @@ -20442,6 +21786,13 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/comparison-tab/comparison-tab.ts + var _tabId_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _label_accessor_storage$7 = /* @__PURE__ */ new WeakMap(); + var _detail_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _active_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _previewing_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _loading_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _editable_accessor_storage = /* @__PURE__ */ new WeakMap(); /** * `comparison-tab` renders a single comparison date-window tab in the chart * tab rail, handling active, previewing, loading, and editable states. @@ -20453,23 +21804,58 @@ * @fires dp-tab-delete - `{ tabId: string }` fired when the delete action button is clicked */ var ComparisonTab = class extends i$2 { - /** Unique identifier for this tab; included in all event detail objects. */ - @n$1({ - type: String, - attribute: "tab-id" - }) accessor tabId = ""; - /** Primary label displayed in the tab (e.g. "Selected range" or a user-defined name). */ - @n$1({ type: String }) accessor label = ""; - /** Secondary detail text shown below the label (e.g. formatted date range). */ - @n$1({ type: String }) accessor detail = ""; - /** Whether this tab represents the currently active date window. */ - @n$1({ type: Boolean }) accessor active = false; - /** Whether the chart is previewing this tab's date range on hover/focus. */ - @n$1({ type: Boolean }) accessor previewing = false; - /** Whether this tab's data is currently loading. */ - @n$1({ type: Boolean }) accessor loading = false; - /** Whether edit and delete action buttons are shown. */ - @n$1({ type: Boolean }) accessor editable = false; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _tabId_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _label_accessor_storage$7, ""); + _classPrivateFieldInitSpec(this, _detail_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _active_accessor_storage, false); + _classPrivateFieldInitSpec(this, _previewing_accessor_storage, false); + _classPrivateFieldInitSpec(this, _loading_accessor_storage, false); + _classPrivateFieldInitSpec(this, _editable_accessor_storage, false); + } + get tabId() { + return _classPrivateFieldGet2(_tabId_accessor_storage, this); + } + set tabId(value) { + _classPrivateFieldSet2(_tabId_accessor_storage, this, value); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$7, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$7, this, value); + } + get detail() { + return _classPrivateFieldGet2(_detail_accessor_storage, this); + } + set detail(value) { + _classPrivateFieldSet2(_detail_accessor_storage, this, value); + } + get active() { + return _classPrivateFieldGet2(_active_accessor_storage, this); + } + set active(value) { + _classPrivateFieldSet2(_active_accessor_storage, this, value); + } + get previewing() { + return _classPrivateFieldGet2(_previewing_accessor_storage, this); + } + set previewing(value) { + _classPrivateFieldSet2(_previewing_accessor_storage, this, value); + } + get loading() { + return _classPrivateFieldGet2(_loading_accessor_storage, this); + } + set loading(value) { + _classPrivateFieldSet2(_loading_accessor_storage, this, value); + } + get editable() { + return _classPrivateFieldGet2(_editable_accessor_storage, this); + } + set editable(value) { + _classPrivateFieldSet2(_editable_accessor_storage, this, value); + } _emit(name) { this.dispatchEvent(new CustomEvent(name, { detail: { tabId: this.tabId }, @@ -20560,28 +21946,53 @@ } }; _defineProperty(ComparisonTab, "styles", styles$28); + __decorate([n$1({ + type: String, + attribute: "tab-id" + })], ComparisonTab.prototype, "tabId", null); + __decorate([n$1({ type: String })], ComparisonTab.prototype, "label", null); + __decorate([n$1({ type: String })], ComparisonTab.prototype, "detail", null); + __decorate([n$1({ type: Boolean })], ComparisonTab.prototype, "active", null); + __decorate([n$1({ type: Boolean })], ComparisonTab.prototype, "previewing", null); + __decorate([n$1({ type: Boolean })], ComparisonTab.prototype, "loading", null); + __decorate([n$1({ type: Boolean })], ComparisonTab.prototype, "editable", null); customElements.define("comparison-tab", ComparisonTab); //#endregion //#region custom_components/hass_datapoints/src/molecules/comparison-tab-rail/comparison-tab-rail.ts - var ComparisonTabRail = @localized() class extends i$2 { + var _ComparisonTabRail, _tabs_accessor_storage, _loadingIds_accessor_storage, _hoveredId_accessor_storage, _overflowing_accessor_storage; + var ComparisonTabRail = (_tabs_accessor_storage = /* @__PURE__ */ new WeakMap(), _loadingIds_accessor_storage = /* @__PURE__ */ new WeakMap(), _hoveredId_accessor_storage = /* @__PURE__ */ new WeakMap(), _overflowing_accessor_storage = /* @__PURE__ */ new WeakMap(), _ComparisonTabRail = class ComparisonTabRail extends i$2 { constructor(..._args) { super(..._args); + _classPrivateFieldInitSpec(this, _tabs_accessor_storage, []); + _classPrivateFieldInitSpec(this, _loadingIds_accessor_storage, []); + _classPrivateFieldInitSpec(this, _hoveredId_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _overflowing_accessor_storage, false); _defineProperty(this, "_resizeObserver", void 0); } - /** Array of tab descriptors to render. */ - @n$1({ type: Array }) accessor tabs = []; - /** IDs of tabs that are currently loading data. */ - @n$1({ - type: Array, - attribute: false - }) accessor loadingIds = []; - /** ID of the tab currently being previewed (hovered). */ - @n$1({ - type: String, - attribute: "hovered-id" - }) accessor hoveredId = ""; - /** Whether the rail is overflowing horizontally (collapses "Add" button to icon only). */ - @n$1({ type: Boolean }) accessor overflowing = false; + get tabs() { + return _classPrivateFieldGet2(_tabs_accessor_storage, this); + } + set tabs(value) { + _classPrivateFieldSet2(_tabs_accessor_storage, this, value); + } + get loadingIds() { + return _classPrivateFieldGet2(_loadingIds_accessor_storage, this); + } + set loadingIds(value) { + _classPrivateFieldSet2(_loadingIds_accessor_storage, this, value); + } + get hoveredId() { + return _classPrivateFieldGet2(_hoveredId_accessor_storage, this); + } + set hoveredId(value) { + _classPrivateFieldSet2(_hoveredId_accessor_storage, this, value); + } + get overflowing() { + return _classPrivateFieldGet2(_overflowing_accessor_storage, this); + } + set overflowing(value) { + _classPrivateFieldSet2(_overflowing_accessor_storage, this, value); + } connectedCallback() { super.connectedCallback(); if (this._resizeObserver) return; @@ -20639,8 +22050,18 @@
`; } - }; - _defineProperty(ComparisonTabRail, "styles", styles$29); + }, _defineProperty(_ComparisonTabRail, "styles", styles$29), _ComparisonTabRail); + __decorate([n$1({ type: Array })], ComparisonTabRail.prototype, "tabs", null); + __decorate([n$1({ + type: Array, + attribute: false + })], ComparisonTabRail.prototype, "loadingIds", null); + __decorate([n$1({ + type: String, + attribute: "hovered-id" + })], ComparisonTabRail.prototype, "hoveredId", null); + __decorate([n$1({ type: Boolean })], ComparisonTabRail.prototype, "overflowing", null); + ComparisonTabRail = __decorate([localized()], ComparisonTabRail); customElements.define("comparison-tab-rail", ComparisonTabRail); //#endregion //#region custom_components/hass_datapoints/src/molecules/date-window-dialog/date-window-dialog.styles.ts @@ -21191,6 +22612,9 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/range-handle/range-handle.ts + var _position_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _label_accessor_storage$6 = /* @__PURE__ */ new WeakMap(); + var _live_accessor_storage = /* @__PURE__ */ new WeakMap(); /** * `range-handle` is a circular drag-handle button for a timeline range slider. * @@ -21206,9 +22630,30 @@ * @fires dp-handle-blur - `{}` — handle lost focus */ var RangeHandle = class extends i$2 { - @n$1({ type: Number }) accessor position = 0; - @n$1({ type: String }) accessor label = ""; - @n$1({ type: Boolean }) accessor live = false; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _position_accessor_storage, 0); + _classPrivateFieldInitSpec(this, _label_accessor_storage$6, ""); + _classPrivateFieldInitSpec(this, _live_accessor_storage, false); + } + get position() { + return _classPrivateFieldGet2(_position_accessor_storage, this); + } + set position(value) { + _classPrivateFieldSet2(_position_accessor_storage, this, value); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$6, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$6, this, value); + } + get live() { + return _classPrivateFieldGet2(_live_accessor_storage, this); + } + set live(value) { + _classPrivateFieldSet2(_live_accessor_storage, this, value); + } updated(changed) { if (changed.has("position")) this.style.left = `${this.position}%`; } @@ -21285,19 +22730,65 @@ } }; _defineProperty(RangeHandle, "styles", styles$25); + __decorate([n$1({ type: Number })], RangeHandle.prototype, "position", null); + __decorate([n$1({ type: String })], RangeHandle.prototype, "label", null); + __decorate([n$1({ type: Boolean })], RangeHandle.prototype, "live", null); customElements.define("range-handle", RangeHandle); //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/range-timeline/range-timeline.ts - var RangeTimeline = @localized() class extends i$2 { - @n$1({ type: Object }) accessor startTime = null; - @n$1({ type: Object }) accessor endTime = null; - @n$1({ type: Object }) accessor rangeBounds = null; - @n$1({ type: String }) accessor zoomLevel = "day"; - @n$1({ type: String }) accessor dateSnapping = "auto"; - @n$1({ type: Boolean }) accessor isLiveEdge = false; - @n$1({ type: String }) accessor locale = ""; + var _RangeTimeline, _startTime_accessor_storage$2, _endTime_accessor_storage$2, _rangeBounds_accessor_storage$3, _zoomLevel_accessor_storage$3, _dateSnapping_accessor_storage$3, _isLiveEdge_accessor_storage$2, _locale_accessor_storage$1; + var RangeTimeline = (_startTime_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _endTime_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _rangeBounds_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _zoomLevel_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _dateSnapping_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _isLiveEdge_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _locale_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _RangeTimeline = class RangeTimeline extends i$2 { + get startTime() { + return _classPrivateFieldGet2(_startTime_accessor_storage$2, this); + } + set startTime(value) { + _classPrivateFieldSet2(_startTime_accessor_storage$2, this, value); + } + get endTime() { + return _classPrivateFieldGet2(_endTime_accessor_storage$2, this); + } + set endTime(value) { + _classPrivateFieldSet2(_endTime_accessor_storage$2, this, value); + } + get rangeBounds() { + return _classPrivateFieldGet2(_rangeBounds_accessor_storage$3, this); + } + set rangeBounds(value) { + _classPrivateFieldSet2(_rangeBounds_accessor_storage$3, this, value); + } + get zoomLevel() { + return _classPrivateFieldGet2(_zoomLevel_accessor_storage$3, this); + } + set zoomLevel(value) { + _classPrivateFieldSet2(_zoomLevel_accessor_storage$3, this, value); + } + get dateSnapping() { + return _classPrivateFieldGet2(_dateSnapping_accessor_storage$3, this); + } + set dateSnapping(value) { + _classPrivateFieldSet2(_dateSnapping_accessor_storage$3, this, value); + } + get isLiveEdge() { + return _classPrivateFieldGet2(_isLiveEdge_accessor_storage$2, this); + } + set isLiveEdge(value) { + _classPrivateFieldSet2(_isLiveEdge_accessor_storage$2, this, value); + } + get locale() { + return _classPrivateFieldGet2(_locale_accessor_storage$1, this); + } + set locale(value) { + _classPrivateFieldSet2(_locale_accessor_storage$1, this, value); + } constructor() { super(); + _classPrivateFieldInitSpec(this, _startTime_accessor_storage$2, null); + _classPrivateFieldInitSpec(this, _endTime_accessor_storage$2, null); + _classPrivateFieldInitSpec(this, _rangeBounds_accessor_storage$3, null); + _classPrivateFieldInitSpec(this, _zoomLevel_accessor_storage$3, "day"); + _classPrivateFieldInitSpec(this, _dateSnapping_accessor_storage$3, "auto"); + _classPrivateFieldInitSpec(this, _isLiveEdge_accessor_storage$2, false); + _classPrivateFieldInitSpec(this, _locale_accessor_storage$1, ""); _defineProperty(this, "_draftStartTime", null); _defineProperty(this, "_draftEndTime", null); _defineProperty(this, "_activeRangeHandle", null); @@ -22149,55 +23640,100 @@ })); } } - }; - _defineProperty(RangeTimeline, "styles", styles$26); + }, _defineProperty(_RangeTimeline, "styles", styles$26), _RangeTimeline); + __decorate([n$1({ type: Object })], RangeTimeline.prototype, "startTime", null); + __decorate([n$1({ type: Object })], RangeTimeline.prototype, "endTime", null); + __decorate([n$1({ type: Object })], RangeTimeline.prototype, "rangeBounds", null); + __decorate([n$1({ type: String })], RangeTimeline.prototype, "zoomLevel", null); + __decorate([n$1({ type: String })], RangeTimeline.prototype, "dateSnapping", null); + __decorate([n$1({ type: Boolean })], RangeTimeline.prototype, "isLiveEdge", null); + __decorate([n$1({ type: String })], RangeTimeline.prototype, "locale", null); + RangeTimeline = __decorate([localized()], RangeTimeline); customElements.define("range-timeline", RangeTimeline); //#endregion //#region custom_components/hass_datapoints/src/molecules/date-window-dialog/date-window-dialog.ts - var DateWindowDialog = @localized() class extends i$2 { - /** Whether the dialog is open. */ - @n$1({ type: Boolean }) accessor open = false; - /** Title shown in the dialog header. */ - @n$1({ type: String }) accessor heading = "Add date window"; - /** Current value of the name text field. */ - @n$1({ type: String }) accessor name = ""; - /** Current value of the start datetime-local input (ISO string or datetime-local format). */ - @n$1({ - type: String, - attribute: "start-value" - }) accessor startValue = ""; - /** Current value of the end datetime-local input (ISO string or datetime-local format). */ - @n$1({ - type: String, - attribute: "end-value" - }) accessor endValue = ""; - /** Whether the Delete button is shown (true when editing an existing window). */ - @n$1({ - type: Boolean, - attribute: "show-delete" - }) accessor showDelete = false; - /** Whether the "Use previous range" / "Use next range" shortcut buttons are shown. */ - @n$1({ - type: Boolean, - attribute: "show-shortcuts" - }) accessor showShortcuts = false; - /** Label for the submit button (e.g. "Create date window" or "Save date window"). */ - @n$1({ - type: String, - attribute: "submit-label" - }) accessor submitLabel = "Create date window"; - /** Optional range bounds from the parent panel — used to set the timeline slider context. */ - @n$1({ type: Object }) accessor rangeBounds = null; - /** Effective zoom level for the timeline slider (already resolved from "auto"). */ - @n$1({ - type: String, - attribute: "zoom-level" - }) accessor zoomLevel = "auto"; - /** Date snapping mode passed to the timeline slider. */ - @n$1({ - type: String, - attribute: "date-snapping" - }) accessor dateSnapping = "hour"; + var _DateWindowDialog, _open_accessor_storage$1, _heading_accessor_storage, _name_accessor_storage$1, _startValue_accessor_storage, _endValue_accessor_storage, _showDelete_accessor_storage, _showShortcuts_accessor_storage, _submitLabel_accessor_storage, _rangeBounds_accessor_storage$2, _zoomLevel_accessor_storage$2, _dateSnapping_accessor_storage$2; + var DateWindowDialog = (_open_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _heading_accessor_storage = /* @__PURE__ */ new WeakMap(), _name_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _startValue_accessor_storage = /* @__PURE__ */ new WeakMap(), _endValue_accessor_storage = /* @__PURE__ */ new WeakMap(), _showDelete_accessor_storage = /* @__PURE__ */ new WeakMap(), _showShortcuts_accessor_storage = /* @__PURE__ */ new WeakMap(), _submitLabel_accessor_storage = /* @__PURE__ */ new WeakMap(), _rangeBounds_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _zoomLevel_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _dateSnapping_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _DateWindowDialog = class DateWindowDialog extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _open_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _heading_accessor_storage, "Add date window"); + _classPrivateFieldInitSpec(this, _name_accessor_storage$1, ""); + _classPrivateFieldInitSpec(this, _startValue_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _endValue_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _showDelete_accessor_storage, false); + _classPrivateFieldInitSpec(this, _showShortcuts_accessor_storage, false); + _classPrivateFieldInitSpec(this, _submitLabel_accessor_storage, "Create date window"); + _classPrivateFieldInitSpec(this, _rangeBounds_accessor_storage$2, null); + _classPrivateFieldInitSpec(this, _zoomLevel_accessor_storage$2, "auto"); + _classPrivateFieldInitSpec(this, _dateSnapping_accessor_storage$2, "hour"); + } + get open() { + return _classPrivateFieldGet2(_open_accessor_storage$1, this); + } + set open(value) { + _classPrivateFieldSet2(_open_accessor_storage$1, this, value); + } + get heading() { + return _classPrivateFieldGet2(_heading_accessor_storage, this); + } + set heading(value) { + _classPrivateFieldSet2(_heading_accessor_storage, this, value); + } + get name() { + return _classPrivateFieldGet2(_name_accessor_storage$1, this); + } + set name(value) { + _classPrivateFieldSet2(_name_accessor_storage$1, this, value); + } + get startValue() { + return _classPrivateFieldGet2(_startValue_accessor_storage, this); + } + set startValue(value) { + _classPrivateFieldSet2(_startValue_accessor_storage, this, value); + } + get endValue() { + return _classPrivateFieldGet2(_endValue_accessor_storage, this); + } + set endValue(value) { + _classPrivateFieldSet2(_endValue_accessor_storage, this, value); + } + get showDelete() { + return _classPrivateFieldGet2(_showDelete_accessor_storage, this); + } + set showDelete(value) { + _classPrivateFieldSet2(_showDelete_accessor_storage, this, value); + } + get showShortcuts() { + return _classPrivateFieldGet2(_showShortcuts_accessor_storage, this); + } + set showShortcuts(value) { + _classPrivateFieldSet2(_showShortcuts_accessor_storage, this, value); + } + get submitLabel() { + return _classPrivateFieldGet2(_submitLabel_accessor_storage, this); + } + set submitLabel(value) { + _classPrivateFieldSet2(_submitLabel_accessor_storage, this, value); + } + get rangeBounds() { + return _classPrivateFieldGet2(_rangeBounds_accessor_storage$2, this); + } + set rangeBounds(value) { + _classPrivateFieldSet2(_rangeBounds_accessor_storage$2, this, value); + } + get zoomLevel() { + return _classPrivateFieldGet2(_zoomLevel_accessor_storage$2, this); + } + set zoomLevel(value) { + _classPrivateFieldSet2(_zoomLevel_accessor_storage$2, this, value); + } + get dateSnapping() { + return _classPrivateFieldGet2(_dateSnapping_accessor_storage$2, this); + } + set dateSnapping(value) { + _classPrivateFieldSet2(_dateSnapping_accessor_storage$2, this, value); + } /** Shake the dialog — call this when the parent detects a validation error. */ shake() { const dialog = this.shadowRoot?.querySelector("ha-dialog"); @@ -22374,8 +23910,40 @@ `; } - }; - _defineProperty(DateWindowDialog, "styles", styles$27); + }, _defineProperty(_DateWindowDialog, "styles", styles$27), _DateWindowDialog); + __decorate([n$1({ type: Boolean })], DateWindowDialog.prototype, "open", null); + __decorate([n$1({ type: String })], DateWindowDialog.prototype, "heading", null); + __decorate([n$1({ type: String })], DateWindowDialog.prototype, "name", null); + __decorate([n$1({ + type: String, + attribute: "start-value" + })], DateWindowDialog.prototype, "startValue", null); + __decorate([n$1({ + type: String, + attribute: "end-value" + })], DateWindowDialog.prototype, "endValue", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-delete" + })], DateWindowDialog.prototype, "showDelete", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-shortcuts" + })], DateWindowDialog.prototype, "showShortcuts", null); + __decorate([n$1({ + type: String, + attribute: "submit-label" + })], DateWindowDialog.prototype, "submitLabel", null); + __decorate([n$1({ type: Object })], DateWindowDialog.prototype, "rangeBounds", null); + __decorate([n$1({ + type: String, + attribute: "zoom-level" + })], DateWindowDialog.prototype, "zoomLevel", null); + __decorate([n$1({ + type: String, + attribute: "date-snapping" + })], DateWindowDialog.prototype, "dateSnapping", null); + DateWindowDialog = __decorate([localized()], DateWindowDialog); customElements.define("date-window-dialog", DateWindowDialog); //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/resizable-panes/resizable-panes.styles.ts @@ -22509,6 +24077,11 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/resizable-panes/resizable-panes.ts + var _direction_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _ratio_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _min_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _max_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _secondHidden_accessor_storage = /* @__PURE__ */ new WeakMap(); /** * `resizable-panes` is a two-pane layout atom with a draggable splitter. * @@ -22525,6 +24098,11 @@ var ResizablePanes = class extends i$2 { constructor(..._args) { super(..._args); + _classPrivateFieldInitSpec(this, _direction_accessor_storage, "vertical"); + _classPrivateFieldInitSpec(this, _ratio_accessor_storage, .5); + _classPrivateFieldInitSpec(this, _min_accessor_storage, .25); + _classPrivateFieldInitSpec(this, _max_accessor_storage, .75); + _classPrivateFieldInitSpec(this, _secondHidden_accessor_storage, false); _defineProperty(this, "_pointerId", null); _defineProperty(this, "_splitterEl", null); _defineProperty(this, "_onPointerDown", (ev) => { @@ -22569,23 +24147,36 @@ })); }); } - /** Layout direction: "vertical" (default) or "horizontal". */ - @n$1({ - type: String, - reflect: true - }) accessor direction = "vertical"; - /** Fraction (0..1) of total space given to the first pane. */ - @n$1({ type: Number }) accessor ratio = .5; - /** Minimum allowed ratio (0..1). */ - @n$1({ type: Number }) accessor min = .25; - /** Maximum allowed ratio (0..1). */ - @n$1({ type: Number }) accessor max = .75; - /** When true the splitter and second pane are hidden; the first pane fills all space. */ - @n$1({ - type: Boolean, - attribute: "second-hidden", - reflect: true - }) accessor secondHidden = false; + get direction() { + return _classPrivateFieldGet2(_direction_accessor_storage, this); + } + set direction(value) { + _classPrivateFieldSet2(_direction_accessor_storage, this, value); + } + get ratio() { + return _classPrivateFieldGet2(_ratio_accessor_storage, this); + } + set ratio(value) { + _classPrivateFieldSet2(_ratio_accessor_storage, this, value); + } + get min() { + return _classPrivateFieldGet2(_min_accessor_storage, this); + } + set min(value) { + _classPrivateFieldSet2(_min_accessor_storage, this, value); + } + get max() { + return _classPrivateFieldGet2(_max_accessor_storage, this); + } + set max(value) { + _classPrivateFieldSet2(_max_accessor_storage, this, value); + } + get secondHidden() { + return _classPrivateFieldGet2(_secondHidden_accessor_storage, this); + } + set secondHidden(value) { + _classPrivateFieldSet2(_secondHidden_accessor_storage, this, value); + } firstUpdated() { this._splitterEl = this.shadowRoot?.querySelector(".pane-splitter") ?? null; this._applyRatio(); @@ -22622,6 +24213,18 @@ } }; _defineProperty(ResizablePanes, "styles", styles$24); + __decorate([n$1({ + type: String, + reflect: true + })], ResizablePanes.prototype, "direction", null); + __decorate([n$1({ type: Number })], ResizablePanes.prototype, "ratio", null); + __decorate([n$1({ type: Number })], ResizablePanes.prototype, "min", null); + __decorate([n$1({ type: Number })], ResizablePanes.prototype, "max", null); + __decorate([n$1({ + type: Boolean, + attribute: "second-hidden", + reflect: true + })], ResizablePanes.prototype, "secondHidden", null); customElements.define("resizable-panes", ResizablePanes); //#endregion //#region custom_components/hass_datapoints/src/molecules/history-chart/history-chart.ts @@ -23123,6 +24726,7 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/floating-menu/floating-menu.ts + var _open_accessor_storage = /* @__PURE__ */ new WeakMap(); /** * `floating-menu` renders a positioned floating overlay panel. * @@ -23135,11 +24739,16 @@ * @fires dp-menu-close - `{}` fired when the user clicks outside the menu while it is open */ var FloatingMenu = class extends i$2 { - /** Whether the menu is currently visible. */ - @n$1({ - type: Boolean, - reflect: true - }) accessor open = false; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _open_accessor_storage, false); + } + get open() { + return _classPrivateFieldGet2(_open_accessor_storage, this); + } + set open(value) { + _classPrivateFieldSet2(_open_accessor_storage, this, value); + } connectedCallback() { super.connectedCallback(); this._onPointerDown = this._onPointerDown.bind(this); @@ -23166,6 +24775,10 @@ } }; _defineProperty(FloatingMenu, "styles", styles$22); + __decorate([n$1({ + type: Boolean, + reflect: true + })], FloatingMenu.prototype, "open", null); customElements.define("floating-menu", FloatingMenu); //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/page-menu-item/page-menu-item.styles.ts @@ -23212,10 +24825,34 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/page-menu-item/page-menu-item.ts + var _icon_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _label_accessor_storage$5 = /* @__PURE__ */ new WeakMap(); + var _disabled_accessor_storage = /* @__PURE__ */ new WeakMap(); var PageMenuItem = class extends i$2 { - @n$1({ type: String }) accessor icon = ""; - @n$1({ type: String }) accessor label = ""; - @n$1({ type: Boolean }) accessor disabled = false; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _icon_accessor_storage$1, ""); + _classPrivateFieldInitSpec(this, _label_accessor_storage$5, ""); + _classPrivateFieldInitSpec(this, _disabled_accessor_storage, false); + } + get icon() { + return _classPrivateFieldGet2(_icon_accessor_storage$1, this); + } + set icon(value) { + _classPrivateFieldSet2(_icon_accessor_storage$1, this, value); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$5, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$5, this, value); + } + get disabled() { + return _classPrivateFieldGet2(_disabled_accessor_storage, this); + } + set disabled(value) { + _classPrivateFieldSet2(_disabled_accessor_storage, this, value); + } _onClick() { if (this.disabled) return; this.dispatchEvent(new CustomEvent("dp-menu-action", { @@ -23233,33 +24870,59 @@ } }; _defineProperty(PageMenuItem, "styles", styles$21); + __decorate([n$1({ type: String })], PageMenuItem.prototype, "icon", null); + __decorate([n$1({ type: String })], PageMenuItem.prototype, "label", null); + __decorate([n$1({ type: Boolean })], PageMenuItem.prototype, "disabled", null); customElements.define("page-menu-item", PageMenuItem); //#endregion //#region custom_components/hass_datapoints/src/panels/datapoints/components/panel-shell/panel-shell.ts - var PanelShell = @localized() class extends i$2 { - /** Home Assistant object — forwarded to ha-menu-button and ha-top-app-bar-fixed. */ - @n$1({ type: Object }) accessor hass = null; - /** Narrow layout flag — forwarded to ha-menu-button and ha-top-app-bar-fixed. */ - @n$1({ type: Boolean }) accessor narrow = false; - /** Whether the sidebar is currently in collapsed state. Controlled by the parent. */ - @n$1({ - type: Boolean, - attribute: "sidebar-collapsed" - }) accessor sidebarCollapsed = false; - /** Whether a saved page state exists — controls visibility of Restore/Clear menu items. */ - @n$1({ - type: Boolean, - attribute: "has-saved-state" - }) accessor hasSavedState = false; - /** - * Current layout mode. Used to determine scrim visibility. - * "desktop" | "tablet" | "mobile" - */ - @n$1({ - type: String, - attribute: "layout-mode" - }) accessor layoutMode = "desktop"; - @r$1() accessor _pageMenuOpen = false; + var _PanelShell, _hass_accessor_storage$5, _narrow_accessor_storage, _sidebarCollapsed_accessor_storage$2, _hasSavedState_accessor_storage, _layoutMode_accessor_storage, _pageMenuOpen_accessor_storage; + var PanelShell = (_hass_accessor_storage$5 = /* @__PURE__ */ new WeakMap(), _narrow_accessor_storage = /* @__PURE__ */ new WeakMap(), _sidebarCollapsed_accessor_storage$2 = /* @__PURE__ */ new WeakMap(), _hasSavedState_accessor_storage = /* @__PURE__ */ new WeakMap(), _layoutMode_accessor_storage = /* @__PURE__ */ new WeakMap(), _pageMenuOpen_accessor_storage = /* @__PURE__ */ new WeakMap(), _PanelShell = class PanelShell extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$5, null); + _classPrivateFieldInitSpec(this, _narrow_accessor_storage, false); + _classPrivateFieldInitSpec(this, _sidebarCollapsed_accessor_storage$2, false); + _classPrivateFieldInitSpec(this, _hasSavedState_accessor_storage, false); + _classPrivateFieldInitSpec(this, _layoutMode_accessor_storage, "desktop"); + _classPrivateFieldInitSpec(this, _pageMenuOpen_accessor_storage, false); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$5, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$5, this, value); + } + get narrow() { + return _classPrivateFieldGet2(_narrow_accessor_storage, this); + } + set narrow(value) { + _classPrivateFieldSet2(_narrow_accessor_storage, this, value); + } + get sidebarCollapsed() { + return _classPrivateFieldGet2(_sidebarCollapsed_accessor_storage$2, this); + } + set sidebarCollapsed(value) { + _classPrivateFieldSet2(_sidebarCollapsed_accessor_storage$2, this, value); + } + get hasSavedState() { + return _classPrivateFieldGet2(_hasSavedState_accessor_storage, this); + } + set hasSavedState(value) { + _classPrivateFieldSet2(_hasSavedState_accessor_storage, this, value); + } + get layoutMode() { + return _classPrivateFieldGet2(_layoutMode_accessor_storage, this); + } + set layoutMode(value) { + _classPrivateFieldSet2(_layoutMode_accessor_storage, this, value); + } + get _pageMenuOpen() { + return _classPrivateFieldGet2(_pageMenuOpen_accessor_storage, this); + } + set _pageMenuOpen(value) { + _classPrivateFieldSet2(_pageMenuOpen_accessor_storage, this, value); + } /** Returns the `#page-content` element for layout height calculations. */ getPageContentEl() { return this.shadowRoot?.querySelector("#page-content") ?? null; @@ -23470,8 +25133,23 @@ `; } - }; - _defineProperty(PanelShell, "styles", styles$23); + }, _defineProperty(_PanelShell, "styles", styles$23), _PanelShell); + __decorate([n$1({ type: Object })], PanelShell.prototype, "hass", null); + __decorate([n$1({ type: Boolean })], PanelShell.prototype, "narrow", null); + __decorate([n$1({ + type: Boolean, + attribute: "sidebar-collapsed" + })], PanelShell.prototype, "sidebarCollapsed", null); + __decorate([n$1({ + type: Boolean, + attribute: "has-saved-state" + })], PanelShell.prototype, "hasSavedState", null); + __decorate([n$1({ + type: String, + attribute: "layout-mode" + })], PanelShell.prototype, "layoutMode", null); + __decorate([r$1()], PanelShell.prototype, "_pageMenuOpen", null); + PanelShell = __decorate([localized()], PanelShell); customElements.define("panel-shell", PanelShell); //#endregion //#region custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.styles.ts @@ -23688,27 +25366,60 @@ `; //#endregion //#region custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.ts - var HistoryTargets = @localized() class extends i$2 { - /** Series row descriptors to display. */ - @n$1({ type: Array }) accessor rows = []; - /** HA states map — passed to the target row list for icon/label resolution. */ - @n$1({ type: Object }) accessor states = {}; - /** HA connection object — passed to sub-elements. */ - @n$1({ type: Object }) accessor hass = null; - /** Available comparison windows for delta analysis. */ - @n$1({ type: Array }) accessor comparisonWindows = []; - /** Whether the delta analysis option should be offered in row analysis panels. */ - @n$1({ - type: Boolean, - attribute: "can-show-delta-analysis" - }) accessor canShowDeltaAnalysis = false; - /** When true, the sidebar is collapsed — show icon summary instead of full list. */ - @n$1({ - type: Boolean, - attribute: "sidebar-collapsed", - reflect: true - }) accessor sidebarCollapsed = false; - @r$1() accessor _collapsedSummaryKey = ""; + var _HistoryTargets, _rows_accessor_storage, _states_accessor_storage, _hass_accessor_storage$4, _comparisonWindows_accessor_storage, _canShowDeltaAnalysis_accessor_storage, _sidebarCollapsed_accessor_storage$1, _collapsedSummaryKey_accessor_storage; + var HistoryTargets = (_rows_accessor_storage = /* @__PURE__ */ new WeakMap(), _states_accessor_storage = /* @__PURE__ */ new WeakMap(), _hass_accessor_storage$4 = /* @__PURE__ */ new WeakMap(), _comparisonWindows_accessor_storage = /* @__PURE__ */ new WeakMap(), _canShowDeltaAnalysis_accessor_storage = /* @__PURE__ */ new WeakMap(), _sidebarCollapsed_accessor_storage$1 = /* @__PURE__ */ new WeakMap(), _collapsedSummaryKey_accessor_storage = /* @__PURE__ */ new WeakMap(), _HistoryTargets = class HistoryTargets extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _rows_accessor_storage, []); + _classPrivateFieldInitSpec(this, _states_accessor_storage, {}); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$4, null); + _classPrivateFieldInitSpec(this, _comparisonWindows_accessor_storage, []); + _classPrivateFieldInitSpec(this, _canShowDeltaAnalysis_accessor_storage, false); + _classPrivateFieldInitSpec(this, _sidebarCollapsed_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _collapsedSummaryKey_accessor_storage, ""); + } + get rows() { + return _classPrivateFieldGet2(_rows_accessor_storage, this); + } + set rows(value) { + _classPrivateFieldSet2(_rows_accessor_storage, this, value); + } + get states() { + return _classPrivateFieldGet2(_states_accessor_storage, this); + } + set states(value) { + _classPrivateFieldSet2(_states_accessor_storage, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$4, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$4, this, value); + } + get comparisonWindows() { + return _classPrivateFieldGet2(_comparisonWindows_accessor_storage, this); + } + set comparisonWindows(value) { + _classPrivateFieldSet2(_comparisonWindows_accessor_storage, this, value); + } + get canShowDeltaAnalysis() { + return _classPrivateFieldGet2(_canShowDeltaAnalysis_accessor_storage, this); + } + set canShowDeltaAnalysis(value) { + _classPrivateFieldSet2(_canShowDeltaAnalysis_accessor_storage, this, value); + } + get sidebarCollapsed() { + return _classPrivateFieldGet2(_sidebarCollapsed_accessor_storage$1, this); + } + set sidebarCollapsed(value) { + _classPrivateFieldSet2(_sidebarCollapsed_accessor_storage$1, this, value); + } + get _collapsedSummaryKey() { + return _classPrivateFieldGet2(_collapsedSummaryKey_accessor_storage, this); + } + set _collapsedSummaryKey(value) { + _classPrivateFieldSet2(_collapsedSummaryKey_accessor_storage, this, value); + } _emit(name, detail = {}) { this.dispatchEvent(new CustomEvent(name, { detail, @@ -23814,8 +25525,22 @@
`; } - }; - _defineProperty(HistoryTargets, "styles", styles$20); + }, _defineProperty(_HistoryTargets, "styles", styles$20), _HistoryTargets); + __decorate([n$1({ type: Array })], HistoryTargets.prototype, "rows", null); + __decorate([n$1({ type: Object })], HistoryTargets.prototype, "states", null); + __decorate([n$1({ type: Object })], HistoryTargets.prototype, "hass", null); + __decorate([n$1({ type: Array })], HistoryTargets.prototype, "comparisonWindows", null); + __decorate([n$1({ + type: Boolean, + attribute: "can-show-delta-analysis" + })], HistoryTargets.prototype, "canShowDeltaAnalysis", null); + __decorate([n$1({ + type: Boolean, + attribute: "sidebar-collapsed", + reflect: true + })], HistoryTargets.prototype, "sidebarCollapsed", null); + __decorate([r$1()], HistoryTargets.prototype, "_collapsedSummaryKey", null); + HistoryTargets = __decorate([localized()], HistoryTargets); customElements.define("history-targets", HistoryTargets); //#endregion //#region custom_components/hass_datapoints/src/panels/datapoints/components/range-toolbar/range-toolbar.styles.ts @@ -24265,6 +25990,20 @@ `; //#endregion //#region custom_components/hass_datapoints/src/molecules/panel-timeline/panel-timeline.ts + var _startTime_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _endTime_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _rangeBounds_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _zoomLevel_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _dateSnapping_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _isLiveEdge_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _locale_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _hoveredPeriodRange_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _comparisonPreview_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _zoomRange_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _zoomWindowRange_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _chartHoverTimeMs_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _chartHoverWindowTime_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _events_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); /** * `panel-timeline` is a panel-history-specific wrapper around `range-timeline`. * @@ -24284,6 +26023,20 @@ var PanelTimeline = class extends i$2 { constructor(..._args) { super(..._args); + _classPrivateFieldInitSpec(this, _startTime_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _endTime_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _rangeBounds_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _zoomLevel_accessor_storage$1, "day"); + _classPrivateFieldInitSpec(this, _dateSnapping_accessor_storage$1, "auto"); + _classPrivateFieldInitSpec(this, _isLiveEdge_accessor_storage$1, false); + _classPrivateFieldInitSpec(this, _locale_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _hoveredPeriodRange_accessor_storage, null); + _classPrivateFieldInitSpec(this, _comparisonPreview_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _zoomRange_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _zoomWindowRange_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _chartHoverTimeMs_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _chartHoverWindowTime_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _events_accessor_storage$1, []); _defineProperty(this, "_rangeHoverPreviewEl", null); _defineProperty(this, "_rangeComparisonPreviewEl", null); _defineProperty(this, "_rangeZoomHighlightEl", null); @@ -24294,20 +26047,90 @@ _defineProperty(this, "_liveZoomRange", void 0); _defineProperty(this, "_liveZoomWindowRange", void 0); } - @n$1({ type: Object }) accessor startTime = null; - @n$1({ type: Object }) accessor endTime = null; - @n$1({ type: Object }) accessor rangeBounds = null; - @n$1({ type: String }) accessor zoomLevel = "day"; - @n$1({ type: String }) accessor dateSnapping = "auto"; - @n$1({ type: Boolean }) accessor isLiveEdge = false; - @n$1({ type: String }) accessor locale = ""; - @r$1() accessor hoveredPeriodRange = null; - @n$1({ type: Object }) accessor comparisonPreview = null; - @n$1({ type: Object }) accessor zoomRange = null; - @n$1({ type: Object }) accessor zoomWindowRange = null; - @n$1({ type: Number }) accessor chartHoverTimeMs = null; - @n$1({ type: Number }) accessor chartHoverWindowTimeMs = null; - @n$1({ type: Array }) accessor events = []; + get startTime() { + return _classPrivateFieldGet2(_startTime_accessor_storage$1, this); + } + set startTime(value) { + _classPrivateFieldSet2(_startTime_accessor_storage$1, this, value); + } + get endTime() { + return _classPrivateFieldGet2(_endTime_accessor_storage$1, this); + } + set endTime(value) { + _classPrivateFieldSet2(_endTime_accessor_storage$1, this, value); + } + get rangeBounds() { + return _classPrivateFieldGet2(_rangeBounds_accessor_storage$1, this); + } + set rangeBounds(value) { + _classPrivateFieldSet2(_rangeBounds_accessor_storage$1, this, value); + } + get zoomLevel() { + return _classPrivateFieldGet2(_zoomLevel_accessor_storage$1, this); + } + set zoomLevel(value) { + _classPrivateFieldSet2(_zoomLevel_accessor_storage$1, this, value); + } + get dateSnapping() { + return _classPrivateFieldGet2(_dateSnapping_accessor_storage$1, this); + } + set dateSnapping(value) { + _classPrivateFieldSet2(_dateSnapping_accessor_storage$1, this, value); + } + get isLiveEdge() { + return _classPrivateFieldGet2(_isLiveEdge_accessor_storage$1, this); + } + set isLiveEdge(value) { + _classPrivateFieldSet2(_isLiveEdge_accessor_storage$1, this, value); + } + get locale() { + return _classPrivateFieldGet2(_locale_accessor_storage, this); + } + set locale(value) { + _classPrivateFieldSet2(_locale_accessor_storage, this, value); + } + get hoveredPeriodRange() { + return _classPrivateFieldGet2(_hoveredPeriodRange_accessor_storage, this); + } + set hoveredPeriodRange(value) { + _classPrivateFieldSet2(_hoveredPeriodRange_accessor_storage, this, value); + } + get comparisonPreview() { + return _classPrivateFieldGet2(_comparisonPreview_accessor_storage$1, this); + } + set comparisonPreview(value) { + _classPrivateFieldSet2(_comparisonPreview_accessor_storage$1, this, value); + } + get zoomRange() { + return _classPrivateFieldGet2(_zoomRange_accessor_storage$1, this); + } + set zoomRange(value) { + _classPrivateFieldSet2(_zoomRange_accessor_storage$1, this, value); + } + get zoomWindowRange() { + return _classPrivateFieldGet2(_zoomWindowRange_accessor_storage$1, this); + } + set zoomWindowRange(value) { + _classPrivateFieldSet2(_zoomWindowRange_accessor_storage$1, this, value); + } + get chartHoverTimeMs() { + return _classPrivateFieldGet2(_chartHoverTimeMs_accessor_storage$1, this); + } + set chartHoverTimeMs(value) { + _classPrivateFieldSet2(_chartHoverTimeMs_accessor_storage$1, this, value); + } + get chartHoverWindowTimeMs() { + return _classPrivateFieldGet2(_chartHoverWindowTime_accessor_storage$1, this); + } + set chartHoverWindowTimeMs(value) { + _classPrivateFieldSet2(_chartHoverWindowTime_accessor_storage$1, this, value); + } + get events() { + return _classPrivateFieldGet2(_events_accessor_storage$1, this); + } + set events(value) { + _classPrivateFieldSet2(_events_accessor_storage$1, this, value); + } firstUpdated() { const sr = this.shadowRoot; this._rangeHoverPreviewEl = sr.getElementById("range-hover-preview"); @@ -24479,6 +26302,20 @@ } }; _defineProperty(PanelTimeline, "styles", styles$18); + __decorate([n$1({ type: Object })], PanelTimeline.prototype, "startTime", null); + __decorate([n$1({ type: Object })], PanelTimeline.prototype, "endTime", null); + __decorate([n$1({ type: Object })], PanelTimeline.prototype, "rangeBounds", null); + __decorate([n$1({ type: String })], PanelTimeline.prototype, "zoomLevel", null); + __decorate([n$1({ type: String })], PanelTimeline.prototype, "dateSnapping", null); + __decorate([n$1({ type: Boolean })], PanelTimeline.prototype, "isLiveEdge", null); + __decorate([n$1({ type: String })], PanelTimeline.prototype, "locale", null); + __decorate([r$1()], PanelTimeline.prototype, "hoveredPeriodRange", null); + __decorate([n$1({ type: Object })], PanelTimeline.prototype, "comparisonPreview", null); + __decorate([n$1({ type: Object })], PanelTimeline.prototype, "zoomRange", null); + __decorate([n$1({ type: Object })], PanelTimeline.prototype, "zoomWindowRange", null); + __decorate([n$1({ type: Number })], PanelTimeline.prototype, "chartHoverTimeMs", null); + __decorate([n$1({ type: Number })], PanelTimeline.prototype, "chartHoverWindowTimeMs", null); + __decorate([n$1({ type: Array })], PanelTimeline.prototype, "events", null); customElements.define("panel-timeline", PanelTimeline); //#endregion //#region custom_components/hass_datapoints/src/atoms/form/date-time-input/date-time-input.styles.ts @@ -24511,9 +26348,26 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/form/date-time-input/date-time-input.ts + var _value_accessor_storage$4 = /* @__PURE__ */ new WeakMap(); + var _label_accessor_storage$4 = /* @__PURE__ */ new WeakMap(); var DateTimeInput = class extends i$2 { - @n$1({ type: String }) accessor value = ""; - @n$1({ type: String }) accessor label = ""; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _value_accessor_storage$4, ""); + _classPrivateFieldInitSpec(this, _label_accessor_storage$4, ""); + } + get value() { + return _classPrivateFieldGet2(_value_accessor_storage$4, this); + } + set value(value) { + _classPrivateFieldSet2(_value_accessor_storage$4, this, value); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$4, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$4, this, value); + } _onChange(e) { this.dispatchEvent(new CustomEvent("dp-datetime-change", { detail: { value: e.target.value }, @@ -24533,71 +26387,135 @@ } }; _defineProperty(DateTimeInput, "styles", styles$17); + __decorate([n$1({ type: String })], DateTimeInput.prototype, "value", null); + __decorate([n$1({ type: String })], DateTimeInput.prototype, "label", null); customElements.define("date-time-input", DateTimeInput); //#endregion //#region custom_components/hass_datapoints/src/panels/datapoints/components/range-toolbar/range-toolbar.ts - var RangeToolbar = @localized() class extends i$2 { - /** Home Assistant instance forwarded to the date picker. */ - @n$1({ attribute: false }) accessor hass = null; - /** Current chart start time. */ - @n$1({ type: Object }) accessor startTime = null; - /** Current chart end time. */ - @n$1({ type: Object }) accessor endTime = null; - /** Timeline bounds — passed to panel-timeline. */ - @n$1({ type: Object }) accessor rangeBounds = null; - /** Current zoom level value (e.g. "auto", "day", "week_expanded"). */ - @n$1({ - type: String, - attribute: "zoom-level" - }) accessor zoomLevel = "auto"; - /** Current date snapping mode (e.g. "hour", "day"). */ - @n$1({ - type: String, - attribute: "date-snapping" - }) accessor dateSnapping = "hour"; - /** Whether the sidebar is collapsed — used to set the sidebar toggle icon direction. */ - @n$1({ - type: Boolean, - attribute: "sidebar-collapsed" - }) accessor sidebarCollapsed = false; - /** Whether the current range is at the live edge. */ - @n$1({ - type: Boolean, - attribute: "is-live-edge" - }) accessor isLiveEdge = false; - /** Timeline event markers. */ - @n$1({ - type: Array, - attribute: false - }) accessor timelineEvents = []; - /** Comparison overlay range in timeline coordinates. */ - @n$1({ - type: Object, - attribute: false - }) accessor comparisonPreview = null; - /** Main chart zoom highlight range. */ - @n$1({ - type: Object, - attribute: false - }) accessor zoomRange = null; - /** Comparison window zoom highlight range. */ - @n$1({ - type: Object, - attribute: false - }) accessor zoomWindowRange = null; - /** Chart hover line timestamp. */ - @n$1({ - type: Number, - attribute: false - }) accessor chartHoverTimeMs = null; - /** Comparison window hover line timestamp. */ - @n$1({ - type: Number, - attribute: false - }) accessor chartHoverWindowTimeMs = null; - @r$1() accessor _optionsView = "root"; - @r$1() accessor _optionsOpen = false; - @r$1() accessor _pickerOpen = false; + var _RangeToolbar, _hass_accessor_storage$3, _startTime_accessor_storage, _endTime_accessor_storage, _rangeBounds_accessor_storage, _zoomLevel_accessor_storage, _dateSnapping_accessor_storage, _sidebarCollapsed_accessor_storage, _isLiveEdge_accessor_storage, _timelineEvents_accessor_storage, _comparisonPreview_accessor_storage, _zoomRange_accessor_storage, _zoomWindowRange_accessor_storage, _chartHoverTimeMs_accessor_storage, _chartHoverWindowTime_accessor_storage, _optionsView_accessor_storage, _optionsOpen_accessor_storage, _pickerOpen_accessor_storage; + var RangeToolbar = (_hass_accessor_storage$3 = /* @__PURE__ */ new WeakMap(), _startTime_accessor_storage = /* @__PURE__ */ new WeakMap(), _endTime_accessor_storage = /* @__PURE__ */ new WeakMap(), _rangeBounds_accessor_storage = /* @__PURE__ */ new WeakMap(), _zoomLevel_accessor_storage = /* @__PURE__ */ new WeakMap(), _dateSnapping_accessor_storage = /* @__PURE__ */ new WeakMap(), _sidebarCollapsed_accessor_storage = /* @__PURE__ */ new WeakMap(), _isLiveEdge_accessor_storage = /* @__PURE__ */ new WeakMap(), _timelineEvents_accessor_storage = /* @__PURE__ */ new WeakMap(), _comparisonPreview_accessor_storage = /* @__PURE__ */ new WeakMap(), _zoomRange_accessor_storage = /* @__PURE__ */ new WeakMap(), _zoomWindowRange_accessor_storage = /* @__PURE__ */ new WeakMap(), _chartHoverTimeMs_accessor_storage = /* @__PURE__ */ new WeakMap(), _chartHoverWindowTime_accessor_storage = /* @__PURE__ */ new WeakMap(), _optionsView_accessor_storage = /* @__PURE__ */ new WeakMap(), _optionsOpen_accessor_storage = /* @__PURE__ */ new WeakMap(), _pickerOpen_accessor_storage = /* @__PURE__ */ new WeakMap(), _RangeToolbar = class RangeToolbar extends i$2 { + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$3, null); + _classPrivateFieldInitSpec(this, _startTime_accessor_storage, null); + _classPrivateFieldInitSpec(this, _endTime_accessor_storage, null); + _classPrivateFieldInitSpec(this, _rangeBounds_accessor_storage, null); + _classPrivateFieldInitSpec(this, _zoomLevel_accessor_storage, "auto"); + _classPrivateFieldInitSpec(this, _dateSnapping_accessor_storage, "hour"); + _classPrivateFieldInitSpec(this, _sidebarCollapsed_accessor_storage, false); + _classPrivateFieldInitSpec(this, _isLiveEdge_accessor_storage, false); + _classPrivateFieldInitSpec(this, _timelineEvents_accessor_storage, []); + _classPrivateFieldInitSpec(this, _comparisonPreview_accessor_storage, null); + _classPrivateFieldInitSpec(this, _zoomRange_accessor_storage, null); + _classPrivateFieldInitSpec(this, _zoomWindowRange_accessor_storage, null); + _classPrivateFieldInitSpec(this, _chartHoverTimeMs_accessor_storage, null); + _classPrivateFieldInitSpec(this, _chartHoverWindowTime_accessor_storage, null); + _classPrivateFieldInitSpec(this, _optionsView_accessor_storage, "root"); + _classPrivateFieldInitSpec(this, _optionsOpen_accessor_storage, false); + _classPrivateFieldInitSpec(this, _pickerOpen_accessor_storage, false); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$3, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$3, this, value); + } + get startTime() { + return _classPrivateFieldGet2(_startTime_accessor_storage, this); + } + set startTime(value) { + _classPrivateFieldSet2(_startTime_accessor_storage, this, value); + } + get endTime() { + return _classPrivateFieldGet2(_endTime_accessor_storage, this); + } + set endTime(value) { + _classPrivateFieldSet2(_endTime_accessor_storage, this, value); + } + get rangeBounds() { + return _classPrivateFieldGet2(_rangeBounds_accessor_storage, this); + } + set rangeBounds(value) { + _classPrivateFieldSet2(_rangeBounds_accessor_storage, this, value); + } + get zoomLevel() { + return _classPrivateFieldGet2(_zoomLevel_accessor_storage, this); + } + set zoomLevel(value) { + _classPrivateFieldSet2(_zoomLevel_accessor_storage, this, value); + } + get dateSnapping() { + return _classPrivateFieldGet2(_dateSnapping_accessor_storage, this); + } + set dateSnapping(value) { + _classPrivateFieldSet2(_dateSnapping_accessor_storage, this, value); + } + get sidebarCollapsed() { + return _classPrivateFieldGet2(_sidebarCollapsed_accessor_storage, this); + } + set sidebarCollapsed(value) { + _classPrivateFieldSet2(_sidebarCollapsed_accessor_storage, this, value); + } + get isLiveEdge() { + return _classPrivateFieldGet2(_isLiveEdge_accessor_storage, this); + } + set isLiveEdge(value) { + _classPrivateFieldSet2(_isLiveEdge_accessor_storage, this, value); + } + get timelineEvents() { + return _classPrivateFieldGet2(_timelineEvents_accessor_storage, this); + } + set timelineEvents(value) { + _classPrivateFieldSet2(_timelineEvents_accessor_storage, this, value); + } + get comparisonPreview() { + return _classPrivateFieldGet2(_comparisonPreview_accessor_storage, this); + } + set comparisonPreview(value) { + _classPrivateFieldSet2(_comparisonPreview_accessor_storage, this, value); + } + get zoomRange() { + return _classPrivateFieldGet2(_zoomRange_accessor_storage, this); + } + set zoomRange(value) { + _classPrivateFieldSet2(_zoomRange_accessor_storage, this, value); + } + get zoomWindowRange() { + return _classPrivateFieldGet2(_zoomWindowRange_accessor_storage, this); + } + set zoomWindowRange(value) { + _classPrivateFieldSet2(_zoomWindowRange_accessor_storage, this, value); + } + get chartHoverTimeMs() { + return _classPrivateFieldGet2(_chartHoverTimeMs_accessor_storage, this); + } + set chartHoverTimeMs(value) { + _classPrivateFieldSet2(_chartHoverTimeMs_accessor_storage, this, value); + } + get chartHoverWindowTimeMs() { + return _classPrivateFieldGet2(_chartHoverWindowTime_accessor_storage, this); + } + set chartHoverWindowTimeMs(value) { + _classPrivateFieldSet2(_chartHoverWindowTime_accessor_storage, this, value); + } + get _optionsView() { + return _classPrivateFieldGet2(_optionsView_accessor_storage, this); + } + set _optionsView(value) { + _classPrivateFieldSet2(_optionsView_accessor_storage, this, value); + } + get _optionsOpen() { + return _classPrivateFieldGet2(_optionsOpen_accessor_storage, this); + } + set _optionsOpen(value) { + _classPrivateFieldSet2(_optionsOpen_accessor_storage, this, value); + } + get _pickerOpen() { + return _classPrivateFieldGet2(_pickerOpen_accessor_storage, this); + } + set _pickerOpen(value) { + _classPrivateFieldSet2(_pickerOpen_accessor_storage, this, value); + } /** Sync mobile date inputs to the given start/end values. */ syncMobileDates(start, end) { const fmtInput = (d) => { @@ -24966,8 +26884,55 @@ updated() { this.syncOptionsLabels(); } - }; - _defineProperty(RangeToolbar, "styles", styles$19); + }, _defineProperty(_RangeToolbar, "styles", styles$19), _RangeToolbar); + __decorate([n$1({ attribute: false })], RangeToolbar.prototype, "hass", null); + __decorate([n$1({ type: Object })], RangeToolbar.prototype, "startTime", null); + __decorate([n$1({ type: Object })], RangeToolbar.prototype, "endTime", null); + __decorate([n$1({ type: Object })], RangeToolbar.prototype, "rangeBounds", null); + __decorate([n$1({ + type: String, + attribute: "zoom-level" + })], RangeToolbar.prototype, "zoomLevel", null); + __decorate([n$1({ + type: String, + attribute: "date-snapping" + })], RangeToolbar.prototype, "dateSnapping", null); + __decorate([n$1({ + type: Boolean, + attribute: "sidebar-collapsed" + })], RangeToolbar.prototype, "sidebarCollapsed", null); + __decorate([n$1({ + type: Boolean, + attribute: "is-live-edge" + })], RangeToolbar.prototype, "isLiveEdge", null); + __decorate([n$1({ + type: Array, + attribute: false + })], RangeToolbar.prototype, "timelineEvents", null); + __decorate([n$1({ + type: Object, + attribute: false + })], RangeToolbar.prototype, "comparisonPreview", null); + __decorate([n$1({ + type: Object, + attribute: false + })], RangeToolbar.prototype, "zoomRange", null); + __decorate([n$1({ + type: Object, + attribute: false + })], RangeToolbar.prototype, "zoomWindowRange", null); + __decorate([n$1({ + type: Number, + attribute: false + })], RangeToolbar.prototype, "chartHoverTimeMs", null); + __decorate([n$1({ + type: Number, + attribute: false + })], RangeToolbar.prototype, "chartHoverWindowTimeMs", null); + __decorate([r$1()], RangeToolbar.prototype, "_optionsView", null); + __decorate([r$1()], RangeToolbar.prototype, "_optionsOpen", null); + __decorate([r$1()], RangeToolbar.prototype, "_pickerOpen", null); + RangeToolbar = __decorate([localized()], RangeToolbar); customElements.define("range-toolbar", RangeToolbar); //#endregion //#region custom_components/hass_datapoints/src/panels/datapoints/context/app-state-context.ts @@ -30815,9 +32780,26 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/search-bar/search-bar.ts + var _query_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _placeholder_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); var SearchBar = class extends i$2 { - @n$1({ type: String }) accessor query = ""; - @n$1({ type: String }) accessor placeholder = "Search..."; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _query_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _placeholder_accessor_storage$1, "Search..."); + } + get query() { + return _classPrivateFieldGet2(_query_accessor_storage, this); + } + set query(value) { + _classPrivateFieldSet2(_query_accessor_storage, this, value); + } + get placeholder() { + return _classPrivateFieldGet2(_placeholder_accessor_storage$1, this); + } + set placeholder(value) { + _classPrivateFieldSet2(_placeholder_accessor_storage$1, this, value); + } _onInput(e) { this.dispatchEvent(new CustomEvent("dp-search", { detail: { query: e.target.value }, @@ -30839,6 +32821,8 @@ } }; _defineProperty(SearchBar, "styles", styles$15); + __decorate([n$1({ type: String })], SearchBar.prototype, "query", null); + __decorate([n$1({ type: String })], SearchBar.prototype, "placeholder", null); customElements.define("search-bar", SearchBar); //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/pagination/pagination.styles.ts @@ -30882,11 +32866,42 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/interactive/pagination/pagination.ts + var _page_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _totalPages_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _totalItems_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _label_accessor_storage$3 = /* @__PURE__ */ new WeakMap(); var Pagination = class extends i$2 { - @n$1({ type: Number }) accessor page = 0; - @n$1({ type: Number }) accessor totalPages = 1; - @n$1({ type: Number }) accessor totalItems = 0; - @n$1({ type: String }) accessor label = "records"; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _page_accessor_storage$1, 0); + _classPrivateFieldInitSpec(this, _totalPages_accessor_storage, 1); + _classPrivateFieldInitSpec(this, _totalItems_accessor_storage, 0); + _classPrivateFieldInitSpec(this, _label_accessor_storage$3, "records"); + } + get page() { + return _classPrivateFieldGet2(_page_accessor_storage$1, this); + } + set page(value) { + _classPrivateFieldSet2(_page_accessor_storage$1, this, value); + } + get totalPages() { + return _classPrivateFieldGet2(_totalPages_accessor_storage, this); + } + set totalPages(value) { + _classPrivateFieldSet2(_totalPages_accessor_storage, this, value); + } + get totalItems() { + return _classPrivateFieldGet2(_totalItems_accessor_storage, this); + } + set totalItems(value) { + _classPrivateFieldSet2(_totalItems_accessor_storage, this, value); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$3, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$3, this, value); + } _onPrev() { if (this.page > 0) this.dispatchEvent(new CustomEvent("dp-page-change", { detail: { page: this.page - 1 }, @@ -30930,6 +32945,10 @@ } }; _defineProperty(Pagination, "styles", styles$14); + __decorate([n$1({ type: Number })], Pagination.prototype, "page", null); + __decorate([n$1({ type: Number })], Pagination.prototype, "totalPages", null); + __decorate([n$1({ type: Number })], Pagination.prototype, "totalItems", null); + __decorate([n$1({ type: String })], Pagination.prototype, "label", null); customElements.define("pagination-nav", Pagination); //#endregion //#region custom_components/hass_datapoints/src/cards/list/list-event-item/list-event-item.styles.ts @@ -31280,14 +33299,66 @@ message: "Message", annotationFullMessage: "Annotation / full message" }; + var _eventRecord_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _hass_accessor_storage$2 = /* @__PURE__ */ new WeakMap(); + var _color_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _language_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _message_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _annotation_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _icon_accessor_storage = /* @__PURE__ */ new WeakMap(); var CardListEditForm = class extends i$2 { - @n$1({ attribute: false }) accessor eventRecord = null; - @n$1({ attribute: false }) accessor hass = null; - @n$1({ type: String }) accessor color = "#03a9f4"; - @n$1({ attribute: false }) accessor language = DEFAULT_LANGUAGE$1; - @r$1() accessor _message = ""; - @r$1() accessor _annotation = ""; - @r$1() accessor _icon = "mdi:bookmark"; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _eventRecord_accessor_storage$1, null); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$2, null); + _classPrivateFieldInitSpec(this, _color_accessor_storage, "#03a9f4"); + _classPrivateFieldInitSpec(this, _language_accessor_storage, DEFAULT_LANGUAGE$1); + _classPrivateFieldInitSpec(this, _message_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _annotation_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _icon_accessor_storage, "mdi:bookmark"); + } + get eventRecord() { + return _classPrivateFieldGet2(_eventRecord_accessor_storage$1, this); + } + set eventRecord(value) { + _classPrivateFieldSet2(_eventRecord_accessor_storage$1, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$2, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$2, this, value); + } + get color() { + return _classPrivateFieldGet2(_color_accessor_storage, this); + } + set color(value) { + _classPrivateFieldSet2(_color_accessor_storage, this, value); + } + get language() { + return _classPrivateFieldGet2(_language_accessor_storage, this); + } + set language(value) { + _classPrivateFieldSet2(_language_accessor_storage, this, value); + } + get _message() { + return _classPrivateFieldGet2(_message_accessor_storage, this); + } + set _message(value) { + _classPrivateFieldSet2(_message_accessor_storage, this, value); + } + get _annotation() { + return _classPrivateFieldGet2(_annotation_accessor_storage, this); + } + set _annotation(value) { + _classPrivateFieldSet2(_annotation_accessor_storage, this, value); + } + get _icon() { + return _classPrivateFieldGet2(_icon_accessor_storage, this); + } + set _icon(value) { + _classPrivateFieldSet2(_icon_accessor_storage, this, value); + } willUpdate(changedProperties) { if (changedProperties.has("eventRecord") && this.eventRecord) { this._message = this.eventRecord.message; @@ -31374,6 +33445,13 @@ } }; _defineProperty(CardListEditForm, "styles", styles$12); + __decorate([n$1({ attribute: false })], CardListEditForm.prototype, "eventRecord", null); + __decorate([n$1({ attribute: false })], CardListEditForm.prototype, "hass", null); + __decorate([n$1({ type: String })], CardListEditForm.prototype, "color", null); + __decorate([n$1({ attribute: false })], CardListEditForm.prototype, "language", null); + __decorate([r$1()], CardListEditForm.prototype, "_message", null); + __decorate([r$1()], CardListEditForm.prototype, "_annotation", null); + __decorate([r$1()], CardListEditForm.prototype, "_icon", null); customElements.define("list-edit-form", CardListEditForm); //#endregion //#region custom_components/hass_datapoints/src/cards/list/list-event-item/list-event-item.ts @@ -31390,20 +33468,44 @@ message: "Message", annotationFullMessage: "Annotation / full message" }; + var _eventRecord_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _context_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _annotationExpanded_accessor_storage = /* @__PURE__ */ new WeakMap(); var CardListEventItem = class extends i$2 { - @n$1({ attribute: false }) accessor eventRecord = null; - @n$1({ attribute: false }) accessor context = { - hass: null, - showActions: true, - canEdit: true, - showEntities: true, - showFullMessage: true, - hidden: false, - editing: false, - editColor: "#03a9f4", - language: DEFAULT_LANGUAGE - }; - @r$1() accessor _annotationExpanded = false; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _eventRecord_accessor_storage, null); + _classPrivateFieldInitSpec(this, _context_accessor_storage, { + hass: null, + showActions: true, + canEdit: true, + showEntities: true, + showFullMessage: true, + hidden: false, + editing: false, + editColor: "#03a9f4", + language: DEFAULT_LANGUAGE + }); + _classPrivateFieldInitSpec(this, _annotationExpanded_accessor_storage, false); + } + get eventRecord() { + return _classPrivateFieldGet2(_eventRecord_accessor_storage, this); + } + set eventRecord(value) { + _classPrivateFieldSet2(_eventRecord_accessor_storage, this, value); + } + get context() { + return _classPrivateFieldGet2(_context_accessor_storage, this); + } + set context(value) { + _classPrivateFieldSet2(_context_accessor_storage, this, value); + } + get _annotationExpanded() { + return _classPrivateFieldGet2(_annotationExpanded_accessor_storage, this); + } + set _annotationExpanded(value) { + _classPrivateFieldSet2(_annotationExpanded_accessor_storage, this, value); + } _dispatch(name, detail = {}) { this.dispatchEvent(new CustomEvent(name, { detail, @@ -31618,10 +33720,14 @@ } }; _defineProperty(CardListEventItem, "styles", styles$13); + __decorate([n$1({ attribute: false })], CardListEventItem.prototype, "eventRecord", null); + __decorate([n$1({ attribute: false })], CardListEventItem.prototype, "context", null); + __decorate([r$1()], CardListEventItem.prototype, "_annotationExpanded", null); customElements.define("list-event-item", CardListEventItem); //#endregion //#region custom_components/hass_datapoints/src/cards/list/list.ts - var HassRecordsListCard = @localized() class extends i$2 { + var _HassRecordsListCard; + var HassRecordsListCard = (_HassRecordsListCard = class HassRecordsListCard extends i$2 { constructor() { super(); _defineProperty(this, "_pageSize", 15); @@ -31946,8 +34052,7 @@ min_rows: rows }; } - }; - _defineProperty(HassRecordsListCard, "properties", { + }, _defineProperty(_HassRecordsListCard, "properties", { _config: { state: true }, _hass: { state: true }, _allEvents: { state: true }, @@ -31955,8 +34060,8 @@ _page: { state: true }, _editingId: { state: true }, _editColor: { state: true } - }); - _defineProperty(HassRecordsListCard, "styles", styles$16); + }), _defineProperty(_HassRecordsListCard, "styles", styles$16), _HassRecordsListCard); + HassRecordsListCard = __decorate([localized()], HassRecordsListCard); //#endregion //#region custom_components/hass_datapoints/src/cards/list/editor.styles.ts var styles$11 = i$5``; @@ -32155,10 +34260,34 @@ `; //#endregion //#region custom_components/hass_datapoints/src/cards/quick/quick-annotation/quick-annotation.ts + var _label_accessor_storage$2 = /* @__PURE__ */ new WeakMap(); + var _placeholder_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _value_accessor_storage$3 = /* @__PURE__ */ new WeakMap(); var CardQuickAnnotation = class extends i$2 { - @n$1({ type: String }) accessor label = "Annotation"; - @n$1({ type: String }) accessor placeholder = "Detailed note shown on chart hover…"; - @n$1({ type: String }) accessor value = ""; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _label_accessor_storage$2, "Annotation"); + _classPrivateFieldInitSpec(this, _placeholder_accessor_storage, "Detailed note shown on chart hover…"); + _classPrivateFieldInitSpec(this, _value_accessor_storage$3, ""); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$2, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$2, this, value); + } + get placeholder() { + return _classPrivateFieldGet2(_placeholder_accessor_storage, this); + } + set placeholder(value) { + _classPrivateFieldSet2(_placeholder_accessor_storage, this, value); + } + get value() { + return _classPrivateFieldGet2(_value_accessor_storage$3, this); + } + set value(value) { + _classPrivateFieldSet2(_value_accessor_storage$3, this, value); + } _onInput(event) { this.value = event.currentTarget.value; this.dispatchEvent(new CustomEvent("dp-annotation-input", { @@ -32182,6 +34311,9 @@ } }; _defineProperty(CardQuickAnnotation, "styles", styles$9); + __decorate([n$1({ type: String })], CardQuickAnnotation.prototype, "label", null); + __decorate([n$1({ type: String })], CardQuickAnnotation.prototype, "placeholder", null); + __decorate([n$1({ type: String })], CardQuickAnnotation.prototype, "value", null); customElements.define("quick-annotation", CardQuickAnnotation); //#endregion //#region custom_components/hass_datapoints/src/cards/quick/quick.ts @@ -32583,18 +34715,50 @@ `; //#endregion //#region custom_components/hass_datapoints/src/cards/sensor/sensor-header/sensor-header.ts + var _name_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _value_accessor_storage$2 = /* @__PURE__ */ new WeakMap(); + var _unit_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _stateObj_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _hass_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); var SensorHeader = class extends i$2 { - @n$1({ type: String }) accessor name = "—"; - @n$1({ type: String }) accessor value = "—"; - @n$1({ type: String }) accessor unit = ""; - @n$1({ - type: Object, - attribute: false - }) accessor stateObj = null; - @n$1({ - type: Object, - attribute: false - }) accessor hass = null; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _name_accessor_storage, "—"); + _classPrivateFieldInitSpec(this, _value_accessor_storage$2, "—"); + _classPrivateFieldInitSpec(this, _unit_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _stateObj_accessor_storage, null); + _classPrivateFieldInitSpec(this, _hass_accessor_storage$1, null); + } + get name() { + return _classPrivateFieldGet2(_name_accessor_storage, this); + } + set name(value) { + _classPrivateFieldSet2(_name_accessor_storage, this, value); + } + get value() { + return _classPrivateFieldGet2(_value_accessor_storage$2, this); + } + set value(value) { + _classPrivateFieldSet2(_value_accessor_storage$2, this, value); + } + get unit() { + return _classPrivateFieldGet2(_unit_accessor_storage, this); + } + set unit(value) { + _classPrivateFieldSet2(_unit_accessor_storage, this, value); + } + get stateObj() { + return _classPrivateFieldGet2(_stateObj_accessor_storage, this); + } + set stateObj(value) { + _classPrivateFieldSet2(_stateObj_accessor_storage, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage$1, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage$1, this, value); + } _onHeaderClick(event) { event.preventDefault(); event.stopPropagation(); @@ -32622,6 +34786,17 @@ } }; _defineProperty(SensorHeader, "styles", styles$6); + __decorate([n$1({ type: String })], SensorHeader.prototype, "name", null); + __decorate([n$1({ type: String })], SensorHeader.prototype, "value", null); + __decorate([n$1({ type: String })], SensorHeader.prototype, "unit", null); + __decorate([n$1({ + type: Object, + attribute: false + })], SensorHeader.prototype, "stateObj", null); + __decorate([n$1({ + type: Object, + attribute: false + })], SensorHeader.prototype, "hass", null); customElements.define("sensor-header", SensorHeader); //#endregion //#region custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.styles.ts @@ -33308,17 +35483,42 @@ `; //#endregion //#region custom_components/hass_datapoints/src/cards/sensor/sensor-record-item/sensor-record-item.ts + var _event_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _hidden_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _showFullMessage_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _noteExpanded_accessor_storage = /* @__PURE__ */ new WeakMap(); var SensorRecordItem = class extends i$2 { - @n$1({ - type: Object, - attribute: false - }) accessor event = null; - @n$1({ type: Boolean }) accessor hidden = false; - @n$1({ - type: Boolean, - attribute: "show-full-message" - }) accessor showFullMessage = true; - @r$1() accessor _noteExpanded = false; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _event_accessor_storage, null); + _classPrivateFieldInitSpec(this, _hidden_accessor_storage, false); + _classPrivateFieldInitSpec(this, _showFullMessage_accessor_storage$1, true); + _classPrivateFieldInitSpec(this, _noteExpanded_accessor_storage, false); + } + get event() { + return _classPrivateFieldGet2(_event_accessor_storage, this); + } + set event(value) { + _classPrivateFieldSet2(_event_accessor_storage, this, value); + } + get hidden() { + return _classPrivateFieldGet2(_hidden_accessor_storage, this); + } + set hidden(value) { + _classPrivateFieldSet2(_hidden_accessor_storage, this, value); + } + get showFullMessage() { + return _classPrivateFieldGet2(_showFullMessage_accessor_storage$1, this); + } + set showFullMessage(value) { + _classPrivateFieldSet2(_showFullMessage_accessor_storage$1, this, value); + } + get _noteExpanded() { + return _classPrivateFieldGet2(_noteExpanded_accessor_storage, this); + } + set _noteExpanded(value) { + _classPrivateFieldSet2(_noteExpanded_accessor_storage, this, value); + } _onToggleVisibility(e) { e.preventDefault(); e.stopPropagation(); @@ -33406,32 +35606,72 @@ } }; _defineProperty(SensorRecordItem, "styles", styles$3); + __decorate([n$1({ + type: Object, + attribute: false + })], SensorRecordItem.prototype, "event", null); + __decorate([n$1({ type: Boolean })], SensorRecordItem.prototype, "hidden", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-full-message" + })], SensorRecordItem.prototype, "showFullMessage", null); + __decorate([r$1()], SensorRecordItem.prototype, "_noteExpanded", null); customElements.define("sensor-record-item", SensorRecordItem); //#endregion //#region custom_components/hass_datapoints/src/cards/sensor/sensor-records/sensor-records.ts + var _events_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _hiddenEventIds_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _pageSize_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _limit_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _showFullMessage_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _page_accessor_storage = /* @__PURE__ */ new WeakMap(); var SensorRecords = class extends i$2 { constructor(..._args) { super(..._args); + _classPrivateFieldInitSpec(this, _events_accessor_storage, []); + _classPrivateFieldInitSpec(this, _hiddenEventIds_accessor_storage, /* @__PURE__ */ new Set()); + _classPrivateFieldInitSpec(this, _pageSize_accessor_storage, null); + _classPrivateFieldInitSpec(this, _limit_accessor_storage, null); + _classPrivateFieldInitSpec(this, _showFullMessage_accessor_storage, true); + _classPrivateFieldInitSpec(this, _page_accessor_storage, 0); _defineProperty(this, "_paginationNotifyRaf", null); } - @n$1({ - type: Array, - attribute: false - }) accessor events = []; - @n$1({ - type: Object, - attribute: false - }) accessor hiddenEventIds = /* @__PURE__ */ new Set(); - @n$1({ - type: Number, - attribute: "page-size" - }) accessor pageSize = null; - @n$1({ type: Number }) accessor limit = null; - @n$1({ - type: Boolean, - attribute: "show-full-message" - }) accessor showFullMessage = true; - @r$1() accessor _page = 0; + get events() { + return _classPrivateFieldGet2(_events_accessor_storage, this); + } + set events(value) { + _classPrivateFieldSet2(_events_accessor_storage, this, value); + } + get hiddenEventIds() { + return _classPrivateFieldGet2(_hiddenEventIds_accessor_storage, this); + } + set hiddenEventIds(value) { + _classPrivateFieldSet2(_hiddenEventIds_accessor_storage, this, value); + } + get pageSize() { + return _classPrivateFieldGet2(_pageSize_accessor_storage, this); + } + set pageSize(value) { + _classPrivateFieldSet2(_pageSize_accessor_storage, this, value); + } + get limit() { + return _classPrivateFieldGet2(_limit_accessor_storage, this); + } + set limit(value) { + _classPrivateFieldSet2(_limit_accessor_storage, this, value); + } + get showFullMessage() { + return _classPrivateFieldGet2(_showFullMessage_accessor_storage, this); + } + set showFullMessage(value) { + _classPrivateFieldSet2(_showFullMessage_accessor_storage, this, value); + } + get _page() { + return _classPrivateFieldGet2(_page_accessor_storage, this); + } + set _page(value) { + _classPrivateFieldSet2(_page_accessor_storage, this, value); + } updated(changedProps) { if (changedProps.has("events") || changedProps.has("pageSize") || changedProps.has("limit") || changedProps.has("_page")) { const sorted = [...this.events].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); @@ -33503,6 +35743,24 @@ } }; _defineProperty(SensorRecords, "styles", styles$4); + __decorate([n$1({ + type: Array, + attribute: false + })], SensorRecords.prototype, "events", null); + __decorate([n$1({ + type: Object, + attribute: false + })], SensorRecords.prototype, "hiddenEventIds", null); + __decorate([n$1({ + type: Number, + attribute: "page-size" + })], SensorRecords.prototype, "pageSize", null); + __decorate([n$1({ type: Number })], SensorRecords.prototype, "limit", null); + __decorate([n$1({ + type: Boolean, + attribute: "show-full-message" + })], SensorRecords.prototype, "showFullMessage", null); + __decorate([r$1()], SensorRecords.prototype, "_page", null); customElements.define("sensor-records", SensorRecords); //#endregion //#region custom_components/hass_datapoints/src/cards/sensor/sensor.ts @@ -33785,10 +36043,34 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/form/editor-entity-picker/editor-entity-picker.ts + var _label_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _value_accessor_storage$1 = /* @__PURE__ */ new WeakMap(); + var _hass_accessor_storage = /* @__PURE__ */ new WeakMap(); var EditorEntityPicker = class extends i$2 { - @n$1({ type: String }) accessor label = ""; - @n$1({ type: String }) accessor value = ""; - @n$1({ type: Object }) accessor hass = null; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _label_accessor_storage$1, ""); + _classPrivateFieldInitSpec(this, _value_accessor_storage$1, ""); + _classPrivateFieldInitSpec(this, _hass_accessor_storage, null); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage$1, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage$1, this, value); + } + get value() { + return _classPrivateFieldGet2(_value_accessor_storage$1, this); + } + set value(value) { + _classPrivateFieldSet2(_value_accessor_storage$1, this, value); + } + get hass() { + return _classPrivateFieldGet2(_hass_accessor_storage, this); + } + set hass(value) { + _classPrivateFieldSet2(_hass_accessor_storage, this, value); + } firstUpdated() { const el = this.shadowRoot.querySelector("ha-selector"); if (el) { @@ -33818,6 +36100,9 @@ } }; _defineProperty(EditorEntityPicker, "styles", styles$1); + __decorate([n$1({ type: String })], EditorEntityPicker.prototype, "label", null); + __decorate([n$1({ type: String })], EditorEntityPicker.prototype, "value", null); + __decorate([n$1({ type: Object })], EditorEntityPicker.prototype, "hass", null); customElements.define("editor-entity-picker", EditorEntityPicker); //#endregion //#region custom_components/hass_datapoints/src/atoms/form/editor-select/editor-select.styles.ts @@ -33832,10 +36117,34 @@ `; //#endregion //#region custom_components/hass_datapoints/src/atoms/form/editor-select/editor-select.ts + var _label_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _value_accessor_storage = /* @__PURE__ */ new WeakMap(); + var _options_accessor_storage = /* @__PURE__ */ new WeakMap(); var EditorSelect = class extends i$2 { - @n$1({ type: String }) accessor label = ""; - @n$1({ type: String }) accessor value = ""; - @n$1({ type: Array }) accessor options = []; + constructor(..._args) { + super(..._args); + _classPrivateFieldInitSpec(this, _label_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _value_accessor_storage, ""); + _classPrivateFieldInitSpec(this, _options_accessor_storage, []); + } + get label() { + return _classPrivateFieldGet2(_label_accessor_storage, this); + } + set label(value) { + _classPrivateFieldSet2(_label_accessor_storage, this, value); + } + get value() { + return _classPrivateFieldGet2(_value_accessor_storage, this); + } + set value(value) { + _classPrivateFieldSet2(_value_accessor_storage, this, value); + } + get options() { + return _classPrivateFieldGet2(_options_accessor_storage, this); + } + set options(value) { + _classPrivateFieldSet2(_options_accessor_storage, this, value); + } firstUpdated() { const el = this.shadowRoot.querySelector("ha-selector"); if (el) { @@ -33864,6 +36173,9 @@ } }; _defineProperty(EditorSelect, "styles", styles); + __decorate([n$1({ type: String })], EditorSelect.prototype, "label", null); + __decorate([n$1({ type: String })], EditorSelect.prototype, "value", null); + __decorate([n$1({ type: Array })], EditorSelect.prototype, "options", null); customElements.define("editor-select", EditorSelect); //#endregion //#region custom_components/hass_datapoints/src/cards/sensor/editor.ts diff --git a/tsconfig.json b/tsconfig.json index 27ab3c0d..4e6aa78b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", + "experimentalDecorators": true, "allowJs": false, "checkJs": false, "noEmit": true, From 13605a765ea0ff0fb7d840b216b3c2e8e085e3b8 Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 16:11:09 +0300 Subject: [PATCH 05/14] feat(annotation-dialog): redesign layout and fix target selector UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Co-locate Message + Date/time in a two-column row; co-locate Icon + Color in a second two-column row; both rows collapse to single column below 600px - Replace the hidden ha-selector behind an "Add target" toggle button with an always-visible target selector — no extra click required - Fix target accumulation: each value-changed event now merges into the running _target state via mergeTargetSelections, so selecting a second entity/device/area no longer replaces the first - Remove chip-row's internal label (outer form field owns the label now) - Widen sidebar overlay to 95vw on screens ≤545px - Update tests: chip-row label tests reflect new ownership model; dialog tests cover layout rows, always-visible selector, and accumulation --- .../hass_datapoints/hass-datapoints-cards.js | 88 ++++++----- .../__tests__/annotation-dialog.spec.ts | 137 ++++++++++++++++++ .../annotation-dialog/annotation-dialog.ts | 94 +++++++----- .../__tests__/annotation-chip-row.spec.ts | 22 ++- .../annotation-chip-row.ts | 1 - .../history-targets/history-targets.styles.ts | 2 +- .../panel-shell/panel-shell.styles.ts | 7 + .../panels/datapoints/datapoints.styles.ts | 7 + 8 files changed, 271 insertions(+), 87 deletions(-) diff --git a/custom_components/hass_datapoints/hass-datapoints-cards.js b/custom_components/hass_datapoints/hass-datapoints-cards.js index 64c6028a..920e494c 100644 --- a/custom_components/hass_datapoints/hass-datapoints-cards.js +++ b/custom_components/hass_datapoints/hass-datapoints-cards.js @@ -14907,7 +14907,6 @@ render() { return b`
-
${this.chips.length > 0 ? this.helpText : this.emptyText}
@@ -15074,7 +15073,7 @@ this._linkedTarget = current; if (this._chipRowEl) { this._chipRowEl.hass = this._host._hass ?? null; - this._chipRowEl.chips = this._buildChips(this._linkedTarget); + this._chipRowEl.chips = this._buildChips(mergeTargetSelections(this._linkedTarget, this._target)); } } bindTargetChipActions() {} @@ -15095,14 +15094,6 @@ iconPicker.hass = this._host._hass; iconPicker.value = prefill.icon || hover?.event?.icon || "mdi:bookmark"; } - const targetSel = this._panelEl.querySelector("#chart-context-target"); - if (targetSel) { - targetSel.hass = this._host._hass; - targetSel.value = "{}"; - targetSel.addEventListener("value-changed", (ev) => { - this._target = normalizeTargetSelection(ev.detail.value || {}); - }); - } if (messageEl) messageEl.value = prefill.message || ""; if (annotationEl) annotationEl.value = prefill.annotation || ""; const chipContainer = this._panelEl.querySelector("#chart-context-linked-targets"); @@ -15117,6 +15108,18 @@ this._chipRowEl = chipRow; } if (this._chipRowEl) this._chipRowEl.chips = this._buildChips(this._linkedTarget); + const targetSel = this._panelEl.querySelector("#chart-context-target"); + if (targetSel) { + targetSel.hass = this._host._hass; + targetSel.selector = { target: {} }; + targetSel.addEventListener("value-changed", (ev) => { + this._target = mergeTargetSelections(this._target, normalizeTargetSelection(ev.detail.value || {})); + if (this._chipRowEl) { + this._chipRowEl.hass = this._host._hass ?? null; + this._chipRowEl.chips = this._buildChips(mergeTargetSelections(this._linkedTarget, this._target)); + } + }); + } this.bindTargetChipActions(); const colorInput = this._panelEl.querySelector("#chart-context-color"); const colorPreview = this._panelEl.querySelector("#chart-context-color-preview"); @@ -15207,10 +15210,10 @@ .context-dialog-content { display: grid; gap: 16px; padding-top: 4px; } .context-form { display: grid; gap: 16px; } .context-form-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; } - .context-form-main, .context-form-side { display: grid; gap: 16px; min-width: 0; } - .context-form-side { align-content: start; justify-items: start; } + .context-form-row { display: grid; gap: 16px; align-items: start; } + .context-form-row-message-date { grid-template-columns: minmax(0, 1fr) 220px; } + .context-form-row-icon-color { grid-template-columns: 220px auto; } .context-form-field { display: grid; gap: 6px; min-width: 0; } - .context-form-field.compact-field { justify-items: start; } .context-form-label { font-size: 0.9rem; font-weight: 600; color: var(--primary-text-color); } .context-form-help { font-size: 0.8rem; color: var(--secondary-text-color); line-height: 1.45; } .context-form-help-inline { display: inline-flex; align-items: center; gap: 6px; } @@ -15227,40 +15230,47 @@ .context-color-control { display: flex; align-items: center; gap: 10px; } .context-color-preview { width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--divider-color, #ccc); background: ${esc(defaultColor)}; flex: 0 0 auto; } .context-color-input { width: 56px; height: 36px; padding: 0; border: none; background: transparent; cursor: pointer; } - .context-date-input { width: 220px; max-width: 100%; } - .context-icon-input { width: 220px; max-width: 100%; } + .context-date-input { width: 100%; box-sizing: border-box; } + .context-icon-input { width: 100%; } .context-form-feedback { color: var(--error-color); font-size: 0.84rem; } .context-form-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-top: 8px; } .context-form-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-left: auto; } + @media (max-width: 600px) { + .context-form-row-message-date, + .context-form-row-icon-color { grid-template-columns: minmax(0, 1fr); } + }
-
+ +
Use a short title that will be shown in the chart tooltip and records list.
- -
Add any longer context, outcome, or note you want to keep with this data point.
- -
-
-
- -
Optionally add more entities, devices, areas, or labels that should also be linked to this annotation.
- -
-
-
-
The annotation will be placed at this exact moment on the chart.
-
+
+ +
+ +
Add any longer context, outcome, or note you want to keep with this data point.
+ +
+ +
+ +
+ +
+ +
+
Choose the icon shown for this data point in the chart and records list.
@@ -15292,8 +15302,6 @@ this._dialogEl.hass = this._host._hass; this._dialogEl.dialogInitialFocus = "#chart-context-message"; } - const targetSel = this._panelEl.querySelector("#chart-context-target"); - if (targetSel) targetSel.selector = { target: {} }; this.bindFields(hover); this._dialogEl.open = true; this._host._creatingContextAnnotation = true; @@ -24624,6 +24632,7 @@ position: absolute; top: 0; left: 0; + min-width: min(380px, 85vw); width: min(380px, 85vw); height: 100%; z-index: 10; @@ -24689,6 +24698,12 @@ display: none; } } + + @media (max-width: 545px) { + .page-sidebar { + width: min(380px, 95vw); + } + } `; //#endregion //#region custom_components/hass_datapoints/src/molecules/floating-menu/floating-menu.styles.ts @@ -25177,7 +25192,7 @@ } .history-target-rows { - width: calc(var(--sidebar-width-expanded) - var(--dp-spacing-lg) * 2); + width: 100%; } .sidebar-section-header { @@ -29882,6 +29897,7 @@ position: absolute; top: 0; left: 0; + min-width: min(380px, 85vw); width: min(380px, 85vw); height: 100%; z-index: 10; @@ -29963,6 +29979,12 @@ display: flex; } } + + @media (max-width: 545px) { + .page-sidebar { + width: min(380px, 95vw); + } + } `; var PANEL_HISTORY_LOADING_STYLE = ` :host { diff --git a/custom_components/hass_datapoints/src/components/annotation-dialog/__tests__/annotation-dialog.spec.ts b/custom_components/hass_datapoints/src/components/annotation-dialog/__tests__/annotation-dialog.spec.ts index 4398f6e1..ae584bdf 100644 --- a/custom_components/hass_datapoints/src/components/annotation-dialog/__tests__/annotation-dialog.spec.ts +++ b/custom_components/hass_datapoints/src/components/annotation-dialog/__tests__/annotation-dialog.spec.ts @@ -150,4 +150,141 @@ describe("HistoryAnnotationDialogController", () => { }); }); }); + + // --------------------------------------------------------------------------- + // Layout + // --------------------------------------------------------------------------- + + describe("GIVEN dialog is open", () => { + beforeEach(() => { + host = createHost(); + controller = new HistoryAnnotationDialogController(host); + controller.open(makeHover()); + }); + + describe("WHEN rendered", () => { + it("THEN message and date fields share the message-date row", () => { + const panel = host.shadowRoot.querySelector( + "#chart-context-dialog-panel" + ); + const row = panel?.querySelector(".context-form-row-message-date"); + expect(row).not.toBeNull(); + expect(row?.querySelector("#chart-context-message")).not.toBeNull(); + expect(row?.querySelector("#chart-context-date")).not.toBeNull(); + }); + + it("THEN icon and color fields share the icon-color row", () => { + const panel = host.shadowRoot.querySelector( + "#chart-context-dialog-panel" + ); + const row = panel?.querySelector(".context-form-row-icon-color"); + expect(row).not.toBeNull(); + expect(row?.querySelector("#chart-context-icon")).not.toBeNull(); + expect(row?.querySelector("#chart-context-color")).not.toBeNull(); + }); + }); + }); + + // --------------------------------------------------------------------------- + // Target selector — always visible, no toggle button + // --------------------------------------------------------------------------- + + describe("GIVEN dialog is open", () => { + let panel; + + beforeEach(() => { + host = createHost(); + controller = new HistoryAnnotationDialogController(host); + controller.open(makeHover()); + panel = host.shadowRoot.querySelector("#chart-context-dialog-panel"); + }); + + describe("WHEN rendered", () => { + it("THEN the ha-selector for targets is present in the DOM", () => { + expect(panel?.querySelector("#chart-context-target")).not.toBeNull(); + }); + + it("THEN there is no hidden target wrapper element", () => { + expect(panel?.querySelector("#chart-context-target-wrap")).toBeNull(); + }); + + it("THEN there is no Add-target toggle button", () => { + expect(panel?.querySelector("#chart-context-add-target")).toBeNull(); + }); + }); + }); + + // --------------------------------------------------------------------------- + // Target accumulation + // --------------------------------------------------------------------------- + + describe("GIVEN dialog is open", () => { + let panel; + let chipRow; + + beforeEach(() => { + host = createHost(); + // Reset entity ids so the default linked target is empty, giving us a + // clean baseline for testing extra-target accumulation. + host._entityIds = []; + controller = new HistoryAnnotationDialogController(host); + controller.open(makeHover()); + panel = host.shadowRoot.querySelector("#chart-context-dialog-panel"); + chipRow = panel?.querySelector("annotation-chip-row"); + }); + + describe("WHEN value-changed fires twice with different entity_ids", () => { + beforeEach(() => { + const targetSel = panel?.querySelector("#chart-context-target"); + targetSel?.dispatchEvent( + new CustomEvent("value-changed", { + detail: { value: { entity_id: ["sensor.a"] } }, + bubbles: false, + }) + ); + targetSel?.dispatchEvent( + new CustomEvent("value-changed", { + detail: { value: { entity_id: ["sensor.b"] } }, + bubbles: false, + }) + ); + }); + + it("THEN the chip row contains chips for both entities", () => { + const chips = chipRow?.chips ?? []; + const ids = chips.map((c) => c.itemId); + expect(ids).toContain("sensor.a"); + expect(ids).toContain("sensor.b"); + }); + + it("THEN the chip row contains exactly two chips", () => { + expect((chipRow?.chips ?? []).length).toBe(2); + }); + }); + + describe("WHEN value-changed fires with entity_id then device_id", () => { + beforeEach(() => { + const targetSel = panel?.querySelector("#chart-context-target"); + targetSel?.dispatchEvent( + new CustomEvent("value-changed", { + detail: { value: { entity_id: ["sensor.x"] } }, + bubbles: false, + }) + ); + targetSel?.dispatchEvent( + new CustomEvent("value-changed", { + detail: { value: { device_id: ["device-1"] } }, + bubbles: false, + }) + ); + }); + + it("THEN the chip row contains both the entity and the device", () => { + const chips = chipRow?.chips ?? []; + const ids = chips.map((c) => c.itemId); + expect(ids).toContain("sensor.x"); + expect(ids).toContain("device-1"); + }); + }); + }); }); diff --git a/custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts b/custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts index e594164c..12182b4b 100644 --- a/custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts +++ b/custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts @@ -233,7 +233,9 @@ export class HistoryAnnotationDialogController { this._linkedTarget = current; if (this._chipRowEl) { this._chipRowEl.hass = this._host._hass ?? null; - this._chipRowEl.chips = this._buildChips(this._linkedTarget); + this._chipRowEl.chips = this._buildChips( + mergeTargetSelections(this._linkedTarget, this._target) + ); } } @@ -294,18 +296,6 @@ export class HistoryAnnotationDialogController { iconPicker.hass = this._host._hass; iconPicker.value = prefill.icon || hover?.event?.icon || "mdi:bookmark"; } - const targetSel = this._panelEl.querySelector( - "#chart-context-target" - ) as Nullable; - if (targetSel) { - targetSel.hass = this._host._hass; - targetSel.value = "{}"; - targetSel.addEventListener("value-changed", (ev) => { - this._target = normalizeTargetSelection( - (ev as CustomEvent).detail.value || {} - ); - }); - } if (messageEl) { messageEl.value = prefill.message || ""; } @@ -333,6 +323,31 @@ export class HistoryAnnotationDialogController { this._chipRowEl.chips = this._buildChips(this._linkedTarget); } + // Wire up the always-visible target selector. + const targetSel = this._panelEl.querySelector( + "#chart-context-target" + ) as Nullable; + if (targetSel) { + targetSel.hass = this._host._hass; + (targetSel as HaInputElement & { selector: unknown }).selector = { + target: {}, + }; + targetSel.addEventListener("value-changed", (ev) => { + // Merge incoming selection with existing _target so picking a second + // entity/device doesn't wipe out the first. + this._target = mergeTargetSelections( + this._target, + normalizeTargetSelection((ev as CustomEvent).detail.value || {}) + ); + if (this._chipRowEl) { + this._chipRowEl.hass = this._host._hass ?? null; + this._chipRowEl.chips = this._buildChips( + mergeTargetSelections(this._linkedTarget, this._target) + ); + } + }); + } + this.bindTargetChipActions(); const colorInput = this._panelEl.querySelector( "#chart-context-color" @@ -490,10 +505,10 @@ export class HistoryAnnotationDialogController { .context-dialog-content { display: grid; gap: 16px; padding-top: 4px; } .context-form { display: grid; gap: 16px; } .context-form-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; } - .context-form-main, .context-form-side { display: grid; gap: 16px; min-width: 0; } - .context-form-side { align-content: start; justify-items: start; } + .context-form-row { display: grid; gap: 16px; align-items: start; } + .context-form-row-message-date { grid-template-columns: minmax(0, 1fr) 220px; } + .context-form-row-icon-color { grid-template-columns: 220px auto; } .context-form-field { display: grid; gap: 6px; min-width: 0; } - .context-form-field.compact-field { justify-items: start; } .context-form-label { font-size: 0.9rem; font-weight: 600; color: var(--primary-text-color); } .context-form-help { font-size: 0.8rem; color: var(--secondary-text-color); line-height: 1.45; } .context-form-help-inline { display: inline-flex; align-items: center; gap: 6px; } @@ -510,40 +525,47 @@ export class HistoryAnnotationDialogController { .context-color-control { display: flex; align-items: center; gap: 10px; } .context-color-preview { width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--divider-color, #ccc); background: ${esc(defaultColor)}; flex: 0 0 auto; } .context-color-input { width: 56px; height: 36px; padding: 0; border: none; background: transparent; cursor: pointer; } - .context-date-input { width: 220px; max-width: 100%; } - .context-icon-input { width: 220px; max-width: 100%; } + .context-date-input { width: 100%; box-sizing: border-box; } + .context-icon-input { width: 100%; } .context-form-feedback { color: var(--error-color); font-size: 0.84rem; } .context-form-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-top: 8px; } .context-form-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-left: auto; } + @media (max-width: 600px) { + .context-form-row-message-date, + .context-form-row-icon-color { grid-template-columns: minmax(0, 1fr); } + }
-
+ +
Use a short title that will be shown in the chart tooltip and records list.
- -
Add any longer context, outcome, or note you want to keep with this data point.
- -
-
-
- -
Optionally add more entities, devices, areas, or labels that should also be linked to this annotation.
- -
-
-
-
The annotation will be placed at this exact moment on the chart.
-
+
+ +
+ +
Add any longer context, outcome, or note you want to keep with this data point.
+ +
+ +
+ +
+ +
+ +
+
Choose the icon shown for this data point in the chart and records list.
@@ -575,12 +597,6 @@ export class HistoryAnnotationDialogController { this._dialogEl.hass = this._host._hass; this._dialogEl.dialogInitialFocus = "#chart-context-message"; } - const targetSel = this._panelEl.querySelector( - "#chart-context-target" - ) as Nullable; - if (targetSel) { - targetSel.selector = { target: {} }; - } this.bindFields(hover); this._dialogEl.open = true; this._host._creatingContextAnnotation = true; diff --git a/custom_components/hass_datapoints/src/molecules/annotation-chip-row/__tests__/annotation-chip-row.spec.ts b/custom_components/hass_datapoints/src/molecules/annotation-chip-row/__tests__/annotation-chip-row.spec.ts index b083eb61..96a40ec0 100644 --- a/custom_components/hass_datapoints/src/molecules/annotation-chip-row/__tests__/annotation-chip-row.spec.ts +++ b/custom_components/hass_datapoints/src/molecules/annotation-chip-row/__tests__/annotation-chip-row.spec.ts @@ -61,13 +61,9 @@ describe("annotation-chip-row", () => { }); describe("WHEN rendered", () => { - it("THEN renders the label", () => { + it("THEN does not render an internal label (label is owned by the outer form field)", () => { expect.assertions(1); - expect( - el - .shadowRoot!.querySelector(".context-form-label") - ?.textContent?.trim() - ).toBe("Linked targets"); + expect(el.shadowRoot!.querySelector(".context-form-label")).toBeNull(); }); it("THEN renders the help text", () => { @@ -217,23 +213,23 @@ describe("annotation-chip-row", () => { }); // --------------------------------------------------------------------------- - // Custom label / text + // Custom text props // --------------------------------------------------------------------------- - describe("GIVEN a custom label", () => { + describe("GIVEN a custom emptyText", () => { beforeEach(async () => { - el = createElement({ label: "Related items" }); + el = createElement({ chips: [], emptyText: "Nothing here yet." }); await el.updateComplete; }); - describe("WHEN rendered", () => { - it("THEN shows the custom label", () => { + describe("WHEN rendered with no chips", () => { + it("THEN shows the custom empty text", () => { expect.assertions(1); expect( el - .shadowRoot!.querySelector(".context-form-label") + .shadowRoot!.querySelector(".context-form-help") ?.textContent?.trim() - ).toBe("Related items"); + ).toBe("Nothing here yet."); }); }); }); diff --git a/custom_components/hass_datapoints/src/molecules/annotation-chip-row/annotation-chip-row.ts b/custom_components/hass_datapoints/src/molecules/annotation-chip-row/annotation-chip-row.ts index afdbb616..c983af8d 100644 --- a/custom_components/hass_datapoints/src/molecules/annotation-chip-row/annotation-chip-row.ts +++ b/custom_components/hass_datapoints/src/molecules/annotation-chip-row/annotation-chip-row.ts @@ -70,7 +70,6 @@ export class AnnotationChipRow extends LitElement { render() { return html`
-
${this.chips.length > 0 ? this.helpText : this.emptyText}
diff --git a/custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.styles.ts b/custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.styles.ts index b6baf5af..5345624c 100644 --- a/custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.styles.ts +++ b/custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.styles.ts @@ -24,7 +24,7 @@ export const styles = css` } .history-target-rows { - width: calc(var(--sidebar-width-expanded) - var(--dp-spacing-lg) * 2); + width: 100%; } .sidebar-section-header { diff --git a/custom_components/hass_datapoints/src/panels/datapoints/components/panel-shell/panel-shell.styles.ts b/custom_components/hass_datapoints/src/panels/datapoints/components/panel-shell/panel-shell.styles.ts index b4a5beac..e9915e01 100644 --- a/custom_components/hass_datapoints/src/panels/datapoints/components/panel-shell/panel-shell.styles.ts +++ b/custom_components/hass_datapoints/src/panels/datapoints/components/panel-shell/panel-shell.styles.ts @@ -342,6 +342,7 @@ export const styles = css` position: absolute; top: 0; left: 0; + min-width: min(380px, 85vw); width: min(380px, 85vw); height: 100%; z-index: 10; @@ -407,4 +408,10 @@ export const styles = css` display: none; } } + + @media (max-width: 545px) { + .page-sidebar { + width: min(380px, 95vw); + } + } `; diff --git a/custom_components/hass_datapoints/src/panels/datapoints/datapoints.styles.ts b/custom_components/hass_datapoints/src/panels/datapoints/datapoints.styles.ts index da695a30..95fa5fc7 100644 --- a/custom_components/hass_datapoints/src/panels/datapoints/datapoints.styles.ts +++ b/custom_components/hass_datapoints/src/panels/datapoints/datapoints.styles.ts @@ -2114,6 +2114,7 @@ export const PANEL_HISTORY_STYLE = ` position: absolute; top: 0; left: 0; + min-width: min(380px, 85vw); width: min(380px, 85vw); height: 100%; z-index: 10; @@ -2195,6 +2196,12 @@ export const PANEL_HISTORY_STYLE = ` display: flex; } } + + @media (max-width: 545px) { + .page-sidebar { + width: min(380px, 95vw); + } + } `; export const PANEL_HISTORY_LOADING_STYLE = ` From 032dfb349238ea55a53ff99a105d28584c680c85 Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 16:22:42 +0300 Subject: [PATCH 06/14] fix(security): enforce entity-level read permissions on all data endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Authenticated non-admin users could previously request history, anomaly detection, or filtered events for any entity in the system — including ones hidden from their dashboard. This closes that hole: websocket_api.py - Add _can_read_entity(user, entity_id) helper: admin users always pass; non-admin users are checked against their HA permission policy via user.permissions.check_entity(entity_id, POLICY_READ) - ws_get_events: strip forbidden entity_ids from the filter list before hitting the store (unfiltered queries remain unchanged) - ws_get_history: reject with "unauthorized" if caller cannot read the requested entity - ws_get_anomalies: same check for both primary and comparison entity __init__.py - handle_record service: for user-initiated calls (user_id present), filter entity_ids to only those the caller is permitted to read; automation/system calls (no user_id) bypass the check conftest.py / test_websocket_api.py - Stub homeassistant.auth.permissions.const with a real POLICY_READ value - Extend _make_connection to configure entity-level permission mocks - Add DescribeCanReadEntity, DescribeWsGetEventsPermissions, DescribeWsGetHistoryPermissions, DescribeWsGetAnomaliesPermissions (15 new tests) The ha-selector target picker in the frontend already scopes its entity list to the authenticated user's permissions natively. --- .github/workflows/ci.yml | 4 +- custom_components/hass_datapoints/__init__.py | 13 +- .../hass_datapoints/websocket_api.py | 46 ++- tests/conftest.py | 9 + tests/test_websocket_api.py | 306 +++++++++++++++++- 5 files changed, 372 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5feace8e..f67636a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,8 +101,8 @@ jobs: with: python-version: "3.12" - - name: Install pytest - run: pip install pytest pytest-asyncio --quiet + - name: Install Python dependencies + run: pip install -r requirements_dev.txt --quiet - name: Run Python tests run: pytest tests/ -v diff --git a/custom_components/hass_datapoints/__init__.py b/custom_components/hass_datapoints/__init__.py index 3ebe40df..cd225c17 100644 --- a/custom_components/hass_datapoints/__init__.py +++ b/custom_components/hass_datapoints/__init__.py @@ -154,10 +154,21 @@ async def handle_record(call: ServiceCall) -> None: automation_id = _find_automation_id(hass, call.context) + # For user-initiated calls, filter entity_ids to those the caller is + # permitted to read. Automation / system calls (no user_id) bypass + # this check so they can tag events with any entity. + entity_ids = call.data.get(ATTR_ENTITY_IDS) + if call.context.user_id: + user = await hass.auth.async_get_user(call.context.user_id) + if user is not None and not user.is_admin and entity_ids: + entity_ids = [ + eid for eid in entity_ids if ws_api._can_read_entity(user, eid) + ] + event_data = await store.async_record( message=call.data[ATTR_MESSAGE], annotation=call.data.get(ATTR_ANNOTATION), - entity_ids=call.data.get(ATTR_ENTITY_IDS), + entity_ids=entity_ids, device_ids=call.data.get(ATTR_DEVICE_IDS), area_ids=call.data.get(ATTR_AREA_IDS), label_ids=call.data.get(ATTR_LABEL_IDS), diff --git a/custom_components/hass_datapoints/websocket_api.py b/custom_components/hass_datapoints/websocket_api.py index 34c4de15..bd5cec09 100644 --- a/custom_components/hass_datapoints/websocket_api.py +++ b/custom_components/hass_datapoints/websocket_api.py @@ -8,6 +8,7 @@ from datetime import UTC, datetime import voluptuous as vol +from homeassistant.auth.permissions.const import POLICY_READ from homeassistant.components import websocket_api from homeassistant.components.recorder import get_instance from homeassistant.core import HomeAssistant, callback @@ -121,10 +122,17 @@ async def ws_get_events( ) -> None: """Return recorded events, with optional time/entity filters.""" store = hass.data[DOMAIN]["store"] + entity_ids = msg.get("entity_ids") + # For non-admin users, remove any entity IDs they are not permitted to read + # so the store never returns events tagged with inaccessible entities. + if entity_ids is not None and not connection.user.is_admin: + entity_ids = [ + eid for eid in entity_ids if _can_read_entity(connection.user, eid) + ] events = store.get_events( start=msg.get("start_time"), end=msg.get("end_time"), - entity_ids=msg.get("entity_ids"), + entity_ids=entity_ids, ) connection.send_result(msg["id"], {"events": events}) @@ -308,6 +316,22 @@ def _require_admin(connection: websocket_api.ActiveConnection, msg: dict) -> boo return True +def _can_read_entity(user, entity_id: str) -> bool: + """Return True if *user* has read permission for *entity_id*. + + Admin users always pass. Non-admin users are checked against the HA + permission policy attached to their account. Any unexpected error + (e.g. missing permissions object on a system user) is treated as + permissive so we don't accidentally block valid callers. + """ + if user.is_admin: + return True + try: + return bool(user.permissions.check_entity(entity_id, POLICY_READ)) + except Exception: # noqa: BLE001 + return True + + def _valid_uuid(value: str) -> str: """Voluptuous validator — raises Invalid if value is not a well-formed UUID.""" try: @@ -421,6 +445,11 @@ async def ws_get_history( ) -> None: """Return (optionally downsampled) entity history as [[timeMs, value], ...].""" entity_id: str = msg["entity_id"] + if not _can_read_entity(connection.user, entity_id): + connection.send_error( + msg["id"], "unauthorized", "Access to this entity is not permitted" + ) + return start_time: str = msg["start_time"] end_time: str = msg["end_time"] interval: str = msg["interval"] @@ -553,6 +582,21 @@ async def ws_get_anomalies( ) -> None: """Run backend anomaly detection for a single entity and return clusters.""" entity_id: str = msg["entity_id"] + if not _can_read_entity(connection.user, entity_id): + connection.send_error( + msg["id"], "unauthorized", "Access to this entity is not permitted" + ) + return + comparison_entity_id: str | None = msg.get("comparison_entity_id") + if comparison_entity_id and not _can_read_entity( + connection.user, comparison_entity_id + ): + connection.send_error( + msg["id"], + "unauthorized", + "Access to the comparison entity is not permitted", + ) + return start_time: str = msg["start_time"] end_time: str = msg["end_time"] diff --git a/tests/conftest.py b/tests/conftest.py index 0d619d47..85a9f663 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,6 +47,15 @@ class _Unauthorized(Exception): _ha_exceptions.Unauthorized = _Unauthorized _stub("homeassistant.exceptions", _ha_exceptions) +# -- homeassistant.auth ------------------------------------------------------- +# POLICY_READ must be a real string constant so _can_read_entity can pass it +# as an argument to user.permissions.check_entity in tests. +_ha_auth_permissions_const = MagicMock() +_ha_auth_permissions_const.POLICY_READ = "read" +_stub("homeassistant.auth", MagicMock()) +_stub("homeassistant.auth.permissions", MagicMock()) +_stub("homeassistant.auth.permissions.const", _ha_auth_permissions_const) + # -- homeassistant core ------------------------------------------------------- _ha_core = MagicMock() _ha_core.HomeAssistant = MagicMock diff --git a/tests/test_websocket_api.py b/tests/test_websocket_api.py index 798588f4..34bc71d9 100644 --- a/tests/test_websocket_api.py +++ b/tests/test_websocket_api.py @@ -22,12 +22,15 @@ _VALID_ANOMALY_OVERLAP_MODES, _VALID_ANOMALY_SENSITIVITY, _VALID_TREND_METHODS, + _can_read_entity, _normalize_recorder_timestamp, _require_admin, ws_clear_cache, ws_delete_dev_events, ws_delete_event, + ws_get_anomalies, ws_get_events, + ws_get_history, ws_update_event, ) @@ -97,12 +100,26 @@ def _make_hass(store: object) -> MagicMock: return hass -def _make_connection(*, is_admin: bool = True) -> MagicMock: - """Return a mock ActiveConnection. Defaults to an admin user.""" +def _make_connection( + *, + is_admin: bool = True, + allowed_entities: list[str] | None = None, +) -> MagicMock: + """Return a mock ActiveConnection. + + *allowed_entities* is only relevant for non-admin users. When provided, + ``user.permissions.check_entity`` returns True only for listed entity IDs. + When omitted for a non-admin user, all entity checks return False (no access). + """ connection = MagicMock() connection.send_result = MagicMock() connection.send_error = MagicMock() connection.user.is_admin = is_admin + if not is_admin: + allowed = set(allowed_entities or []) + connection.user.permissions.check_entity.side_effect = ( + lambda entity_id, _policy: entity_id in allowed + ) return connection @@ -532,3 +549,288 @@ def test_GIVEN_message_exceeds_limit_WHEN_validated_THEN_raises(self): validator = vol.Length(max=_MAX_LEN_MESSAGE) with pytest.raises(vol.Invalid): validator("x" * (_MAX_LEN_MESSAGE + 1)) + + +# --------------------------------------------------------------------------- +# _can_read_entity — unit tests for the permission helper +# --------------------------------------------------------------------------- + + +class DescribeCanReadEntity: + def test_GIVEN_admin_user_WHEN_called_THEN_returns_true_for_any_entity(self): + user = MagicMock() + user.is_admin = True + assert _can_read_entity(user, "sensor.secret") is True + + def test_GIVEN_non_admin_with_permission_WHEN_called_THEN_returns_true(self): + user = MagicMock() + user.is_admin = False + user.permissions.check_entity.return_value = True + assert _can_read_entity(user, "sensor.allowed") is True + + def test_GIVEN_non_admin_without_permission_WHEN_called_THEN_returns_false(self): + user = MagicMock() + user.is_admin = False + user.permissions.check_entity.return_value = False + assert _can_read_entity(user, "sensor.forbidden") is False + + def test_GIVEN_permissions_raises_WHEN_called_THEN_returns_true_permissive(self): + """Unexpected permission errors should not block the caller.""" + user = MagicMock() + user.is_admin = False + user.permissions.check_entity.side_effect = RuntimeError("boom") + assert _can_read_entity(user, "sensor.any") is True + + +# --------------------------------------------------------------------------- +# ws_get_events — entity permission filtering +# --------------------------------------------------------------------------- + + +class DescribeWsGetEventsPermissions: + async def test_GIVEN_admin_user_with_entity_filter_WHEN_called_THEN_passes_filter_unchanged( + self, + ): + store = MagicMock() + store.get_events.return_value = [] + hass = _make_hass(store) + connection = _make_connection(is_admin=True) + msg = { + "id": 1, + "type": f"{DOMAIN}/events", + "entity_ids": ["sensor.a", "sensor.b"], + } + + await ws_get_events(hass, connection, msg) + + store.get_events.assert_called_once_with( + start=None, end=None, entity_ids=["sensor.a", "sensor.b"] + ) + + async def test_GIVEN_non_admin_user_WHEN_entity_ids_include_forbidden_THEN_forbidden_stripped( + self, + ): + store = MagicMock() + store.get_events.return_value = [] + hass = _make_hass(store) + # sensor.a is allowed, sensor.secret is not + connection = _make_connection(is_admin=False, allowed_entities=["sensor.a"]) + msg = { + "id": 1, + "type": f"{DOMAIN}/events", + "entity_ids": ["sensor.a", "sensor.secret"], + } + + await ws_get_events(hass, connection, msg) + + store.get_events.assert_called_once_with( + start=None, end=None, entity_ids=["sensor.a"] + ) + + async def test_GIVEN_non_admin_user_WHEN_all_entity_ids_forbidden_THEN_empty_filter_passed( + self, + ): + store = MagicMock() + store.get_events.return_value = [] + hass = _make_hass(store) + connection = _make_connection(is_admin=False, allowed_entities=[]) + msg = { + "id": 1, + "type": f"{DOMAIN}/events", + "entity_ids": ["sensor.secret"], + } + + await ws_get_events(hass, connection, msg) + + store.get_events.assert_called_once_with(start=None, end=None, entity_ids=[]) + + async def test_GIVEN_non_admin_user_WHEN_no_entity_filter_THEN_store_called_with_none( + self, + ): + """No entity_ids in message → pass None to store (no filtering applied).""" + store = MagicMock() + store.get_events.return_value = [] + hass = _make_hass(store) + connection = _make_connection(is_admin=False, allowed_entities=[]) + msg = {"id": 1, "type": f"{DOMAIN}/events"} + + await ws_get_events(hass, connection, msg) + + store.get_events.assert_called_once_with(start=None, end=None, entity_ids=None) + + +# --------------------------------------------------------------------------- +# ws_get_history — entity permission check +# --------------------------------------------------------------------------- + + +def _make_hass_for_history() -> MagicMock: + """hass mock whose recorder instance has an awaitable async_add_executor_job.""" + import custom_components.hass_datapoints.websocket_api as ws_mod # noqa: PLC0415 + + hass = MagicMock() + hass.data = {DOMAIN: {}} + recorder_instance = MagicMock() + recorder_instance.async_add_executor_job = AsyncMock(return_value=[]) + # get_instance is imported at module level; patch it on the module object. + ws_mod.get_instance = MagicMock(return_value=recorder_instance) + return hass + + +class DescribeWsGetHistoryPermissions: + async def test_GIVEN_admin_user_WHEN_entity_requested_THEN_proceeds(self): + hass = _make_hass_for_history() + connection = _make_connection(is_admin=True) + msg = { + "id": 1, + "entity_id": "sensor.secret", + "start_time": "2024-01-01T00:00:00+00:00", + "end_time": "2024-01-02T00:00:00+00:00", + "interval": "raw", + "aggregate": "mean", + } + + await ws_get_history(hass, connection, msg) + + connection.send_error.assert_not_called() + connection.send_result.assert_called_once() + + async def test_GIVEN_non_admin_user_with_permission_WHEN_called_THEN_proceeds(self): + hass = _make_hass_for_history() + connection = _make_connection( + is_admin=False, allowed_entities=["sensor.allowed"] + ) + msg = { + "id": 1, + "entity_id": "sensor.allowed", + "start_time": "2024-01-01T00:00:00+00:00", + "end_time": "2024-01-02T00:00:00+00:00", + "interval": "raw", + "aggregate": "mean", + } + + await ws_get_history(hass, connection, msg) + + connection.send_error.assert_not_called() + connection.send_result.assert_called_once() + + async def test_GIVEN_non_admin_user_without_permission_WHEN_called_THEN_sends_unauthorized( + self, + ): + hass = _make_hass_for_history() + connection = _make_connection(is_admin=False, allowed_entities=[]) + msg = { + "id": 1, + "entity_id": "sensor.secret", + "start_time": "2024-01-01T00:00:00+00:00", + "end_time": "2024-01-02T00:00:00+00:00", + "interval": "raw", + "aggregate": "mean", + } + + await ws_get_history(hass, connection, msg) + + connection.send_result.assert_not_called() + connection.send_error.assert_called_once() + error_code = connection.send_error.call_args[0][1] + assert error_code == "unauthorized" + + +# --------------------------------------------------------------------------- +# ws_get_anomalies — entity permission check +# --------------------------------------------------------------------------- + + +def _make_hass_for_anomalies() -> MagicMock: + """hass mock suitable for ws_get_anomalies (cache absent, executor returns []).""" + import custom_components.hass_datapoints.websocket_api as ws_mod # noqa: PLC0415 + + hass = MagicMock() + hass.data = {DOMAIN: {}} # no anomaly_cache + recorder_instance = MagicMock() + recorder_instance.async_add_executor_job = AsyncMock(return_value=[]) + ws_mod.get_instance = MagicMock(return_value=recorder_instance) + hass.async_add_executor_job = AsyncMock(return_value=[]) + return hass + + +def _anomaly_msg(entity_id: str, comparison_entity_id: str | None = None) -> dict: + msg = { + "id": 1, + "entity_id": entity_id, + "start_time": "2024-01-01T00:00:00+00:00", + "end_time": "2024-01-02T00:00:00+00:00", + "anomaly_methods": ["iqr"], + "anomaly_sensitivity": "medium", + "anomaly_overlap_mode": "all", + "anomaly_rate_window": "1h", + "anomaly_zscore_window": "24h", + "anomaly_persistence_window": "1h", + "trend_method": "rolling_average", + "trend_window": "24h", + "comparison_time_offset_ms": 0, + } + if comparison_entity_id: + msg["comparison_entity_id"] = comparison_entity_id + msg["comparison_start_time"] = "2024-01-01T00:00:00+00:00" + msg["comparison_end_time"] = "2024-01-02T00:00:00+00:00" + return msg + + +class DescribeWsGetAnomaliesPermissions: + async def test_GIVEN_non_admin_without_permission_WHEN_called_THEN_sends_unauthorized( + self, + ): + hass = _make_hass_for_anomalies() + connection = _make_connection(is_admin=False, allowed_entities=[]) + + await ws_get_anomalies(hass, connection, _anomaly_msg("sensor.secret")) + + connection.send_result.assert_not_called() + connection.send_error.assert_called_once() + assert connection.send_error.call_args[0][1] == "unauthorized" + + async def test_GIVEN_admin_WHEN_called_THEN_proceeds(self): + hass = _make_hass_for_anomalies() + connection = _make_connection(is_admin=True) + + await ws_get_anomalies(hass, connection, _anomaly_msg("sensor.any")) + + connection.send_error.assert_not_called() + connection.send_result.assert_called_once() + + async def test_GIVEN_non_admin_with_primary_permission_but_not_comparison_WHEN_called_THEN_sends_unauthorized( + self, + ): + hass = _make_hass_for_anomalies() + connection = _make_connection( + is_admin=False, allowed_entities=["sensor.primary"] + ) + + await ws_get_anomalies( + hass, + connection, + _anomaly_msg("sensor.primary", comparison_entity_id="sensor.forbidden"), + ) + + connection.send_result.assert_not_called() + connection.send_error.assert_called_once() + assert connection.send_error.call_args[0][1] == "unauthorized" + + async def test_GIVEN_non_admin_with_both_permissions_WHEN_called_THEN_proceeds( + self, + ): + hass = _make_hass_for_anomalies() + connection = _make_connection( + is_admin=False, + allowed_entities=["sensor.primary", "sensor.comparison"], + ) + + await ws_get_anomalies( + hass, + connection, + _anomaly_msg("sensor.primary", comparison_entity_id="sensor.comparison"), + ) + + connection.send_error.assert_not_called() + connection.send_result.assert_called_once() From ff72284fa78984086f57907a0686c5ac4752f93c Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 16:27:26 +0300 Subject: [PATCH 07/14] chore(tooling): add version:patch/minor/major scripts to package.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend bump-version.sh to accept a bump type positional argument (patch | minor | major, defaulting to patch for backwards compatibility). Wire up three pnpm scripts so releases can be cut with: pnpm version:patch # 0.4.1 → 0.4.2 pnpm version:minor # 0.4.1 → 0.5.0 pnpm version:major # 0.4.1 → 1.0.0 All three support --dry-run passthrough. Additionally bumps the version up to v0.4.2 --- .../hass_datapoints/manifest.json | 2 +- package.json | 5 ++++- scripts/bump-version.sh | 20 +++++++++++-------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/custom_components/hass_datapoints/manifest.json b/custom_components/hass_datapoints/manifest.json index 096ed3bf..4c5a07c4 100644 --- a/custom_components/hass_datapoints/manifest.json +++ b/custom_components/hass_datapoints/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/buggedcom/hass-datapoints/issues", "requirements": [], - "version": "0.4.1" + "version": "0.4.2" } diff --git a/package.json b/package.json index cef7ddc7..9df3bff8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hass-datapoints", - "version": "0.4.1", + "version": "0.4.2", "private": true, "description": "Build and development entrypoints for the hass-datapoints Home Assistant integration.", "packageManager": "pnpm@10.33.0", @@ -8,6 +8,9 @@ "node": ">=24.0.0" }, "scripts": { + "version:patch": "bash scripts/bump-version.sh patch", + "version:minor": "bash scripts/bump-version.sh minor", + "version:major": "bash scripts/bump-version.sh major", "build": "bash scripts/build.sh", "test": "vitest run", "test:watch": "vitest", diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 299e87db..cd894789 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -1,13 +1,12 @@ #!/usr/bin/env bash -# bump-version.sh — Increment the patch segment of the version in +# bump-version.sh — Increment the version in # custom_components/hass_datapoints/manifest.json (canonical source) # package.json (kept in sync) # -# Usage: bash scripts/bump-version.sh [--dry-run] +# Usage: bash scripts/bump-version.sh [patch|minor|major] [--dry-run] # -# Reads the current version from manifest.json, bumps the PATCH component by 1, -# and writes the new version back to both files. Prints the new version string -# to stdout so callers can capture it. +# Bump type defaults to "patch" when omitted. +# Prints the new version string to stdout so callers can capture it. set -euo pipefail @@ -17,11 +16,13 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" MANIFEST="$REPO_ROOT/custom_components/hass_datapoints/manifest.json" PACKAGE="$REPO_ROOT/package.json" +BUMP_TYPE="patch" DRY_RUN=0 while [[ $# -gt 0 ]]; do case "$1" in + patch|minor|major) BUMP_TYPE="$1" ;; --dry-run) DRY_RUN=1 ;; - *) echo "Unknown argument: $1" >&2; exit 1 ;; + *) echo "Usage: bump-version.sh [patch|minor|major] [--dry-run]" >&2; exit 1 ;; esac shift done @@ -40,8 +41,11 @@ if [[ -z "$MAJOR" || -z "$MINOR" || -z "$PATCH" ]]; then exit 1 fi -NEW_PATCH=$(( PATCH + 1 )) -NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}" +case "$BUMP_TYPE" in + major) NEW_VERSION="$(( MAJOR + 1 )).0.0" ;; + minor) NEW_VERSION="${MAJOR}.$(( MINOR + 1 )).0" ;; + patch) NEW_VERSION="${MAJOR}.${MINOR}.$(( PATCH + 1 ))" ;; +esac echo "[bump-version] $CURRENT_VERSION → $NEW_VERSION" From de895cc0f3180eed9d9dc61ea57122475e8b1d26 Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 16:32:45 +0300 Subject: [PATCH 08/14] fix(deps): override lodash to ^4.18.0 to patch prototype pollution (GHSA) lodash <4.18.0 is vulnerable to Prototype Pollution via an array-path bypass in _.unset and _.omit. The fixed version is 4.18.0. lodash 4.17.23 was pulled in transitively by @storybook/test 8.6.15. Adding a pnpm.overrides entry forces all dependents to resolve to ^4.18.0 (currently 4.18.1). Resolves: buggedcom/HASS-Data-Points Dependabot alert #1 #2 --- package.json | 5 +++++ pnpm-lock.yaml | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9df3bff8..07a9d85f 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,11 @@ "vite": "8.0.5", "vitest": "3.2.4" }, + "pnpm": { + "overrides": { + "lodash": "4.18.0" + } + }, "dependencies": { "@kipk/load-ha-components": "1.0.3", "@lit/context": "1.1.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25a03bef..d0b6f5c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + lodash: 4.18.0 + importers: .: dependencies: @@ -3947,11 +3950,12 @@ packages: } engines: { node: ">=10" } - lodash@4.17.23: + lodash@4.18.0: resolution: { - integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==, + integrity: sha512-l1mfj2atMqndAHI3ls7XqPxEjV2J9ZkcNyHpoZA3r2T1LLwDB69jgkMWh71YKwhBbK0G2f4WSn05ahmQXVxupA==, } + deprecated: Bad release. Please use lodash@4.17.21 instead. log-symbols@4.1.0: resolution: @@ -6189,7 +6193,7 @@ snapshots: chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 - lodash: 4.17.23 + lodash: 4.18.0 redent: 3.0.0 "@testing-library/jest-dom@6.9.1": @@ -7937,7 +7941,7 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash@4.17.23: {} + lodash@4.18.0: {} log-symbols@4.1.0: dependencies: From 22ac2c03abbc48a438c514aa7a4a45dbe31a459c Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 16:41:15 +0300 Subject: [PATCH 09/14] fix(ci): add explicit least-privilege permissions to all workflows (CodeQL) Addresses actions/missing-workflow-permissions (CodeQL rule) across ci.yml and commit-lint.yml. ci.yml - Add top-level `permissions: contents: read` so every job without its own block is locked to read-only rather than inheriting the organisation/repository default (which may be read-write on repos created before Feb 2023). - Add `contents: read` to the build job's existing block; a job-level block overrides the workflow-level block entirely, so omitting it would leave checkout with none. - The build job retains `id-token: write` and `attestations: write` for OIDC-based build provenance attestation. commit-lint.yml - Add job-level `permissions: contents: read, pull-requests: read`; wagoid/commitlint-github-action requires both to fetch the PR commit list via the GitHub API. --- .github/{commitlint.config.js => commitlint.config.mjs} | 4 +++- .github/workflows/ci.yml | 7 +++++++ .github/workflows/commit-lint.yml | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) rename .github/{commitlint.config.js => commitlint.config.mjs} (83%) diff --git a/.github/commitlint.config.js b/.github/commitlint.config.mjs similarity index 83% rename from .github/commitlint.config.js rename to .github/commitlint.config.mjs index 5ea5fd56..4be77761 100644 --- a/.github/commitlint.config.js +++ b/.github/commitlint.config.mjs @@ -1,5 +1,5 @@ /** @type {import('@commitlint/types').UserConfig} */ -module.exports = { +export default { extends: ["@commitlint/config-conventional"], rules: { // Allow both `doc` (historically used here) and `docs` (conventional spec) @@ -22,5 +22,7 @@ module.exports = { ], // Allow any subject casing — sentence case reads better for this project "subject-case": [0], + // Allow long body lines for detailed commit descriptions + "body-max-line-length": [0], }, }; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f67636a1..08e9232c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,12 @@ on: branches: [main] workflow_dispatch: +# Default to read-only for all jobs. Jobs that need additional permissions +# (e.g. build provenance attestation) declare them explicitly at the job level, +# which fully overrides this block for that job. +permissions: + contents: read + jobs: # ══════════════════════════════════════════════════════════════════════════════ # Stage 1 — Lint & structural checks (all run in parallel) @@ -197,6 +203,7 @@ jobs: needs: [test-python, test-unit, test-storybook, test-chromatic, hacs] runs-on: ubuntu-latest permissions: + contents: read id-token: write attestations: write steps: diff --git a/.github/workflows/commit-lint.yml b/.github/workflows/commit-lint.yml index a801d443..24d498ba 100644 --- a/.github/workflows/commit-lint.yml +++ b/.github/workflows/commit-lint.yml @@ -9,6 +9,9 @@ jobs: commitlint: name: Validate commit messages runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read steps: - uses: actions/checkout@v4 with: @@ -17,6 +20,6 @@ jobs: - name: Lint commit messages uses: wagoid/commitlint-github-action@v6 with: - configFile: .github/commitlint.config.js + configFile: .github/commitlint.config.mjs failOnWarnings: false helpURL: https://www.conventionalcommits.org/en/v1.0.0/ From 77571b3bfe5f173d9e7573875039904b68412656 Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 16:43:01 +0300 Subject: [PATCH 10/14] fix(ci): exclude generated JS bundle from CodeQL analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hass-datapoints-cards.js is a Vite build artifact. Scanning it causes false positives — most notably lit-html's internal /-->/ template-parser regex being flagged as a bad HTML comment filter (CodeQL rule js/bad-regexp-for-html-filtering). The regex is used to locate comment boundaries in template strings, not to sanitise user input, so the finding has no actionable remediation path. Adds .github/codeql/codeql-config.yml with a paths-ignore entry for the bundle. The TypeScript sources under src/ are still analysed. --- .github/codeql/codeql-config.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/codeql/codeql-config.yml diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 00000000..6f4a5816 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,9 @@ +name: "CodeQL config" + +# Exclude the Vite-built JS bundle from analysis. It is generated output — +# the authoritative source is the TypeScript under src/, which CodeQL analyses +# directly. Scanning the bundle produces false positives (e.g. lit-html's +# internal /-->/ template-parser regex flagged as a bad HTML sanitiser) with +# no actionable remediation path. +paths-ignore: + - "custom_components/hass_datapoints/hass-datapoints-cards.js" From 41dd88df2676b811ea0ba4ba86e5785fae14ef42 Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 16:51:14 +0300 Subject: [PATCH 11/14] fix(security): escape ha-stubs field label/value via Lit html+render Replace raw innerHTML concatenation in HaFieldStub._render() with Lit's html tagged template + render(), which auto-escapes all text interpolations into DOM text nodes rather than raw HTML. Styles are injected once via adoptedStyleSheets to sidestep Lit's restriction on interpolating inside -
- ${label ? `${label}` : ""} - ${hasValue ? `${value}` : ""} - -
- `; + render( + html` +
+ ${label ? html`${label}` : ""} + ${hasValue ? html`${value}` : ""} + +
+ `, + this.shadowRoot + ); } } From 70c6f00c4bae3edffea2f7cbb6c0572ce442d65e Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 17:01:53 +0300 Subject: [PATCH 12/14] fix(security): replace innerHTML with adoptedStyleSheets + Lit render in stubs Apply the same adoptedStyleSheets + Lit html/render pattern to all remaining dynamic innerHTML calls in ha-stubs.ts: - HaIconStub: extract HA_ICON_STYLES constant, inject once via adoptedStyleSheets in constructor, render SVG via Lit html template (path attribute set via setAttribute, not string concatenation) - ha-svg-icon: inherit base class stylesheet, rewrite _render() with Lit, eliminating the public path property injection vector - ha-icon-button: extract HA_ICON_BUTTON_STYLES, use adoptedStyleSheets plus DOM createElement for the slot instead of innerHTML - ha-switch: extract HA_SWITCH_STYLES, inject once via adoptedStyleSheets in constructor, rewrite _renderSwitch() with Lit html template so the dynamic CSS class uses a safe text binding not string concatenation Fix annotation-dialog.ts CSS injection: defaultColor was interpolated directly into a style block where esc() only handles HTML entities not CSS. Remove the background rule as bindFields() already calls syncColor() which sets colorPreview.style.background from the safe input type color element. --- .../hass_datapoints/hass-datapoints-cards.js | 2 +- .../annotation-dialog/annotation-dialog.ts | 2 +- .../src/test-support/ha-stubs.ts | 210 ++++++++++-------- 3 files changed, 118 insertions(+), 96 deletions(-) diff --git a/custom_components/hass_datapoints/hass-datapoints-cards.js b/custom_components/hass_datapoints/hass-datapoints-cards.js index f3f2456e..6b97f67b 100644 --- a/custom_components/hass_datapoints/hass-datapoints-cards.js +++ b/custom_components/hass_datapoints/hass-datapoints-cards.js @@ -15228,7 +15228,7 @@ .context-chip-remove:hover { background: color-mix(in srgb, currentColor 12%, transparent); } .context-chip-remove ha-icon { --mdc-icon-size: 12px; pointer-events: none; } .context-color-control { display: flex; align-items: center; gap: 10px; } - .context-color-preview { width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--divider-color, #ccc); background: ${esc(defaultColor)}; flex: 0 0 auto; } + .context-color-preview { width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--divider-color, #ccc); flex: 0 0 auto; } .context-color-input { width: 56px; height: 36px; padding: 0; border: none; background: transparent; cursor: pointer; } .context-date-input { width: 100%; box-sizing: border-box; } .context-icon-input { width: 100%; } diff --git a/custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts b/custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts index 12182b4b..1d15d9b1 100644 --- a/custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts +++ b/custom_components/hass_datapoints/src/components/annotation-dialog/annotation-dialog.ts @@ -523,7 +523,7 @@ export class HistoryAnnotationDialogController { .context-chip-remove:hover { background: color-mix(in srgb, currentColor 12%, transparent); } .context-chip-remove ha-icon { --mdc-icon-size: 12px; pointer-events: none; } .context-color-control { display: flex; align-items: center; gap: 10px; } - .context-color-preview { width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--divider-color, #ccc); background: ${esc(defaultColor)}; flex: 0 0 auto; } + .context-color-preview { width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--divider-color, #ccc); flex: 0 0 auto; } .context-color-input { width: 56px; height: 36px; padding: 0; border: none; background: transparent; cursor: pointer; } .context-date-input { width: 100%; box-sizing: border-box; } .context-icon-input { width: 100%; } diff --git a/custom_components/hass_datapoints/src/test-support/ha-stubs.ts b/custom_components/hass_datapoints/src/test-support/ha-stubs.ts index e2399321..2db75cf0 100644 --- a/custom_components/hass_datapoints/src/test-support/ha-stubs.ts +++ b/custom_components/hass_datapoints/src/test-support/ha-stubs.ts @@ -7,7 +7,7 @@ * Containers pass child content through a . */ -import { html, render } from "lit"; +import { html, svg, render } from "lit"; import * as mdi from "@mdi/js"; type MdiIconModule = RecordWithStringValues; @@ -54,13 +54,44 @@ function resolveMdiPath( // Icon elements — ha-icon, ha-state-icon, ha-svg-icon // --------------------------------------------------------------------------- +const HA_ICON_STYLES = ` + :host { + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--mdc-icon-size, 24px); + height: var(--mdc-icon-size, 24px); + flex-shrink: 0; + } + svg { + width: 100%; + height: 100%; + display: block; + fill: currentColor; + } +`; + class HaIconStub extends HTMLElement { protected _icon: Nullable; + // Shared stylesheet — created once per class, applied to every icon shadow root. + private static _sheet: CSSStyleSheet | null = null; + + private static _getSheet(): CSSStyleSheet { + if (!HaIconStub._sheet) { + HaIconStub._sheet = new CSSStyleSheet(); + HaIconStub._sheet.replaceSync(HA_ICON_STYLES); + } + return HaIconStub._sheet; + } + constructor() { super(); this.attachShadow({ mode: "open" }); this._icon = null; + if (this.shadowRoot) { + this.shadowRoot.adoptedStyleSheets = [HaIconStub._getSheet()]; + } } connectedCallback() { @@ -95,31 +126,17 @@ class HaIconStub extends HTMLElement { if (!this.shadowRoot) { return; } - this.shadowRoot.innerHTML = ` - - `, + this.shadowRoot + ); } } @@ -174,26 +191,20 @@ customElements.define( _render(): void { const path = this.path || null; - const fallback = ``; if (!this.shadowRoot) { return; } - this.shadowRoot.innerHTML = ` - - - `; + // Styles are inherited via adoptedStyleSheets set in HaIconStub constructor. + render( + svg``, + this.shadowRoot + ); } } ); @@ -202,6 +213,23 @@ customElements.define( // Button elements — ha-icon-button // --------------------------------------------------------------------------- +const HA_ICON_BUTTON_STYLES = ` + :host { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 50%; + background: transparent; + cursor: pointer; + box-sizing: border-box; + transition: background 120ms; + } + :host(:hover) { background: rgba(128, 128, 128, 0.12); } + ::slotted(*), slot { pointer-events: none; } +`; + customElements.define( "ha-icon-button", class extends HTMLElement { @@ -211,25 +239,10 @@ customElements.define( if (!this.shadowRoot) { return; } - this.shadowRoot.innerHTML = ` - - - `; + const sheet = new CSSStyleSheet(); + sheet.replaceSync(HA_ICON_BUTTON_STYLES); + this.shadowRoot.adoptedStyleSheets = [sheet]; + this.shadowRoot.appendChild(document.createElement("slot")); } } ); @@ -382,6 +395,37 @@ for (const tag of FIELD_ELEMENTS) { // Switch / toggle — ha-switch // --------------------------------------------------------------------------- +const HA_SWITCH_STYLES = ` + :host { + display: inline-flex; + align-items: center; + cursor: pointer; + } + .track { + width: 36px; + height: 20px; + border-radius: 10px; + background: var(--disabled-text-color, #bdbdbd); + position: relative; + transition: background 120ms; + } + :host([checked]) .track, + .track.on { background: var(--primary-color, #03a9f4); } + .thumb { + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + border-radius: 50%; + background: #fff; + box-shadow: 0 1px 3px rgba(0,0,0,0.3); + transition: transform 120ms; + } + :host([checked]) .thumb, + .track.on .thumb { transform: translateX(16px); } +`; + if (!customElements.get("ha-switch")) { customElements.define( "ha-switch", @@ -391,6 +435,11 @@ if (!customElements.get("ha-switch")) { constructor() { super(); this.attachShadow({ mode: "open" }); + if (this.shadowRoot) { + const sheet = new CSSStyleSheet(); + sheet.replaceSync(HA_SWITCH_STYLES); + this.shadowRoot.adoptedStyleSheets = [sheet]; + } this._renderSwitch(); } @@ -407,41 +456,14 @@ if (!customElements.get("ha-switch")) { if (!this.shadowRoot) { return; } - this.shadowRoot.innerHTML = ` - -
-
-
- `; + render( + html` +
+
+
+ `, + this.shadowRoot + ); } } ); From e15bc9b9ab3cd93e50910333f70b449fa9d5081c Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 17:25:24 +0300 Subject: [PATCH 13/14] refactor: migrate esc() innerHTML patterns to Lit html+render throughout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all uses of the custom esc() HTML-escaping function with Lit's html tagged template + render(), which auto-escapes every ${} binding into a DOM text node or setAttribute() call — no manual escaping needed. Files changed: - chart-interaction.ts: buildTooltipRelatedChips now returns TemplateResult|null; buildAnnotationTooltip, showTooltip (ttEntities), ttSeries multi-row block, and showLineChartCrosshair points all use render(html`...`, el) — removes 8 esc() calls - chart-dom.ts: buildAxisMarkup returns TemplateResult; leftEl/rightEl use render(); axisTextStyle drops esc() — removes 3 esc() calls - history-chart.ts: overlayEl, _renderLegend (+ removes querySelectorAll event-binding loop in favour of inline @click), iconEl, and the labelsHtml axis loop all migrated — removes 6 esc() calls - sensor-chart.ts: annotation icon el migrated — removes 1 esc() call - ha-components.ts: confirmDestructiveAction dialog uses render() with inline @click handlers; finish() moved before render() — removes 3 esc() calls - dev-tool.ts: _render() uses adoptedStyleSheets + render() — removes 1 esc() call - history-targets.ts: removed esc() wrappers from inside Lit html templates (were causing double-escaping) — removes 2 esc() calls Test fix: chart-interaction.spec.ts renders the TemplateResult from buildTooltipRelatedChips into a container and checks textContent. esc() remains in format.ts (annotation-dialog.ts still uses it for HTML attribute values in a non-Lit template) and keeps its own tests. --- .../hass_datapoints/hass-datapoints-cards.js | 458 ++++++++++-------- .../src/cards/dev-tool/dev-tool.ts | 80 +-- .../history/history-chart/history-chart.ts | 104 ++-- .../cards/sensor/sensor-chart/sensor-chart.ts | 11 +- .../src/charts/utils/chart-dom.ts | 63 ++- .../chart/__tests__/chart-interaction.spec.ts | 9 +- .../src/lib/chart/chart-interaction.ts | 185 ++++--- .../src/lib/ha/ha-components.ts | 119 ++--- .../history-targets/history-targets.ts | 5 +- 9 files changed, 613 insertions(+), 421 deletions(-) diff --git a/custom_components/hass_datapoints/hass-datapoints-cards.js b/custom_components/hass_datapoints/hass-datapoints-cards.js index 6b97f67b..d2abb6ec 100644 --- a/custom_components/hass_datapoints/hass-datapoints-cards.js +++ b/custom_components/hass_datapoints/hass-datapoints-cards.js @@ -5779,37 +5779,6 @@ } }; //#endregion - //#region custom_components/hass_datapoints/src/lib/util/format.ts - function fmtTime(iso) { - return new Date(iso).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit" - }); - } - function fmtDateTime(iso) { - return new Date(iso).toLocaleString([], { - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit" - }); - } - function fmtRelativeTime(iso) { - const diff = Date.now() - new Date(iso).getTime(); - const mins = Math.floor(diff / 6e4); - if (mins < 1) return "Just now"; - if (mins < 60) return `${mins}m ago`; - const hours = Math.floor(mins / 60); - if (hours < 24) return `${hours}h ago`; - const days = Math.floor(hours / 24); - if (days < 7) return `${days}d ago`; - return fmtDateTime(iso); - } - /** Escape HTML for safe inline insertion */ - function esc(str) { - return String(str).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); - } - //#endregion //#region custom_components/hass_datapoints/src/lib/ha/ha-components.ts var HA_COMPONENT_LOAD_TIMEOUT_MS = 6e3; var HA_COMPONENT_LOADER_SUPPORTED_TAGS = new Set([ @@ -5902,47 +5871,6 @@ dialog.open = false; dialog.headerTitle = options.title || msg("Confirm delete"); if (host?._hass) dialog.hass = host._hass; - dialog.innerHTML = ` - -
-
${esc(options.message || msg("Are you sure you want to delete this item?"))}
-
- - -
-
- `; let settled = false; const finish = (value) => { if (settled) return; @@ -5950,14 +5878,61 @@ dialog.open = false; resolve(value); }; - const cancelButton = dialog.querySelector(".confirm-dialog-button.cancel"); - const confirmButton = dialog.querySelector(".confirm-dialog-button.confirm"); - cancelButton?.addEventListener("click", () => { - finish(false); - }); - confirmButton?.addEventListener("click", () => { - finish(true); - }); + D(b` + +
+
+ ${options.message || msg("Are you sure you want to delete this item?")} +
+
+ + +
+
+ `, dialog); dialog.addEventListener("keydown", (event) => { if (event.key !== "Enter" || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; event.preventDefault(); @@ -5970,7 +5945,7 @@ root.appendChild(dialog); dialog.open = true; window.requestAnimationFrame(() => { - confirmButton?.focus(); + dialog.querySelector(".confirm-dialog-button.confirm")?.focus(); }); })); } @@ -6173,6 +6148,37 @@ color: var(--secondary-text-color); } `; + //#endregion + //#region custom_components/hass_datapoints/src/lib/util/format.ts + function fmtTime(iso) { + return new Date(iso).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit" + }); + } + function fmtDateTime(iso) { + return new Date(iso).toLocaleString([], { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit" + }); + } + function fmtRelativeTime(iso) { + const diff = Date.now() - new Date(iso).getTime(); + const mins = Math.floor(diff / 6e4); + if (mins < 1) return "Just now"; + if (mins < 60) return `${mins}m ago`; + const hours = Math.floor(mins / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + if (days < 7) return `${days}d ago`; + return fmtDateTime(iso); + } + /** Escape HTML for safe inline insertion */ + function esc(str) { + return String(str).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + } //#endregion //#region custom_components/hass_datapoints/src/cards/dev-tool/dev-tool-results/dev-tool-results.ts var _results_accessor_storage = /* @__PURE__ */ new WeakMap(); @@ -6680,39 +6686,48 @@ _render() { this._rendered = true; const cfg = this._config; - this.shadowRoot.innerHTML = ` - - - ${cfg.title ? `
${esc(cfg.title)}
` : ""} - -
Analyze HA History
- -
- -
- - - -
- Analyze all windows -
- - - - - -
- -
-
Dev Datapoints
-
- Currently recorded:  dev data points + if (!this.shadowRoot.adoptedStyleSheets.length) { + const sheet = new CSSStyleSheet(); + sheet.replaceSync(styles$58); + this.shadowRoot.adoptedStyleSheets = [sheet]; + } + D(b` + + ${cfg.title ? b`
${cfg.title}
` : ""} +
Analyze HA History
+
+
- Delete all dev datapoints - -
- - `; + +
+ Analyze all windows +
+ + +
+
+
Dev Datapoints
+
+ Currently recorded:  dev data points +
+ Delete all dev datapoints + +
+ + `, this.shadowRoot); const entityPicker = this.shadowRoot.getElementById("entity-picker"); if (entityPicker) { entityPicker.selector = { entity: { multiple: true } }; @@ -8438,18 +8453,31 @@ }, /* @__PURE__ */ new Map()); const axisTextStyle = (axis) => { if (!(!!axis?.unit && (unitCounts.get(axis.unit) || 0) > 1) || !axis?.color) return ""; - return `color:${esc(axis.color)};`; + return `color:${axis.color};`; }; const buildAxisMarkup = (axis) => { - return `${(axis.ticks || []).map((tick) => { + return b`${(axis.ticks || []).map((tick) => { const y = renderer.yOf(tick, axis.min, axis.max); - return `
${esc(renderer._formatAxisTick(tick, axis.unit))}
`; - }).join("")}${axis.unit ? `
${esc(axis.unit)}
` : ""}`; + const sideStyle = axis.side === "left" ? `right:${axisOffset(axis)}px;text-align:right;` : `left:${axisOffset(axis)}px;text-align:left;`; + return b`
+ ${renderer._formatAxisTick(tick, axis.unit)} +
`; + })}${axis.unit ? b`
+ ${axis.unit} +
` : ""}`; }; const leftAxes = axes.filter((axis) => axis.side !== "right"); const rightAxes = axes.filter((axis) => axis.side === "right"); - leftEl.innerHTML = leftAxes.length ? `
${leftAxes.map((axis) => buildAxisMarkup(axis)).join("")}` : ""; - rightEl.innerHTML = rightAxes.length ? `
${rightAxes.map((axis) => buildAxisMarkup(axis)).join("")}` : ""; + D(leftAxes.length ? b`
+ ${leftAxes.map((axis) => buildAxisMarkup(axis))}` : b``, leftEl); + D(rightAxes.length ? b`
+ ${rightAxes.map((axis) => buildAxisMarkup(axis))}` : b``, rightEl); leftEl.classList.toggle("visible", !!leftAxes.length); rightEl.classList.toggle("visible", !!rightAxes.length); } @@ -8766,20 +8794,32 @@ const interactionState = getInteractionState(card); const tooltip = document.createElement("div"); tooltip.className = "tooltip secondary annotation-tooltip"; - const valueMarkup = event?.chart_value != null && event.chart_value !== "" ? `
${esc(formatTooltipValue(event.chart_value, event.chart_unit))}
` : ""; + const hasValue = event?.chart_value != null && event.chart_value !== ""; const message = event?.message || "Data point"; const annotation = event?.annotation && event.annotation !== event.message ? event.annotation : ""; - const relatedMarkup = buildTooltipRelatedChips(interactionState._hass, event); - tooltip.innerHTML = ` -
${esc(fmtDateTime(event.timestamp))}
- ${valueMarkup} -
- - ${esc(message)} -
-
${esc(annotation)}
-
${relatedMarkup}
- `; + const chips = buildTooltipRelatedChips(interactionState._hass, event); + D(b` +
${fmtDateTime(event.timestamp)}
+ ${hasValue ? b`
+ ${formatTooltipValue(event.chart_value, event.chart_unit)} +
` : ""} +
+ + ${message} +
+
+ ${annotation} +
+
+ ${chips} +
+ `, tooltip); return tooltip; } function renderAnnotationTooltips(card, hover, anchorTooltip, bounds = null) { @@ -9061,22 +9101,31 @@ ttValue.textContent = value ? formatTooltipDisplayValue(value.value, value.unit) : ""; ttValue.style.display = value ? "block" : "none"; if (ttSeries) { - ttSeries.innerHTML = ""; + D(b``, ttSeries); ttSeries.style.display = "none"; } } else { ttValue.textContent = ""; ttValue.style.display = "none"; if (ttSeries) { - ttSeries.innerHTML = displayRows.map((entry) => ` -
-
- ${entry.grouped === true && entry.rawVisible === true ? "" : ``} - ${esc(resolveTooltipSeriesLabel(entry))} -
- ${esc(formatTooltipDisplayValue(entry.value, entry.unit))} -
- `).join(""); + D(b`${displayRows.map((entry) => b` +
+
+ ${entry.grouped === true && entry.rawVisible === true ? "" : b``} + ${resolveTooltipSeriesLabel(entry)} +
+ ${formatTooltipDisplayValue(entry.value, entry.unit)} +
+ `)}`, ttSeries); ttSeries.style.display = displayRows.length ? "grid" : "none"; } } @@ -9084,7 +9133,7 @@ ttMsg.textContent = ""; ttAnn.textContent = ""; ttAnn.style.display = "none"; - ttEntities.innerHTML = ""; + D(b``, ttEntities); ttEntities.style.display = "none"; if (anomalyTooltip && ttSecondaryTitle && ttSecondaryDescription && ttSecondaryAlert && ttSecondaryInstruction) { const anomalyContent = buildAnomalyTooltipContent(hover.anomalyRegions); @@ -9130,13 +9179,13 @@ label: labelName(hass, id) })) ].filter((chip) => chip.label); - if (!chips.length) return ""; - return chips.map((chip) => ` - - - ${esc(chip.label)} - - `).join(""); + if (!chips.length) return null; + return b`${chips.map((chip) => b` + + + ${chip.label} + + `)}`; } function showLineChartCrosshair(card, renderer, hover) { const overlay = getRoot(card).getElementById("chart-crosshair"); @@ -9161,20 +9210,20 @@ ...hover.showRateCrosshairs === true ? (hover.rateValues || []).filter((entry) => entry.showCrosshair === true) : [], ...hover.comparisonValues || [] ]; - points.innerHTML = ` - ${crosshairValues.filter((entry) => entry.hasValue !== false).map((entry) => ` - - `).join("")} - ${crosshairValues.filter((entry) => entry.hasValue !== false).map((entry) => ` - - `).join("")} - `; + D(b` + ${crosshairValues.filter((entry) => entry.hasValue !== false).map((entry) => b` + + `)} + ${crosshairValues.filter((entry) => entry.hasValue !== false).map((entry) => b` + + `)} + `, points); renderChartAxisHoverDots(card, crosshairValues); if (addButton && addButton.dataset.allowAddAnnotation !== "false") { addButton.hidden = false; @@ -9228,7 +9277,7 @@ const points = getRoot(card).getElementById("crosshair-points"); const addButton = getRoot(card).getElementById("chart-add-annotation"); if (overlay) overlay.hidden = true; - if (points) points.innerHTML = ""; + if (points) D(b``, points); renderChartAxisHoverDots(card, []); const horizontal = getRoot(card).getElementById("crosshair-horizontal"); if (horizontal) horizontal.hidden = true; @@ -11513,10 +11562,14 @@ } if (renderer?.pad?.left != null) overlayEl.style.left = `${Math.max(8, renderer.pad.left + 8)}px`; else overlayEl.style.left = ""; - overlayEl.innerHTML = ` -
${msg("Date window:")} ${esc(overlay.window_range_label)}
-
${msg("Actual:")} ${esc(overlay.actual_range_label)}
- `; + D(b` +
+ ${msg("Date window:")} ${overlay.window_range_label} +
+
+ ${msg("Actual:")} ${overlay.actual_range_label} +
+ `, overlayEl); overlayEl.hidden = false; } /** @@ -11610,24 +11663,26 @@ if (!legendEl) return; const allItems = [...series, ...binaryBackgrounds]; this._updateLegendLayout(legendEl); - legendEl.innerHTML = allItems.map((item) => { - return `
- -
`; - }).join(""); - legendEl.querySelectorAll(".legend-toggle").forEach((btn) => { - btn.addEventListener("click", () => { - const entityId = btn.dataset.entityId; - if (!entityId || !this._hiddenSeries) return; - if (this._hiddenSeries.has(entityId)) this._hiddenSeries.delete(entityId); - else this._hiddenSeries.add(entityId); - btn.setAttribute("aria-pressed", this._hiddenSeries.has(entityId) ? "false" : "true"); + D(b`${allItems.map((item) => { + return b` +
+ +
+ `; + })}`, legendEl); } /** Draw a single series line onto the renderer, with optional data-gap rendering. */ _drawSeriesLine(renderer, pts, color, t0, t1, min, max, opts) { @@ -11763,7 +11818,10 @@ iconEl.style.top = `${y + yOffset}px`; iconEl.title = event.message || "Open related history"; iconEl.setAttribute("aria-label", event.message || "Open related history"); - iconEl.innerHTML = ``; + D(b``, iconEl); iconEl.addEventListener("click", navigateToHistory); overlay.appendChild(iconEl); } else if (overlay) { @@ -12165,20 +12223,31 @@ this.style.setProperty("--dp-chart-axis-right-width", "0px"); this.style.setProperty("--dp-chart-axis-bottom-height", `${bottomHeight}px`); const labelRight = 10; - let labelsHtml = ""; + const labelItems = []; for (const { renderer, axis, rowOffset } of tracks) { if (!axis?.ticks?.length) continue; for (const tick of axis.ticks) { const y = rowOffset + renderer.yOf(tick, axis.min, axis.max); const formatted = renderer._formatAxisTick(tick, axis.unit); - labelsHtml += `
${esc(formatted)}
`; + labelItems.push(b`
+ ${formatted} +
`); } if (axis.unit) { const unitY = rowOffset + Math.max(0, primaryRenderer.pad.top - 18); - labelsHtml += `
${esc(axis.unit)}
`; + labelItems.push(b`
+ ${axis.unit} +
`); } } - leftEl.innerHTML = `
${labelsHtml}`; + D(b`
+ ${labelItems}`, leftEl); leftEl.classList.add("visible"); rightEl.innerHTML = ""; rightEl.classList.remove("visible"); @@ -25474,8 +25543,8 @@ type="button" class="history-targets-collapsed-item ${row.visible === false ? "is-hidden" : ""}" data-entity-id=${row.entity_id} - style="--row-color:${esc(row.color)}" - aria-label=${esc(label)} + style="--row-color:${row.color}" + aria-label=${label} aria-pressed=${row.visible === false ? "false" : "true"} @click=${(ev) => this._onCollapsedEntityClick(ev, row.entity_id)} > @@ -35187,7 +35256,10 @@ el.style.left = `${hit.x}px`; el.style.top = `${hit.y}px`; el.style.background = bgColor; - el.innerHTML = ``; + D(b``, el); el.dataset.eventId = hit.event.id; el.addEventListener("click", (e) => { if (this.showAnnotationTooltips) { diff --git a/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool.ts b/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool.ts index d3a83919..74ed11e4 100644 --- a/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool.ts +++ b/custom_components/hass_datapoints/src/cards/dev-tool/dev-tool.ts @@ -1,6 +1,6 @@ +import { html, render } from "lit"; import { DOMAIN } from "@/constants"; import { confirmDestructiveAction } from "@/lib/ha/ha-components"; -import { esc } from "@/lib/util/format"; import type { HassLike } from "@/lib/types"; import { logger } from "@/lib/logger"; import { styles } from "./dev-tool.styles"; @@ -75,39 +75,53 @@ export class HassRecordsDevToolCard extends HTMLElement { _render() { this._rendered = true; const cfg = this._config; - this.shadowRoot!.innerHTML = ` - - - ${cfg.title ? `
${esc(cfg.title as string)}
` : ""} - -
Analyze HA History
- -
- -
- - - -
- Analyze all windows -
- - - - - -
- -
-
Dev Datapoints
-
- Currently recorded:  dev data points + if (!this.shadowRoot!.adoptedStyleSheets.length) { + const sheet = new CSSStyleSheet(); + sheet.replaceSync(styles); + this.shadowRoot!.adoptedStyleSheets = [sheet]; + } + render( + html` + + ${cfg.title + ? html`
${cfg.title as string}
` + : ""} +
Analyze HA History
+
+
- Delete all dev datapoints - -
- - `; + +
+ Analyze all windows +
+ + +
+
+
Dev Datapoints
+
+ Currently recorded:  dev data points +
+ Delete all dev datapoints + +
+ + `, + this.shadowRoot! + ); const entityPicker = this.shadowRoot!.getElementById( "entity-picker" diff --git a/custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.ts b/custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.ts index 764f2da0..e12dc8b2 100644 --- a/custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.ts +++ b/custom_components/hass_datapoints/src/cards/history/history-chart/history-chart.ts @@ -9,8 +9,8 @@ * legend remain accessible from the parent card's shadow root. */ +import { html, render } from "lit"; import { msg } from "@/lib/i18n/localize"; -import { esc } from "@/lib/util/format"; import { binaryOffLabel, binaryOnLabel, @@ -531,10 +531,17 @@ export class HistoryChart extends HTMLElement { } else { overlayEl.style.left = ""; } - overlayEl.innerHTML = ` -
${msg("Date window:")} ${esc(overlay.window_range_label)}
-
${msg("Actual:")} ${esc(overlay.actual_range_label)}
- `; + render( + html` +
+ ${msg("Date window:")} ${overlay.window_range_label} +
+
+ ${msg("Actual:")} ${overlay.actual_range_label} +
+ `, + overlayEl + ); overlayEl.hidden = false; } @@ -720,33 +727,33 @@ export class HistoryChart extends HTMLElement { }>), ]; this._updateLegendLayout(legendEl); - legendEl.innerHTML = allItems - .map((item) => { + render( + html`${allItems.map((item) => { const hidden = this._hiddenSeries?.has(item.entityId); - return `
- -
`; - }) - .join(""); - legendEl.querySelectorAll(".legend-toggle").forEach((btn) => { - btn.addEventListener("click", () => { - const entityId = (btn as HTMLElement).dataset.entityId; - if (!entityId || !this._hiddenSeries) return; - if (this._hiddenSeries.has(entityId)) { - this._hiddenSeries.delete(entityId); - } else { - this._hiddenSeries.add(entityId); - } - btn.setAttribute( - "aria-pressed", - this._hiddenSeries.has(entityId) ? "false" : "true" - ); - this._redrawLastDraw(); - }); - }); + return html` +
+ +
+ `; + })}`, + legendEl + ); } /** Draw a single series line onto the renderer, with optional data-gap rendering. */ @@ -1038,7 +1045,13 @@ export class HistoryChart extends HTMLElement { "aria-label", event.message || "Open related history" ); - iconEl.innerHTML = ``; + render( + html``, + iconEl + ); iconEl.addEventListener("click", navigateToHistory); overlay.appendChild(iconEl); } else if (overlay) { @@ -1725,21 +1738,40 @@ export class HistoryChart extends HTMLElement { ); const labelRight = 10; - let labelsHtml = ""; + const labelItems: import("lit").TemplateResult[] = []; for (const { renderer, axis, rowOffset } of tracks) { if (!axis?.ticks?.length) continue; for (const tick of axis.ticks) { const y = rowOffset + renderer.yOf(tick, axis.min, axis.max); const formatted = renderer._formatAxisTick(tick, axis.unit); - labelsHtml += `
${esc(formatted)}
`; + labelItems.push( + html`
+ ${formatted} +
` + ); } if (axis.unit) { const unitY = rowOffset + Math.max(0, primaryRenderer.pad.top - 18); - labelsHtml += `
${esc(axis.unit)}
`; + labelItems.push( + html`
+ ${axis.unit} +
` + ); } } - leftEl.innerHTML = `
${labelsHtml}`; + render( + html`
+ ${labelItems}`, + leftEl + ); leftEl.classList.add("visible"); rightEl.innerHTML = ""; rightEl.classList.remove("visible"); diff --git a/custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.ts b/custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.ts index 51c87d4f..78d01732 100644 --- a/custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.ts +++ b/custom_components/hass_datapoints/src/cards/sensor/sensor-chart/sensor-chart.ts @@ -1,7 +1,6 @@ -import { html, LitElement } from "lit"; +import { html, LitElement, render } from "lit"; import { COLORS } from "@/constants"; import { contrastColor } from "@/lib/util/color"; -import { esc } from "@/lib/util/format"; import { setupCanvas } from "@/charts/utils/chart-dom"; import { ChartRenderer } from "@/lib/chart/chart-renderer"; import type { @@ -407,7 +406,13 @@ export class SensorChart extends LitElement { el.style.left = `${hit.x}px`; el.style.top = `${hit.y}px`; el.style.background = bgColor; - el.innerHTML = ``; + render( + html``, + el + ); el.dataset.eventId = hit.event.id; el.addEventListener("click", (e) => { if (this.showAnnotationTooltips) { diff --git a/custom_components/hass_datapoints/src/charts/utils/chart-dom.ts b/custom_components/hass_datapoints/src/charts/utils/chart-dom.ts index e04f8d95..31ec420e 100644 --- a/custom_components/hass_datapoints/src/charts/utils/chart-dom.ts +++ b/custom_components/hass_datapoints/src/charts/utils/chart-dom.ts @@ -1,4 +1,4 @@ -import { esc } from "@/lib/util/format"; +import { html, render, type TemplateResult } from "lit"; import { ChartRenderer, type ResolvedAxis } from "@/lib/chart/chart-renderer"; /** @@ -696,7 +696,7 @@ export function buildChartCardShell(title?: Nullable): string { return ` - ${title ? `
${esc(title)}
` : ""} + ${title ? `
${title}
` : ""}
@@ -856,31 +856,56 @@ export function renderChartAxisOverlays( if (!duplicateUnit || !axis?.color) { return ""; } - return `color:${esc(axis.color)};`; + return `color:${axis.color};`; }; - const buildAxisMarkup = (axis: ResolvedAxis) => { - const labels = (axis.ticks || []) - .map((tick: number) => { - const y = renderer.yOf(tick, axis.min, axis.max); - return `
${esc(renderer._formatAxisTick(tick, axis.unit))}
`; - }) - .join(""); - const unit = axis.unit - ? `
${esc(axis.unit)}
` + const buildAxisMarkup = (axis: ResolvedAxis): TemplateResult => { + const labelItems = (axis.ticks || []).map((tick: number) => { + const y = renderer.yOf(tick, axis.min, axis.max); + const sideStyle = + axis.side === "left" + ? `right:${axisOffset(axis)}px;text-align:right;` + : `left:${axisOffset(axis)}px;text-align:left;`; + return html`
+ ${renderer._formatAxisTick(tick, axis.unit)} +
`; + }); + const unitItem = axis.unit + ? html`
+ ${axis.unit} +
` : ""; - return `${labels}${unit}`; + return html`${labelItems}${unitItem}`; }; const leftAxes = axes.filter((axis) => axis.side !== "right"); const rightAxes = axes.filter((axis) => axis.side === "right"); - leftEl.innerHTML = leftAxes.length - ? `
${leftAxes.map((axis) => buildAxisMarkup(axis)).join("")}` - : ""; - rightEl.innerHTML = rightAxes.length - ? `
${rightAxes.map((axis) => buildAxisMarkup(axis)).join("")}` - : ""; + render( + leftAxes.length + ? html`
+ ${leftAxes.map((axis) => buildAxisMarkup(axis))}` + : html``, + leftEl + ); + render( + rightAxes.length + ? html`
+ ${rightAxes.map((axis) => buildAxisMarkup(axis))}` + : html``, + rightEl + ); leftEl.classList.toggle("visible", !!leftAxes.length); rightEl.classList.toggle("visible", !!rightAxes.length); diff --git a/custom_components/hass_datapoints/src/lib/chart/__tests__/chart-interaction.spec.ts b/custom_components/hass_datapoints/src/lib/chart/__tests__/chart-interaction.spec.ts index 461efca5..0ec34122 100644 --- a/custom_components/hass_datapoints/src/lib/chart/__tests__/chart-interaction.spec.ts +++ b/custom_components/hass_datapoints/src/lib/chart/__tests__/chart-interaction.spec.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import { html, render } from "lit"; import { buildTooltipRelatedChips, @@ -103,13 +104,15 @@ describe("chart-interaction", () => { expect.assertions(2); const card = createCard(); - const markup = buildTooltipRelatedChips(card._hass, { + const chips = buildTooltipRelatedChips(card._hass, { entity_ids: ["sensor.alpha"], device_ids: ["device.one"], }); + const container = document.createElement("div"); + render(chips ?? html``, container); - expect(markup).toContain("Alpha Sensor"); - expect(markup).toContain("Radiator"); + expect(container.textContent).toContain("Alpha Sensor"); + expect(container.textContent).toContain("Radiator"); }); }); }); diff --git a/custom_components/hass_datapoints/src/lib/chart/chart-interaction.ts b/custom_components/hass_datapoints/src/lib/chart/chart-interaction.ts index 2fa5a251..044338d9 100644 --- a/custom_components/hass_datapoints/src/lib/chart/chart-interaction.ts +++ b/custom_components/hass_datapoints/src/lib/chart/chart-interaction.ts @@ -1,3 +1,4 @@ +import { html, render, type TemplateResult } from "lit"; import { msg } from "@/lib/i18n/localize"; import { areaIcon, @@ -9,7 +10,7 @@ import { labelIcon, labelName, } from "@/lib/ha/entity-name"; -import { esc, fmtDateTime } from "@/lib/util/format"; +import { fmtDateTime } from "@/lib/util/format"; import { clampChartValue, formatTooltipDisplayValue, @@ -662,26 +663,40 @@ function buildAnnotationTooltip( tooltip.className = "tooltip secondary annotation-tooltip"; const hasValue = event?.chart_value != null && event.chart_value !== ""; - const valueMarkup = hasValue - ? `
${esc(formatTooltipValue(event.chart_value, event.chart_unit))}
` - : ""; const message = event?.message || "Data point"; const annotation = event?.annotation && event.annotation !== event.message ? event.annotation : ""; - const relatedMarkup = buildTooltipRelatedChips(interactionState._hass, event); - - tooltip.innerHTML = ` -
${esc(fmtDateTime(event.timestamp))}
- ${valueMarkup} -
- - ${esc(message)} -
-
${esc(annotation)}
-
${relatedMarkup}
- `; + const chips = buildTooltipRelatedChips(interactionState._hass, event); + + render( + html` +
${fmtDateTime(event.timestamp)}
+ ${hasValue + ? html`
+ ${formatTooltipValue(event.chart_value, event.chart_unit)} +
` + : ""} +
+ + ${message} +
+
+ ${annotation} +
+
+ ${chips} +
+ `, + tooltip + ); return tooltip; } @@ -759,7 +774,7 @@ export function showTooltip( : ""; ttValue.style.display = hasValue ? "block" : "none"; if (ttSeries) { - ttSeries.innerHTML = ""; + render(html``, ttSeries); ttSeries.style.display = "none"; } ttDot.style.background = event.color || "#03a9f4"; @@ -770,9 +785,9 @@ export function showTooltip( const ann = event.annotation !== event.message ? event.annotation : ""; ttAnn.textContent = ann || ""; ttAnn.style.display = ann ? "block" : "none"; - const relatedMarkup = buildTooltipRelatedChips(interactionState._hass, event); - ttEntities.innerHTML = relatedMarkup; - ttEntities.style.display = relatedMarkup ? "flex" : "none"; + const chips = buildTooltipRelatedChips(interactionState._hass, event); + render(chips ?? html``, ttEntities); + ttEntities.style.display = chips ? "flex" : "none"; const chartBounds = ( root.querySelector(".chart-wrap") ?? card @@ -1238,30 +1253,41 @@ export function showLineChartTooltip( : ""; ttValue.style.display = value ? "block" : "none"; if (ttSeries) { - ttSeries.innerHTML = ""; + render(html``, ttSeries); ttSeries.style.display = "none"; } } else { ttValue.textContent = ""; ttValue.style.display = "none"; if (ttSeries) { - ttSeries.innerHTML = displayRows - .map( - (entry) => ` -
-
- ${ - entry.grouped === true && entry.rawVisible === true - ? "" - : `` - } - ${esc(resolveTooltipSeriesLabel(entry))} -
- ${esc(formatTooltipDisplayValue(entry.value, entry.unit))} -
- ` - ) - .join(""); + render( + html`${displayRows.map( + (entry) => html` +
+
+ ${entry.grouped === true && entry.rawVisible === true + ? "" + : html``} + ${resolveTooltipSeriesLabel(entry)} +
+ ${formatTooltipDisplayValue(entry.value, entry.unit)} +
+ ` + )}`, + ttSeries + ); ttSeries.style.display = displayRows.length ? "grid" : "none"; } } @@ -1270,7 +1296,7 @@ export function showLineChartTooltip( ttMsg.textContent = ""; ttAnn.textContent = ""; ttAnn.style.display = "none"; - ttEntities.innerHTML = ""; + render(html``, ttEntities); ttEntities.style.display = "none"; if ( @@ -1318,7 +1344,7 @@ export function showLineChartTooltip( export function buildTooltipRelatedChips( hass: Nullable | undefined, event: Nullable | undefined -): string { +): TemplateResult | null { const entities = Array.isArray(event?.entity_ids) ? event.entity_ids : []; const devices = Array.isArray(event?.device_ids) ? event.device_ids : []; const areas = Array.isArray(event?.area_ids) ? event.area_ids : []; @@ -1341,17 +1367,15 @@ export function buildTooltipRelatedChips( label: labelName(hass, id), })), ].filter((chip) => chip.label); - if (!chips.length) return ""; - return chips - .map( - (chip) => ` - - - ${esc(chip.label)} - - ` - ) - .join(""); + if (!chips.length) return null; + return html`${chips.map( + (chip) => html` + + + ${chip.label} + + ` + )}`; } export function showLineChartCrosshair( @@ -1388,30 +1412,39 @@ export function showLineChartCrosshair( : []), ...(hover.comparisonValues || []), ]; - points.innerHTML = ` - ${crosshairValues - .filter((entry) => entry.hasValue !== false) - .map( - (entry) => ` - - ` - ) - .join("")} - ${crosshairValues - .filter((entry) => entry.hasValue !== false) - .map( - (entry) => ` - - ` - ) - .join("")} - `; + render( + html` + ${crosshairValues + .filter((entry) => entry.hasValue !== false) + .map( + (entry) => html` + + ` + )} + ${crosshairValues + .filter((entry) => entry.hasValue !== false) + .map( + (entry) => html` + + ` + )} + `, + points + ); renderChartAxisHoverDots(card, crosshairValues); if (addButton && addButton.dataset.allowAddAnnotation !== "false") { addButton.hidden = false; @@ -1499,7 +1532,7 @@ export function hideLineChartHover(card: ChartInteractionHost): void { const points = getRoot(card).getElementById("crosshair-points"); const addButton = getRoot(card).getElementById("chart-add-annotation"); if (overlay) overlay.hidden = true; - if (points) points.innerHTML = ""; + if (points) render(html``, points); renderChartAxisHoverDots(card, []); const horizontal = getRoot(card).getElementById("crosshair-horizontal"); if (horizontal) horizontal.hidden = true; diff --git a/custom_components/hass_datapoints/src/lib/ha/ha-components.ts b/custom_components/hass_datapoints/src/lib/ha/ha-components.ts index 361f0384..ff7bba2a 100644 --- a/custom_components/hass_datapoints/src/lib/ha/ha-components.ts +++ b/custom_components/hass_datapoints/src/lib/ha/ha-components.ts @@ -1,5 +1,5 @@ import { loadHaComponents } from "@kipk/load-ha-components"; -import { esc } from "@/lib/util/format"; +import { html, render } from "lit"; import { msg } from "@/lib/i18n/localize"; const HA_COMPONENT_LOAD_TIMEOUT_MS = 6000; @@ -192,48 +192,6 @@ export function confirmDestructiveAction( if (host?._hass) { dialog.hass = host._hass; } - dialog.innerHTML = ` - -
-
${esc(options.message || msg("Are you sure you want to delete this item?"))}
-
- - -
-
- `; - let settled = false; const finish = (value: boolean) => { if (settled) { @@ -244,19 +202,66 @@ export function confirmDestructiveAction( resolve(value); }; - const cancelButton = dialog.querySelector( - ".confirm-dialog-button.cancel" - ); - const confirmButton = dialog.querySelector( - ".confirm-dialog-button.confirm" + render( + html` + +
+
+ ${options.message || + msg("Are you sure you want to delete this item?")} +
+
+ + +
+
+ `, + dialog ); - cancelButton?.addEventListener("click", () => { - finish(false); - }); - confirmButton?.addEventListener("click", () => { - finish(true); - }); dialog.addEventListener("keydown", (event) => { if ( event.key !== "Enter" || @@ -284,7 +289,11 @@ export function confirmDestructiveAction( root.appendChild(dialog); dialog.open = true; window.requestAnimationFrame(() => { - (confirmButton as Nullable)?.focus(); + ( + dialog.querySelector( + ".confirm-dialog-button.confirm" + ) as Nullable + )?.focus(); }); }) ); diff --git a/custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.ts b/custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.ts index 66549192..67aa4c94 100644 --- a/custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.ts +++ b/custom_components/hass_datapoints/src/panels/datapoints/components/history-targets/history-targets.ts @@ -4,7 +4,6 @@ import { localized, msg } from "@/lib/i18n/localize"; import { styles } from "./history-targets.styles"; import { entityName } from "@/lib/ha/entity-name"; -import { esc } from "@/lib/util/format"; import type { HassLike, HassState } from "@/lib/types"; import "@/molecules/target-row-list/target-row-list"; @@ -125,8 +124,8 @@ export class HistoryTargets extends LitElement { ? "is-hidden" : ""}" data-entity-id=${row.entity_id} - style="--row-color:${esc(row.color)}" - aria-label=${esc(label)} + style="--row-color:${row.color}" + aria-label=${label} aria-pressed=${row.visible === false ? "false" : "true"} @click=${(ev: Event) => this._onCollapsedEntityClick(ev, row.entity_id)} From 73520ef721e064eba84da79872e6d08572d43d0c Mon Sep 17 00:00:00 2001 From: Oliver Lillie Date: Thu, 9 Apr 2026 17:25:44 +0300 Subject: [PATCH 14/14] feat(dev): Add dev-sync build ID to browser console badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generates a random 4-digit ID in dev-sync.sh (VITE_DEV_SYNC_ID) that Vite bakes into the bundle at sync time. register.ts reads the env var and, when present, appends an orange badge with the ID to the console.groupCollapsed output — making it easy to confirm which dev-sync build is active in the browser console. Regular builds never set the var, so production bundles carry no sync ID. --- .../hass_datapoints/hass-datapoints-cards.js | 2 +- custom_components/hass_datapoints/src/register.ts | 11 +++++++++-- scripts/dev-sync.sh | 12 +++++++++++- scripts/pre-commit | 2 +- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/custom_components/hass_datapoints/hass-datapoints-cards.js b/custom_components/hass_datapoints/hass-datapoints-cards.js index d2abb6ec..c8d7ffd2 100644 --- a/custom_components/hass_datapoints/hass-datapoints-cards.js +++ b/custom_components/hass_datapoints/hass-datapoints-cards.js @@ -36411,7 +36411,7 @@ ].forEach((card) => { if (!registeredTypes.has(card.type)) window.customCards?.push(card); }); - console.groupCollapsed("%c hass-datapoints %c v0.4.2 loaded ", "color:#fff;background:#03a9f4;font-weight:bold;padding:2px 6px;border-radius:3px 0 0 3px", "color:#03a9f4;background:#fff;font-weight:bold;padding:2px 6px;border:1px solid #03a9f4;border-radius:0 3px 3px 0"); + console.groupCollapsed(`%c hass-datapoints %c v0.4.2 loaded `, "color:#fff;background:#03a9f4;font-weight:bold;padding:2px 6px;border-radius:3px 0 0 3px", "color:#03a9f4;background:#fff;font-weight:bold;padding:2px 6px;border:1px solid #03a9f4;border-radius:0 3px 3px 0", ...[]); console.log("Enable debug logging by setting %cwindow.__HASS_DATAPOINTS_DEV__ = true", "color:#333;background:#eee;border:1px solid #777;padding:2px 6px;border-radius:5px; font-family: Courier"); console.groupEnd(); //#endregion diff --git a/custom_components/hass_datapoints/src/register.ts b/custom_components/hass_datapoints/src/register.ts index 7de58d58..933e247e 100644 --- a/custom_components/hass_datapoints/src/register.ts +++ b/custom_components/hass_datapoints/src/register.ts @@ -150,10 +150,17 @@ cardsToAdd.forEach((card) => { } }); +const _devSyncId = import.meta.env.VITE_DEV_SYNC_ID as string | undefined; console.groupCollapsed( - "%c hass-datapoints %c v0.3.0 loaded ", + `%c hass-datapoints %c v0.3.0 loaded${_devSyncId ? `%c %c DEV#${_devSyncId} ` : " "}`, "color:#fff;background:#03a9f4;font-weight:bold;padding:2px 6px;border-radius:3px 0 0 3px", - "color:#03a9f4;background:#fff;font-weight:bold;padding:2px 6px;border:1px solid #03a9f4;border-radius:0 3px 3px 0" + "color:#03a9f4;background:#fff;font-weight:bold;padding:2px 6px;border:1px solid #03a9f4;border-radius:0 3px 3px 0", + ...(_devSyncId + ? [ + "background:transparent;", + "color:#fff;background:#f57c00;font-weight:bold;padding:2px 6px;border-radius:3px", + ] + : []) ); console.log( "Enable debug logging by setting %cwindow.__HASS_DATAPOINTS_DEV__ = true", diff --git a/scripts/dev-sync.sh b/scripts/dev-sync.sh index b8168b60..cda2c7f9 100755 --- a/scripts/dev-sync.sh +++ b/scripts/dev-sync.sh @@ -135,8 +135,14 @@ fi # Build # --------------------------------------------------------------------------- +# Generate a short ID stamped into the bundle so the browser console confirms +# which dev-sync build is loaded. Only set here — build.sh never sets it, so +# production/CI builds never carry a sync ID. +VITE_DEV_SYNC_ID=$(( RANDOM % 9000 + 1000 )) +export VITE_DEV_SYNC_ID + if [[ $DO_BUILD -eq 1 ]]; then - echo "Building frontend bundle..." + echo "Building frontend bundle... (sync #${VITE_DEV_SYNC_ID})" bash "$REPO_ROOT/scripts/build.sh" fi @@ -269,3 +275,7 @@ if [[ $DO_RESTART -eq 1 ]]; then fi echo "Sync complete." + +if [[ $DO_BUILD -eq 1 ]]; then + echo "Look for DEV#${VITE_DEV_SYNC_ID}" +fi diff --git a/scripts/pre-commit b/scripts/pre-commit index 0bbe08be..86a31d0d 100755 --- a/scripts/pre-commit +++ b/scripts/pre-commit @@ -102,7 +102,7 @@ if [ -n "$STAGED_SRC" ]; then echo "[pre-commit] pnpm is required to build frontend assets." >&2 exit 1 fi - echo "[pre-commit] src/ files changed — rebuilding $OUT_FILE…" + echo "[pre-commit] src/ files changed — rebuilding $OUT_FILE …" pnpm build git add "$OUT_FILE" echo "[pre-commit] ✓ $OUT_FILE rebuilt and staged"