diff --git a/custom_components/spook/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 00f41710..00000000
Binary files a/custom_components/spook/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/binary_sensor.cpython-314.pyc b/custom_components/spook/__pycache__/binary_sensor.cpython-314.pyc
deleted file mode 100644
index 461e4ba3..00000000
Binary files a/custom_components/spook/__pycache__/binary_sensor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/button.cpython-314.pyc b/custom_components/spook/__pycache__/button.cpython-314.pyc
deleted file mode 100644
index 32ebd996..00000000
Binary files a/custom_components/spook/__pycache__/button.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/config_flow.cpython-314.pyc b/custom_components/spook/__pycache__/config_flow.cpython-314.pyc
deleted file mode 100644
index 5a7c0346..00000000
Binary files a/custom_components/spook/__pycache__/config_flow.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/const.cpython-314.pyc b/custom_components/spook/__pycache__/const.cpython-314.pyc
deleted file mode 100644
index 93b930a2..00000000
Binary files a/custom_components/spook/__pycache__/const.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/entity.cpython-314.pyc b/custom_components/spook/__pycache__/entity.cpython-314.pyc
deleted file mode 100644
index 2382e23e..00000000
Binary files a/custom_components/spook/__pycache__/entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/entity_filtering.cpython-314.pyc b/custom_components/spook/__pycache__/entity_filtering.cpython-314.pyc
deleted file mode 100644
index 7f7759dc..00000000
Binary files a/custom_components/spook/__pycache__/entity_filtering.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/event.cpython-314.pyc b/custom_components/spook/__pycache__/event.cpython-314.pyc
deleted file mode 100644
index e7e5c2f5..00000000
Binary files a/custom_components/spook/__pycache__/event.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/integration_linking.cpython-314.pyc b/custom_components/spook/__pycache__/integration_linking.cpython-314.pyc
deleted file mode 100644
index f3fde785..00000000
Binary files a/custom_components/spook/__pycache__/integration_linking.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/listeners.cpython-314.pyc b/custom_components/spook/__pycache__/listeners.cpython-314.pyc
deleted file mode 100644
index 442c6eba..00000000
Binary files a/custom_components/spook/__pycache__/listeners.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/number.cpython-314.pyc b/custom_components/spook/__pycache__/number.cpython-314.pyc
deleted file mode 100644
index 4a626d42..00000000
Binary files a/custom_components/spook/__pycache__/number.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/repairs.cpython-314.pyc b/custom_components/spook/__pycache__/repairs.cpython-314.pyc
deleted file mode 100644
index 6beb7c87..00000000
Binary files a/custom_components/spook/__pycache__/repairs.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/select.cpython-314.pyc b/custom_components/spook/__pycache__/select.cpython-314.pyc
deleted file mode 100644
index 0045b9dd..00000000
Binary files a/custom_components/spook/__pycache__/select.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/sensor.cpython-314.pyc b/custom_components/spook/__pycache__/sensor.cpython-314.pyc
deleted file mode 100644
index 557015ef..00000000
Binary files a/custom_components/spook/__pycache__/sensor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/services.cpython-314.pyc b/custom_components/spook/__pycache__/services.cpython-314.pyc
deleted file mode 100644
index 3893392a..00000000
Binary files a/custom_components/spook/__pycache__/services.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/setup_helpers.cpython-314.pyc b/custom_components/spook/__pycache__/setup_helpers.cpython-314.pyc
deleted file mode 100644
index b63b848d..00000000
Binary files a/custom_components/spook/__pycache__/setup_helpers.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/switch.cpython-314.pyc b/custom_components/spook/__pycache__/switch.cpython-314.pyc
deleted file mode 100644
index 0fa615e5..00000000
Binary files a/custom_components/spook/__pycache__/switch.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/__pycache__/time.cpython-314.pyc b/custom_components/spook/__pycache__/time.cpython-314.pyc
deleted file mode 100644
index 85a123f6..00000000
Binary files a/custom_components/spook/__pycache__/time.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/conditions.yaml b/custom_components/spook/conditions.yaml
index 891fce02..ef71b89b 100644
--- a/custom_components/spook/conditions.yaml
+++ b/custom_components/spook/conditions.yaml
@@ -54,6 +54,28 @@ repair_issue_present:
- critical
- error
- warning
+is_at_target_temperature:
+ target:
+ entity:
+ - domain: climate
+ - domain: water_heater
+ fields:
+ behavior:
+ required: true
+ default: any
+ selector:
+ automation_behavior:
+ mode: condition
+ tolerance:
+ required: false
+ default: 0
+ example: 0.5
+ selector:
+ number:
+ min: 0
+ max: 10
+ step: 0.1
+ mode: box
quota:
fields:
limit:
diff --git a/custom_components/spook/ectoplasms/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 20445849..00000000
Binary files a/custom_components/spook/ectoplasms/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/automation/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/automation/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index d077905b..00000000
Binary files a/custom_components/spook/ectoplasms/automation/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/automation/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index c3f47b2b..00000000
Binary files a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_area_references.cpython-314.pyc b/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_area_references.cpython-314.pyc
deleted file mode 100644
index 6bdf3c52..00000000
Binary files a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_area_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_device_references.cpython-314.pyc b/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_device_references.cpython-314.pyc
deleted file mode 100644
index 7b32502e..00000000
Binary files a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_device_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_entity_references.cpython-314.pyc b/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_entity_references.cpython-314.pyc
deleted file mode 100644
index d78137ce..00000000
Binary files a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_entity_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_floor_references.cpython-314.pyc b/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_floor_references.cpython-314.pyc
deleted file mode 100644
index e90f01b8..00000000
Binary files a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_floor_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_label_references.cpython-314.pyc b/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_label_references.cpython-314.pyc
deleted file mode 100644
index 2f5a1e2f..00000000
Binary files a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_label_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_service_references.cpython-314.pyc b/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_service_references.cpython-314.pyc
deleted file mode 100644
index 98c2271d..00000000
Binary files a/custom_components/spook/ectoplasms/automation/repairs/__pycache__/unknown_service_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/automation/repairs/unknown_area_references.py b/custom_components/spook/ectoplasms/automation/repairs/unknown_area_references.py
index a12cf641..27388fac 100644
--- a/custom_components/spook/ectoplasms/automation/repairs/unknown_area_references.py
+++ b/custom_components/spook/ectoplasms/automation/repairs/unknown_area_references.py
@@ -8,7 +8,10 @@ from homeassistant.components import automation
from homeassistant.helpers import area_registry as ar
from ....entity_filtering import async_filter_known_area_ids, async_get_all_area_ids
-from ....reference_extraction import extract_targets_from_config
+from ....reference_extraction import (
+ extract_targets_from_config,
+ only_in_disabled_steps,
+)
from . import AbstractSpookAutomationReferencesRepair
if TYPE_CHECKING:
@@ -45,6 +48,10 @@ class SpookRepair(AbstractSpookAutomationReferencesRepair):
# references nested in some step types, like repeat sequences.
if raw_config := getattr(entity, "raw_config", None):
area_ids.update(extract_targets_from_config(raw_config).area_ids)
+ # A disabled step does nothing, so what only it names is left out.
+ area_ids -= only_in_disabled_steps(
+ raw_config, lambda found: extract_targets_from_config(found).area_ids
+ )
return async_filter_known_area_ids(
self.hass,
diff --git a/custom_components/spook/ectoplasms/automation/repairs/unknown_device_references.py b/custom_components/spook/ectoplasms/automation/repairs/unknown_device_references.py
index 7d95f33a..c170f616 100644
--- a/custom_components/spook/ectoplasms/automation/repairs/unknown_device_references.py
+++ b/custom_components/spook/ectoplasms/automation/repairs/unknown_device_references.py
@@ -8,7 +8,10 @@ from homeassistant.components import automation
from homeassistant.helpers import device_registry as dr
from ....entity_filtering import async_filter_known_device_ids, async_get_all_device_ids
-from ....reference_extraction import extract_targets_from_config
+from ....reference_extraction import (
+ extract_targets_from_config,
+ only_in_disabled_steps,
+)
from ....template_extraction import extract_device_ids_from_config
from . import AbstractSpookAutomationReferencesRepair
@@ -91,6 +94,14 @@ class SpookRepair(AbstractSpookAutomationReferencesRepair):
)
# Devices referenced via device_entities() in templates.
device_ids.update(extract_device_ids_from_config(entity.raw_config))
+ # A disabled step does nothing, so what only it names is left out.
+ device_ids -= only_in_disabled_steps(
+ entity.raw_config,
+ lambda found: (
+ extract_targets_from_config(found).device_ids
+ | extract_device_ids_from_config(found)
+ ),
+ )
return async_filter_known_device_ids(
self.hass,
diff --git a/custom_components/spook/ectoplasms/automation/repairs/unknown_entity_references.py b/custom_components/spook/ectoplasms/automation/repairs/unknown_entity_references.py
index e1b6d6df..cc073ceb 100644
--- a/custom_components/spook/ectoplasms/automation/repairs/unknown_entity_references.py
+++ b/custom_components/spook/ectoplasms/automation/repairs/unknown_entity_references.py
@@ -13,6 +13,7 @@ from ....action_extraction import (
async_extract_entities_from_value,
)
from ....entity_filtering import async_get_all_entity_ids, async_get_all_services
+from ....reference_extraction import without_disabled_steps
from ....template_extraction import (
KNOWN_DOMAINS,
async_extract_entities_from_config,
@@ -291,6 +292,21 @@ class SpookRepair(AbstractSpookAutomationReferencesRepair):
)
self._known_services = async_get_all_services(self.hass)
+ async def _async_named_in(self, config: dict[str, Any]) -> set[str]:
+ """Return the entities a configuration names, the way this repair reads it.
+
+ The same reading the report is built from, structure and templates
+ alike, so comparing it with and without the disabled steps leaves out
+ exactly what only those name.
+ """
+ named = await extract_entities_from_automation_config(
+ self.hass, config, self._known_services
+ )
+ named |= await async_extract_entities_from_config(
+ self.hass, config, self._known_services
+ )
+ return named
+
async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
"""Return unknown entity IDs referenced by ``entity`` (incl. templates)."""
all_entities = set(entity.referenced_entities)
@@ -314,6 +330,15 @@ class SpookRepair(AbstractSpookAutomationReferencesRepair):
)
)
+ # Home Assistant's own list includes disabled steps, triggers and
+ # conditions too. Something parked that way does nothing, so what only
+ # it names is left out of the report: whatever this repair finds in
+ # the configuration, and no longer finds once those are pruned.
+ if isinstance(raw_config := getattr(entity, "raw_config", None), dict):
+ named = await self._async_named_in(raw_config)
+ still_named = await self._async_named_in(without_disabled_steps(raw_config))
+ all_entities -= named - still_named
+
return await async_filter_known_entity_ids_with_templates(
self.hass,
entity_ids=all_entities,
diff --git a/custom_components/spook/ectoplasms/automation/repairs/unknown_floor_references.py b/custom_components/spook/ectoplasms/automation/repairs/unknown_floor_references.py
index 70c0e7a4..a39a2405 100644
--- a/custom_components/spook/ectoplasms/automation/repairs/unknown_floor_references.py
+++ b/custom_components/spook/ectoplasms/automation/repairs/unknown_floor_references.py
@@ -8,7 +8,10 @@ from homeassistant.components import automation
from homeassistant.helpers import floor_registry as fr
from ....entity_filtering import async_filter_known_floor_ids, async_get_all_floor_ids
-from ....reference_extraction import extract_targets_from_config
+from ....reference_extraction import (
+ extract_targets_from_config,
+ only_in_disabled_steps,
+)
from . import AbstractSpookAutomationReferencesRepair
if TYPE_CHECKING:
@@ -44,6 +47,10 @@ class SpookRepair(AbstractSpookAutomationReferencesRepair):
# references nested in some step types, like repeat sequences.
if raw_config := getattr(entity, "raw_config", None):
floor_ids.update(extract_targets_from_config(raw_config).floor_ids)
+ # A disabled step does nothing, so what only it names is left out.
+ floor_ids -= only_in_disabled_steps(
+ raw_config, lambda found: extract_targets_from_config(found).floor_ids
+ )
return async_filter_known_floor_ids(
self.hass,
diff --git a/custom_components/spook/ectoplasms/automation/repairs/unknown_label_references.py b/custom_components/spook/ectoplasms/automation/repairs/unknown_label_references.py
index 1c72e347..990b0e6f 100644
--- a/custom_components/spook/ectoplasms/automation/repairs/unknown_label_references.py
+++ b/custom_components/spook/ectoplasms/automation/repairs/unknown_label_references.py
@@ -8,7 +8,10 @@ from homeassistant.components import automation
from homeassistant.helpers import label_registry as lr
from ....entity_filtering import async_filter_known_label_ids, async_get_all_label_ids
-from ....reference_extraction import extract_targets_from_config
+from ....reference_extraction import (
+ extract_targets_from_config,
+ only_in_disabled_steps,
+)
from . import AbstractSpookAutomationReferencesRepair
if TYPE_CHECKING:
@@ -44,6 +47,10 @@ class SpookRepair(AbstractSpookAutomationReferencesRepair):
# references nested in some step types, like repeat sequences.
if raw_config := getattr(entity, "raw_config", None):
label_ids.update(extract_targets_from_config(raw_config).label_ids)
+ # A disabled step does nothing, so what only it names is left out.
+ label_ids -= only_in_disabled_steps(
+ raw_config, lambda found: extract_targets_from_config(found).label_ids
+ )
return async_filter_known_label_ids(
self.hass,
diff --git a/custom_components/spook/ectoplasms/blueprint/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/blueprint/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 93f682b7..00000000
Binary files a/custom_components/spook/ectoplasms/blueprint/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/blueprint/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/blueprint/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index be0e42b4..00000000
Binary files a/custom_components/spook/ectoplasms/blueprint/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/blueprint/services/__pycache__/importer.cpython-314.pyc b/custom_components/spook/ectoplasms/blueprint/services/__pycache__/importer.cpython-314.pyc
deleted file mode 100644
index dbc0e916..00000000
Binary files a/custom_components/spook/ectoplasms/blueprint/services/__pycache__/importer.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/cloud/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/cloud/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 045282e9..00000000
Binary files a/custom_components/spook/ectoplasms/cloud/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/cloud/__pycache__/entity.cpython-314.pyc b/custom_components/spook/ectoplasms/cloud/__pycache__/entity.cpython-314.pyc
deleted file mode 100644
index 8bbd64a4..00000000
Binary files a/custom_components/spook/ectoplasms/cloud/__pycache__/entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/cloud/__pycache__/switch.cpython-314.pyc b/custom_components/spook/ectoplasms/cloud/__pycache__/switch.cpython-314.pyc
deleted file mode 100644
index d5603d50..00000000
Binary files a/custom_components/spook/ectoplasms/cloud/__pycache__/switch.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/group/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/group/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 7da7a0f6..00000000
Binary files a/custom_components/spook/ectoplasms/group/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/group/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/group/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 62aa3bad..00000000
Binary files a/custom_components/spook/ectoplasms/group/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/group/repairs/__pycache__/unknown_members.cpython-314.pyc b/custom_components/spook/ectoplasms/group/repairs/__pycache__/unknown_members.cpython-314.pyc
deleted file mode 100644
index 762ce506..00000000
Binary files a/custom_components/spook/ectoplasms/group/repairs/__pycache__/unknown_members.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 1ca9a594..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/__pycache__/button.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/__pycache__/button.cpython-314.pyc
deleted file mode 100644
index 4da384d3..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/__pycache__/button.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/__pycache__/device.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/__pycache__/device.cpython-314.pyc
deleted file mode 100644
index 1b3b715d..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/__pycache__/device.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/__pycache__/entity.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/__pycache__/entity.cpython-314.pyc
deleted file mode 100644
index 3b0735fc..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/__pycache__/entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/__pycache__/sensor.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/__pycache__/sensor.cpython-314.pyc
deleted file mode 100644
index 61950e9f..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/__pycache__/sensor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__init__.py b/custom_components/spook/ectoplasms/homeassistant/services/__init__.py
index edc73352..3ae08fe6 100644
--- a/custom_components/spook/ectoplasms/homeassistant/services/__init__.py
+++ b/custom_components/spook/ectoplasms/homeassistant/services/__init__.py
@@ -13,13 +13,47 @@ from homeassistant.components.homeassistant.exposed_entities import (
async_expose_entity,
)
from homeassistant.const import ATTR_ENTITY_ID
-from homeassistant.exceptions import HomeAssistantError
+from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, entity_registry as er
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant, ServiceCall
CONF_ASSISTANTS = "assistants"
+CONF_CONFIG_ENTRY_ID = "config_entry_id"
+CONF_DOMAIN = "domain"
+
+CONFIG_ENTRY_SERVICE_SCHEMA = {
+ vol.Optional(CONF_CONFIG_ENTRY_ID): vol.All(cv.ensure_list, [cv.string]),
+ # Every entry of these integrations: the ones added later included, which
+ # a list of entry IDs written down today would miss.
+ vol.Optional(CONF_DOMAIN): vol.All(cv.ensure_list, [cv.string]),
+}
+
+
+def async_config_entry_ids(hass: HomeAssistant, call: ServiceCall) -> list[str]:
+ """Return the integration entries an action asks for, by ID and by domain.
+
+ A domain without any entries is refused rather than skipped: a typo in it
+ would otherwise do nothing, and say nothing about it either.
+ """
+ entry_ids: list[str] = list(call.data.get(CONF_CONFIG_ENTRY_ID, []))
+ domains: list[str] = call.data.get(CONF_DOMAIN, [])
+
+ if not entry_ids and not domains:
+ msg = "Name the integration entries to change, or their integration"
+ raise ServiceValidationError(msg)
+
+ for domain in domains:
+ if not (entries := hass.config_entries.async_entries(domain)):
+ msg = f"The {domain} integration has no entries"
+ raise ServiceValidationError(msg)
+ entry_ids.extend(
+ entry.entry_id for entry in entries if entry.entry_id not in entry_ids
+ )
+
+ return entry_ids
+
EXPOSURE_SERVICE_SCHEMA = {
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index e82b0823..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_alias_to_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_alias_to_area.cpython-314.pyc
deleted file mode 100644
index 82dec317..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_alias_to_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_alias_to_floor.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_alias_to_floor.cpython-314.pyc
deleted file mode 100644
index eb0ced2d..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_alias_to_floor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_area_to_floor.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_area_to_floor.cpython-314.pyc
deleted file mode 100644
index 76ec1930..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_area_to_floor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_device_to_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_device_to_area.cpython-314.pyc
deleted file mode 100644
index 5f66ffab..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_device_to_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_entity_to_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_entity_to_area.cpython-314.pyc
deleted file mode 100644
index 3a17f8e1..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_entity_to_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_label_to_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_label_to_area.cpython-314.pyc
deleted file mode 100644
index 2141a44e..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_label_to_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_label_to_device.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_label_to_device.cpython-314.pyc
deleted file mode 100644
index 851a647d..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_label_to_device.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_label_to_entity.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_label_to_entity.cpython-314.pyc
deleted file mode 100644
index 79d74b99..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/add_label_to_entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/create_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/create_area.cpython-314.pyc
deleted file mode 100644
index 9f36a708..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/create_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/create_floor.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/create_floor.cpython-314.pyc
deleted file mode 100644
index a3be4bd6..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/create_floor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/create_label.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/create_label.cpython-314.pyc
deleted file mode 100644
index 6ec6621e..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/create_label.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_all_orphaned_entities.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_all_orphaned_entities.cpython-314.pyc
deleted file mode 100644
index 623cef64..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_all_orphaned_entities.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_area.cpython-314.pyc
deleted file mode 100644
index 06ea7ce2..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_floor.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_floor.cpython-314.pyc
deleted file mode 100644
index b7f9c4c4..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_floor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_label.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_label.cpython-314.pyc
deleted file mode 100644
index 2362e7f2..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/delete_label.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_config_entry.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_config_entry.cpython-314.pyc
deleted file mode 100644
index af0930c8..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_config_entry.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_device.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_device.cpython-314.pyc
deleted file mode 100644
index 7831c96f..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_device.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_entity.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_entity.cpython-314.pyc
deleted file mode 100644
index 0809a122..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_polling.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_polling.cpython-314.pyc
deleted file mode 100644
index 114d1f62..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_polling.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_user.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_user.cpython-314.pyc
deleted file mode 100644
index 5e937d5f..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/disable_user.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_config_entry.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_config_entry.cpython-314.pyc
deleted file mode 100644
index 841dcf66..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_config_entry.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_device.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_device.cpython-314.pyc
deleted file mode 100644
index 29495400..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_device.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_entity.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_entity.cpython-314.pyc
deleted file mode 100644
index f7a485b6..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_polling.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_polling.cpython-314.pyc
deleted file mode 100644
index 6a70e925..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_polling.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_user.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_user.cpython-314.pyc
deleted file mode 100644
index f3f7a58f..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/enable_user.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/hide_entity.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/hide_entity.cpython-314.pyc
deleted file mode 100644
index 4a826c3f..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/hide_entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/ignore_all_discovered.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/ignore_all_discovered.cpython-314.pyc
deleted file mode 100644
index 38ebf7c3..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/ignore_all_discovered.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/list_orphaned_database_entities.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/list_orphaned_database_entities.cpython-314.pyc
deleted file mode 100644
index dca1a533..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/list_orphaned_database_entities.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_alias_from_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_alias_from_area.cpython-314.pyc
deleted file mode 100644
index abc1a5eb..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_alias_from_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_alias_from_floor.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_alias_from_floor.cpython-314.pyc
deleted file mode 100644
index 6ec4b0dd..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_alias_from_floor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_area_from_floor.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_area_from_floor.cpython-314.pyc
deleted file mode 100644
index 2cbeaad0..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_area_from_floor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_device_from_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_device_from_area.cpython-314.pyc
deleted file mode 100644
index 54d7897b..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_device_from_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_entity_from_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_entity_from_area.cpython-314.pyc
deleted file mode 100644
index 396a86de..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_entity_from_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_label_from_area.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_label_from_area.cpython-314.pyc
deleted file mode 100644
index 49bd8785..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_label_from_area.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_label_from_device.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_label_from_device.cpython-314.pyc
deleted file mode 100644
index 5ef7e261..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_label_from_device.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_label_from_entity.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_label_from_entity.cpython-314.pyc
deleted file mode 100644
index 22d8acf4..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/remove_label_from_entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/rename_entity.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/rename_entity.cpython-314.pyc
deleted file mode 100644
index ce8f1073..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/rename_entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/restart.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/restart.cpython-314.pyc
deleted file mode 100644
index 8f5da5c2..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/restart.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/set_area_aliases.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/set_area_aliases.cpython-314.pyc
deleted file mode 100644
index fe429e2d..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/set_area_aliases.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/set_floor_aliases.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/set_floor_aliases.cpython-314.pyc
deleted file mode 100644
index 5ff26828..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/set_floor_aliases.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/unhide_entity.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/unhide_entity.cpython-314.pyc
deleted file mode 100644
index b72c48a9..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/unhide_entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/update_entity_id.cpython-314.pyc b/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/update_entity_id.cpython-314.pyc
deleted file mode 100644
index 5a8e13a6..00000000
Binary files a/custom_components/spook/ectoplasms/homeassistant/services/__pycache__/update_entity_id.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/disable_config_entry.py b/custom_components/spook/ectoplasms/homeassistant/services/disable_config_entry.py
index 61165bb5..83af9c31 100644
--- a/custom_components/spook/ectoplasms/homeassistant/services/disable_config_entry.py
+++ b/custom_components/spook/ectoplasms/homeassistant/services/disable_config_entry.py
@@ -4,13 +4,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING
-import voluptuous as vol
-
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.config_entries import ConfigEntryDisabler
-from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
+from . import CONFIG_ENTRY_SERVICE_SCHEMA, async_config_entry_ids
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
@@ -21,11 +19,11 @@ class SpookService(AbstractSpookAdminService):
domain = DOMAIN
service = "disable_config_entry"
- schema = {vol.Required("config_entry_id"): vol.All(cv.ensure_list, [cv.string])}
+ schema = CONFIG_ENTRY_SERVICE_SCHEMA
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
- for config_entry_id in call.data["config_entry_id"]:
+ for config_entry_id in async_config_entry_ids(self.hass, call):
await self.hass.config_entries.async_set_disabled_by(
config_entry_id,
disabled_by=ConfigEntryDisabler.USER,
diff --git a/custom_components/spook/ectoplasms/homeassistant/services/enable_config_entry.py b/custom_components/spook/ectoplasms/homeassistant/services/enable_config_entry.py
index 849faac0..f88bd981 100644
--- a/custom_components/spook/ectoplasms/homeassistant/services/enable_config_entry.py
+++ b/custom_components/spook/ectoplasms/homeassistant/services/enable_config_entry.py
@@ -4,12 +4,10 @@ from __future__ import annotations
from typing import TYPE_CHECKING
-import voluptuous as vol
-
from homeassistant.components.homeassistant import DOMAIN
-from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
+from . import CONFIG_ENTRY_SERVICE_SCHEMA, async_config_entry_ids
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
@@ -20,11 +18,11 @@ class SpookService(AbstractSpookAdminService):
domain = DOMAIN
service = "enable_config_entry"
- schema = {vol.Required("config_entry_id"): vol.All(cv.ensure_list, [cv.string])}
+ schema = CONFIG_ENTRY_SERVICE_SCHEMA
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
- for config_entry_id in call.data["config_entry_id"]:
+ for config_entry_id in async_config_entry_ids(self.hass, call):
await self.hass.config_entries.async_set_disabled_by(
config_entry_id,
disabled_by=None,
diff --git a/custom_components/spook/ectoplasms/input_number/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/input_number/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index d18b2c36..00000000
Binary files a/custom_components/spook/ectoplasms/input_number/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_number/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/input_number/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 0f8ad366..00000000
Binary files a/custom_components/spook/ectoplasms/input_number/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_number/services/__pycache__/create.cpython-314.pyc b/custom_components/spook/ectoplasms/input_number/services/__pycache__/create.cpython-314.pyc
deleted file mode 100644
index 8dc1ced6..00000000
Binary files a/custom_components/spook/ectoplasms/input_number/services/__pycache__/create.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_number/services/__pycache__/decrement.cpython-314.pyc b/custom_components/spook/ectoplasms/input_number/services/__pycache__/decrement.cpython-314.pyc
deleted file mode 100644
index 512e12ea..00000000
Binary files a/custom_components/spook/ectoplasms/input_number/services/__pycache__/decrement.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_number/services/__pycache__/delete.cpython-314.pyc b/custom_components/spook/ectoplasms/input_number/services/__pycache__/delete.cpython-314.pyc
deleted file mode 100644
index f0ab3d0b..00000000
Binary files a/custom_components/spook/ectoplasms/input_number/services/__pycache__/delete.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_number/services/__pycache__/increment.cpython-314.pyc b/custom_components/spook/ectoplasms/input_number/services/__pycache__/increment.cpython-314.pyc
deleted file mode 100644
index baf6b56f..00000000
Binary files a/custom_components/spook/ectoplasms/input_number/services/__pycache__/increment.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_number/services/__pycache__/max.cpython-314.pyc b/custom_components/spook/ectoplasms/input_number/services/__pycache__/max.cpython-314.pyc
deleted file mode 100644
index 9602f4e8..00000000
Binary files a/custom_components/spook/ectoplasms/input_number/services/__pycache__/max.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_number/services/__pycache__/min.cpython-314.pyc b/custom_components/spook/ectoplasms/input_number/services/__pycache__/min.cpython-314.pyc
deleted file mode 100644
index 7e25045d..00000000
Binary files a/custom_components/spook/ectoplasms/input_number/services/__pycache__/min.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_select/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/input_select/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index fa343152..00000000
Binary files a/custom_components/spook/ectoplasms/input_select/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_select/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/input_select/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 2e6d5e28..00000000
Binary files a/custom_components/spook/ectoplasms/input_select/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_select/services/__pycache__/random.cpython-314.pyc b/custom_components/spook/ectoplasms/input_select/services/__pycache__/random.cpython-314.pyc
deleted file mode 100644
index cf2f8ea2..00000000
Binary files a/custom_components/spook/ectoplasms/input_select/services/__pycache__/random.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_select/services/__pycache__/shuffle.cpython-314.pyc b/custom_components/spook/ectoplasms/input_select/services/__pycache__/shuffle.cpython-314.pyc
deleted file mode 100644
index cfd4a020..00000000
Binary files a/custom_components/spook/ectoplasms/input_select/services/__pycache__/shuffle.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/input_select/services/__pycache__/sort.cpython-314.pyc b/custom_components/spook/ectoplasms/input_select/services/__pycache__/sort.cpython-314.pyc
deleted file mode 100644
index 2642f963..00000000
Binary files a/custom_components/spook/ectoplasms/input_select/services/__pycache__/sort.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/integration/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/integration/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 23247e2e..00000000
Binary files a/custom_components/spook/ectoplasms/integration/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/integration/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/integration/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 3ae83c07..00000000
Binary files a/custom_components/spook/ectoplasms/integration/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/integration/repairs/__pycache__/unknown_source.cpython-314.pyc b/custom_components/spook/ectoplasms/integration/repairs/__pycache__/unknown_source.cpython-314.pyc
deleted file mode 100644
index 210c17ea..00000000
Binary files a/custom_components/spook/ectoplasms/integration/repairs/__pycache__/unknown_source.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/integration/repairs/unknown_source.py b/custom_components/spook/ectoplasms/integration/repairs/unknown_source.py
deleted file mode 100644
index fa05dfcd..00000000
--- a/custom_components/spook/ectoplasms/integration/repairs/unknown_source.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""Spook - Your homie."""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from homeassistant.components import sensor
-from homeassistant.const import EVENT_COMPONENT_LOADED
-from homeassistant.helpers import entity_registry as er
-
-from ....repairs import AbstractSpookEntityPlatformUnknownSourceRepair
-
-if TYPE_CHECKING:
- from typing import Any
-
-
-class SpookRepair(AbstractSpookEntityPlatformUnknownSourceRepair):
- """Spook repair tries to find unknown source entities for integration."""
-
- domain = "integration"
- repair = "integration_unknown_source"
- inspect_events = {
- EVENT_COMPONENT_LOADED,
- er.EVENT_ENTITY_REGISTRY_UPDATED,
- }
- inspect_config_entry_changed = True
- inspect_on_reload = "integration"
-
- source_platform_domain = sensor.DOMAIN
-
- def _get_source_entity_id(self, entity: Any) -> str:
- """Return the integrating sensor's source entity ID."""
- # pylint: disable-next=protected-access
- return entity._sensor_source_id # noqa: SLF001
diff --git a/custom_components/spook/ectoplasms/lovelace/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/lovelace/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index e64d1a22..00000000
Binary files a/custom_components/spook/ectoplasms/lovelace/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/lovelace/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/lovelace/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 3470ccef..00000000
Binary files a/custom_components/spook/ectoplasms/lovelace/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/lovelace/repairs/__pycache__/unknown_entity_references.cpython-314.pyc b/custom_components/spook/ectoplasms/lovelace/repairs/__pycache__/unknown_entity_references.cpython-314.pyc
deleted file mode 100644
index abe351a2..00000000
Binary files a/custom_components/spook/ectoplasms/lovelace/repairs/__pycache__/unknown_entity_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/number/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/number/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 491fab7b..00000000
Binary files a/custom_components/spook/ectoplasms/number/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/number/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/number/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index c9948e00..00000000
Binary files a/custom_components/spook/ectoplasms/number/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/number/services/__pycache__/decrement.cpython-314.pyc b/custom_components/spook/ectoplasms/number/services/__pycache__/decrement.cpython-314.pyc
deleted file mode 100644
index b408df7f..00000000
Binary files a/custom_components/spook/ectoplasms/number/services/__pycache__/decrement.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/number/services/__pycache__/increment.cpython-314.pyc b/custom_components/spook/ectoplasms/number/services/__pycache__/increment.cpython-314.pyc
deleted file mode 100644
index f5629d56..00000000
Binary files a/custom_components/spook/ectoplasms/number/services/__pycache__/increment.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/number/services/__pycache__/max.cpython-314.pyc b/custom_components/spook/ectoplasms/number/services/__pycache__/max.cpython-314.pyc
deleted file mode 100644
index 5b152e60..00000000
Binary files a/custom_components/spook/ectoplasms/number/services/__pycache__/max.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/number/services/__pycache__/min.cpython-314.pyc b/custom_components/spook/ectoplasms/number/services/__pycache__/min.cpython-314.pyc
deleted file mode 100644
index 78c9d0c6..00000000
Binary files a/custom_components/spook/ectoplasms/number/services/__pycache__/min.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/person/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/person/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index c23d5bdf..00000000
Binary files a/custom_components/spook/ectoplasms/person/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/person/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/person/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index aa7fc576..00000000
Binary files a/custom_components/spook/ectoplasms/person/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/person/services/__pycache__/add_device_tracker.cpython-314.pyc b/custom_components/spook/ectoplasms/person/services/__pycache__/add_device_tracker.cpython-314.pyc
deleted file mode 100644
index 11017ddf..00000000
Binary files a/custom_components/spook/ectoplasms/person/services/__pycache__/add_device_tracker.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/person/services/__pycache__/remove_device_tracker.cpython-314.pyc b/custom_components/spook/ectoplasms/person/services/__pycache__/remove_device_tracker.cpython-314.pyc
deleted file mode 100644
index 39bbd41d..00000000
Binary files a/custom_components/spook/ectoplasms/person/services/__pycache__/remove_device_tracker.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/proximity/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/proximity/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 07eedd2a..00000000
Binary files a/custom_components/spook/ectoplasms/proximity/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 5855c473..00000000
Binary files a/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/unknown_ignored_zones.cpython-314.pyc b/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/unknown_ignored_zones.cpython-314.pyc
deleted file mode 100644
index 9006ba2f..00000000
Binary files a/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/unknown_ignored_zones.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/unknown_tracked_entities.cpython-314.pyc b/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/unknown_tracked_entities.cpython-314.pyc
deleted file mode 100644
index 2d45cb00..00000000
Binary files a/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/unknown_tracked_entities.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/unknown_zone.cpython-314.pyc b/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/unknown_zone.cpython-314.pyc
deleted file mode 100644
index f7123cf7..00000000
Binary files a/custom_components/spook/ectoplasms/proximity/repairs/__pycache__/unknown_zone.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/recorder/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/recorder/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 088056b5..00000000
Binary files a/custom_components/spook/ectoplasms/recorder/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/recorder/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/recorder/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index d4624cda..00000000
Binary files a/custom_components/spook/ectoplasms/recorder/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/recorder/services/__pycache__/import_statistics.cpython-314.pyc b/custom_components/spook/ectoplasms/recorder/services/__pycache__/import_statistics.cpython-314.pyc
deleted file mode 100644
index 6fa96ce0..00000000
Binary files a/custom_components/spook/ectoplasms/recorder/services/__pycache__/import_statistics.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 42766c72..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/__pycache__/button.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/__pycache__/button.cpython-314.pyc
deleted file mode 100644
index 4e65f898..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/__pycache__/button.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/__pycache__/entity.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/__pycache__/entity.cpython-314.pyc
deleted file mode 100644
index 78d23c80..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/__pycache__/entity.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/__pycache__/event.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/__pycache__/event.cpython-314.pyc
deleted file mode 100644
index c797dae0..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/__pycache__/event.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/__pycache__/sensor.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/__pycache__/sensor.cpython-314.pyc
deleted file mode 100644
index 38bc2c47..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/__pycache__/sensor.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index bc5582f6..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/services/__pycache__/create.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/services/__pycache__/create.cpython-314.pyc
deleted file mode 100644
index 3dcdeeeb..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/services/__pycache__/create.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/services/__pycache__/ignore_all.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/services/__pycache__/ignore_all.cpython-314.pyc
deleted file mode 100644
index acbfb956..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/services/__pycache__/ignore_all.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/services/__pycache__/remove.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/services/__pycache__/remove.cpython-314.pyc
deleted file mode 100644
index 448231bb..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/services/__pycache__/remove.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/services/__pycache__/unignore_all.cpython-314.pyc b/custom_components/spook/ectoplasms/repairs/services/__pycache__/unignore_all.cpython-314.pyc
deleted file mode 100644
index 4ece3c51..00000000
Binary files a/custom_components/spook/ectoplasms/repairs/services/__pycache__/unignore_all.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/repairs/services/ignore_all.py b/custom_components/spook/ectoplasms/repairs/services/ignore_all.py
index e8fbadba..edfdad28 100644
--- a/custom_components/spook/ectoplasms/repairs/services/ignore_all.py
+++ b/custom_components/spook/ectoplasms/repairs/services/ignore_all.py
@@ -4,8 +4,10 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+import voluptuous as vol
+
from homeassistant.components.repairs import DOMAIN
-from homeassistant.helpers import issue_registry as ir
+from homeassistant.helpers import config_validation as cv, issue_registry as ir
from ....services import AbstractSpookAdminService
@@ -19,8 +21,20 @@ class SpookService(AbstractSpookAdminService):
domain = DOMAIN
service = "ignore_all"
- async def async_handle_service(self, _: ServiceCall) -> None:
+ schema = {
+ # Left out, every issue. Named, only the issues those integrations
+ # raised: HACS nagging for a restart after every update, for example,
+ # without taking anything else down with it. Named, it has to name
+ # something: an empty list from a template that found nothing must
+ # not turn into every issue there is.
+ vol.Optional("domain"): vol.All(cv.ensure_list, [cv.string], vol.Length(min=1)),
+ }
+
+ async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
+ domains = set(call.data.get("domain", []))
issue_registry = ir.async_get(self.hass)
for domain, issue_id in issue_registry.issues:
+ if domains and domain not in domains:
+ continue
issue_registry.async_ignore(domain, issue_id, ignore=True)
diff --git a/custom_components/spook/ectoplasms/repairs/services/unignore_all.py b/custom_components/spook/ectoplasms/repairs/services/unignore_all.py
index 158ee6c4..a90a7ae0 100644
--- a/custom_components/spook/ectoplasms/repairs/services/unignore_all.py
+++ b/custom_components/spook/ectoplasms/repairs/services/unignore_all.py
@@ -4,8 +4,10 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+import voluptuous as vol
+
from homeassistant.components.repairs import DOMAIN
-from homeassistant.helpers import issue_registry as ir
+from homeassistant.helpers import config_validation as cv, issue_registry as ir
from ....services import AbstractSpookAdminService
@@ -19,8 +21,20 @@ class SpookService(AbstractSpookAdminService):
domain = DOMAIN
service = "unignore_all"
- async def async_handle_service(self, _: ServiceCall) -> None:
+ schema = {
+ # Left out, every issue. Named, only the issues those integrations
+ # raised: HACS nagging for a restart after every update, for example,
+ # without taking anything else down with it. Named, it has to name
+ # something: an empty list from a template that found nothing must
+ # not turn into every issue there is.
+ vol.Optional("domain"): vol.All(cv.ensure_list, [cv.string], vol.Length(min=1)),
+ }
+
+ async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
+ domains = set(call.data.get("domain", []))
issue_registry = ir.async_get(self.hass)
for domain, issue_id in issue_registry.issues:
+ if domains and domain not in domains:
+ continue
issue_registry.async_ignore(domain, issue_id, ignore=False)
diff --git a/custom_components/spook/ectoplasms/scene/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/scene/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 6b719787..00000000
Binary files a/custom_components/spook/ectoplasms/scene/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/scene/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/scene/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index b5bd145b..00000000
Binary files a/custom_components/spook/ectoplasms/scene/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/scene/repairs/__pycache__/unknown_entity_references.cpython-314.pyc b/custom_components/spook/ectoplasms/scene/repairs/__pycache__/unknown_entity_references.cpython-314.pyc
deleted file mode 100644
index a8d39838..00000000
Binary files a/custom_components/spook/ectoplasms/scene/repairs/__pycache__/unknown_entity_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/script/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/script/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 8fdbe3ad..00000000
Binary files a/custom_components/spook/ectoplasms/script/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/script/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/script/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 326382a1..00000000
Binary files a/custom_components/spook/ectoplasms/script/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_area_references.cpython-314.pyc b/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_area_references.cpython-314.pyc
deleted file mode 100644
index 0fef1fb9..00000000
Binary files a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_area_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_device_references.cpython-314.pyc b/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_device_references.cpython-314.pyc
deleted file mode 100644
index 53ff649a..00000000
Binary files a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_device_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_entity_references.cpython-314.pyc b/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_entity_references.cpython-314.pyc
deleted file mode 100644
index f3df29ac..00000000
Binary files a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_entity_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_floor_references.cpython-314.pyc b/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_floor_references.cpython-314.pyc
deleted file mode 100644
index 7819bf5a..00000000
Binary files a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_floor_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_label_references.cpython-314.pyc b/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_label_references.cpython-314.pyc
deleted file mode 100644
index 22902350..00000000
Binary files a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_label_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_service_references.cpython-314.pyc b/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_service_references.cpython-314.pyc
deleted file mode 100644
index e29ea1a9..00000000
Binary files a/custom_components/spook/ectoplasms/script/repairs/__pycache__/unknown_service_references.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/script/repairs/unknown_area_references.py b/custom_components/spook/ectoplasms/script/repairs/unknown_area_references.py
index 7a182672..32e51312 100644
--- a/custom_components/spook/ectoplasms/script/repairs/unknown_area_references.py
+++ b/custom_components/spook/ectoplasms/script/repairs/unknown_area_references.py
@@ -8,7 +8,10 @@ from homeassistant.components import script
from homeassistant.helpers import area_registry as ar
from ....entity_filtering import async_filter_known_area_ids, async_get_all_area_ids
-from ....reference_extraction import extract_targets_from_config
+from ....reference_extraction import (
+ extract_targets_from_config,
+ only_in_disabled_steps,
+)
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
if TYPE_CHECKING:
@@ -42,6 +45,10 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
# references nested in some step types, like repeat sequences.
if raw_config := getattr(entity, "raw_config", None):
area_ids.update(extract_targets_from_config(raw_config).area_ids)
+ # A disabled step does nothing, so what only it names is left out.
+ area_ids -= only_in_disabled_steps(
+ raw_config, lambda found: extract_targets_from_config(found).area_ids
+ )
return async_filter_known_area_ids(
self.hass,
diff --git a/custom_components/spook/ectoplasms/script/repairs/unknown_device_references.py b/custom_components/spook/ectoplasms/script/repairs/unknown_device_references.py
index 84f79931..58f5d8db 100644
--- a/custom_components/spook/ectoplasms/script/repairs/unknown_device_references.py
+++ b/custom_components/spook/ectoplasms/script/repairs/unknown_device_references.py
@@ -8,7 +8,10 @@ from homeassistant.components import script
from homeassistant.helpers import device_registry as dr
from ....entity_filtering import async_filter_known_device_ids, async_get_all_device_ids
-from ....reference_extraction import extract_targets_from_config
+from ....reference_extraction import (
+ extract_targets_from_config,
+ only_in_disabled_steps,
+)
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
from ....template_extraction import extract_device_ids_from_config
@@ -46,6 +49,14 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
device_ids.update(extract_targets_from_config(raw_config).device_ids)
# Devices referenced via device_entities() in templates.
device_ids.update(extract_device_ids_from_config(raw_config))
+ # A disabled step does nothing, so what only it names is left out.
+ device_ids -= only_in_disabled_steps(
+ raw_config,
+ lambda found: (
+ extract_targets_from_config(found).device_ids
+ | extract_device_ids_from_config(found)
+ ),
+ )
return async_filter_known_device_ids(
self.hass,
diff --git a/custom_components/spook/ectoplasms/script/repairs/unknown_entity_references.py b/custom_components/spook/ectoplasms/script/repairs/unknown_entity_references.py
index 1d879401..7e04f86f 100644
--- a/custom_components/spook/ectoplasms/script/repairs/unknown_entity_references.py
+++ b/custom_components/spook/ectoplasms/script/repairs/unknown_entity_references.py
@@ -8,7 +8,9 @@ from homeassistant.components import script
from homeassistant.const import EVENT_COMPONENT_LOADED
from homeassistant.helpers import entity_registry as er
+from ....action_extraction import async_extract_entities_from_action_config
from ....entity_filtering import async_get_all_entity_ids, async_get_all_services
+from ....reference_extraction import without_disabled_steps
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
from ....template_extraction import (
async_extract_entities_from_config,
@@ -19,37 +21,6 @@ if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
-def extract_entities_from_trigger_config(config: dict[str, Any] | list) -> set[str]:
- """Extract entity IDs from a trigger config."""
- entities = set()
-
- if not config:
- return entities
-
- if isinstance(config, list):
- for item in config:
- entities.update(extract_entities_from_trigger_config(item))
- return entities
-
- if not isinstance(config, dict):
- return entities
-
- # Extract entity_id from trigger config
- if "entity_id" in config:
- entity_id = config["entity_id"]
- if isinstance(entity_id, str):
- entities.add(entity_id)
- elif isinstance(entity_id, list):
- entities.update([e for e in entity_id if isinstance(e, str)])
-
- # Recursively process nested configs
- for value in config.values():
- if isinstance(value, (dict, list)):
- entities.update(extract_entities_from_trigger_config(value))
-
- return entities
-
-
def extract_referenced_entities_from_script(entity: script.ScriptEntity) -> set[str]:
"""Return entity references from a script entity."""
try:
@@ -74,18 +45,12 @@ async def extract_template_entities_from_script_entity(
``known_services`` is built once per inspection and handed down, because
building it flattens every service Home Assistant has and every script
with a template in it needs the same answer.
- """
- # Get the script configuration
- config = None
- if hasattr(entity, "script"):
- # Try to get configuration safely
- if hasattr(entity.script, "config"):
- config = entity.script.config
- elif hasattr(entity.script, "_config"):
- # Fallback to _config if needed
- config = getattr(entity.script, "_config", None)
- if not config:
+ Read from the configuration as written, like the automation repair does.
+ The script helper underneath keeps no configuration of its own, so this
+ used to find nothing at all.
+ """
+ if not (config := getattr(entity, "raw_config", None)):
return set()
return await async_extract_entities_from_config(hass, config, known_services)
@@ -112,34 +77,6 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
_known_entity_ids: set[str]
_known_services: set[str]
- def _get_blueprint_trigger_entities(self, entity: script.ScriptEntity) -> set[str]:
- """Extract entity references from blueprint trigger inputs."""
- entities = set()
-
- if (
- not hasattr(entity, "referenced_blueprint")
- or not entity.referenced_blueprint
- ):
- return entities
-
- config = getattr(entity, "_config", None)
- if not config or not isinstance(config, dict) or "use_blueprint" not in config:
- return entities
-
- blueprint_config = config["use_blueprint"]
- if "input" not in blueprint_config:
- return entities
-
- input_config = blueprint_config["input"]
- # Look for inputs that might contain triggers (like discard_when)
- for value in input_config.values():
- if isinstance(value, (dict, list)) and "trigger" in str(value):
- trigger_entities = extract_entities_from_trigger_config(value)
- if trigger_entities:
- entities.update(trigger_entities)
-
- return entities
-
async def _async_setup_inspection(self) -> None:
"""Cache what every script in this cycle needs looked up.
@@ -152,13 +89,42 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
)
self._known_services = async_get_all_services(self.hass)
+ async def _async_named_in(self, config: dict[str, Any]) -> set[str]:
+ """Return the entities a configuration names, the way this repair reads it.
+
+ The same reading the report is built from, the steps and templates
+ alike, so comparing it with and without the disabled steps leaves out
+ exactly what only those name.
+ """
+ steps = config.get("sequence") or []
+ named = await async_extract_entities_from_action_config(
+ self.hass,
+ # A single step can be written without a list.
+ [steps] if isinstance(steps, dict) else steps,
+ known_services=self._known_services,
+ )
+ named |= await async_extract_entities_from_config(
+ self.hass, config, self._known_services
+ )
+ return named
+
async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
"""Return unknown entity IDs referenced by ``entity`` (incl. templates)."""
# Get all referenced entities from the script
all_entities = extract_referenced_entities_from_script(entity)
- # Check for blueprint trigger inputs
- all_entities.update(self._get_blueprint_trigger_entities(entity))
+ # Home Assistant's own list leaves out entities handed over as action
+ # data, like `entity: light.kitchen` in a call to another script. The
+ # automation repair reads those from the configuration as written, and
+ # so does this one.
+ if isinstance(raw_config := getattr(entity, "raw_config", None), dict):
+ all_entities.update(
+ await async_extract_entities_from_action_config(
+ self.hass,
+ raw_config.get("sequence") or [],
+ known_services=self._known_services,
+ )
+ )
# Extract entities from Template objects within the script entity
all_entities.update(
@@ -167,6 +133,15 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
)
)
+ # Home Assistant's own list includes disabled steps too. A step parked
+ # that way does nothing, so what only it names is left out: whatever
+ # this repair finds in the configuration, and no longer finds once
+ # those are pruned.
+ if isinstance(raw_config, dict):
+ named = await self._async_named_in(raw_config)
+ still_named = await self._async_named_in(without_disabled_steps(raw_config))
+ all_entities -= named - still_named
+
return await async_filter_known_entity_ids_with_templates(
self.hass,
entity_ids=all_entities,
diff --git a/custom_components/spook/ectoplasms/script/repairs/unknown_floor_references.py b/custom_components/spook/ectoplasms/script/repairs/unknown_floor_references.py
index 230a4aa8..4f858dba 100644
--- a/custom_components/spook/ectoplasms/script/repairs/unknown_floor_references.py
+++ b/custom_components/spook/ectoplasms/script/repairs/unknown_floor_references.py
@@ -8,7 +8,10 @@ from homeassistant.components import script
from homeassistant.helpers import floor_registry as fr
from ....entity_filtering import async_filter_known_floor_ids, async_get_all_floor_ids
-from ....reference_extraction import extract_targets_from_config
+from ....reference_extraction import (
+ extract_targets_from_config,
+ only_in_disabled_steps,
+)
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
if TYPE_CHECKING:
@@ -42,6 +45,10 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
# references nested in some step types, like repeat sequences.
if raw_config := getattr(entity, "raw_config", None):
floor_ids.update(extract_targets_from_config(raw_config).floor_ids)
+ # A disabled step does nothing, so what only it names is left out.
+ floor_ids -= only_in_disabled_steps(
+ raw_config, lambda found: extract_targets_from_config(found).floor_ids
+ )
return async_filter_known_floor_ids(
self.hass,
diff --git a/custom_components/spook/ectoplasms/script/repairs/unknown_label_references.py b/custom_components/spook/ectoplasms/script/repairs/unknown_label_references.py
index 5ff765b9..68f182ef 100644
--- a/custom_components/spook/ectoplasms/script/repairs/unknown_label_references.py
+++ b/custom_components/spook/ectoplasms/script/repairs/unknown_label_references.py
@@ -8,7 +8,10 @@ from homeassistant.components import script
from homeassistant.helpers import label_registry as lr
from ....entity_filtering import async_filter_known_label_ids, async_get_all_label_ids
-from ....reference_extraction import extract_targets_from_config
+from ....reference_extraction import (
+ extract_targets_from_config,
+ only_in_disabled_steps,
+)
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair
if TYPE_CHECKING:
@@ -42,6 +45,10 @@ class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
# references nested in some step types, like repeat sequences.
if raw_config := getattr(entity, "raw_config", None):
label_ids.update(extract_targets_from_config(raw_config).label_ids)
+ # A disabled step does nothing, so what only it names is left out.
+ label_ids -= only_in_disabled_steps(
+ raw_config, lambda found: extract_targets_from_config(found).label_ids
+ )
return async_filter_known_label_ids(
self.hass,
diff --git a/custom_components/spook/ectoplasms/select/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/select/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 352780f9..00000000
Binary files a/custom_components/spook/ectoplasms/select/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/select/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/select/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index bcc21b69..00000000
Binary files a/custom_components/spook/ectoplasms/select/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/select/services/__pycache__/random.cpython-314.pyc b/custom_components/spook/ectoplasms/select/services/__pycache__/random.cpython-314.pyc
deleted file mode 100644
index 806123dc..00000000
Binary files a/custom_components/spook/ectoplasms/select/services/__pycache__/random.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/spook/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/spook/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 60ef61fd..00000000
Binary files a/custom_components/spook/ectoplasms/spook/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/spook/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/spook/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 76b853c0..00000000
Binary files a/custom_components/spook/ectoplasms/spook/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/spook/services/__pycache__/boo.cpython-314.pyc b/custom_components/spook/ectoplasms/spook/services/__pycache__/boo.cpython-314.pyc
deleted file mode 100644
index 40302194..00000000
Binary files a/custom_components/spook/ectoplasms/spook/services/__pycache__/boo.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/spook/services/__pycache__/random_fail.cpython-314.pyc b/custom_components/spook/ectoplasms/spook/services/__pycache__/random_fail.cpython-314.pyc
deleted file mode 100644
index 57a2f1e1..00000000
Binary files a/custom_components/spook/ectoplasms/spook/services/__pycache__/random_fail.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/switch_as_x/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/switch_as_x/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index d56843ea..00000000
Binary files a/custom_components/spook/ectoplasms/switch_as_x/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/switch_as_x/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/switch_as_x/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 1197b61d..00000000
Binary files a/custom_components/spook/ectoplasms/switch_as_x/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/switch_as_x/repairs/__pycache__/unknown_source.cpython-314.pyc b/custom_components/spook/ectoplasms/switch_as_x/repairs/__pycache__/unknown_source.cpython-314.pyc
deleted file mode 100644
index 1151de44..00000000
Binary files a/custom_components/spook/ectoplasms/switch_as_x/repairs/__pycache__/unknown_source.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/switch_as_x/repairs/unknown_source.py b/custom_components/spook/ectoplasms/switch_as_x/repairs/unknown_source.py
deleted file mode 100644
index 8e5f5404..00000000
--- a/custom_components/spook/ectoplasms/switch_as_x/repairs/unknown_source.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""Spook - Your homie."""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from homeassistant.const import EVENT_COMPONENT_LOADED
-from homeassistant.helpers import entity_registry as er
-
-from ....repairs import AbstractSpookEntityPlatformUnknownSourceRepair
-
-if TYPE_CHECKING:
- from typing import Any
-
-
-class SpookRepair(AbstractSpookEntityPlatformUnknownSourceRepair):
- """Spook repair tries to find unknown source entities for switch_as_x."""
-
- domain = "switch_as_x"
- repair = "switch_as_x_unknown_source"
- inspect_events = {
- EVENT_COMPONENT_LOADED,
- er.EVENT_ENTITY_REGISTRY_UPDATED,
- }
- inspect_config_entry_changed = "switch_as_x"
-
- def _get_source_entity_id(self, entity: Any) -> str:
- """Return the wrapped switch's entity ID."""
- # pylint: disable-next=protected-access
- return entity._switch_entity_id # noqa: SLF001
diff --git a/custom_components/spook/ectoplasms/timer/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/timer/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 35ab8a28..00000000
Binary files a/custom_components/spook/ectoplasms/timer/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/timer/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/timer/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 77d791fb..00000000
Binary files a/custom_components/spook/ectoplasms/timer/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/timer/services/__pycache__/set_duration.cpython-314.pyc b/custom_components/spook/ectoplasms/timer/services/__pycache__/set_duration.cpython-314.pyc
deleted file mode 100644
index 3da73d56..00000000
Binary files a/custom_components/spook/ectoplasms/timer/services/__pycache__/set_duration.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/trend/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/trend/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index db889d3c..00000000
Binary files a/custom_components/spook/ectoplasms/trend/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/trend/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/trend/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index f9302a7d..00000000
Binary files a/custom_components/spook/ectoplasms/trend/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/trend/repairs/__pycache__/unknown_source.cpython-314.pyc b/custom_components/spook/ectoplasms/trend/repairs/__pycache__/unknown_source.cpython-314.pyc
deleted file mode 100644
index 76a3c9de..00000000
Binary files a/custom_components/spook/ectoplasms/trend/repairs/__pycache__/unknown_source.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/trend/repairs/unknown_source.py b/custom_components/spook/ectoplasms/trend/repairs/unknown_source.py
deleted file mode 100644
index 8c31078a..00000000
--- a/custom_components/spook/ectoplasms/trend/repairs/unknown_source.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""Spook - Your homie."""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from homeassistant.components import binary_sensor
-from homeassistant.const import EVENT_COMPONENT_LOADED
-from homeassistant.helpers import entity_registry as er
-
-from ....repairs import AbstractSpookEntityPlatformUnknownSourceRepair
-
-if TYPE_CHECKING:
- from typing import Any
-
-
-class SpookRepair(AbstractSpookEntityPlatformUnknownSourceRepair):
- """Spook repair tries to find unknown source entities for trend sensors."""
-
- domain = "trend"
- repair = "trend_unknown_source"
- inspect_events = {
- EVENT_COMPONENT_LOADED,
- er.EVENT_ENTITY_REGISTRY_UPDATED,
- }
- inspect_on_reload = "trend"
-
- source_platform_domain = binary_sensor.DOMAIN
-
- def _get_source_entity_id(self, entity: Any) -> str:
- """Return the trend sensor's source entity ID."""
- # pylint: disable-next=protected-access
- return entity._entity_id # noqa: SLF001
diff --git a/custom_components/spook/ectoplasms/utility_meter/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/utility_meter/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 61c9cf41..00000000
Binary files a/custom_components/spook/ectoplasms/utility_meter/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/utility_meter/repairs/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/utility_meter/repairs/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 0b69911a..00000000
Binary files a/custom_components/spook/ectoplasms/utility_meter/repairs/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/utility_meter/repairs/__pycache__/unknown_source.cpython-314.pyc b/custom_components/spook/ectoplasms/utility_meter/repairs/__pycache__/unknown_source.cpython-314.pyc
deleted file mode 100644
index 6a8d41f7..00000000
Binary files a/custom_components/spook/ectoplasms/utility_meter/repairs/__pycache__/unknown_source.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/utility_meter/repairs/unknown_source.py b/custom_components/spook/ectoplasms/utility_meter/repairs/unknown_source.py
deleted file mode 100644
index 89f7dabc..00000000
--- a/custom_components/spook/ectoplasms/utility_meter/repairs/unknown_source.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""Spook - Your homie."""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from homeassistant.components import sensor
-from homeassistant.const import EVENT_COMPONENT_LOADED
-from homeassistant.helpers import entity_registry as er
-
-from ....repairs import AbstractSpookEntityPlatformUnknownSourceRepair
-
-if TYPE_CHECKING:
- from typing import Any
-
-
-class SpookRepair(AbstractSpookEntityPlatformUnknownSourceRepair):
- """Spook repair tries to find unknown source entities for utility meters."""
-
- domain = "utility_meter"
- repair = "utility_meter_unknown_source"
- inspect_events = {
- EVENT_COMPONENT_LOADED,
- er.EVENT_ENTITY_REGISTRY_UPDATED,
- }
- inspect_on_reload = "utility_meter"
-
- source_platform_domain = sensor.DOMAIN
-
- def _get_source_entity_id(self, entity: Any) -> str:
- """Return the utility-meter sensor's source entity ID."""
- # pylint: disable-next=protected-access
- return entity._sensor_source_id # noqa: SLF001
diff --git a/custom_components/spook/ectoplasms/zone/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/zone/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index d5eb3369..00000000
Binary files a/custom_components/spook/ectoplasms/zone/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/zone/services/__pycache__/__init__.cpython-314.pyc b/custom_components/spook/ectoplasms/zone/services/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index b40de0b6..00000000
Binary files a/custom_components/spook/ectoplasms/zone/services/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/zone/services/__pycache__/create.cpython-314.pyc b/custom_components/spook/ectoplasms/zone/services/__pycache__/create.cpython-314.pyc
deleted file mode 100644
index afbb4139..00000000
Binary files a/custom_components/spook/ectoplasms/zone/services/__pycache__/create.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/zone/services/__pycache__/delete.cpython-314.pyc b/custom_components/spook/ectoplasms/zone/services/__pycache__/delete.cpython-314.pyc
deleted file mode 100644
index 4009ac6f..00000000
Binary files a/custom_components/spook/ectoplasms/zone/services/__pycache__/delete.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/zone/services/__pycache__/update.cpython-314.pyc b/custom_components/spook/ectoplasms/zone/services/__pycache__/update.cpython-314.pyc
deleted file mode 100644
index 2786616c..00000000
Binary files a/custom_components/spook/ectoplasms/zone/services/__pycache__/update.cpython-314.pyc and /dev/null differ
diff --git a/custom_components/spook/ectoplasms/zone/services/update.py b/custom_components/spook/ectoplasms/zone/services/update.py
index 3ca76206..ca2dfc7f 100644
--- a/custom_components/spook/ectoplasms/zone/services/update.py
+++ b/custom_components/spook/ectoplasms/zone/services/update.py
@@ -8,10 +8,12 @@ import voluptuous as vol
from homeassistant.components.zone import (
DOMAIN,
+ ENTITY_ID_HOME,
UPDATE_FIELDS,
Zone,
ZoneStorageCollection,
)
+from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_component import DATA_INSTANCES, EntityComponent
@@ -22,8 +24,24 @@ if TYPE_CHECKING:
from homeassistant.core import ServiceCall
+def _is_the_house(entity: Zone) -> bool:
+ """Tell whether a zone is the home zone drawn from the house's location.
+
+ Home Assistant only draws it when no zone has taken `zone.home` already:
+ zones from YAML and storage are set up first, and one named Home gets that
+ entity ID. Those are ordinary zones, and moving the house would not move
+ them. The drawn one is the only editable zone without a stored ID.
+ """
+ return (
+ entity.entity_id == ENTITY_ID_HOME
+ and entity.editable
+ # pylint: disable-next=protected-access
+ and "id" not in entity._config # noqa: SLF001
+ )
+
+
class SpookService(AbstractSpookAdminService):
- """Zone service to update a zone on the fly."""
+ """Zone service to update a zone on the fly, the home zone included."""
domain = DOMAIN
service = "update"
@@ -50,6 +68,10 @@ class SpookService(AbstractSpookAdminService):
message = f"Could not find entity_id: {call.data['entity_id']}"
raise HomeAssistantError(message)
+ if _is_the_house(entity):
+ await self._async_update_home(call)
+ return
+
# pylint: disable-next=protected-access
if not entity.editable or "id" not in entity._config: # noqa: SLF001
message = f"This zone is not editable: {call.data['entity_id']}"
@@ -60,3 +82,37 @@ class SpookService(AbstractSpookAdminService):
# pylint: disable-next=protected-access
await collection.async_update_item(entity._config["id"], data) # noqa: SLF001
+
+ async def _async_update_home(self, call: ServiceCall) -> None:
+ """Move or resize the home zone.
+
+ It is not a stored zone: Home Assistant draws it from the location
+ set for the whole house, and redraws it when that changes. So this
+ changes the location, the same way Home Assistant's own
+ `homeassistant.set_location` does, and the radius along with it.
+
+ Only those. Its name is the name of the whole house, and a home zone
+ is never passive.
+ """
+ data = dict(call.data)
+ data.pop("entity_id")
+
+ if unsupported := set(data) - {CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS}:
+ message = (
+ "The home zone only takes a latitude, longitude and radius, "
+ f"not: {', '.join(sorted(unsupported))}"
+ )
+ raise HomeAssistantError(message)
+
+ # Home Assistant keeps the radius of the house in whole meters, and
+ # never below zero. Cutting a fraction off quietly would be a
+ # different zone than was asked for, and the location is stored
+ # without going through the checks its configuration normally does.
+ if CONF_RADIUS in data:
+ radius = data[CONF_RADIUS]
+ if radius != int(radius) or radius < 0:
+ message = "The home zone takes its radius in whole meters, zero or more"
+ raise HomeAssistantError(message)
+ data[CONF_RADIUS] = int(radius)
+
+ await self.hass.config.async_update(**data)
diff --git a/custom_components/spook/entity_filtering.py b/custom_components/spook/entity_filtering.py
index 757e3f1c..23af8f3f 100644
--- a/custom_components/spook/entity_filtering.py
+++ b/custom_components/spook/entity_filtering.py
@@ -660,17 +660,45 @@ def async_get_all_services(hass: HomeAssistant) -> set[str]:
}
+@callback
+def async_get_disabled_integrations(hass: HomeAssistant) -> set[str]:
+ """Return the integrations whose config entries are all disabled.
+
+ Turning off an integration takes its actions with it. Its config entries
+ are still there, which is how this tells "switched off on purpose" apart
+ from "gone".
+ """
+ entries = hass.config_entries.async_entries()
+ with_entries = {entry.domain for entry in entries}
+ with_enabled_entries = {
+ entry.domain for entry in entries if entry.disabled_by is None
+ }
+
+ return with_entries - with_enabled_entries
+
+
@callback
def async_filter_known_services(
hass: HomeAssistant, *, services: set[str], known_services: set[str] | None = None
) -> set[str]:
- """Filter out known services."""
+ """Filter out known services.
+
+ An action of an integration that is disabled is not unknown, it is
+ switched off, sometimes by Spook's own `homeassistant.disable_config_entry`.
+ Those are left out too. A typo in such an action only shows up once the
+ integration is enabled again, which is when it would break anyway.
+ """
if known_services is None:
known_services = async_get_all_services(hass)
+
+ disabled_integrations = async_get_disabled_integrations(hass)
+
return {
service.lower()
for service in services - known_services
- if isinstance(service, str) and service
+ if isinstance(service, str)
+ and service
+ and service.lower().split(".", 1)[0] not in disabled_integrations
}
diff --git a/custom_components/spook/icons.json b/custom_components/spook/icons.json
index 6eae7e09..dcea4c05 100644
--- a/custom_components/spook/icons.json
+++ b/custom_components/spook/icons.json
@@ -1,5 +1,8 @@
{
"conditions": {
+ "is_at_target_temperature": {
+ "condition": "mdi:thermometer-check"
+ },
"chance": {
"condition": "mdi:dice-multiple"
},
@@ -23,6 +26,9 @@
}
},
"triggers": {
+ "automation_turned_off": {
+ "trigger": "mdi:robot-off"
+ },
"all_of": {
"trigger": "mdi:checkbox-multiple-marked"
},
@@ -71,6 +77,18 @@
"while": {
"trigger": "mdi:repeat"
},
+ "target_temperature_reached": {
+ "trigger": "mdi:thermometer-check"
+ },
+ "update_installed": {
+ "trigger": "mdi:package-up"
+ },
+ "position_reached": {
+ "trigger": "mdi:window-shutter-settings"
+ },
+ "script_started": {
+ "trigger": "mdi:script-text-play"
+ },
"user_added": {
"trigger": "mdi:account-plus"
}
diff --git a/custom_components/spook/integrations/spook_calibration/manifest.json b/custom_components/spook/integrations/spook_calibration/manifest.json
index 334225f4..07bbccb0 100644
--- a/custom_components/spook/integrations/spook_calibration/manifest.json
+++ b/custom_components/spook/integrations/spook_calibration/manifest.json
@@ -10,5 +10,5 @@
"iot_class": "calculated",
"issue_tracker": "https://github.com/frenck/spook/issues",
"requirements": [],
- "version": "5.8.1"
+ "version": "5.9.0"
}
diff --git a/custom_components/spook/integrations/spook_calibration/translations/hu.json b/custom_components/spook/integrations/spook_calibration/translations/hu.json
index 0967ef42..b5f55bce 100644
--- a/custom_components/spook/integrations/spook_calibration/translations/hu.json
+++ b/custom_components/spook/integrations/spook_calibration/translations/hu.json
@@ -1 +1,28 @@
-{}
+{
+ "config": {
+ "step": {
+ "user": {
+ "data": {
+ "name": "Megnevezés",
+ "entity_id": "Forrás entitás",
+ "offset": "Eltolás mértéke",
+ "factor": "Szorzó"
+ },
+ "title": "Kalibrálás 👻"
+ }
+ }
+ },
+ "options": {
+ "step": {
+ "init": {
+ "data": {
+ "entity_id": "Forrás entitás",
+ "offset": "Eltolás mértéke",
+ "factor": "Szorzó"
+ },
+ "title": "Kalibrálás 👻"
+ }
+ }
+ },
+ "title": "Kalibrálás 👻"
+}
diff --git a/custom_components/spook/integrations/spook_inverse/manifest.json b/custom_components/spook/integrations/spook_inverse/manifest.json
index 27c97122..f3e210d4 100644
--- a/custom_components/spook/integrations/spook_inverse/manifest.json
+++ b/custom_components/spook/integrations/spook_inverse/manifest.json
@@ -10,5 +10,5 @@
"iot_class": "calculated",
"issue_tracker": "https://github.com/frenck/spook/issues",
"requirements": [],
- "version": "5.8.1"
+ "version": "5.9.0"
}
diff --git a/custom_components/spook/integrations/spook_inverse/translations/de.json b/custom_components/spook/integrations/spook_inverse/translations/de.json
index e2e3e8a4..7bf817d1 100644
--- a/custom_components/spook/integrations/spook_inverse/translations/de.json
+++ b/custom_components/spook/integrations/spook_inverse/translations/de.json
@@ -11,10 +11,12 @@
"title": "Umkehren 👻"
},
"user": {
- "description": "Dieser Helfer ermöglicht es Ihnen, das Verhalten einer Entität umzukehren. Zum Beispiel können Sie den Zustand von binären Sensoren und Schaltern umkehren, wie z.B. Öffnen/Schließen oder Ein/Aus.",
+ "description": "Dieser Helfer ermöglicht es, das Verhalten einer Entität zu invertieren. So lassen sich beispielsweise die Zustände „offen/geschlossen“ oder „ein/aus“ von binären Sensoren, Abdeckungen, Schaltern und Ventilen invertieren.",
"menu_options": {
"switch": "Einen Schalter umkehren",
- "binary_sensor": "Einen binären Sensor umkehren"
+ "binary_sensor": "Einen binären Sensor umkehren",
+ "cover": "Abdeckung invertieren",
+ "valve": "Ein Ventil invertieren"
},
"title": "Umkehren 👻"
},
@@ -25,6 +27,24 @@
"entity_id": "Quell-Entität"
},
"title": "Umkehren 👻"
+ },
+ "cover": {
+ "data": {
+ "entity_id": "Quell-Entität",
+ "hide_source": "Quell-Entität verbergen",
+ "inverse_tilt": "Neigung invertieren",
+ "name": "Name",
+ "inverse_position": "Invertiert offen, geschlossen und Position"
+ },
+ "title": "Invertieren 👻"
+ },
+ "valve": {
+ "data": {
+ "entity_id": "Quell-Entität",
+ "hide_source": "Quell-Entität verbergen",
+ "name": "Name"
+ },
+ "title": "Invertieren 👻"
}
}
},
@@ -45,6 +65,24 @@
"hide_source": "Quell-Entität verbergen",
"entity_id": "Quell-Entität"
}
+ },
+ "cover": {
+ "data": {
+ "entity_id": "Quell-Entität",
+ "hide_source": "Quell-Entität verbergen",
+ "name": "Name",
+ "inverse_tilt": "Neigung invertieren",
+ "inverse_position": "Invertiert öffnen, schliessen und Position"
+ },
+ "title": "Invertieren 👻"
+ },
+ "valve": {
+ "data": {
+ "hide_source": "Quell-Entität verbergen",
+ "name": "Name",
+ "entity_id": "Quell-Entität"
+ },
+ "title": "Invertieren 👻"
}
}
}
diff --git a/custom_components/spook/integrations/spook_inverse/translations/es.json b/custom_components/spook/integrations/spook_inverse/translations/es.json
index 3145d947..4a5ca5fb 100644
--- a/custom_components/spook/integrations/spook_inverse/translations/es.json
+++ b/custom_components/spook/integrations/spook_inverse/translations/es.json
@@ -1,5 +1,5 @@
{
- "title": "Inverso 👻",
+ "title": "Invertir 👻",
"config": {
"step": {
"switch": {
@@ -8,15 +8,17 @@
"hide_source": "Ocultar entidad origen",
"entity_id": "Entidad origen"
},
- "title": "Inverso 👻"
+ "title": "Invertir 👻"
},
"user": {
"description": "Este ayudante te permite invertir el comportamiento de una entidad. Por ejemplo, invertir abrir/cerrar o encender/apagar de sensores binarios, e interruptores.",
"menu_options": {
"switch": "Invertir un interruptor",
- "binary_sensor": "Invertir un sensor binario"
+ "binary_sensor": "Invertir un sensor binario",
+ "cover": "Invertir una persiana",
+ "valve": "Invertir una válvula"
},
- "title": "Inverso 👻"
+ "title": "Invertir 👻"
},
"binary_sensor": {
"data": {
@@ -24,7 +26,24 @@
"name": "Nombre",
"entity_id": "Entidad origen"
},
- "title": "Inverso 👻"
+ "title": "Invertir 👻"
+ },
+ "cover": {
+ "data": {
+ "entity_id": "Entidad origen",
+ "inverse_position": "Invertir apertura, cierre y poisición",
+ "inverse_tilt": "Invertir inclinación",
+ "name": "Nombre"
+ },
+ "title": "Invertir 👻"
+ },
+ "valve": {
+ "data": {
+ "entity_id": "Entidad origen",
+ "hide_source": "Ocultar entidad origen",
+ "name": "Nombre"
+ },
+ "title": "Invertir 👻"
}
}
},
@@ -36,15 +55,33 @@
"entity_id": "Entidad origen",
"name": "Nombre"
},
- "title": "Inverso 👻"
+ "title": "Invertir 👻"
},
"binary_sensor": {
- "title": "Inverso 👻",
+ "title": "Invertir 👻",
"data": {
"name": "Nombre",
"hide_source": "Ocultar entidad origen",
"entity_id": "Entidad origen"
}
+ },
+ "cover": {
+ "data": {
+ "entity_id": "Entidad origen",
+ "hide_source": "Ocultar entidad origen",
+ "inverse_position": "Invertir apertura, cierre y posición",
+ "inverse_tilt": "Invertir inclinación",
+ "name": "Nombre"
+ },
+ "title": "Invertir 👻"
+ },
+ "valve": {
+ "data": {
+ "entity_id": "Entidad origen",
+ "hide_source": "Ocultar entidad origen",
+ "name": "Nombre"
+ },
+ "title": "Invertir 👻"
}
}
}
diff --git a/custom_components/spook/integrations/spook_inverse/translations/hu.json b/custom_components/spook/integrations/spook_inverse/translations/hu.json
index b304e1c9..bd456332 100644
--- a/custom_components/spook/integrations/spook_inverse/translations/hu.json
+++ b/custom_components/spook/integrations/spook_inverse/translations/hu.json
@@ -25,6 +25,22 @@
"entity_id": "Forrás entitás"
},
"title": "Inverz 👻"
+ },
+ "cover": {
+ "data": {
+ "entity_id": "Forrás entitás",
+ "hide_source": "Forrás entitás elrejtése",
+ "name": "Megnevezés"
+ },
+ "title": "Inverz 👻"
+ },
+ "valve": {
+ "data": {
+ "entity_id": "Forrás entitás",
+ "hide_source": "Forrás entitás elrejtése",
+ "name": "Megnevezés"
+ },
+ "title": "Inverz 👻"
}
}
},
@@ -45,6 +61,22 @@
"hide_source": "Forrás entitás elrejtése",
"entity_id": "Forrás entitás"
}
+ },
+ "cover": {
+ "data": {
+ "entity_id": "Forrás entitás",
+ "hide_source": "Forrás entitás elrejtése",
+ "name": "Megnevezés"
+ },
+ "title": "Inverz 👻"
+ },
+ "valve": {
+ "data": {
+ "entity_id": "Forrás entitás",
+ "hide_source": "Forrás entitás elrejtése",
+ "name": "Megnevezés"
+ },
+ "title": "Inverz 👻"
}
}
}
diff --git a/custom_components/spook/integrations/spook_time_in_state/manifest.json b/custom_components/spook/integrations/spook_time_in_state/manifest.json
index 1602f64e..92095a78 100644
--- a/custom_components/spook/integrations/spook_time_in_state/manifest.json
+++ b/custom_components/spook/integrations/spook_time_in_state/manifest.json
@@ -10,5 +10,5 @@
"iot_class": "calculated",
"issue_tracker": "https://github.com/frenck/spook/issues",
"requirements": [],
- "version": "5.8.1"
+ "version": "5.9.0"
}
diff --git a/custom_components/spook/manifest.json b/custom_components/spook/manifest.json
index fb804bf0..ae4483bd 100644
--- a/custom_components/spook/manifest.json
+++ b/custom_components/spook/manifest.json
@@ -48,5 +48,5 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/frenck/spook/issues",
"requirements": [],
- "version": "5.8.1"
+ "version": "5.9.0"
}
diff --git a/custom_components/spook/services.yaml b/custom_components/spook/services.yaml
index b8a18aec..18bb86bd 100644
--- a/custom_components/spook/services.yaml
+++ b/custom_components/spook/services.yaml
@@ -527,26 +527,46 @@ homeassistant_delete_area:
homeassistant_disable_config_entry:
name: Disable an integration 👻
description: >-
- Disables an integration entry.
+ Disables integration entries, by entry or every entry of an integration.
fields:
config_entry_id:
name: Integration entry
description: The integration entry to disable.
- required: true
+ required: false
selector:
config_entry:
+ domain:
+ name: Integration
+ description: >-
+ Disable every entry of these integrations, the ones added later
+ included.
+ required: false
+ example: hue
+ selector:
+ text:
+ multiple: true
homeassistant_enable_config_entry:
name: Enable an integration 👻
description: >-
- Enables an integration entry.
+ Enables integration entries, by entry or every entry of an integration.
fields:
config_entry_id:
name: Integration entry
description: The integration entry to enable.
- required: true
+ required: false
selector:
config_entry:
+ domain:
+ name: Integration
+ description: >-
+ Enable every entry of these integrations, the ones added later
+ included.
+ required: false
+ example: hue
+ selector:
+ text:
+ multiple: true
homeassistant_disable_device:
name: Disable a device 👻
@@ -1641,6 +1661,16 @@ input_select_random:
selector:
object:
+input_select_reverse:
+ name: Reverse options 👻
+ description: >-
+ Reverses the order of the selectable options for an `input_select`
+ entity, back to front. This is not persistent and will be undone once
+ reloaded or Home Assistant restarts.
+ target:
+ entity:
+ domain: input_select
+
input_select_shuffle:
name: Shuffle options 👻
description: >-
@@ -1779,6 +1809,44 @@ input_number_min:
entity:
domain: input_number
+input_number_set_range:
+ name: Set range 👻
+ description: >-
+ Changes the minimum, maximum or step of an input number made in the UI,
+ the same as editing it. A value outside the new range is moved inside it.
+ target:
+ entity:
+ domain: input_number
+ fields:
+ min:
+ name: Minimum
+ description: The new minimum value.
+ required: false
+ example: 0
+ selector:
+ number:
+ mode: box
+ step: any
+ max:
+ name: Maximum
+ description: The new maximum value.
+ required: false
+ example: 3600
+ selector:
+ number:
+ mode: box
+ step: any
+ step:
+ name: Step
+ description: The new step size.
+ required: false
+ example: 1
+ selector:
+ number:
+ min: 0.000000001
+ mode: box
+ step: any
+
counter_create:
name: Create a counter 👻
description: >-
@@ -2341,7 +2409,17 @@ repairs_create:
repairs_ignore_all:
name: Ignore all issues 👻
description: >-
- Ignore all issues currently raised in Home Assistant Repairs.
+ Ignore all issues currently raised in Home Assistant Repairs, or only
+ those raised by particular integrations.
+ fields:
+ domain:
+ name: Domain
+ description: Only ignore the issues raised by these integrations.
+ required: false
+ example: hacs
+ selector:
+ text:
+ multiple: true
repairs_list:
name: List issues 👻
@@ -2394,7 +2472,17 @@ repairs_remove:
repairs_unignore_all:
name: Unignore all issues 👻
description: >-
- Unignore all issues currently raised in Home Assistant Repairs.
+ Unignore all issues currently raised in Home Assistant Repairs, or only
+ those raised by particular integrations.
+ fields:
+ domain:
+ name: Domain
+ description: Only unignore the issues raised by these integrations.
+ required: false
+ example: hacs
+ selector:
+ text:
+ multiple: true
sensor_set_display_precision:
name: Set display precision 👻
@@ -2540,6 +2628,15 @@ zone_create:
max: 999999999999
unit_of_measurement: m
mode: box
+ passive:
+ name: Passive
+ description: >-
+ A passive zone is only there for automations: it is not shown on the
+ map, and does not change where people and device trackers are.
+ required: false
+ default: false
+ selector:
+ boolean:
zone_update:
name: Update a zone 👻
@@ -2591,6 +2688,14 @@ zone_update:
step: any
unit_of_measurement: m
mode: box
+ passive:
+ name: Passive
+ description: >-
+ A passive zone is only there for automations: it is not shown on the
+ map, and does not change where people and device trackers are.
+ required: false
+ selector:
+ boolean:
zone_delete:
name: Delete a zone 👻
diff --git a/custom_components/spook/translations/de.json b/custom_components/spook/translations/de.json
index 81a85296..008fc543 100644
--- a/custom_components/spook/translations/de.json
+++ b/custom_components/spook/translations/de.json
@@ -282,7 +282,8 @@
"abort": {
"issue_ignored": "Spook wird ab jetzt nichts mehr zu dieser Gruppe berichten.",
"not_editable": "Diese Gruppe ist in YAML definiert, daher kann Spook sie nicht bearbeiten. Passe die Gruppe stattdessen in deiner Konfiguration an.",
- "manage": "Du kannst diese Gruppe selbst auf der [Seite „Helfer“](/config/helpers) verwalten."
+ "manage": "Du kannst diese Gruppe selbst auf der [Seite „Helfer“](/config/helpers) verwalten.",
+ "changed": "Die in dieser Reparaturliste aufgeführten Mitglieder sind wieder dabei oder haben sich seit Spooks letzter Überprüfung verändert, sodass niemand aus der Gruppe entfernt wurde. Spook überprüft die Liste erneut selbstständig und aktualisiert diese Reparaturliste."
}
}
},
@@ -428,7 +429,8 @@
},
"abort": {
"issue_ignored": "Spook wird zu diesem Bereich nichts mehr sagen.",
- "manage": "Du kannst diesen Bereich selbst auf der [Seite „Bereiche“](/config/areas/dashboard) verwalten."
+ "manage": "Du kannst diesen Bereich selbst auf der [Seite „Bereiche“](/config/areas/dashboard) verwalten.",
+ "changed": "Dieser Bereich wird wieder genutzt, seit Spook ihn überprüft hat: Entweder befindet sich etwas darin, oder er wird von einer Automatisierung oder einem Skript verwendet. Daher wurde er nicht verändert. Spook überprüft ihn erneut selbstständig und aktualisiert diesen Reparaturvorgang."
}
}
},
@@ -456,7 +458,8 @@
},
"abort": {
"issue_ignored": "Spook wird ab jetzt zu dieser Etage nichts mehr berichten.",
- "manage": "Du kannst diese Etage selbst auf der [Seite „Bereiche“](/config/areas/dashboard) verwalten."
+ "manage": "Du kannst diese Etage selbst auf der [Seite „Bereiche“](/config/areas/dashboard) verwalten.",
+ "changed": "Diese Etage wird seit Spooks letzter Überprüfung wieder genutzt: Es befindet sich ein Bereich darauf, oder eine Automatisierung bzw. ein Skript nutzt sie. Daher wurde sie nicht verändert. Spook überprüft sie erneut von selbst und aktualisiert diese Reparatur."
}
}
},
@@ -465,7 +468,8 @@
"abort": {
"too_few_members": "Ein Min/Max-Helfer benötigt mindestens zwei Elemente; würden die fehlenden entfernt, wären zu wenige übrig. Passe den Helfer selbst auf der [Seite „Helfers“](/config/helpers) an oder entferne ihn.",
"issue_ignored": "Spook wird ab jetzt nichts mehr zu diesem Helfer berichten.",
- "manage": "Du kannst diesen Helper selbst auf der [Seite „Helpers“](/config/helpers) verwalten."
+ "manage": "Du kannst diesen Helper selbst auf der [Seite „Helpers“](/config/helpers) verwalten.",
+ "changed": "Die in dieser Reparaturliste aufgeführten Mitglieder sind wieder da oder haben sich seit Spooks letzter Überprüfung geändert, daher wurde nichts aus dem Helfer entfernt. Spook überprüft die Liste erneut selbstständig und aktualisiert diese Reparaturliste."
},
"step": {
"init": {
@@ -486,8 +490,17 @@
"fix_flow": {
"step": {
"init": {
- "title": "Verwaiste Langzeitstatistiken gefunden"
+ "title": "Verwaiste Langzeitstatistiken gefunden",
+ "menu_options": {
+ "manage": "Lass es mich selbst beheben",
+ "remove": "Diese Statistiken leeren"
+ },
+ "description": "Spook hat einen Geist in deinem Aufzeichnungsgerät gefunden 👻\n\nHome Assistant führt Langzeitstatistiken für die folgenden Elemente, aber es gibt keine entsprechenden Entitäten mehr dafür:\n\n{statistics}\n\nDas passiert normalerweise, wenn ein Sensor entfernt wurde, während seine Statistiken weiterhin gespeichert wurden. Durch das Löschen dieser Statistiken wird der Verlauf endgültig verworfen. Deshalb schaut Spook noch einmal nach, bevor es das tut, und lässt alles in Ruhe, was in der Zwischenzeit wieder aufgetaucht ist.\n\nSpook 👻 Dein Kumpel."
}
+ },
+ "abort": {
+ "manage": "Du kannst dies selbst auf der [Seite „Statistiken“] (/config/tools/statistics) löschen.",
+ "clearing_took_too_long": "Spook hat darum gebeten, die Statistiken zu löschen, und der Recorder hat noch nicht bestätigt, dass dies geschehen ist. Der Vorgang steht in der Warteschlange und wird höchstwahrscheinlich ausgeführt, daher bleibt diese Korrektur bestehen, bis Spook sich die Sache erneut angesehen hat."
}
}
},
@@ -592,6 +605,22 @@
"homekit_unknown_entity_references_yaml": {
"title": "Unbekannte Entitäten in HomeKit-Bridge verwendet: {bridge}",
"description": "Spook hat einen Geist in deiner HomeKit-Bridge gefunden 👻\n\nBeim Herumschweben begegnete Spook der folgenden HomeKit-Bridge:\n\n{bridge}\n\nDiese Bridge nennt die folgenden Entitäten, die Home Assistant unbekannt sind:\n\n{entities}\n\n\n\nHomeKit folgt einer Entität nicht, wenn sie umbenannt wird. Eine eingeschlossene Entität, die umbenannt oder entfernt wurde, verschwindet aus der Home-App. Eine ausgeschlossene Entität, die umbenannt wurde, taucht dort wieder auf.\n\nDiese Bridge ist in deiner YAML-Konfiguration eingerichtet. Um diesen Fehler zu beheben, bearbeite den `filter` dieser Bridge unter `homekit:` und entferne oder korrigiere diese Entitäten.\n\nSpook 👻 Dein Kumpel."
+ },
+ "unknown_area_sensors": {
+ "fix_flow": {
+ "step": {
+ "init": {
+ "menu_options": {
+ "manage": "Lass es mich selbst beheben"
+ },
+ "description": "Spook hat in deinen Bereichen einen Geist entdeckt 👻\n\nDer Bereich {area} verwendet einen {sensors}-Sensor, der Home Assistant unbekannt ist:\n\n{entities}\n\nHome Assistant überprüft diesen Sensor nur, wenn er ausgewählt ist. Als er entfernt oder umbenannt wurde, zeigte der Bereich daher ins Leere. Assist- und Bereichskarten erhalten keine Messwerte mehr von ihm.\n\nLösche die Einstellung, damit du wieder einen Sensor auswählen kannst, oder behalte sie bei – dann wird Spook nicht mehr darauf hinweisen.\n\nSpook 👻 Dein Kumpel."
+ }
+ }
+ }
+ },
+ "unknown_engines": {
+ "title": "Die „Assist“-Pipeline verwendet Engines, die nicht mehr verfügbar sind: {pipeline}",
+ "description": "Die Assist-Pipeline {pipeline} verwendet Folgendes, was Home Assistant nicht bekannt ist:\n\n{engines}\n\nDas bedeutet in der Regel, dass die dahinterstehende Integration entfernt wurde. Die Pipeline wurde nicht darüber informiert, sodass sie bei der nächsten Verwendung mit einer Fehlermeldung über einen fehlenden Anbieter oder Agenten fehlschlägt, und nichts zuvor weist darauf hin.\n\nUm dies zu beheben, öffne die Einstellungen dieses Assistenten unter [Sprachassistenten](/config/voice-assistants/assistants) und wähle für jeden dieser Einträge eine Engine aus, die noch verfügbar ist.\n\nSpook 👻 Dein Kumpel."
}
},
"services": {
@@ -790,7 +819,7 @@
"description": "Die Maßeinheit der Eingabezahl."
}
},
- "description": "Legt einen neuen Eingabezahl-Helfer direkt an.",
+ "description": "Erstellt direkt einen neuen Eingabezahlen-Helfer. Gibt die Entitäts-ID der neuen Eingabezahl zurück.",
"name": "Erstelle eine Zahleneingabe"
},
"timer_set_duration": {
@@ -1405,7 +1434,12 @@
},
"input_number_delete": {
"name": "Löscht eine Zahleneingabe",
- "description": "Einen Eingabezahl-Helfer löschen. Dies funktioniert nur mit Eingabezahlen, die über die Benutzeroberfläche erstellt und verwaltet wurden. Eingabezahlen, die in YAML erstellt und verwaltet wurden, können von Spook nicht verwaltet werden."
+ "description": "Einen Eingabezahl-Helfer löschen. Dies funktioniert nur mit Eingabezahlen, die über die Benutzeroberfläche erstellt und verwaltet wurden. Eingabezahlen, die in YAML erstellt und verwaltet wurden, können von Spook nicht verwaltet werden.",
+ "fields": {
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ }
},
"zone_delete": {
"name": "Löscht eine Zone",
@@ -1672,6 +1706,411 @@
},
"description": "Ersetzt die Mitglieder einer über die Oberfläche erstellten Gruppe durch die angegebenen, während Home Assistant läuft.",
"name": "Lege die Mitglieder einer Gruppe fest"
+ },
+ "homeassistant_add_alias_to_entity": {
+ "fields": {
+ "alias": {
+ "name": "Alias"
+ },
+ "entity_id": {
+ "name": "Entität"
+ }
+ },
+ "description": "Fügt einer Entität (oder mehreren Entitäten) einen oder mehrere Aliase hinzu, unter denen Sprachassistenten sie aufrufen können."
+ },
+ "homeassistant_remove_alias_from_entity": {
+ "fields": {
+ "alias": {
+ "name": "Alias"
+ },
+ "entity_id": {
+ "name": "Entität"
+ }
+ }
+ },
+ "homeassistant_set_entity_aliases": {
+ "fields": {
+ "aliases": {
+ "name": "Aliase"
+ },
+ "entity_id": {
+ "name": "Entität"
+ }
+ }
+ },
+ "homeassistant_update_category": {
+ "fields": {
+ "category_id": {
+ "name": "Kategorie"
+ },
+ "icon": {
+ "name": "Symbol"
+ },
+ "name": {
+ "name": "Name"
+ }
+ }
+ },
+ "homeassistant_delete_category": {
+ "fields": {
+ "category_id": {
+ "name": "Kategorie"
+ }
+ },
+ "name": "Löscht eine Kategorie"
+ },
+ "homeassistant_add_category_to_entity": {
+ "fields": {
+ "category_id": {
+ "name": "Kategorie"
+ },
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ },
+ "name": "Eine Kategorie einer Entität zuweisen"
+ },
+ "homeassistant_remove_category_from_entity": {
+ "fields": {
+ "category_id": {
+ "name": "Kategorie"
+ },
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ }
+ },
+ "humidifier_decrease_humidity": {
+ "name": "Luftfeuchtigkeit verringern",
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "cover_decrease_position": {
+ "name": "Position verringern",
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "valve_decrease_position": {
+ "name": "Position verringern",
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "climate_decrease_temperature": {
+ "name": "Temperatur verringern",
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "water_heater_decrease_temperature": {
+ "name": "Temperatur verringern",
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "media_player_decrease_volume": {
+ "name": "Lautstärke verringern",
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "counter_delete": {
+ "name": "Löscht einen Zähler",
+ "fields": {
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ }
+ },
+ "calendar_update_event": {
+ "fields": {
+ "description": {
+ "name": "Beschreibung"
+ },
+ "duration": {
+ "name": "Dauer"
+ },
+ "shift": {
+ "name": "Verschieben"
+ },
+ "summary": {
+ "name": "Zusammenfassung"
+ },
+ "start_date_time": {
+ "description": "Wo soll die Suche beginnen? Standardmäßig ab dem aktuellen Zeitpunkt."
+ },
+ "end_date_time": {
+ "description": "Wo soll die Suche enden? Gib dies oder eine Dauer an."
+ }
+ },
+ "description": "Ändert Ereignisse in einem Kalender, die anhand ihres Titels oder ihrer UID innerhalb eines bestimmten Zeitraums gefunden wurden. Gibt die geänderten Elemente zurück, einschließlich ihrer UIDs."
+ },
+ "timer_create": {
+ "fields": {
+ "duration": {
+ "name": "Dauer"
+ },
+ "icon": {
+ "name": "Symbol"
+ },
+ "name": {
+ "name": "Name"
+ }
+ }
+ },
+ "calendar_delete_event": {
+ "fields": {
+ "duration": {
+ "name": "Dauer"
+ },
+ "summary": {
+ "name": "Zusammenfassung"
+ },
+ "start_date_time": {
+ "description": "Wo soll die Suche beginnen? Standardmäßig ab dem aktuellen Zeitpunkt."
+ },
+ "end_date_time": {
+ "description": "Wo soll die Suche enden? Gib dies oder eine Dauer an."
+ }
+ }
+ },
+ "homeassistant_set_entity_icon": {
+ "fields": {
+ "entity_id": {
+ "name": "Entität"
+ },
+ "icon": {
+ "name": "Symbol"
+ }
+ }
+ },
+ "input_select_delete": {
+ "fields": {
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ }
+ },
+ "input_boolean_delete": {
+ "fields": {
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ }
+ },
+ "input_text_delete": {
+ "fields": {
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ }
+ },
+ "input_button_delete": {
+ "fields": {
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ }
+ },
+ "input_datetime_delete": {
+ "fields": {
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ }
+ },
+ "timer_delete": {
+ "fields": {
+ "entity_id": {
+ "name": "Entitäts-ID"
+ }
+ }
+ },
+ "input_datetime_create": {
+ "fields": {
+ "has_date": {
+ "name": "Hat ein Datum",
+ "description": "Ob ein Datum angegeben ist. Entweder das Datum oder die Uhrzeit muss angegeben sein."
+ },
+ "has_time": {
+ "name": "Hat eine Uhrzeit",
+ "description": "Ob eine Uhrzeit gespeichert ist. Entweder das Datum oder die Uhrzeit muss mindestens angegeben sein."
+ },
+ "icon": {
+ "name": "Symbol"
+ },
+ "initial": {
+ "name": "Startwert"
+ },
+ "name": {
+ "name": "Name"
+ }
+ }
+ },
+ "homeassistant_create_category": {
+ "fields": {
+ "icon": {
+ "name": "Symbol"
+ },
+ "name": {
+ "name": "Name"
+ }
+ },
+ "name": "Kategorie anlegen"
+ },
+ "input_select_create": {
+ "fields": {
+ "icon": {
+ "name": "Symbol"
+ },
+ "name": {
+ "name": "Name"
+ }
+ }
+ },
+ "counter_create": {
+ "fields": {
+ "icon": {
+ "name": "Symbol"
+ },
+ "initial": {
+ "name": "Startwert"
+ },
+ "maximum": {
+ "name": "Maximum"
+ },
+ "minimum": {
+ "name": "Minimum"
+ },
+ "name": {
+ "name": "Name"
+ },
+ "step": {
+ "name": "Schritt"
+ }
+ },
+ "name": "Zähler anlegen"
+ },
+ "input_boolean_create": {
+ "fields": {
+ "icon": {
+ "name": "Symbol"
+ },
+ "initial": {
+ "name": "Startwert"
+ },
+ "name": {
+ "name": "Name"
+ }
+ }
+ },
+ "input_text_create": {
+ "fields": {
+ "icon": {
+ "name": "Symbol"
+ },
+ "initial": {
+ "name": "Startwert"
+ },
+ "max": {
+ "name": "Maximale Länge"
+ },
+ "min": {
+ "name": "Minimale Länge"
+ },
+ "mode": {
+ "name": "Modus"
+ },
+ "name": {
+ "name": "Name"
+ },
+ "pattern": {
+ "description": "Ein regulärer Ausdruck, dem der Text entsprechen muss."
+ }
+ }
+ },
+ "input_button_create": {
+ "fields": {
+ "icon": {
+ "name": "Symbol"
+ },
+ "name": {
+ "name": "Name"
+ }
+ }
+ },
+ "repairs_list": {
+ "fields": {
+ "severity": {
+ "name": "Schweregrad"
+ },
+ "include_ignored": {
+ "description": "Liste auch die Probleme auf, die bisher außer Acht gelassen wurden."
+ }
+ }
+ },
+ "climate_increase_temperature": {
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "water_heater_increase_temperature": {
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "humidifier_increase_humidity": {
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "cover_increase_position": {
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "valve_increase_position": {
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "media_player_increase_volume": {
+ "fields": {
+ "step": {
+ "name": "Schritt"
+ }
+ }
+ },
+ "todo_move_item": {
+ "fields": {
+ "after": {
+ "name": "Danach"
+ }
+ }
}
},
"conditions": {
@@ -1934,6 +2373,10 @@
"device_discovered": {
"name": "Gerät entdeckt",
"description": "Wird ausgelöst, wenn Home Assistant etwas Neues entdeckt, das es einrichten könnte, etwa ein Gerät, das gerade in deinem Netzwerk aufgetaucht ist."
+ },
+ "update_installed": {
+ "name": "Update installiert",
+ "description": "Wird ausgelöst, wenn eine Update-Entität eine andere installierte Version meldet – unabhängig davon, ob jemand auf „Installieren“ geklickt hat, das Gerät sich selbst aktualisiert hat oder die Aktualisierung außerhalb von Home Assistant erfolgte."
}
},
"selector": {
@@ -1943,6 +2386,12 @@
"error": "Fehler",
"warning": "Warnung"
}
+ },
+ "todo_move_position": {
+ "options": {
+ "bottom": "Ende",
+ "top": "Anfang"
+ }
}
}
}
diff --git a/custom_components/spook/translations/en.json b/custom_components/spook/translations/en.json
index f56d885e..eb4592be 100644
--- a/custom_components/spook/translations/en.json
+++ b/custom_components/spook/translations/en.json
@@ -1,5 +1,9 @@
{
"triggers": {
+ "automation_turned_off": {
+ "name": "Automation turned off",
+ "description": "Fires when somebody, or something, turns an automation off. Leave the target empty to watch every automation."
+ },
"debounce": {
"name": "Once it settles",
"description": "Fires once a trigger has stopped firing for a while, so a burst of them arrives as one. A motion sensor does not report motion once, it reports it twenty times.",
@@ -182,6 +186,34 @@
}
}
},
+ "target_temperature_reached": {
+ "name": "Target temperature reached",
+ "description": "Fires when the temperature of a thermostat or water heater reaches its target, or jumps past it. Changing the target does not count, and neither does a device that is off.",
+ "fields": {
+ "tolerance": {
+ "name": "Tolerance",
+ "description": "How close to the target still counts as there, in the device's unit. For a thermostat that settles just short of its setpoint. Zero means the target itself."
+ }
+ }
+ },
+ "update_installed": {
+ "name": "Update installed",
+ "description": "Fires when an update entity reports a different installed version, whether somebody pressed install, the device updated itself, or it was updated outside of Home Assistant."
+ },
+ "position_reached": {
+ "name": "Position reached",
+ "description": "Fires when a cover or valve gets to a position, or moves past it. One that is already there, or comes back from unavailable at it, does not count.",
+ "fields": {
+ "position": {
+ "name": "Position",
+ "description": "The position to reach, from 0 (closed) to 100 (open)."
+ }
+ }
+ },
+ "script_started": {
+ "name": "Script started",
+ "description": "Fires when a script starts a run. A run the script refuses, because it is already running, does not count. Leave the target empty to watch every script."
+ },
"user_added": {
"name": "User added",
"description": "Fires when somebody is given a login to Home Assistant. Users Home Assistant creates for its own integrations are left out."
@@ -203,6 +235,20 @@
}
},
"conditions": {
+ "is_at_target_temperature": {
+ "name": "Temperature at its target",
+ "description": "Passes when the temperature of a thermostat or water heater is at its target. A device that is off is not at its target, and one that is unavailable is left out.",
+ "fields": {
+ "behavior": {
+ "name": "Behavior",
+ "description": "Whether any of the devices being there is enough, or all of them have to be."
+ },
+ "tolerance": {
+ "name": "Tolerance",
+ "description": "How close to the target still counts as there, in the device's unit. For a thermostat that settles just short of its setpoint. Zero means the target itself."
+ }
+ }
+ },
"repair_issue_present": {
"name": "Repair issue outstanding",
"description": "Passes while a repair issue is outstanding. Issues somebody has ignored do not count.",
@@ -1309,21 +1355,29 @@
},
"homeassistant_disable_config_entry": {
"name": "Disable an integration",
- "description": "Disables an integration entry.",
+ "description": "Disables integration entries, by entry or every entry of an integration.",
"fields": {
"config_entry_id": {
"name": "Integration entry",
"description": "The integration entry to disable."
+ },
+ "domain": {
+ "name": "Integration",
+ "description": "Disable every entry of these integrations, the ones added later included."
}
}
},
"homeassistant_enable_config_entry": {
"name": "Enable an integration",
- "description": "Enables an integration entry.",
+ "description": "Enables integration entries, by entry or every entry of an integration.",
"fields": {
"config_entry_id": {
"name": "Integration entry",
"description": "The integration entry to enable."
+ },
+ "domain": {
+ "name": "Integration",
+ "description": "Enable every entry of these integrations, the ones added later included."
}
}
},
@@ -1983,6 +2037,10 @@
}
}
},
+ "input_select_reverse": {
+ "name": "Reverse options",
+ "description": "Reverses the order of the selectable options for an `input_select` entity, back to front. This is not persistent and will be undone once reloaded or Home Assistant restarts."
+ },
"input_select_shuffle": {
"name": "Shuffle options",
"description": "Shuffles the list of selectable options for an `input_select` entity. This is not persistent and will be undone once reloaded or Home Assistant restarts."
@@ -2055,6 +2113,24 @@
"name": "Set minimum value",
"description": "Set an input number entity to its minimum value."
},
+ "input_number_set_range": {
+ "name": "Set range",
+ "description": "Changes the minimum, maximum or step of an input number made in the UI, the same as editing it. A value outside the new range is moved inside it.",
+ "fields": {
+ "min": {
+ "name": "Minimum",
+ "description": "The new minimum value."
+ },
+ "max": {
+ "name": "Maximum",
+ "description": "The new maximum value."
+ },
+ "step": {
+ "name": "Step",
+ "description": "The new step size."
+ }
+ }
+ },
"counter_create": {
"name": "Create a counter",
"description": "Creates a new counter helper on the fly. Returns the entity ID of the new counter.",
@@ -2367,7 +2443,13 @@
},
"repairs_ignore_all": {
"name": "Ignore all issues",
- "description": "Ignore all issues currently raised in Home Assistant Repairs."
+ "description": "Ignore all issues currently raised in Home Assistant Repairs, or only those raised by particular integrations.",
+ "fields": {
+ "domain": {
+ "name": "Domain",
+ "description": "Only ignore the issues raised by these integrations."
+ }
+ }
},
"repairs_list": {
"name": "List issues",
@@ -2399,7 +2481,13 @@
},
"repairs_unignore_all": {
"name": "Unignore all issues",
- "description": "Unignore all issues currently raised in Home Assistant Repairs."
+ "description": "Unignore all issues currently raised in Home Assistant Repairs, or only those raised by particular integrations.",
+ "fields": {
+ "domain": {
+ "name": "Domain",
+ "description": "Only unignore the issues raised by these integrations."
+ }
+ }
},
"sensor_set_display_precision": {
"name": "Set display precision",
@@ -2484,6 +2572,10 @@
"radius": {
"name": "Radius",
"description": "Radius of the zone"
+ },
+ "passive": {
+ "name": "Passive",
+ "description": "A passive zone is only there for automations: it is not shown on the map, and does not change where people and device trackers are."
}
}
},
@@ -2514,6 +2606,10 @@
"radius": {
"name": "Radius",
"description": "Radius of the zone"
+ },
+ "passive": {
+ "name": "Passive",
+ "description": "A passive zone is only there for automations: it is not shown on the map, and does not change where people and device trackers are."
}
}
},
diff --git a/custom_components/spook/translations/es.json b/custom_components/spook/translations/es.json
index b75a58d6..d76ff84e 100644
--- a/custom_components/spook/translations/es.json
+++ b/custom_components/spook/translations/es.json
@@ -271,7 +271,8 @@
"abort": {
"issue_ignored": "Spook no volverá a decir nada sobre este grupo.",
"manage": "Puedes gestionar este grupo tú mismo en la [página de Asistentes](/config/helpers).",
- "not_editable": "Este grupo está configurado en YAML, así que Spook no puede editarlo. Actualiza el grupo en tu configuración."
+ "not_editable": "Este grupo está configurado en YAML, así que Spook no puede editarlo. Actualiza el grupo en tu configuración.",
+ "changed": "Los miembros que figuraban en esta reparación han vuelto, o han cambiado, desde que Spook la revisó, así que no se ha quitado nada del grupo. Spook vuelve a comprobarlo por su cuenta y actualiza esta reparación."
},
"step": {
"init": {
@@ -372,7 +373,8 @@
"fix_flow": {
"abort": {
"issue_ignored": "Spook no volverá a decir nada sobre esta área.",
- "manage": "Puedes gestionar esta área tú mismo en la [página de Áreas](/config/areas/dashboard)."
+ "manage": "Puedes gestionar esta área tú mismo en la [página de Áreas](/config/areas/dashboard).",
+ "changed": "Esta área vuelve a estar en uso desde que Spook la revisó: contiene algo, o una automatización o un script la utiliza. Por eso se ha dejado en paz. Spook vuelve a comprobarlo por su cuenta y actualiza esta reparación."
},
"step": {
"init": {
@@ -392,7 +394,8 @@
"fix_flow": {
"abort": {
"issue_ignored": "Spook no volverá a decir nada sobre esta planta.",
- "manage": "Puedes gestionar esta planta tú mismo en la [página de Áreas](/config/areas/dashboard)."
+ "manage": "Puedes gestionar esta planta tú mismo en la [página de Áreas](/config/areas/dashboard).",
+ "changed": "Esta planta vuelve a estar en uso desde que Spook la revisó: tiene un área, o una automatización o un script la utiliza. Por eso se ha dejado en paz. Spook vuelve a comprobarlo por su cuenta y actualiza esta reparación."
},
"step": {
"init": {
@@ -425,13 +428,14 @@
"abort": {
"issue_ignored": "Spook no volverá a decir nada sobre este asistente.",
"manage": "Puedes gestionar este asistente tú mismo en la [página de Asistentes](/config/helpers).",
- "too_few_members": "Un asistente mín/máx necesita al menos dos miembros, así que quitar los que faltan dejaría muy pocos. Reconfigura o quita el asistente tú mismo en la [página de Asistentes](/config/helpers)."
+ "too_few_members": "Un asistente mín/máx necesita al menos dos miembros, así que quitar los que faltan dejaría muy pocos. Reconfigura o quita el asistente tú mismo en la [página de Asistentes](/config/helpers).",
+ "changed": "Los miembros que figuraban en esta reparación han vuelto, o han cambiado, desde que Spook la revisó, así que no se ha quitado nada del helper. Spook vuelve a comprobarlo por su cuenta y actualiza esta reparación."
},
"step": {
"init": {
"description": "Spook ha encontrado un fantasma en tus asistentes 👻\n\nEl asistente mín/máx {helper} (`{entity_id}`) combina los siguientes miembros, que son desconocidos para Home Assistant:\n\n{sources}\n\nQuítalos para que el asistente siga con los miembros que quedan, o quédatelos y Spook no volverá a mencionarlos.\n\nSpook 👻 Tu colega.",
"menu_options": {
- "ignore": "Quedárselo y no volver a avisarme",
+ "ignore": "Conservarlas, no me avises más",
"manage": "Lo arreglo yo mismo",
"remove": "Quitar estos miembros"
},
@@ -442,7 +446,20 @@
"title": "Miembros desconocidos usados en el asistente mín/máx: {helper}"
},
"orphaned_statistics": {
- "title": "Se han encontrado estadísticas a largo plazo huérfanas"
+ "title": "Se han encontrado estadísticas a largo plazo huérfanas",
+ "fix_flow": {
+ "step": {
+ "init": {
+ "title": "Se han encontrado estadísticas a largo plazo huérfanas",
+ "description": "Hay un fantasma en tu recorder 👻\n\nHome Assistant está guardando estadísticas a largo plazo de lo siguiente, pero ya no existe ninguna entidad que coincida:\n\n{statistics}\n\nEsto suele pasar cuando se eliminó un sensor pero se conservaron sus estadísticas. Borrarlas tira ese historial a la basura para siempre, así que Spook vuelve a comprobarlo antes de hacerlo y deja en paz todo lo que haya vuelto mientras tanto.\n\nSpook 👻 Tu colega.",
+ "menu_options": {
+ "remove": "Borrar estas estadísticas",
+ "manage": "Lo hago yo mismo",
+ "ignore": "Conservarlas, no me avises más"
+ }
+ }
+ }
+ }
},
"person_unknown_device_trackers": {
"fix_flow": {
@@ -455,7 +472,7 @@
"init": {
"description": "Spook ha encontrado un fantasma en tus personas 👻\n\n{person} sigue los siguientes rastreadores de dispositivo, que son desconocidos para Home Assistant:\n\n{device_trackers}\n\nEsto suele pasar cuando se ha renombrado un rastreador de dispositivo o se ha quitado su integración.\n\nQuítalos de {person}, o quédatelos y Spook no volverá a mencionarlos.\n\nSpook 👻 Tu colega.",
"menu_options": {
- "ignore": "Quedárselo y no volver a avisarme",
+ "ignore": "Conservarlas, no me avises más",
"manage": "Lo arreglo yo mismo",
"remove": "Quitar estos rastreadores de dispositivo"
},
@@ -1936,6 +1953,12 @@
"error": "Error",
"warning": "Advertencia"
}
+ },
+ "todo_move_position": {
+ "options": {
+ "bottom": "Abajo",
+ "top": "Arriba"
+ }
}
}
}
diff --git a/custom_components/spook/translations/nl.json b/custom_components/spook/translations/nl.json
index 7b284847..df418ed8 100644
--- a/custom_components/spook/translations/nl.json
+++ b/custom_components/spook/translations/nl.json
@@ -2151,7 +2151,202 @@
}
},
"calendar_delete_event": {
- "name": "Afspraak verwijderen"
+ "name": "Afspraak verwijderen",
+ "description": "Verwijdert gebeurtenissen uit een agenda op basis van hun titel of uid binnen een bepaald tijdsbestek. Geeft de verwijderde gebeurtenissen terug, inclusief hun uid.",
+ "fields": {
+ "duration": {
+ "name": "Duur",
+ "description": "Hoe ver moet er vanaf het begin worden gekeken? Geef dit op, of een einddatum en -tijdstip."
+ },
+ "end_date_time": {
+ "name": "Einddatum en -tijd"
+ },
+ "whole_series": {
+ "description": "Bij een terugkerende gebeurtenis, verwijder de volledige reeks, ipv enkel de gebeurtenis die gevonden werd."
+ },
+ "start_date_time": {
+ "name": "Startdatum en -tijd"
+ },
+ "summary": {
+ "name": "Samenvatting",
+ "description": "De titel van de gebeurtenissen die moeten worden verwijderd, precies zoals die in de kalender staat."
+ },
+ "uid": {
+ "description": "De identificatiecode van de gebeurtenis die je wilt verwijderen, voor het geval je die hebt."
+ }
+ }
+ },
+ "todo_move_item": {
+ "fields": {
+ "position": {
+ "description": "Plaats het boven- of onderaan de lijst. Vermeld dit, of een ander item om het achter te plaatsen.",
+ "name": "Positie"
+ },
+ "after": {
+ "name": "Na"
+ },
+ "item": {
+ "name": "Item",
+ "description": "De titel of uid van het item dat verplaatst moet worden."
+ }
+ },
+ "description": "Plaatst een item in een to-do lijst bovenaan, onderaan of achter een ander item.",
+ "name": "Verplaats een item"
+ },
+ "cover_decrease_position": {
+ "description": "Beweegt de verduistering een stap verder gesloten, stopt wanneer volledig dicht.",
+ "name": "Verlaag positie",
+ "fields": {
+ "step": {
+ "description": "Met hoeveel procent moet het worden aangepast? Tien procent wanneer niet ingevuld.",
+ "name": "Stap"
+ }
+ }
+ },
+ "cover_increase_position": {
+ "description": "Beweegt een verduistering een stap verder open, stoppend wanneer volledig open.",
+ "fields": {
+ "step": {
+ "description": "Met hoeveel procent moet het worden aangepast? Tien procent wanneer niet ingevuld.",
+ "name": "Stap"
+ }
+ },
+ "name": "Verhoog positie"
+ },
+ "valve_decrease_position": {
+ "description": "Draait een klep een stap verder dicht, stoppend wanneer volledig dicht.",
+ "name": "Verlaag positie",
+ "fields": {
+ "step": {
+ "description": "Met hoeveel procent moet het worden aangepast? Tien procent wanneer niet ingevuld.",
+ "name": "Stap"
+ }
+ }
+ },
+ "valve_increase_position": {
+ "description": "Draait een klep verder open, stoppend wanneer volledig open.",
+ "fields": {
+ "step": {
+ "description": "Met hoeveel procent moet het worden aangepast? Tien procent wanneer niet ingevuld.",
+ "name": "Stap"
+ }
+ },
+ "name": "Verhoog positie"
+ },
+ "calendar_update_event": {
+ "fields": {
+ "new_summary": {
+ "name": "Nieuwe samenvatting"
+ },
+ "description": {
+ "name": "Omschrijving",
+ "description": "De omschrijving voor de gebeurtenissen"
+ },
+ "duration": {
+ "name": "Duur",
+ "description": "Hoe ver moet er vanaf het begin worden gekeken? Geef dit op, of een einddatum en -tijdstip."
+ },
+ "end_date_time": {
+ "name": "Enddatum en -tijd"
+ },
+ "location": {
+ "name": "Locatie",
+ "description": "De locatie voor de gebeurtenissen"
+ },
+ "shift": {
+ "description": "Verschuif de gebeurtenissen met deze tijd, later of – bij een negatieve waarde – eerder. In plaats van een nieuw begin- of eindtijdstip.",
+ "name": "Verschuif"
+ },
+ "new_end": {
+ "name": "Nieuw einde"
+ },
+ "new_start": {
+ "name": "Nieuwe start"
+ },
+ "start_date_time": {
+ "name": "Startdatum en -tijd"
+ },
+ "summary": {
+ "name": "Samenvatting",
+ "description": "De titel van de gebeurtenissen die moeten worden aangepast, precies zoals die in de kalender staat."
+ },
+ "uid": {
+ "description": "De identificatiecode van de gebeurtenis die je wilt wijzigen, voor het geval je die hebt."
+ }
+ },
+ "description": "Wijzigt gebeurtenissen in een kalender, die op basis van hun titel of uid binnen een bepaald tijdsbestek worden gevonden. Geeft de gewijzigde gegevens terug, inclusief de uid."
+ },
+ "humidifier_decrease_humidity": {
+ "name": "Verlaag vochtigheid",
+ "fields": {
+ "step": {
+ "description": "Met hoeveel procent moet de waarde worden aangepast? Laat dit veld leeg om de standaardinstelling van de luchtbevochtiger te gebruiken, of vul 1 procent in als er geen standaardinstelling is.",
+ "name": "Stap"
+ }
+ }
+ },
+ "climate_decrease_temperature": {
+ "name": "Verlaag temperatuur",
+ "fields": {
+ "step": {
+ "description": "Met hoeveel moet je het aanpassen? Laat leegf om de eigen instelling van de thermostaat te gebruiken, of stel het in op een halve graad (één in Fahrenheit) als er geen instelling is.",
+ "name": "Stap"
+ }
+ }
+ },
+ "water_heater_decrease_temperature": {
+ "name": "Verlaag temperatuur",
+ "fields": {
+ "step": {
+ "description": "Met hoeveel moet je het aanpassen? Laat leegf om de eigen instelling van de ketel te gebruiken, of stel het in op een halve graad (één in Fahrenheit) als er geen instelling is.",
+ "name": "Stap"
+ }
+ }
+ },
+ "media_player_decrease_volume": {
+ "name": "Verlaag volume",
+ "fields": {
+ "step": {
+ "description": "Met hoeveel procent moet het volume worden aangepast?",
+ "name": "Stap"
+ }
+ }
+ },
+ "humidifier_increase_humidity": {
+ "fields": {
+ "step": {
+ "description": "Met hoeveel procent moet de waarde worden aangepast? Laat dit veld leeg om de standaardinstelling van de luchtbevochtiger te gebruiken, of vul 1 procent in als er geen standaardinstelling is.",
+ "name": "Stap"
+ }
+ },
+ "name": "Verhoog vochtigheid"
+ },
+ "media_player_increase_volume": {
+ "fields": {
+ "step": {
+ "description": "Met hoeveel procent moet het volume worden aangepast?",
+ "name": "Stap"
+ }
+ },
+ "name": "Verhoog volume"
+ },
+ "climate_increase_temperature": {
+ "fields": {
+ "step": {
+ "description": "Met hoeveel moet je het aanpassen? Laat leegf om de eigen instelling van de thermostaat te gebruiken, of stel het in op een halve graad (één in Fahrenheit) als er geen instelling is.",
+ "name": "Stap"
+ }
+ },
+ "name": "Verhoog temperatuur"
+ },
+ "water_heater_increase_temperature": {
+ "fields": {
+ "step": {
+ "description": "Met hoeveel moet je het aanpassen? Laat leegf om de eigen instelling van de ketel te gebruiken, of stel het in op een halve graad (één in Fahrenheit) als er geen instelling is.",
+ "name": "Stap"
+ }
+ },
+ "name": "Verhoog temperatuur"
}
},
"conditions": {
diff --git a/custom_components/spook/translations/sr-Latn.json b/custom_components/spook/translations/sr-Latn.json
index 1273ba4e..b4d1aba3 100644
--- a/custom_components/spook/translations/sr-Latn.json
+++ b/custom_components/spook/translations/sr-Latn.json
@@ -2229,6 +2229,144 @@
"name": "UID"
}
}
+ },
+ "climate_increase_temperature": {
+ "name": "Povećaj temperaturu",
+ "description": "Povećava zadatu temperaturu termostata za jedan korak, odnosno obe zadate vrednosti ako termostat ima režim i za grejanje i za hlađenje. Termostat koji je isključen ostaje isključen.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko promeniti temperaturu. Izostavite ovaj parametar da biste koristili podrazumevani korak samog termostata, ili pola stepena (jedan stepen u Farenhajtiem) kada termostat nema definisan sopstveni korak."
+ }
+ }
+ },
+ "climate_decrease_temperature": {
+ "name": "Smanji temperaturu",
+ "description": "Smanjuje zadatu temperaturu termostata za jedan korak, odnosno obe zadate vrednosti ako termostat ima režim i za grejanje i za hlađenje. Termostat koji je isključen ostaje isključen.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko promeniti temperaturu. Izostavite ovaj parametar da biste koristili podrazumevani korak samog termostata, ili pola stepena (jedan stepen u Farenhajtiem) kada termostat nema definisan sopstveni korak."
+ }
+ }
+ },
+ "water_heater_increase_temperature": {
+ "name": "Povećaj temperaturu",
+ "description": "Povećava zadatu temperaturu bojlera za jedan korak, u okviru njegovih ograničenja.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko promeniti temperaturu. Izostavite ovaj parametar da biste koristili podrazumevani korak samog bojlera, ili pola stepena (jedan stepen u Farenhajtima) kada bojler nema definisan sopstveni korak."
+ }
+ }
+ },
+ "water_heater_decrease_temperature": {
+ "name": "Smanji temperaturu",
+ "description": "Smanjuje zadatu temperaturu bojlera za jedan korak, u okviru njegovih ograničenja.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko promeniti temperaturu. Izostavite ovaj parametar da biste koristili podrazumevani korak samog bojlera, ili pola stepena (jedan stepen u Farenhajtima) kada bojler nema definisan sopstveni korak."
+ }
+ }
+ },
+ "humidifier_increase_humidity": {
+ "name": "Povećaj vlažnost",
+ "description": "Povećava ciljanu vlažnost vazduha na ovlaživaču za jedan korak, u okviru njegovih ograničenja.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko procenata promeniti vlažnost. Izostavite ovaj parametar da biste koristili podrazumevani korak samog ovlaživača, ili jedan procenat kada uređaj nema definisan sopstveni korak."
+ }
+ }
+ },
+ "humidifier_decrease_humidity": {
+ "name": "Smanji vlažnost",
+ "description": "Smanjuje ciljanu vlažnost vazduha na ovlaživaču za jedan korak, u okviru njegovih ograničenja.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko procenata promeniti vlažnost. Izostavite ovaj parametar da biste koristili podrazumevani korak samog ovlaživača, ili jedan procenat kada uređaj nema definisan sopstveni korak."
+ }
+ }
+ },
+ "cover_increase_position": {
+ "name": "Povećaj otvorenost",
+ "description": "Otvara roletnu za jedan korak, zaustavljajući se kada se potpuno otvori.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko procenata je pomeriti. Ukoliko se izostavi, pomera se za deset procenata."
+ }
+ }
+ },
+ "cover_decrease_position": {
+ "name": "Smanji otvorenost",
+ "description": "Zatvara roletnu za jedan korak, zaustavljajući se kada se potpuno zatvori.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko procenata je pomeriti. Ukoliko se izostavi, pomera se za deset procenata."
+ }
+ }
+ },
+ "valve_increase_position": {
+ "name": "Povećaj otvorenost",
+ "description": "Otvara ventil za jedan korak, zaustavljajući se kada se potpuno otvori.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko procenata ga pomeriti. Ukoliko se izostavi, pomera se za deset procenata."
+ }
+ }
+ },
+ "valve_decrease_position": {
+ "name": "Smanji otvorenost",
+ "description": "Zatvara ventil za jedan korak, zaustavljajući se kada se potpuno zatvori.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko procenata ga pomeriti. Ukoliko se izostavi, pomera se za deset procenata."
+ }
+ }
+ },
+ "media_player_increase_volume": {
+ "name": "Pojačaj zvuk",
+ "description": "Pojačava zvuk medijskog plejera za uneti korak, zaustavljajući se na maksimalnoj jačini zvuka.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko procenata promeniti jačinu zvuka."
+ }
+ }
+ },
+ "media_player_decrease_volume": {
+ "name": "Smanji zvuk",
+ "description": "Smanjuje zvuk medijskog plejera za uneti korak, zaustavljajući se kada se potpuno utiša.",
+ "fields": {
+ "step": {
+ "name": "Korak",
+ "description": "Za koliko procenata promeniti jačinu zvuka."
+ }
+ }
+ },
+ "todo_move_item": {
+ "name": "Premesti stavku",
+ "description": "Premešta stavku na listi zadataka na vrh, na dno ili nakon neke druge stavke.",
+ "fields": {
+ "item": {
+ "name": "Stavka",
+ "description": "Naslov ili UID stavke koju treba premestiti."
+ },
+ "after": {
+ "name": "Posle",
+ "description": "Naslov ili UID stavke nakon koje je treba staviti. Navedite ovo ili poziciju."
+ },
+ "position": {
+ "name": "Pozicija",
+ "description": "Premestite je na vrh ili na dno liste. Navedite ovo ili stavku nakon koje je treba staviti."
+ }
+ }
}
},
"conditions": {
@@ -2491,6 +2629,10 @@
"device_discovered": {
"name": "Uređaj je otkriven",
"description": "Aktivira se kada Home Assistant otkrije nešto novo što bi mogao da podesi, poput uređaja koji se upravo pojavio na vašoj mreži."
+ },
+ "update_installed": {
+ "name": "Ažuriranje instalirano",
+ "description": "Aktivira se kada entitet ažuriranja prijavi drugačiju instaliranu verziju, bez obzira na to da li je neko pritisnuo dugme za instalaciju, da li se uređaj sam ažurirao ili je ažuriran van Home Assistant-a."
}
},
"selector": {
@@ -2500,6 +2642,12 @@
"error": "Greška",
"warning": "Upozorenje"
}
+ },
+ "todo_move_position": {
+ "options": {
+ "bottom": "Dole",
+ "top": "Gore"
+ }
}
}
}
diff --git a/custom_components/spook/translations/sr.json b/custom_components/spook/translations/sr.json
index a7173f25..3fbb3351 100644
--- a/custom_components/spook/translations/sr.json
+++ b/custom_components/spook/translations/sr.json
@@ -2229,6 +2229,144 @@
"description": "Померите догађаје за оволико (касније, или раније ако је вредност негативна), уместо уноса новог почетка или завршетка."
}
}
+ },
+ "todo_move_item": {
+ "fields": {
+ "position": {
+ "description": "Преместите је на врх или на дно листе. Наведите ово или ставку након које је треба ставати.",
+ "name": "Позиција"
+ },
+ "after": {
+ "description": "Наслов или „UID” ставке након које је треба ставити. Наведите ово или позицију.",
+ "name": "После"
+ },
+ "item": {
+ "description": "Наслов или „UID” ставке коју треба преместити.",
+ "name": "Ставка"
+ }
+ },
+ "description": "Премешта ставку на листи задатака на врх, на дно или након неке друге ставке.",
+ "name": "Премести ставку"
+ },
+ "media_player_decrease_volume": {
+ "fields": {
+ "step": {
+ "description": "За колико процената променити јачину звука.",
+ "name": "Корак"
+ }
+ },
+ "name": "Смањи звук",
+ "description": "Смањује звук медијског плејера за унети корак, заустављајући се када се потпуно утиша."
+ },
+ "climate_increase_temperature": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико променити температуру. Изоставите овај параметар да бисте користили подразумевани корак самог термостата, или пола степена (један степен у Фаренхајтима) када термостат нема дефинисан сопствени корак."
+ }
+ },
+ "name": "Повећај температуру",
+ "description": "Повећава задату температуру термостата за један корак, односно обе задате вредности ако термостат има режим и за грејање и за хлађење. Термостат који је искључен остаје искључен."
+ },
+ "climate_decrease_temperature": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико променити температуру. Изоставите овај параметар да бисте користили подразумевани корак самог термостата, или пола степена (један степен у Фаренхајтима) када термостат нема дефинисан сопствени корак."
+ }
+ },
+ "name": "Смањи температуру",
+ "description": "Смањује задату температуру термостата за један корак, односно обе задате вредности ако термостат има режим и за грејање и за хлађење. Термостат који је искључен остаје искључен."
+ },
+ "water_heater_increase_temperature": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико променити температуру. Изоставите овај параметар да бисте користили подразумевани корак самог бојлера, или пола степена (један степен у Фаренхајтима) када бојлер нема дефинисан сопствени корак."
+ }
+ },
+ "name": "Повећај температуру",
+ "description": "Повећава задату температуру бојлера за један корак, у оквиру његових ограничења."
+ },
+ "water_heater_decrease_temperature": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико променити температуру. Изоставите овај параметар да бисте користили подразумевани корак самог бојлера, или пола степена (један степен у Фаренхајтима) када бојлер нема дефинисан сопствени корак."
+ }
+ },
+ "name": "Смањи температуру",
+ "description": "Смањује задату температуру бојлера за један корак, у оквиру његових ограничења."
+ },
+ "humidifier_increase_humidity": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико процената променити влажност. Изоставите овај параметар да бисте користили подразумевани корак самог овлаживача, или један проценат када уређај нема дефинисан сопствени корак."
+ }
+ },
+ "name": "Повећај влажност",
+ "description": "Повећава циљану влажност ваздуха на овлаживачу за један корак, у оквиру његових ограничења."
+ },
+ "humidifier_decrease_humidity": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико процената променити влажност. Изоставите овај параметар да бисте користили подразумевани корак самог овлаживача, или један проценат када уређај нема дефинисан сопствени корак."
+ }
+ },
+ "name": "Смањи влажност",
+ "description": "Смањује циљану влажност ваздуха на овлаживачу за један корак, у оквиру његових ограничења."
+ },
+ "cover_increase_position": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико процената је померити. Уколико се изостави, помера се за десет процената."
+ }
+ },
+ "name": "Повећај отвореност",
+ "description": "Отвара ролетну за један корак, заустављајући се када се потпуно отвори."
+ },
+ "cover_decrease_position": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико процената је померити. Уколико се изостави, помера се за десет процената."
+ }
+ },
+ "name": "Смањи отвореност",
+ "description": "Затвара ролетну за један корак, заустављајући се када се потпуно затвори."
+ },
+ "valve_increase_position": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико процената га померити. Уколико се изостави, помера се за десет процената."
+ }
+ },
+ "name": "Повећај отвореност",
+ "description": "Отвара вентил за један корак, заустављајући се када се потпуно отвори."
+ },
+ "valve_decrease_position": {
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико процената га померити. Уколико се изостави, помера се за десет процената."
+ }
+ },
+ "name": "Смањи отвореност",
+ "description": "Затвара вентил за један корак, заустављајући се када се потпуно затвори."
+ },
+ "media_player_increase_volume": {
+ "name": "Појачај звук",
+ "description": "Појачава звук медијског плејера за унети корак, заустављајући се на максималној јачини звука.",
+ "fields": {
+ "step": {
+ "name": "Корак",
+ "description": "За колико процената променити јачину звука."
+ }
+ }
}
},
"triggers": {
@@ -2417,6 +2555,10 @@
"device_discovered": {
"name": "Уређај је откривен",
"description": "Активира се када „Home Assistant“ открије нешто ново што би могао да подеси, попут уређаја који се управо појавио на вашој мрежи."
+ },
+ "update_installed": {
+ "name": "Ажурирање инсталирано",
+ "description": "Активира се када ентитет ажурирања пријави другачију инсталирану верзију, без обзира на то да ли је неко притиснуо дугме за инсталацију, да ли се уређај сам ажурирао или је ажуриран ван Home Assistant-а."
}
},
"conditions": {
@@ -2500,6 +2642,12 @@
"error": "Грешка",
"warning": "Упозорење"
}
+ },
+ "todo_move_position": {
+ "options": {
+ "bottom": "Доле",
+ "top": "Горе"
+ }
}
}
}
diff --git a/custom_components/spook/triggers.yaml b/custom_components/spook/triggers.yaml
index 03a5177c..c4d7001c 100644
--- a/custom_components/spook/triggers.yaml
+++ b/custom_components/spook/triggers.yaml
@@ -17,6 +17,51 @@ stale:
duration:
device_added:
+position_reached:
+ target:
+ entity:
+ - domain: cover
+ - domain: valve
+ fields:
+ position:
+ required: true
+ example: 30
+ selector:
+ number:
+ min: 0
+ max: 100
+ step: 1
+ unit_of_measurement: "%"
+ mode: slider
+script_started:
+ target:
+ entity:
+ domain: script
+target_temperature_reached:
+ target:
+ entity:
+ - domain: climate
+ - domain: water_heater
+ fields:
+ tolerance:
+ required: false
+ default: 0
+ example: 0.5
+ selector:
+ number:
+ min: 0
+ max: 10
+ step: 0.1
+ mode: box
+update_installed:
+ target:
+ entity:
+ domain: update
+ primary_entities_only: false
+automation_turned_off:
+ target:
+ entity:
+ domain: automation
recovered:
target:
fields:
diff --git a/www/community/sensor-bar-card-plus/sensor-bar-card-plus.js b/www/community/sensor-bar-card-plus/sensor-bar-card-plus.js
index b4340686..a8a2a723 100644
--- a/www/community/sensor-bar-card-plus/sensor-bar-card-plus.js
+++ b/www/community/sensor-bar-card-plus/sensor-bar-card-plus.js
@@ -16,6 +16,122 @@
}
};
+ // src/utils/extrema.js
+ function getLocalBoundaryTimestamp(date, unit) {
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) return null;
+ const year = date.getFullYear();
+ const month = date.getMonth();
+ const day = date.getDate();
+ const hour = date.getHours();
+ if (unit === "quarterly") {
+ return new Date(year, month, day, hour, Math.floor(date.getMinutes() / 15) * 15, 0, 0).getTime();
+ }
+ if (unit === "hourly") {
+ return new Date(year, month, day, hour, 0, 0, 0).getTime();
+ }
+ if (unit === "daily") {
+ return new Date(year, month, day, 0, 0, 0, 0).getTime();
+ }
+ if (unit === "weekly") {
+ const daysSinceMonday = (date.getDay() + 6) % 7;
+ return new Date(year, month, day - daysSinceMonday, 0, 0, 0, 0).getTime();
+ }
+ if (unit === "monthly") {
+ return new Date(year, month, 1, 0, 0, 0, 0).getTime();
+ }
+ if (unit === "yearly") {
+ return new Date(year, 0, 1, 0, 0, 0, 0).getTime();
+ }
+ return null;
+ }
+ function normalizeReset(value) {
+ if (value === void 0) value = "never";
+ if (typeof value !== "string") return { kind: "never" };
+ const normalized = value.trim().toLowerCase();
+ if (normalized === "never") return { kind: "never" };
+ if (CALENDAR_RESETS.has(normalized)) {
+ return { kind: "calendar", unit: normalized };
+ }
+ const durationMatch = normalized.match(/^(\d+)(m|h)$/);
+ if (!durationMatch) return { kind: "never" };
+ const amount = Number(durationMatch[1]);
+ const unit = durationMatch[2];
+ if (!Number.isInteger(amount) || amount < 1 || unit === "m" && amount > 59 || unit === "h" && amount > 23) {
+ return { kind: "never" };
+ }
+ return unit === "m" ? { kind: "duration", minutes: amount } : { kind: "duration", hours: amount };
+ }
+ function isValidReset(value) {
+ if (typeof value !== "string") return false;
+ const normalized = value.trim().toLowerCase();
+ if (normalized === "never" || CALENDAR_RESETS.has(normalized)) return true;
+ const match = normalized.match(/^(\d+)(m|h)$/);
+ if (!match) return false;
+ const amount = Number(match[1]);
+ return Number.isInteger(amount) && amount >= 1 && (match[2] === "m" ? amount <= 59 : amount <= 23);
+ }
+ function getResetWindowKey(reset, timestamp) {
+ if ((reset == null ? void 0 : reset.kind) !== "calendar") return null;
+ const boundary = getLocalBoundaryTimestamp(new Date(timestamp), reset.unit);
+ return Number.isFinite(boundary) ? String(boundary) : null;
+ }
+ function getDurationMs(reset) {
+ if ((reset == null ? void 0 : reset.kind) !== "duration") return null;
+ if (Number.isInteger(reset.minutes)) return reset.minutes * 60 * 1e3;
+ if (Number.isInteger(reset.hours)) return reset.hours * 60 * 60 * 1e3;
+ return null;
+ }
+ function initializeExtremum(sample, reset, timestamp) {
+ if ((reset == null ? void 0 : reset.kind) === "duration") {
+ return {
+ value: sample,
+ startedAtMs: Number.isFinite(timestamp) ? timestamp : null,
+ windowKey: null
+ };
+ }
+ if ((reset == null ? void 0 : reset.kind) === "calendar") {
+ return {
+ value: sample,
+ startedAtMs: null,
+ windowKey: getResetWindowKey(reset, timestamp)
+ };
+ }
+ return {
+ value: sample,
+ startedAtMs: null,
+ windowKey: null
+ };
+ }
+ function updateExtremum(previous, sample, reset, direction, timestamp) {
+ if (!Number.isFinite(sample)) return previous != null ? previous : null;
+ const durationMs = getDurationMs(reset);
+ if (!previous) return initializeExtremum(sample, reset, timestamp);
+ if (durationMs !== null && Number.isFinite(previous.startedAtMs) && Number.isFinite(timestamp) && timestamp - previous.startedAtMs >= durationMs) {
+ return initializeExtremum(sample, reset, timestamp);
+ }
+ if ((reset == null ? void 0 : reset.kind) === "calendar") {
+ const windowKey = getResetWindowKey(reset, timestamp);
+ if (windowKey !== null && windowKey !== previous.windowKey) {
+ return initializeExtremum(sample, reset, timestamp);
+ }
+ }
+ const shouldReplace = direction === "min" ? sample < previous.value : sample > previous.value;
+ return shouldReplace ? { ...previous, value: sample } : previous;
+ }
+ var CALENDAR_RESETS;
+ var init_extrema = __esm({
+ "src/utils/extrema.js"() {
+ CALENDAR_RESETS = /* @__PURE__ */ new Set([
+ "quarterly",
+ "hourly",
+ "daily",
+ "weekly",
+ "monthly",
+ "yearly"
+ ]);
+ }
+ });
+
// src/config/normalize.js
function normalizeResolvableValue(value, entityValue, percentValue = null) {
const normalized = {
@@ -80,6 +196,97 @@
}
return normalizeResolvableValue(input, null);
}
+ function hasConfiguredMarkerSource(source) {
+ var _a;
+ return !!source && (getFiniteNumber((_a = source.fixed) != null ? _a : source.value) !== null || Number.isFinite(source.percent) || !!source.entity);
+ }
+ function applyGenericMarkerCapacity(markers, config) {
+ var _a, _b, _c, _d;
+ const occupied = {
+ above: ((_a = config == null ? void 0 : config.peak_marker) == null ? void 0 : _a.show) === true ? 1 : 0,
+ below: (((_b = config == null ? void 0 : config.floor_marker) == null ? void 0 : _b.show) === true ? 1 : 0) + (((_c = config == null ? void 0 : config.target_marker) == null ? void 0 : _c.enabled) !== false && hasConfiguredMarkerSource((_d = config == null ? void 0 : config.target_marker) == null ? void 0 : _d.source) ? 1 : 0)
+ };
+ return markers.map((marker) => {
+ const accepted = marker.valid && occupied[marker.lane] < 4;
+ if (accepted) occupied[marker.lane] += 1;
+ return { ...marker, accepted };
+ });
+ }
+ function normalizeGenericMarkerList(input, capacityConfig = null) {
+ if (!Array.isArray(input)) {
+ return { markers: [], invalidList: input !== void 0 };
+ }
+ const markers = input.map((rawMarker, index) => {
+ var _a;
+ if (!rawMarker || typeof rawMarker !== "object" || Array.isArray(rawMarker)) {
+ return { id: `generic-${index}`, index, valid: false, accepted: false, malformed: true };
+ }
+ const rawAt = rawMarker.at;
+ const hasUnsupportedPercentField = rawAt && typeof rawAt === "object" && Object.prototype.hasOwnProperty.call(rawAt, "percent");
+ const atInput = typeof rawAt === "string" ? rawAt.trim() : rawAt;
+ const source = normalizeStructuredResolvableValue(atInput, null, null, { allowPercent: true });
+ const explicitEntity = rawAt && typeof rawAt === "object" && rawAt.entity !== void 0 ? rawAt.entity : typeof atInput === "string" && looksLikeEntityId(atInput) ? atInput : null;
+ const invalidEntity = explicitEntity !== null && explicitEntity !== void 0 && explicitEntity !== "" && !looksLikeEntityId(explicitEntity);
+ const invalidFixed = rawAt && typeof rawAt === "object" && rawAt.fixed !== void 0 && rawAt.fixed !== null && getFiniteNumber(rawAt.fixed) === null;
+ const hasSource = !!source.entity || getFiniteNumber(source.fixed) !== null || Number.isFinite(source.percent);
+ const invalidPercentage = Number.isFinite(source.percent) && (source.percent < 0 || source.percent > 100);
+ const validSource = rawAt !== void 0 && rawAt !== null && !hasUnsupportedPercentField && !invalidEntity && !invalidFixed && hasSource && !invalidPercentage;
+ const lane = rawMarker.lane === void 0 ? "below" : rawMarker.lane;
+ const validLane = lane === "above" || lane === "below";
+ const supportedShapes = ["circle", "diamond", "triangle", "chevron", "arrow", "pin"];
+ const validShape = rawMarker.shape === void 0 || supportedShapes.includes(rawMarker.shape);
+ const direction = normalizeMarkerDirection(rawMarker.direction);
+ const invalidDirection = rawMarker.direction !== void 0 && !["inward", "outward"].includes(
+ typeof rawMarker.direction === "string" ? rawMarker.direction.trim().toLowerCase() : ""
+ );
+ const label = rawMarker.label && typeof rawMarker.label === "object" && !Array.isArray(rawMarker.label) ? rawMarker.label : {};
+ const labelConfig = normalizeMarkerLabelConfig(label);
+ const labelEntityInput = label.entity;
+ const labelEntity = typeof labelEntityInput === "string" ? labelEntityInput.trim() : "";
+ const invalidLabelEntity = labelEntityInput !== void 0 && labelEntityInput !== null && labelEntityInput !== "" && !looksLikeEntityId(labelEntity);
+ const validLabelEntity = labelEntity && looksLikeEntityId(labelEntity) ? labelEntity : null;
+ const showMarker = typeof rawMarker.show_marker === "boolean" ? rawMarker.show_marker : true;
+ return {
+ id: `generic-${index}`,
+ index,
+ source: {
+ ...source,
+ entity: typeof source.entity === "string" ? source.entity.trim() : source.entity
+ },
+ lane: validLane ? lane : null,
+ shape: validShape ? (_a = rawMarker.shape) != null ? _a : "circle" : "circle",
+ direction,
+ invalidDirection,
+ showMarker,
+ invalidShowMarker: rawMarker.show_marker !== void 0 && typeof rawMarker.show_marker !== "boolean",
+ color: typeof rawMarker.color === "string" && rawMarker.color.trim() ? rawMarker.color : "#888888",
+ label: {
+ show: label.show === true,
+ text: labelConfig.label_text,
+ entity: validLabelEntity,
+ showValue: labelConfig.label_show_value,
+ showUnit: labelConfig.label_show_unit,
+ precision: labelConfig.label_precision,
+ invalidShow: labelConfig.label_invalid_show,
+ invalidText: labelConfig.label_invalid_text,
+ invalidShowValue: labelConfig.label_invalid_show_value,
+ invalidShowUnit: labelConfig.label_invalid_show_unit,
+ invalidPrecision: labelConfig.label_invalid_precision,
+ invalidPrecisionKey: labelConfig.label_precision_key,
+ unsupportedUnit: labelConfig.label_unsupported_unit,
+ invalidEntity: invalidLabelEntity
+ },
+ valid: validSource && validLane,
+ invalidSource: !validSource,
+ invalidPercentage,
+ unsupportedPercentField: hasUnsupportedPercentField,
+ invalidLane: !validLane,
+ invalidShape: !validShape,
+ accepted: false
+ };
+ });
+ return { markers: applyGenericMarkerCapacity(markers, capacityConfig), invalidList: false };
+ }
function normalizeBaselineDirectionConfig(input, inheritedDirection = null) {
var _a;
const inherited = inheritedDirection != null ? inheritedDirection : { color: null };
@@ -169,37 +376,65 @@
});
return inferSegmentEndValues(segments, 100);
}
- function hasResolvableMagnitude(resolvable) {
- return !!resolvable && (Number.isFinite(getFiniteNumber(resolvable.fixed)) || Number.isFinite(resolvable.percent));
+ function normalizeSegmentBoundary(input, legacySegmentSpace = null) {
+ var _a;
+ if (input === void 0) return { value: null, issue: null };
+ if (input === null) return { value: null, issue: "malformed" };
+ if (input && typeof input === "object" && !Array.isArray(input)) {
+ if (input.entity !== void 0 && input.entity !== null && input.entity !== "") {
+ return { value: null, issue: "entity" };
+ }
+ if (Object.prototype.hasOwnProperty.call(input, "percent")) {
+ const percent = getFiniteNumber(input.percent);
+ return Number.isFinite(percent) ? { value: normalizeResolvableValue(null, null, percent), issue: null } : { value: null, issue: "malformed" };
+ }
+ const fixed = getFiniteNumber((_a = input.fixed) != null ? _a : input.value);
+ return Number.isFinite(fixed) ? { value: normalizeResolvableValue(fixed, null), issue: null } : { value: null, issue: "malformed" };
+ }
+ if (typeof input === "string") {
+ const trimmed = input.trim();
+ if (looksLikeEntityId(trimmed)) return { value: null, issue: "entity" };
+ if (trimmed.includes("%")) {
+ const percent = parsePercentLiteral(trimmed);
+ return Number.isFinite(percent) ? { value: normalizeResolvableValue(null, null, percent), issue: null } : { value: null, issue: "malformed_percent" };
+ }
+ }
+ const numeric = getFiniteNumber(input);
+ if (!Number.isFinite(numeric)) return { value: null, issue: "malformed" };
+ return legacySegmentSpace === "percent" ? { value: normalizeResolvableValue(null, null, numeric), issue: null } : { value: normalizeResolvableValue(numeric, null), issue: null };
}
- function normalizeGaugeSegments(input) {
+ function normalizeGaugeSegments(input, options = {}) {
if (!Array.isArray(input)) return null;
+ const { legacySegmentSpace = null } = options;
const segments = input.map((segment) => {
- var _a;
- const from = normalizeStructuredResolvableValue(segment == null ? void 0 : segment.from, null, null, { allowPercent: true });
- const to = (segment == null ? void 0 : segment.to) === void 0 ? null : normalizeStructuredResolvableValue(segment.to, null, null, { allowPercent: true });
- if (!hasResolvableMagnitude(from) || !(segment == null ? void 0 : segment.color)) {
+ var _a, _b;
+ const from = (segment == null ? void 0 : segment.from) === void 0 ? { value: null, issue: "malformed" } : normalizeSegmentBoundary(segment.from, legacySegmentSpace);
+ const to = (segment == null ? void 0 : segment.to) === void 0 ? { value: null, issue: null } : segment.to === null ? { value: null, issue: null } : normalizeSegmentBoundary(segment.to, legacySegmentSpace);
+ if (!(segment == null ? void 0 : segment.color)) {
return null;
}
return {
- from,
- to,
+ from: from.value,
+ to: to.value,
+ invalidBoundary: (_a = from.issue) != null ? _a : to.issue,
color: segment.color,
- label: (_a = segment.label) != null ? _a : null
+ label: (_b = segment.label) != null ? _b : null
};
}).filter(Boolean);
return segments.map((segment, index) => {
var _a;
+ const nextValid = segments.slice(index + 1).find((candidate) => !candidate.invalidBoundary);
return {
- from: { ...segment.from },
- to: segment.to ? { ...segment.to } : index < segments.length - 1 ? { ...segments[index + 1].from } : null,
+ from: segment.from ? { ...segment.from } : null,
+ to: segment.to ? { ...segment.to } : (nextValid == null ? void 0 : nextValid.from) ? { ...nextValid.from } : null,
+ ...segment.invalidBoundary ? { invalidBoundary: segment.invalidBoundary } : {},
color: segment.color,
label: (_a = segment.label) != null ? _a : null
};
});
}
function normalizeScaleBound(entityConfig, cardConfig, key, defaultValue) {
- var _a, _b, _c, _d, _e, _f, _g, _h;
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
const cardScale = cardConfig == null ? void 0 : cardConfig.scale;
const entityScale = entityConfig == null ? void 0 : entityConfig.scale;
const entityKey = `${key}_entity`;
@@ -211,18 +446,28 @@
(_d = cardConfig == null ? void 0 : cardConfig[key]) != null ? _d : defaultValue,
(_e = cardConfig == null ? void 0 : cardConfig[entityKey]) != null ? _e : null
);
+ const inheritedFixedExplicit = cardBound ? cardBound.fixed_explicit !== false && cardBound.fixed !== null && cardBound.fixed !== void 0 : (cardConfig == null ? void 0 : cardConfig[key]) !== null && (cardConfig == null ? void 0 : cardConfig[key]) !== void 0;
+ const withFixedExplicit = (bound, explicit) => {
+ Object.defineProperty(bound, "fixed_explicit", { value: explicit });
+ return bound;
+ };
if ((entityScale == null ? void 0 : entityScale[key]) !== void 0) {
- return normalizeStructuredResolvableValue(
+ const input = entityScale[key];
+ const fixedExplicit = looksLikeEntityId(input) ? inheritedFixedExplicit : typeof input === "object" && input !== null ? ((_f = input.fixed) != null ? _f : input.value) !== null && ((_g = input.fixed) != null ? _g : input.value) !== void 0 : input !== null;
+ return withFixedExplicit(normalizeStructuredResolvableValue(
entityScale[key],
inherited,
defaultValue
- );
+ ), fixedExplicit);
}
const entityOverride = entityConfig[entityKey];
const hasEntityOverride = entityOverride !== void 0 && entityOverride !== null;
- const value = (_g = (_f = entityConfig[key]) != null ? _f : hasEntityOverride ? null : inherited.fixed) != null ? _g : inherited.entity ? null : defaultValue;
- const entity = (_h = entityOverride != null ? entityOverride : inherited.entity) != null ? _h : null;
- return normalizeResolvableValue(value, entity);
+ const value = (_i = (_h = entityConfig[key]) != null ? _h : hasEntityOverride ? null : inherited.fixed) != null ? _i : inherited.entity ? null : defaultValue;
+ const entity = (_j = entityOverride != null ? entityOverride : inherited.entity) != null ? _j : null;
+ return withFixedExplicit(
+ normalizeResolvableValue(value, entity),
+ entityConfig[key] !== null && entityConfig[key] !== void 0 || !hasEntityOverride && inheritedFixedExplicit
+ );
}
function normalizeScaleConfig(entityConfig, cardConfig) {
return {
@@ -260,6 +505,34 @@
return null;
}
}
+ function hasOwnConfigValue(config, key) {
+ return config !== null && config !== void 0 && Object.prototype.hasOwnProperty.call(config, key);
+ }
+ function hasExplicitColor(config) {
+ const values = [
+ hasOwnConfigValue(config, "color") ? config.color : void 0,
+ hasOwnConfigValue(config == null ? void 0 : config.bar, "color") ? config.bar.color : void 0
+ ];
+ return values.some((value) => value !== void 0 && value !== null);
+ }
+ function getPaintExplicitness(config) {
+ const bar = config == null ? void 0 : config.bar;
+ const explicitPaintKeys = [
+ [config, "color_mode"],
+ [bar, "color_mode"],
+ [bar, "fill_style"],
+ [config, "severity"],
+ [config, "segments"],
+ [bar, "segments"],
+ [config, "gradient_stops"],
+ [bar, "gradient_stops"],
+ [bar, "solid_fill"]
+ ];
+ return {
+ color: hasExplicitColor(config),
+ paint: explicitPaintKeys.some(([source, key]) => hasOwnConfigValue(source, key))
+ };
+ }
function normalizeBarModeConfig(barConfig = null, flatColorMode = null) {
var _a, _b, _c, _d, _e, _f;
const fillStyle = (_a = barConfig == null ? void 0 : barConfig.fill_style) != null ? _a : null;
@@ -270,7 +543,12 @@
color_mode: normalizedColorMode
};
}
- function resolveNormalizedBarMode(entityBar, entityConfig, cardBar, cardConfig) {
+ function resolveNormalizedBarMode(entityBar, entityConfig, cardBar, cardConfig, { scopeExplicitness = null, inheritedExplicitness = null, isCardScope = false } = {}) {
+ const colorOnlyScope = (scopeExplicitness == null ? void 0 : scopeExplicitness.color) === true && scopeExplicitness.paint !== true;
+ const inheritedPaintIsExplicit = (inheritedExplicitness == null ? void 0 : inheritedExplicitness.paint) === true;
+ if (colorOnlyScope && (isCardScope || !inheritedPaintIsExplicit)) {
+ return normalizeBarModeConfig({ fill_style: "solid" }, null);
+ }
if ((entityBar == null ? void 0 : entityBar.fill_style) !== void 0 || (entityBar == null ? void 0 : entityBar.color_mode) !== void 0 || entityConfig.color_mode !== void 0) {
return normalizeBarModeConfig(entityBar, entityConfig.color_mode);
}
@@ -313,54 +591,60 @@
}
return { ...base };
}
- function normalizeBarConfig(entityConfig, cardConfig) {
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
+ function normalizeBarConfig(entityConfig, cardConfig, options = {}) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A;
+ const scopeExplicitness = (_a = options.scopeExplicitness) != null ? _a : getPaintExplicitness(entityConfig);
+ const inheritedExplicitness = (_c = (_b = options.inheritedExplicitness) != null ? _b : cardConfig == null ? void 0 : cardConfig[PAINT_EXPLICITNESS]) != null ? _c : getPaintExplicitness(cardConfig);
+ const isCardScope = (_d = options.isCardScope) != null ? _d : cardConfig == null;
const cardBar = cardConfig == null ? void 0 : cardConfig.bar;
const entityBar = entityConfig == null ? void 0 : entityConfig.bar;
const entityStructuredSegments = entityBar == null ? void 0 : entityBar.segments;
const entityTopLevelSegments = entityConfig.segments;
const entityLegacySeverity = entityConfig.severity;
- const cardStructuredSegments = (_a = cardBar == null ? void 0 : cardBar.segments) != null ? _a : null;
- const cardTopLevelSegments = (_b = cardConfig == null ? void 0 : cardConfig.segments) != null ? _b : null;
- const cardLegacySeverity = (_c = cardConfig == null ? void 0 : cardConfig.severity) != null ? _c : null;
+ const cardStructuredSegments = (_e = cardBar == null ? void 0 : cardBar.segments) != null ? _e : null;
+ const cardTopLevelSegments = (_f = cardConfig == null ? void 0 : cardConfig.segments) != null ? _f : null;
+ const cardLegacySeverity = (_g = cardConfig == null ? void 0 : cardConfig.severity) != null ? _g : null;
let segments = null;
- let segment_space = (_d = cardBar == null ? void 0 : cardBar.segment_space) != null ? _d : "percent";
+ let segment_space = (cardBar == null ? void 0 : cardBar.segment_space) === "percent" || (cardBar == null ? void 0 : cardBar.segment_space) === "scale" ? cardBar.segment_space : null;
if (entityStructuredSegments !== void 0 && entityStructuredSegments !== null) {
- segments = normalizeGaugeSegments(entityStructuredSegments);
- segment_space = "scale";
+ segment_space = (entityBar == null ? void 0 : entityBar.segment_space) === "percent" || (entityBar == null ? void 0 : entityBar.segment_space) === "scale" ? entityBar.segment_space : segment_space;
+ segments = normalizeGaugeSegments(entityStructuredSegments, { legacySegmentSpace: segment_space });
} else if (entityTopLevelSegments !== void 0 && entityTopLevelSegments !== null) {
segments = normalizeGaugeSegments(entityTopLevelSegments);
- segment_space = "scale";
} else if (entityLegacySeverity !== void 0 && entityLegacySeverity !== null) {
segments = normalizeSeverityToSegments(entityLegacySeverity);
segment_space = "percent";
} else if (cardStructuredSegments !== null && cardStructuredSegments !== void 0) {
segments = cardStructuredSegments.map((segment) => ({ ...segment }));
- segment_space = (_e = cardBar == null ? void 0 : cardBar.segment_space) != null ? _e : "percent";
} else if (cardTopLevelSegments !== null && cardTopLevelSegments !== void 0) {
segments = normalizeGaugeSegments(cardTopLevelSegments);
- segment_space = "scale";
} else if (cardLegacySeverity !== null && cardLegacySeverity !== void 0) {
segments = normalizeSeverityToSegments(cardLegacySeverity);
segment_space = "percent";
}
- const structuredAboveTargetColor = (entityConfig == null ? void 0 : entityConfig.target) && typeof entityConfig.target === "object" && !Array.isArray(entityConfig.target) ? (_f = entityConfig.target.when_exceeded) == null ? void 0 : _f.fill_color : void 0;
- const inheritedStructuredAboveTargetColor = (cardConfig == null ? void 0 : cardConfig.target) && typeof cardConfig.target === "object" && !Array.isArray(cardConfig.target) ? (_g = cardConfig.target.when_exceeded) == null ? void 0 : _g.fill_color : void 0;
- const normalizedMode = resolveNormalizedBarMode(entityBar, entityConfig, cardBar, cardConfig);
+ const structuredAboveTargetColor = (entityConfig == null ? void 0 : entityConfig.target) && typeof entityConfig.target === "object" && !Array.isArray(entityConfig.target) ? (_h = entityConfig.target.when_exceeded) == null ? void 0 : _h.fill_color : void 0;
+ const inheritedStructuredAboveTargetColor = (cardConfig == null ? void 0 : cardConfig.target) && typeof cardConfig.target === "object" && !Array.isArray(cardConfig.target) ? (_i = cardConfig.target.when_exceeded) == null ? void 0 : _i.fill_color : void 0;
+ const normalizedMode = resolveNormalizedBarMode(
+ entityBar,
+ entityConfig,
+ cardBar,
+ cardConfig,
+ { scopeExplicitness, inheritedExplicitness, isCardScope }
+ );
return {
fill_style: normalizedMode.fill_style,
color_mode: normalizedMode.color_mode,
needle: normalizeNeedleConfig(entityBar == null ? void 0 : entityBar.needle, cardBar == null ? void 0 : cardBar.needle),
- solid_fill: (_i = (_h = entityBar == null ? void 0 : entityBar.solid_fill) != null ? _h : cardBar == null ? void 0 : cardBar.solid_fill) != null ? _i : false,
- color: (_m = (_l = (_k = (_j = entityBar == null ? void 0 : entityBar.color) != null ? _j : entityConfig.color) != null ? _k : cardBar == null ? void 0 : cardBar.color) != null ? _l : cardConfig == null ? void 0 : cardConfig.color) != null ? _m : "#4a9eff",
+ solid_fill: (_k = (_j = entityBar == null ? void 0 : entityBar.solid_fill) != null ? _j : cardBar == null ? void 0 : cardBar.solid_fill) != null ? _k : false,
+ color: (_o = (_n = (_m = (_l = entityBar == null ? void 0 : entityBar.color) != null ? _l : entityConfig.color) != null ? _m : cardBar == null ? void 0 : cardBar.color) != null ? _n : cardConfig == null ? void 0 : cardConfig.color) != null ? _o : "#4a9eff",
gradient_stops: normalizeGradientStops(
- (_q = (_p = (_o = (_n = entityBar == null ? void 0 : entityBar.gradient_stops) != null ? _n : entityConfig.gradient_stops) != null ? _o : cardBar == null ? void 0 : cardBar.gradient_stops) != null ? _p : cardConfig == null ? void 0 : cardConfig.gradient_stops) != null ? _q : null
+ (_s = (_r = (_q = (_p = entityBar == null ? void 0 : entityBar.gradient_stops) != null ? _p : entityConfig.gradient_stops) != null ? _q : cardBar == null ? void 0 : cardBar.gradient_stops) != null ? _r : cardConfig == null ? void 0 : cardConfig.gradient_stops) != null ? _s : null
),
severity: segments,
segments,
segment_space,
- animated: (_u = (_t = (_s = (_r = entityBar == null ? void 0 : entityBar.animated) != null ? _r : entityConfig.animated) != null ? _s : cardBar == null ? void 0 : cardBar.animated) != null ? _t : cardConfig == null ? void 0 : cardConfig.animated) != null ? _u : true,
- above_target_color: (_y = (_x = (_w = (_v = structuredAboveTargetColor != null ? structuredAboveTargetColor : entityConfig.above_target_color) != null ? _v : cardBar == null ? void 0 : cardBar.above_target_color) != null ? _w : inheritedStructuredAboveTargetColor) != null ? _x : cardConfig == null ? void 0 : cardConfig.above_target_color) != null ? _y : null
+ animated: (_w = (_v = (_u = (_t = entityBar == null ? void 0 : entityBar.animated) != null ? _t : entityConfig.animated) != null ? _u : cardBar == null ? void 0 : cardBar.animated) != null ? _v : cardConfig == null ? void 0 : cardConfig.animated) != null ? _w : true,
+ above_target_color: (_A = (_z = (_y = (_x = structuredAboveTargetColor != null ? structuredAboveTargetColor : entityConfig.above_target_color) != null ? _x : cardBar == null ? void 0 : cardBar.above_target_color) != null ? _y : inheritedStructuredAboveTargetColor) != null ? _z : cardConfig == null ? void 0 : cardConfig.above_target_color) != null ? _A : null
};
}
function clampSupportedRowHeight(height) {
@@ -419,46 +703,202 @@
unit: (_h = (_g = (_f = (_e = entityFormatting == null ? void 0 : entityFormatting.unit) != null ? _e : entityConfig.unit) != null ? _f : cardFormatting == null ? void 0 : cardFormatting.unit) != null ? _g : cardConfig == null ? void 0 : cardConfig.unit) != null ? _h : null
};
}
+ function normalizeTargetMarkerShape(value) {
+ const normalized = String(value != null ? value : "").trim().toLowerCase();
+ return normalized === "triangle" || normalized === "diamond" ? normalized : "diamond";
+ }
+ function normalizeMarkerDirection(value) {
+ const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
+ return normalized === "outward" ? "outward" : "inward";
+ }
+ function normalizeMarkerLabelConfig(rawLabel, inherited = {}) {
+ var _a, _b, _c;
+ const label = rawLabel && typeof rawLabel === "object" && !Array.isArray(rawLabel) ? rawLabel : {};
+ const hasText = Object.prototype.hasOwnProperty.call(label, "text");
+ const textValue = hasText ? typeof label.text === "string" ? label.text.replace(/\s+/g, " ").trim() : inherited.label_text : inherited.label_text;
+ const hasPrecision = Object.prototype.hasOwnProperty.call(label, "precision");
+ const hasDecimal = Object.prototype.hasOwnProperty.call(label, "decimal");
+ const precisionValue = hasPrecision ? label.precision : label.decimal;
+ const precisionSet = hasPrecision || hasDecimal;
+ const precision = precisionSet ? getFiniteNumber(precisionValue) : (_a = inherited.label_precision) != null ? _a : null;
+ const precisionInvalid = precisionSet && precisionValue !== null && precisionValue !== "" && !(Number.isInteger(precision) && precision >= 0);
+ return {
+ label_text: textValue != null ? textValue : null,
+ label_show_value: typeof label.show_value === "boolean" ? label.show_value : (_b = inherited.label_show_value) != null ? _b : true,
+ label_show_unit: typeof label.show_unit === "boolean" ? label.show_unit : (_c = inherited.label_show_unit) != null ? _c : true,
+ label_precision: precisionInvalid ? null : precision,
+ label_invalid_show: label.show !== void 0 && typeof label.show !== "boolean",
+ label_invalid_text: hasText && typeof label.text !== "string",
+ label_invalid_show_value: label.show_value !== void 0 && typeof label.show_value !== "boolean",
+ label_invalid_show_unit: label.show_unit !== void 0 && typeof label.show_unit !== "boolean",
+ label_invalid_precision: precisionInvalid,
+ label_precision_key: precisionInvalid ? hasPrecision ? "precision" : "decimal" : void 0,
+ label_unsupported_unit: Object.prototype.hasOwnProperty.call(label, "unit")
+ };
+ }
+ function builtinMarkerLabelFields(options) {
+ const fields = {};
+ if (options.label_text !== null && options.label_text !== void 0) fields.label_text = options.label_text;
+ if (options.label_show_value === false) fields.label_show_value = false;
+ if (options.label_show_unit === false) fields.label_show_unit = false;
+ if (options.label_precision !== null && options.label_precision !== void 0) fields.label_precision = options.label_precision;
+ if (options.label_invalid_text) fields.label_invalid_text = true;
+ if (options.label_invalid_show_value) fields.label_invalid_show_value = true;
+ if (options.label_invalid_show_unit) fields.label_invalid_show_unit = true;
+ if (options.label_invalid_show) fields.label_invalid_show = true;
+ if (options.label_invalid_precision) fields.label_invalid_precision = true;
+ if (options.label_invalid_precision && options.label_precision_key) fields.label_precision_key = options.label_precision_key;
+ if (options.label_unsupported_unit) fields.label_unsupported_unit = true;
+ return fields;
+ }
+ function isInvalidMarkerDirection(value) {
+ return value !== void 0 && !["inward", "outward"].includes(
+ typeof value === "string" ? value.trim().toLowerCase() : ""
+ );
+ }
function normalizeTargetMarkerConfig(entityConfig, cardConfig) {
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B;
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I;
const cardTarget = cardConfig == null ? void 0 : cardConfig.target_marker;
const rawTarget = entityConfig == null ? void 0 : entityConfig.target;
const legacyCardTarget = (cardConfig == null ? void 0 : cardConfig.target) && typeof cardConfig.target === "object" && !Array.isArray(cardConfig.target) ? null : (_a = cardConfig == null ? void 0 : cardConfig.target) != null ? _a : null;
- const inheritedTarget = cardTarget != null ? cardTarget : {
+ const inheritedTarget = cardTarget ? {
+ ...cardTarget,
+ shape: normalizeTargetMarkerShape(cardTarget.shape),
+ direction: normalizeMarkerDirection(cardTarget.direction)
+ } : {
enabled: null,
source: normalizeResolvableValue(null, null),
color: (_b = cardConfig == null ? void 0 : cardConfig.target_color) != null ? _b : "#888",
- show_label: (_c = cardConfig == null ? void 0 : cardConfig.show_target_label) != null ? _c : false
+ show_label: (_c = cardConfig == null ? void 0 : cardConfig.show_target_label) != null ? _c : false,
+ ...builtinMarkerLabelFields(normalizeMarkerLabelConfig((_d = cardConfig == null ? void 0 : cardConfig.target) == null ? void 0 : _d.label)),
+ label_decimal: (_j = (_i = (_f = (_e = cardConfig == null ? void 0 : cardConfig.target) == null ? void 0 : _e.label) == null ? void 0 : _f.precision) != null ? _i : (_h = (_g = cardConfig == null ? void 0 : cardConfig.target) == null ? void 0 : _g.label) == null ? void 0 : _h.decimal) != null ? _j : null,
+ shape: "diamond",
+ direction: "inward"
};
if (rawTarget && typeof rawTarget === "object" && !Array.isArray(rawTarget)) {
- return {
- enabled: (_e = (_d = normalizeOptionalEnabled(rawTarget.enabled)) != null ? _d : inheritedTarget.enabled) != null ? _e : null,
+ const labelOptions = normalizeMarkerLabelConfig(rawTarget.label, inheritedTarget);
+ const normalizedTarget2 = {
+ enabled: (_l = (_k = normalizeOptionalEnabled(rawTarget.enabled)) != null ? _k : inheritedTarget.enabled) != null ? _l : null,
source: normalizeStructuredResolvableValue(rawTarget.at, inheritedTarget.source, null, { allowPercent: true }),
- color: (_g = (_f = rawTarget.color) != null ? _f : entityConfig.target_color) != null ? _g : inheritedTarget.color,
- show_label: (_j = (_i = (_h = rawTarget.label) == null ? void 0 : _h.show) != null ? _i : entityConfig.show_target_label) != null ? _j : inheritedTarget.show_label
+ color: (_n = (_m = rawTarget.color) != null ? _m : entityConfig.target_color) != null ? _n : inheritedTarget.color,
+ show_label: (_q = (_p = (_o = rawTarget.label) == null ? void 0 : _o.show) != null ? _p : entityConfig.show_target_label) != null ? _q : inheritedTarget.show_label,
+ shape: Object.prototype.hasOwnProperty.call(rawTarget, "shape") ? normalizeTargetMarkerShape(rawTarget.shape) : inheritedTarget.shape,
+ direction: Object.prototype.hasOwnProperty.call(rawTarget, "direction") ? normalizeMarkerDirection(rawTarget.direction) : inheritedTarget.direction,
+ ...isInvalidMarkerDirection(rawTarget.direction) ? { direction_invalid: true } : {},
+ ...builtinMarkerLabelFields(labelOptions)
};
+ const labelDecimal2 = labelOptions.label_precision;
+ if (labelDecimal2 !== null && labelDecimal2 !== void 0) {
+ normalizedTarget2.label_decimal = labelDecimal2;
+ normalizedTarget2.label_precision = labelDecimal2;
+ }
+ return normalizedTarget2;
}
- const value = (_o = (_n = (_l = entityConfig.target) != null ? _l : (_k = inheritedTarget.source) == null ? void 0 : _k.fixed) != null ? _n : (_m = inheritedTarget.source) == null ? void 0 : _m.value) != null ? _o : legacyCardTarget;
- const entity = (_s = (_r = (_q = entityConfig.target_entity) != null ? _q : (_p = inheritedTarget.source) == null ? void 0 : _p.entity) != null ? _r : cardConfig == null ? void 0 : cardConfig.target_entity) != null ? _s : null;
- const percent = entityConfig.target === void 0 && entityConfig.target_entity === void 0 ? (_u = (_t = inheritedTarget.source) == null ? void 0 : _t.percent) != null ? _u : null : null;
- return {
- enabled: (_v = inheritedTarget.enabled) != null ? _v : null,
+ const value = (_v = (_u = (_s = entityConfig.target) != null ? _s : (_r = inheritedTarget.source) == null ? void 0 : _r.fixed) != null ? _u : (_t = inheritedTarget.source) == null ? void 0 : _t.value) != null ? _v : legacyCardTarget;
+ const entity = (_z = (_y = (_x = entityConfig.target_entity) != null ? _x : (_w = inheritedTarget.source) == null ? void 0 : _w.entity) != null ? _y : cardConfig == null ? void 0 : cardConfig.target_entity) != null ? _z : null;
+ const percent = entityConfig.target === void 0 && entityConfig.target_entity === void 0 ? (_B = (_A = inheritedTarget.source) == null ? void 0 : _A.percent) != null ? _B : null : null;
+ const normalizedTarget = {
+ enabled: (_C = inheritedTarget.enabled) != null ? _C : null,
source: normalizeResolvableValue(value, entity, percent),
- color: (_y = (_x = (_w = entityConfig.target_color) != null ? _w : inheritedTarget.color) != null ? _x : cardConfig == null ? void 0 : cardConfig.target_color) != null ? _y : "#888",
- show_label: (_B = (_A = (_z = entityConfig.show_target_label) != null ? _z : inheritedTarget.show_label) != null ? _A : cardConfig == null ? void 0 : cardConfig.show_target_label) != null ? _B : false
+ color: (_F = (_E = (_D = entityConfig.target_color) != null ? _D : inheritedTarget.color) != null ? _E : cardConfig == null ? void 0 : cardConfig.target_color) != null ? _F : "#888",
+ show_label: (_I = (_H = (_G = entityConfig.show_target_label) != null ? _G : inheritedTarget.show_label) != null ? _H : cardConfig == null ? void 0 : cardConfig.show_target_label) != null ? _I : false,
+ shape: inheritedTarget.shape,
+ direction: inheritedTarget.direction,
+ ...builtinMarkerLabelFields(inheritedTarget)
};
+ const labelDecimal = normalizedTarget.label_precision;
+ if (labelDecimal !== null && labelDecimal !== void 0) {
+ normalizedTarget.label_decimal = labelDecimal;
+ }
+ return normalizedTarget;
+ }
+ function getRawExtremumConfig(config, key) {
+ const value = config == null ? void 0 : config[key];
+ if (value && typeof value === "object" && !Array.isArray(value)) return value;
+ const markerValue = config == null ? void 0 : config[`${key}_marker`];
+ return markerValue && typeof markerValue === "object" && !Array.isArray(markerValue) ? markerValue : null;
+ }
+ function normalizeLabelConfig(rawConfig, inheritedConfig) {
+ var _a, _b, _c;
+ const rawLabel = rawConfig == null ? void 0 : rawConfig.label;
+ const hasRawLabel = rawLabel && typeof rawLabel === "object" && !Array.isArray(rawLabel);
+ const options = normalizeMarkerLabelConfig(rawLabel, inheritedConfig);
+ return {
+ show: hasRawLabel && typeof rawLabel.show === "boolean" ? rawLabel.show : (_a = inheritedConfig.show_label) != null ? _a : false,
+ text: options.label_text,
+ invalidShow: options.label_invalid_show,
+ showValue: options.label_show_value,
+ showUnit: options.label_show_unit,
+ precision: options.label_invalid_precision ? null : (_c = (_b = options.label_precision) != null ? _b : inheritedConfig.label_decimal) != null ? _c : null,
+ invalidText: options.label_invalid_text,
+ invalidShowValue: options.label_invalid_show_value,
+ invalidShowUnit: options.label_invalid_show_unit,
+ invalidPrecision: options.label_invalid_precision,
+ precisionKey: options.label_precision_key,
+ unsupportedUnit: options.label_unsupported_unit
+ };
+ }
+ function normalizeExtremumMarkerConfig(entityConfig, cardConfig, key, options = {}) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
+ const { legacy = false, defaultColor = "#888888" } = options;
+ const cardMarker = cardConfig == null ? void 0 : cardConfig[`${key}_marker`];
+ const rawMarker = getRawExtremumConfig(entityConfig, key);
+ const inherited = cardMarker != null ? cardMarker : {
+ show: legacy ? (_a = cardConfig == null ? void 0 : cardConfig.show_peak) != null ? _a : false : false,
+ color: legacy ? (_b = cardConfig == null ? void 0 : cardConfig.peak_color) != null ? _b : defaultColor : defaultColor,
+ show_label: false,
+ label_decimal: null,
+ label_text: null,
+ label_show_value: true,
+ label_show_unit: true,
+ label_precision: null,
+ reset: { kind: "never" },
+ direction: "inward"
+ };
+ const hasReset = rawMarker && Object.prototype.hasOwnProperty.call(rawMarker, "reset");
+ const rawReset = hasReset ? rawMarker.reset : void 0;
+ const reset = hasReset ? normalizeReset(rawReset) : (_c = inherited.reset) != null ? _c : { kind: "never" };
+ const rawLabel = rawMarker == null ? void 0 : rawMarker.label;
+ const label = normalizeLabelConfig(rawMarker, inherited);
+ const normalized = {
+ show: (_f = (_e = (_d = rawMarker == null ? void 0 : rawMarker.enabled) != null ? _d : legacy ? entityConfig.show_peak : void 0) != null ? _e : inherited.show) != null ? _f : false,
+ color: (_i = (_h = (_g = rawMarker == null ? void 0 : rawMarker.color) != null ? _g : legacy ? entityConfig.peak_color : void 0) != null ? _h : inherited.color) != null ? _i : defaultColor,
+ direction: rawMarker && Object.prototype.hasOwnProperty.call(rawMarker, "direction") ? normalizeMarkerDirection(rawMarker.direction) : normalizeMarkerDirection(inherited.direction),
+ ...isInvalidMarkerDirection(rawMarker == null ? void 0 : rawMarker.direction) ? { direction_invalid: true } : {}
+ };
+ const inheritedAdvanced = cardMarker && (Object.prototype.hasOwnProperty.call(cardMarker, "show_label") || Object.prototype.hasOwnProperty.call(cardMarker, "label_decimal") || Object.prototype.hasOwnProperty.call(cardMarker, "label_text") || Object.prototype.hasOwnProperty.call(cardMarker, "label_show_value") || Object.prototype.hasOwnProperty.call(cardMarker, "label_show_unit") || Object.prototype.hasOwnProperty.call(cardMarker, "label_precision") || Object.prototype.hasOwnProperty.call(cardMarker, "reset"));
+ const hasAdvancedConfig = key === "floor" || hasReset || rawLabel !== void 0 || inheritedAdvanced;
+ if (hasAdvancedConfig) {
+ normalized.show_label = label.show;
+ normalized.label_decimal = label.precision;
+ normalized.reset = reset;
+ }
+ Object.assign(normalized, builtinMarkerLabelFields({
+ label_text: label.text,
+ label_show_value: label.showValue === true ? void 0 : label.showValue,
+ label_show_unit: label.showUnit === true ? void 0 : label.showUnit,
+ label_precision: label.precision,
+ label_invalid_text: label.invalidText,
+ label_invalid_show: label.invalidShow,
+ label_invalid_show_value: label.invalidShowValue,
+ label_invalid_show_unit: label.invalidShowUnit,
+ label_invalid_precision: label.invalidPrecision,
+ label_precision_key: label.precisionKey,
+ label_unsupported_unit: label.unsupportedUnit
+ }));
+ if (hasReset && !isValidReset(rawReset)) {
+ normalized.reset_invalid = true;
+ }
+ return normalized;
}
function normalizePeakMarkerConfig(entityConfig, cardConfig) {
- var _a, _b, _c, _d, _e, _f, _g, _h;
- const cardPeak = cardConfig == null ? void 0 : cardConfig.peak_marker;
- const entityPeak = entityConfig == null ? void 0 : entityConfig.peak;
- return {
- show: (_d = (_c = (_b = (_a = entityPeak == null ? void 0 : entityPeak.enabled) != null ? _a : entityConfig.show_peak) != null ? _b : cardPeak == null ? void 0 : cardPeak.show) != null ? _c : cardConfig == null ? void 0 : cardConfig.show_peak) != null ? _d : false,
- color: (_h = (_g = (_f = (_e = entityPeak == null ? void 0 : entityPeak.color) != null ? _e : entityConfig.peak_color) != null ? _f : cardPeak == null ? void 0 : cardPeak.color) != null ? _g : cardConfig == null ? void 0 : cardConfig.peak_color) != null ? _h : "#888"
- };
+ return normalizeExtremumMarkerConfig(entityConfig, cardConfig, "peak", { legacy: true, defaultColor: "#888888" });
+ }
+ function normalizeFloorMarkerConfig(entityConfig, cardConfig) {
+ return normalizeExtremumMarkerConfig(entityConfig, cardConfig, "floor", { defaultColor: "#888888" });
}
function normalizeEntityConfig(entityConfig, cardConfig) {
- var _a;
+ var _a, _b, _c;
const normalizedEntity = {
...entityConfig,
_normalized: true,
@@ -468,11 +908,22 @@
};
normalizedEntity.layout = normalizeLayoutConfig(entityConfig, cardConfig);
normalizedEntity.scale = normalizeScaleConfig(entityConfig, cardConfig);
- normalizedEntity.bar = normalizeBarConfig(entityConfig, cardConfig);
+ normalizedEntity.bar = normalizeBarConfig(entityConfig, cardConfig, {
+ scopeExplicitness: getPaintExplicitness(entityConfig),
+ inheritedExplicitness: (_b = cardConfig == null ? void 0 : cardConfig[PAINT_EXPLICITNESS]) != null ? _b : getPaintExplicitness(cardConfig),
+ isCardScope: false
+ });
normalizedEntity.baseline = normalizeBaselineConfig(entityConfig, cardConfig);
normalizedEntity.formatting = normalizeFormattingConfig(entityConfig, cardConfig);
normalizedEntity.target_marker = normalizeTargetMarkerConfig(entityConfig, cardConfig);
normalizedEntity.peak_marker = normalizePeakMarkerConfig(entityConfig, cardConfig);
+ normalizedEntity.floor_marker = normalizeFloorMarkerConfig(entityConfig, cardConfig);
+ const normalizedMarkers = entityConfig.markers === void 0 ? {
+ markers: applyGenericMarkerCapacity((_c = cardConfig == null ? void 0 : cardConfig.generic_markers) != null ? _c : [], normalizedEntity),
+ invalidList: (cardConfig == null ? void 0 : cardConfig.generic_markers_invalid) === true
+ } : normalizeGenericMarkerList(entityConfig.markers, normalizedEntity);
+ normalizedEntity.generic_markers = normalizedMarkers.markers;
+ normalizedEntity.generic_markers_invalid = normalizedMarkers.invalidList;
normalizedEntity.min = normalizedEntity.scale.min.fixed;
normalizedEntity.min_entity = normalizedEntity.scale.min.entity;
normalizedEntity.max = normalizedEntity.scale.max.fixed;
@@ -500,6 +951,7 @@
}
function normalizeCardConfig(rawConfig) {
var _a;
+ const cardPaintExplicitness = getPaintExplicitness(rawConfig);
const baseConfig = {
title: "",
label_position: "left",
@@ -507,7 +959,7 @@
color: "#4a9eff",
animated: true,
show_peak: false,
- peak_color: "#888",
+ peak_color: "#888888",
target: null,
target_entity: null,
target_color: "#888",
@@ -543,20 +995,35 @@
...baseConfig,
_normalized: true
};
+ Object.defineProperty(normalizedCard, PAINT_EXPLICITNESS, {
+ value: cardPaintExplicitness,
+ enumerable: false
+ });
normalizedCard.layout = normalizeLayoutConfig(baseConfig, null);
- normalizedCard.scale = normalizeScaleConfig(baseConfig, null);
- normalizedCard.bar = normalizeBarConfig(baseConfig, null);
+ normalizedCard.scale = normalizeScaleConfig(rawConfig, null);
+ normalizedCard.bar = normalizeBarConfig(baseConfig, null, {
+ scopeExplicitness: cardPaintExplicitness,
+ inheritedExplicitness: null,
+ isCardScope: true
+ });
normalizedCard.baseline = normalizeBaselineConfig(baseConfig, null);
normalizedCard.formatting = normalizeFormattingConfig(baseConfig, null);
normalizedCard.target_marker = normalizeTargetMarkerConfig(baseConfig, null);
normalizedCard.peak_marker = normalizePeakMarkerConfig(baseConfig, null);
+ normalizedCard.floor_marker = normalizeFloorMarkerConfig(baseConfig, null);
+ const normalizedMarkers = normalizeGenericMarkerList(baseConfig.markers, normalizedCard);
+ normalizedCard.generic_markers = normalizedMarkers.markers;
+ normalizedCard.generic_markers_invalid = normalizedMarkers.invalidList;
normalizedCard.entities = baseConfig.entities.map(
(entityCfg) => normalizeEntityConfig(entityCfg, normalizedCard)
);
return normalizedCard;
}
+ var PAINT_EXPLICITNESS;
var init_normalize = __esm({
"src/config/normalize.js"() {
+ init_extrema();
+ PAINT_EXPLICITNESS = /* @__PURE__ */ Symbol("sbcp.paintExplicitness");
}
});
@@ -593,6 +1060,35 @@
}
return null;
}
+ function getResolvedScale(hass, scale, previousScale = null) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
+ const isValid = (bounds) => Number.isFinite(bounds == null ? void 0 : bounds.min) && Number.isFinite(bounds == null ? void 0 : bounds.max) && bounds.min < bounds.max;
+ const fixed = {
+ min: getNumericValue(null, (_c = (_a = scale == null ? void 0 : scale.min) == null ? void 0 : _a.fixed) != null ? _c : (_b = scale == null ? void 0 : scale.min) == null ? void 0 : _b.value),
+ max: getNumericValue(null, (_f = (_d = scale == null ? void 0 : scale.max) == null ? void 0 : _d.fixed) != null ? _f : (_e = scale == null ? void 0 : scale.max) == null ? void 0 : _e.value)
+ };
+ if (((_g = scale == null ? void 0 : scale.min) == null ? void 0 : _g.entity) && ((_h = scale == null ? void 0 : scale.max) == null ? void 0 : _h.entity)) {
+ const dynamic = {
+ min: getEntityNumericValue(hass, scale.min.entity),
+ max: getEntityNumericValue(hass, scale.max.entity)
+ };
+ if (isValid(dynamic)) return dynamic;
+ if (Number.isFinite(dynamic.min) && Number.isFinite(dynamic.max) && isValid(previousScale)) {
+ return previousScale;
+ }
+ return scale.min.fixed_explicit !== false && scale.max.fixed_explicit !== false && isValid(fixed) ? fixed : { min: 0, max: 100 };
+ }
+ const resolved = {
+ min: (_i = getNormalizedResolvableNumericValue(hass, scale == null ? void 0 : scale.min)) != null ? _i : 0,
+ max: (_j = getNormalizedResolvableNumericValue(hass, scale == null ? void 0 : scale.max)) != null ? _j : 100
+ };
+ if (isValid(resolved)) return resolved;
+ const fallback = {
+ min: (_k = fixed.min) != null ? _k : 0,
+ max: (_l = fixed.max) != null ? _l : 100
+ };
+ return isValid(fallback) ? fallback : { min: 0, max: 100 };
+ }
var init_resolve = __esm({
"src/config/resolve.js"() {
init_normalize();
@@ -614,6 +1110,22 @@
diagnostics.warnings.push(createDiagnostic(code, message, path, entity));
}
function validateScaleBounds(diagnostics, scale, path, entity = null) {
+ var _a, _b;
+ if (((_a = scale == null ? void 0 : scale.min) == null ? void 0 : _a.entity) && ((_b = scale == null ? void 0 : scale.max) == null ? void 0 : _b.entity)) {
+ const hasFixed = (bound) => {
+ var _a2, _b2;
+ return bound.fixed_explicit !== false && ((_a2 = bound.fixed) != null ? _a2 : bound.value) !== null && ((_b2 = bound.fixed) != null ? _b2 : bound.value) !== void 0;
+ };
+ if (hasFixed(scale.min) !== hasFixed(scale.max)) {
+ addWarning(
+ diagnostics,
+ "scale.orphan_fixed_fallback",
+ "Both dynamic scale bounds require a complete fixed fallback pair. The single fixed fallback will not be used if the dynamic pair becomes unavailable.",
+ `${path}.scale`,
+ entity
+ );
+ }
+ }
const min = getStaticFixedValue(scale == null ? void 0 : scale.min);
const max = getStaticFixedValue(scale == null ? void 0 : scale.max);
if (Number.isFinite(min) && Number.isFinite(max) && min > max) {
@@ -657,6 +1169,89 @@
);
}
}
+ function validateExtremumReset(diagnostics, config, path, entity = null) {
+ var _a;
+ for (const marker of ["peak_marker", "floor_marker"]) {
+ if ((_a = config == null ? void 0 : config[marker]) == null ? void 0 : _a.reset_invalid) {
+ addWarning(
+ diagnostics,
+ `${marker}.invalid_reset`,
+ "Invalid marker reset; using never.",
+ `${path}.${marker}.reset`,
+ entity
+ );
+ }
+ }
+ }
+ function validateMarkerDirections(diagnostics, config, path, entity = null) {
+ var _a;
+ for (const marker of ["target_marker", "peak_marker", "floor_marker"]) {
+ if ((_a = config == null ? void 0 : config[marker]) == null ? void 0 : _a.direction_invalid) {
+ addWarning(
+ diagnostics,
+ `${marker}.invalid_direction`,
+ "Invalid marker direction; using inward.",
+ `${path}.${marker === "target_marker" ? "target" : marker.replace("_marker", "")}.direction`,
+ entity
+ );
+ }
+ }
+ }
+ function validateBuiltinMarkerLabels(diagnostics, config, path, entity = null) {
+ var _a;
+ for (const [key, publicKey] of [["target_marker", "target"], ["peak_marker", "peak"], ["floor_marker", "floor"]]) {
+ const marker = config == null ? void 0 : config[key];
+ const labelPath = `${path}.${publicKey}.label`;
+ if (marker == null ? void 0 : marker.label_invalid_show) addWarning(diagnostics, "markers.invalid_label_show", "Marker label show must be a boolean; using the existing fallback.", `${labelPath}.show`, entity);
+ if (marker == null ? void 0 : marker.label_invalid_text) addWarning(diagnostics, "markers.invalid_label_text", "Marker label text must be a string; ignoring it.", `${labelPath}.text`, entity);
+ if (marker == null ? void 0 : marker.label_invalid_show_value) addWarning(diagnostics, "markers.invalid_label_show_value", "Marker label show_value must be a boolean; using the inherited or default value.", `${labelPath}.show_value`, entity);
+ if (marker == null ? void 0 : marker.label_invalid_show_unit) addWarning(diagnostics, "markers.invalid_label_show_unit", "Marker label show_unit must be a boolean; using the inherited or default value.", `${labelPath}.show_unit`, entity);
+ if (marker == null ? void 0 : marker.label_invalid_precision) addWarning(diagnostics, "markers.invalid_label_precision", "Marker label precision must be a non-negative integer; using the inherited or row precision.", `${labelPath}.${(_a = marker.label_precision_key) != null ? _a : "precision"}`, entity);
+ if (marker == null ? void 0 : marker.label_unsupported_unit) addWarning(diagnostics, "markers.unsupported_label_unit", "Marker label unit is no longer supported; use show_unit instead.", `${labelPath}.unit`, entity);
+ }
+ }
+ function validateGenericMarkers(diagnostics, markers, invalidList, path, entity = null) {
+ var _a, _b, _c, _d, _e, _f, _g, _h;
+ if (invalidList) {
+ addWarning(diagnostics, "markers.invalid_list", "Markers must be a list; ignoring the malformed value.", path, entity);
+ }
+ for (const marker of markers != null ? markers : []) {
+ const markerPath = `${path}[${marker.index}]`;
+ if (marker.malformed) {
+ addWarning(diagnostics, "markers.invalid_item", "Marker must be an object; ignoring this item.", markerPath, entity);
+ continue;
+ }
+ if (marker.invalidSource && !marker.invalidPercentage && !marker.unsupportedPercentField) {
+ addWarning(diagnostics, "markers.invalid_source", "Marker requires a valid fixed value, percentage, or entity source.", `${markerPath}.at`, entity);
+ }
+ if (marker.invalidPercentage) {
+ addWarning(diagnostics, "markers.invalid_percentage", "Marker percentage must be between 0 and 100 inclusive.", `${markerPath}.at`, entity);
+ }
+ if (marker.unsupportedPercentField) {
+ addWarning(diagnostics, "markers.invalid_source", 'Use a percentage string such as "35%" instead of an at.percent field.', `${markerPath}.at`, entity);
+ }
+ if (marker.invalidLane) {
+ addWarning(diagnostics, "markers.invalid_lane", "Marker lane must be above or below; ignoring this marker.", `${markerPath}.lane`, entity);
+ }
+ if (marker.invalidShape) {
+ addWarning(diagnostics, "markers.invalid_shape", "Invalid marker shape; using circle.", `${markerPath}.shape`, entity);
+ }
+ if (marker.invalidDirection) {
+ addWarning(diagnostics, "markers.invalid_direction", "Invalid marker direction; using inward.", `${markerPath}.direction`, entity);
+ }
+ if (marker.invalidShowMarker) addWarning(diagnostics, "markers.invalid_show_marker", "Marker show_marker must be a boolean; using true.", `${markerPath}.show_marker`, entity);
+ if ((_a = marker.label) == null ? void 0 : _a.invalidEntity) addWarning(diagnostics, "markers.invalid_label_entity", "Marker label entity must be a valid entity ID; ignoring it.", `${markerPath}.label.entity`, entity);
+ if ((_b = marker.label) == null ? void 0 : _b.invalidShow) addWarning(diagnostics, "markers.invalid_label_show", "Marker label show must be a boolean; using the existing fallback.", `${markerPath}.label.show`, entity);
+ if ((_c = marker.label) == null ? void 0 : _c.invalidText) addWarning(diagnostics, "markers.invalid_label_text", "Marker label text must be a string; ignoring it.", `${markerPath}.label.text`, entity);
+ if ((_d = marker.label) == null ? void 0 : _d.invalidShowValue) addWarning(diagnostics, "markers.invalid_label_show_value", "Marker label show_value must be a boolean; using the inherited or default value.", `${markerPath}.label.show_value`, entity);
+ if ((_e = marker.label) == null ? void 0 : _e.invalidShowUnit) addWarning(diagnostics, "markers.invalid_label_show_unit", "Marker label show_unit must be a boolean; using the inherited or default value.", `${markerPath}.label.show_unit`, entity);
+ if ((_f = marker.label) == null ? void 0 : _f.invalidPrecision) addWarning(diagnostics, "markers.invalid_label_precision", "Marker label precision must be a non-negative integer; using the inherited or row precision.", `${markerPath}.label.${(_g = marker.label.invalidPrecisionKey) != null ? _g : "precision"}`, entity);
+ if ((_h = marker.label) == null ? void 0 : _h.unsupportedUnit) addWarning(diagnostics, "markers.unsupported_label_unit", "Marker label unit is no longer supported; use show_unit instead.", `${markerPath}.label.unit`, entity);
+ if (marker.valid && !marker.accepted) {
+ addWarning(diagnostics, "markers.excess_capacity", "This marker is not rendered because its lane already has four markers.", markerPath, entity);
+ }
+ }
+ }
function getStaticSegmentBound(boundary) {
var _a;
if (!boundary || boundary.entity || Number.isFinite(boundary.percent)) return null;
@@ -671,6 +1266,18 @@
const from = getStaticSegmentBound(segment == null ? void 0 : segment.from);
const to = getStaticSegmentBound(segment == null ? void 0 : segment.to);
const segmentPath = `${path}.segments[${index}]`;
+ if ((segment == null ? void 0 : segment.invalidBoundary) === "entity") {
+ addWarning(diagnostics, "segments.unsupported_entity_boundary", "Entity-backed segment boundaries are not supported; ignoring this segment.", segmentPath, entity);
+ continue;
+ }
+ if ((segment == null ? void 0 : segment.invalidBoundary) === "malformed_percent") {
+ addWarning(diagnostics, "segments.invalid_percentage", "Malformed percentage segment boundary; ignoring this segment.", segmentPath, entity);
+ continue;
+ }
+ if (segment == null ? void 0 : segment.invalidBoundary) {
+ addWarning(diagnostics, "segments.invalid_boundary", 'Segment boundary must be a numeric value or a percentage such as "35%"; ignoring this segment.', segmentPath, entity);
+ continue;
+ }
if (Number.isFinite(from) && Number.isFinite(to) && from > to) {
addWarning(diagnostics, "segments.from_gt_to", "Segment start is greater than segment end.", segmentPath, entity);
}
@@ -732,11 +1339,14 @@
validateTargetRange(diagnostics, config, scaleBounds, path, entity);
validateBaselineRange(diagnostics, config, scaleBounds, path, entity);
validateBaselineSuppressesNeedle(diagnostics, config, path, entity);
+ validateExtremumReset(diagnostics, config, path, entity);
+ validateMarkerDirections(diagnostics, config, path, entity);
+ validateBuiltinMarkerLabels(diagnostics, config, path, entity);
validateSegments(diagnostics, (_a = config == null ? void 0 : config.bar) == null ? void 0 : _a.segments, scaleBounds, `${path}.bar`, entity);
validateGradientStops(diagnostics, (_b = config == null ? void 0 : config.bar) == null ? void 0 : _b.gradient_stops, `${path}.bar`, entity);
}
function validateNormalizedConfig(config) {
- var _a;
+ var _a, _b;
const diagnostics = {
warnings: [],
errors: []
@@ -745,6 +1355,7 @@
return diagnostics;
}
validateConfigScope(diagnostics, config, "card");
+ validateGenericMarkers(diagnostics, config.generic_markers, config.generic_markers_invalid, "markers");
const seenEntities = /* @__PURE__ */ new Set();
const entities = Array.isArray(config.entities) ? config.entities : [];
for (let index = 0; index < entities.length; index += 1) {
@@ -761,6 +1372,25 @@
seenEntities.add(entityId);
}
validateConfigScope(diagnostics, entityConfig, path, entityId);
+ if (Object.prototype.hasOwnProperty.call(entityConfig, "markers")) {
+ validateGenericMarkers(
+ diagnostics,
+ entityConfig.generic_markers,
+ entityConfig.generic_markers_invalid,
+ `${path}.markers`,
+ entityId
+ );
+ } else {
+ const inheritedOverflow = ((_b = entityConfig.generic_markers) != null ? _b : []).filter(
+ (marker) => {
+ var _a2, _b2;
+ return marker.valid && !marker.accepted && ((_b2 = (_a2 = config.generic_markers) == null ? void 0 : _a2[marker.index]) == null ? void 0 : _b2.accepted) === true;
+ }
+ );
+ if (inheritedOverflow.length) {
+ validateGenericMarkers(diagnostics, inheritedOverflow, false, `${path}.markers`, entityId);
+ }
+ }
}
return diagnostics;
}
@@ -770,6 +1400,219 @@
}
});
+ // src/utils/format.js
+ function formatNumericDisplay(rawVal, decimal = null) {
+ if (!Number.isFinite(rawVal)) return String(rawVal);
+ if (decimal !== null) {
+ return rawVal.toLocaleString(void 0, {
+ minimumFractionDigits: decimal,
+ maximumFractionDigits: decimal
+ });
+ }
+ return rawVal.toLocaleString();
+ }
+ function isTightUnit(unit) {
+ return ["h", "m", "s"].includes(String(unit || "").trim());
+ }
+ function formatDisplayWithUnit(display, unit) {
+ if (!unit) return String(display);
+ const cleanUnit = String(unit);
+ return `${display}${isTightUnit(cleanUnit) ? "" : " "}${cleanUnit}`;
+ }
+ function createNumericPresentation(value, unit, decimal = null) {
+ const number = formatNumericDisplay(value, decimal);
+ const cleanUnit = unit ? String(unit) : "";
+ return {
+ value,
+ number,
+ unit: cleanUnit,
+ text: formatDisplayWithUnit(number, cleanUnit)
+ };
+ }
+ function createMarkerLabelPresentation(value, unit, precision = null, options = {}) {
+ const semanticText = typeof options.text === "string" ? options.text : "";
+ const number = options.showValue === false || value === null || value === void 0 || value === "" ? "" : Number.isFinite(value) ? formatNumericDisplay(value, precision) : String(value);
+ const cleanUnit = options.showUnit === false ? "" : String(unit != null ? unit : "").trim();
+ const text = [semanticText, number, cleanUnit].filter(Boolean).join(" ");
+ return {
+ value: Number.isFinite(value) ? value : null,
+ semanticText,
+ number,
+ unit: cleanUnit,
+ showValue: options.showValue !== false,
+ showUnit: options.showUnit !== false,
+ text
+ };
+ }
+ function createTextPresentation(text) {
+ const value = String(text);
+ return {
+ value: null,
+ number: value,
+ unit: "",
+ text: value
+ };
+ }
+ var init_format = __esm({
+ "src/utils/format.js"() {
+ }
+ });
+
+ // src/view-model/marker-view-model.js
+ function hasConfiguredSource(source) {
+ if (!source) return false;
+ return source.entity !== null && source.entity !== void 0 && source.entity !== "" || source.fixed !== null && source.fixed !== void 0 && source.fixed !== "" || Number.isFinite(source.percent);
+ }
+ function normalizeMarkerShape(value, fallback = "circle") {
+ return MARKER_SHAPES.has(value) ? value : fallback;
+ }
+ function createMarkerModel({
+ id,
+ type,
+ value = null,
+ position = null,
+ lane,
+ visible = false,
+ color = null,
+ label = null,
+ labelVisible = false,
+ shape = "circle",
+ direction = "inward",
+ showMarker = true
+ }) {
+ return {
+ id,
+ type,
+ value,
+ position,
+ lane,
+ visible: visible === true,
+ color,
+ label,
+ labelVisible: labelVisible === true,
+ shape: normalizeMarkerShape(shape),
+ direction,
+ showMarker: showMarker !== false
+ };
+ }
+ function hasMarkerLabelContent(label) {
+ return !!(typeof (label == null ? void 0 : label.text) === "string" && label.text.trim()) || (label == null ? void 0 : label.showValue) !== false || (label == null ? void 0 : label.showUnit) !== false;
+ }
+ function getMarkerLaneOccupancy(entityConfig) {
+ var _a;
+ const targetConfig = entityConfig == null ? void 0 : entityConfig.target_marker;
+ const peakConfig = entityConfig == null ? void 0 : entityConfig.peak_marker;
+ const floorConfig = entityConfig == null ? void 0 : entityConfig.floor_marker;
+ const genericMarkers = ((_a = entityConfig == null ? void 0 : entityConfig.generic_markers) != null ? _a : []).filter((marker) => marker.accepted);
+ return {
+ below: (targetConfig == null ? void 0 : targetConfig.enabled) !== false && hasConfiguredSource(targetConfig == null ? void 0 : targetConfig.source) || (floorConfig == null ? void 0 : floorConfig.show) === true || genericMarkers.some((marker) => marker.lane === "below"),
+ above: (peakConfig == null ? void 0 : peakConfig.show) === true || genericMarkers.some((marker) => marker.lane === "above")
+ };
+ }
+ function getMarkerLabelLaneOccupancy(entityConfig) {
+ var _a;
+ const targetConfig = entityConfig == null ? void 0 : entityConfig.target_marker;
+ const peakConfig = entityConfig == null ? void 0 : entityConfig.peak_marker;
+ const floorConfig = entityConfig == null ? void 0 : entityConfig.floor_marker;
+ const genericMarkers = ((_a = entityConfig == null ? void 0 : entityConfig.generic_markers) != null ? _a : []).filter((marker) => marker.accepted);
+ return {
+ below: (targetConfig == null ? void 0 : targetConfig.enabled) !== false && hasConfiguredSource(targetConfig == null ? void 0 : targetConfig.source) && (targetConfig == null ? void 0 : targetConfig.show_label) === true && hasMarkerLabelContent({ text: targetConfig.label_text, showValue: targetConfig.label_show_value, showUnit: targetConfig.label_show_unit }) || (floorConfig == null ? void 0 : floorConfig.show) === true && (floorConfig == null ? void 0 : floorConfig.show_label) === true && hasMarkerLabelContent({ text: floorConfig.label_text, showValue: floorConfig.label_show_value, showUnit: floorConfig.label_show_unit }) || genericMarkers.some((marker) => {
+ var _a2;
+ return marker.lane === "below" && ((_a2 = marker.label) == null ? void 0 : _a2.show) === true && hasMarkerLabelContent(marker.label);
+ }),
+ above: (peakConfig == null ? void 0 : peakConfig.show) === true && (peakConfig == null ? void 0 : peakConfig.show_label) === true && hasMarkerLabelContent({ text: peakConfig.label_text, showValue: peakConfig.label_show_value, showUnit: peakConfig.label_show_unit }) || genericMarkers.some((marker) => {
+ var _a2;
+ return marker.lane === "above" && ((_a2 = marker.label) == null ? void 0 : _a2.show) === true && hasMarkerLabelContent(marker.label);
+ })
+ };
+ }
+ function buildMarkerModels({
+ entityConfig,
+ targetValue = null,
+ targetPosition = null,
+ targetPresentation = null,
+ targetLabelPresentation = null,
+ targetVisible = Number.isFinite(targetPosition),
+ peakValue = null,
+ peakPosition = null,
+ peakPresentation = null,
+ peakLabelPresentation = null,
+ peakVisible = Number.isFinite(peakPosition),
+ floorValue = null,
+ floorPosition = null,
+ floorPresentation = null,
+ floorLabelPresentation = null,
+ floorVisible = Number.isFinite(floorPosition),
+ genericMarkers = []
+ }) {
+ var _a, _b, _c, _d, _e, _f, _g;
+ const targetConfig = entityConfig == null ? void 0 : entityConfig.target_marker;
+ const peakConfig = entityConfig == null ? void 0 : entityConfig.peak_marker;
+ const floorConfig = entityConfig == null ? void 0 : entityConfig.floor_marker;
+ const targetEnabled = (targetConfig == null ? void 0 : targetConfig.enabled) !== false;
+ return [
+ createMarkerModel({
+ id: "target",
+ type: "target",
+ value: targetValue,
+ position: targetPosition,
+ lane: "below",
+ visible: targetEnabled && targetVisible,
+ color: (_a = targetConfig == null ? void 0 : targetConfig.color) != null ? _a : null,
+ label: targetLabelPresentation != null ? targetLabelPresentation : targetPresentation,
+ labelVisible: targetEnabled && (targetConfig == null ? void 0 : targetConfig.show_label) === true && hasMarkerLabelContent({ text: targetConfig.label_text, showValue: targetConfig.label_show_value, showUnit: targetConfig.label_show_unit }),
+ shape: (_b = targetConfig == null ? void 0 : targetConfig.shape) != null ? _b : "diamond",
+ direction: (_c = targetConfig == null ? void 0 : targetConfig.direction) != null ? _c : "inward"
+ }),
+ createMarkerModel({
+ id: "floor",
+ type: "floor",
+ value: floorValue,
+ position: floorPosition,
+ lane: "below",
+ visible: (floorConfig == null ? void 0 : floorConfig.show) === true && floorVisible,
+ color: (_d = floorConfig == null ? void 0 : floorConfig.color) != null ? _d : null,
+ label: floorLabelPresentation != null ? floorLabelPresentation : floorPresentation,
+ labelVisible: (floorConfig == null ? void 0 : floorConfig.show) === true && (floorConfig == null ? void 0 : floorConfig.show_label) === true && hasMarkerLabelContent({ text: floorConfig.label_text, showValue: floorConfig.label_show_value, showUnit: floorConfig.label_show_unit }),
+ shape: "triangle",
+ direction: (_e = floorConfig == null ? void 0 : floorConfig.direction) != null ? _e : "inward"
+ }),
+ createMarkerModel({
+ id: "peak",
+ type: "peak",
+ value: peakValue,
+ position: peakPosition,
+ lane: "above",
+ visible: (peakConfig == null ? void 0 : peakConfig.show) === true && peakVisible,
+ color: (_f = peakConfig == null ? void 0 : peakConfig.color) != null ? _f : null,
+ label: peakLabelPresentation != null ? peakLabelPresentation : peakPresentation,
+ labelVisible: (peakConfig == null ? void 0 : peakConfig.show) === true && (peakConfig == null ? void 0 : peakConfig.show_label) === true && hasMarkerLabelContent({ text: peakConfig.label_text, showValue: peakConfig.label_show_value, showUnit: peakConfig.label_show_unit }),
+ shape: "triangle",
+ direction: (_g = peakConfig == null ? void 0 : peakConfig.direction) != null ? _g : "inward"
+ }),
+ ...genericMarkers.map((marker) => createMarkerModel({
+ id: marker.id,
+ type: "generic",
+ value: marker.value,
+ position: marker.position,
+ lane: marker.lane,
+ visible: marker.visible,
+ color: marker.color,
+ label: marker.label,
+ labelVisible: marker.labelVisible && hasMarkerLabelContent(marker.label),
+ shape: marker.shape,
+ direction: marker.direction,
+ showMarker: marker.showMarker
+ }))
+ ];
+ }
+ var MARKER_SHAPES;
+ var init_marker_view_model = __esm({
+ "src/view-model/marker-view-model.js"() {
+ MARKER_SHAPES = /* @__PURE__ */ new Set(["circle", "diamond", "triangle", "chevron", "arrow", "pin"]);
+ }
+ });
+
// src/view-model/row-view-model.js
function getDefaultEntityIcon(stateObj, entityId = "") {
var _a, _b, _c;
@@ -812,21 +1655,6 @@
const range = safeMax - safeMin || 1;
return Math.min(100, Math.max(0, (value - safeMin) / range * 100));
}
- function formatNumericDisplay(rawVal, decimal = null) {
- if (!Number.isFinite(rawVal)) return String(rawVal);
- if (decimal !== null) {
- return parseFloat(rawVal.toFixed(decimal)).toLocaleString();
- }
- return rawVal.toLocaleString();
- }
- function isTightUnit(unit) {
- return ["h", "m", "s"].includes(String(unit || "").trim());
- }
- function formatDisplayWithUnit(display, unit) {
- if (!unit) return String(display);
- const cleanUnit = String(unit);
- return `${display}${isTightUnit(cleanUnit) ? "" : " "}${cleanUnit}`;
- }
function parseColorToRgb(color) {
const value = String(color || "").trim();
if (!value) return null;
@@ -888,8 +1716,8 @@
edge: percent <= 0 ? "left" : percent >= 100 ? "right" : "middle"
};
}
- function getPeakState(entityId, numericValue, minValue, maxValue, peaks, peakEnabled) {
- if (!peakEnabled || !Number.isFinite(numericValue)) {
+ function getExtremumState(numericValue, minValue, maxValue, tracker, direction, enabled) {
+ if (!enabled) {
return {
value: null,
percent: null,
@@ -897,92 +1725,191 @@
visible: false
};
}
- const existingPeak = getFiniteNumber(peaks == null ? void 0 : peaks[entityId]);
- const peakValue = Number.isFinite(existingPeak) ? Math.max(existingPeak, numericValue) : numericValue;
+ const existingValue = getFiniteNumber(tracker == null ? void 0 : tracker.value);
+ if (!Number.isFinite(existingValue) && !Number.isFinite(numericValue)) {
+ return {
+ value: null,
+ percent: null,
+ visible: false
+ };
+ }
+ const value = Number.isFinite(existingValue) ? Number.isFinite(numericValue) ? direction === "min" ? Math.min(existingValue, numericValue) : Math.max(existingValue, numericValue) : existingValue : numericValue;
return {
- value: peakValue,
- percent: toScalePct(peakValue, minValue, maxValue),
+ value,
+ percent: toScalePct(value, minValue, maxValue),
visible: true
};
}
function buildRowViewModel(options) {
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S;
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha;
const {
hass,
cardConfig,
entityConfig,
entityState,
- peaks
+ peaks,
+ extrema,
+ previousScale
} = options;
void cardConfig;
const entityId = (_a = entityConfig == null ? void 0 : entityConfig.entity) != null ? _a : null;
const rawState = (_b = entityState == null ? void 0 : entityState.state) != null ? _b : "";
const numericValue = getFiniteNumber(rawState);
const rawUnit = (_d = (_c = entityState == null ? void 0 : entityState.attributes) == null ? void 0 : _c.unit_of_measurement) != null ? _d : "";
- const displayUnit = numericValue !== null ? (_g = (_f = (_e = entityConfig == null ? void 0 : entityConfig.formatting) == null ? void 0 : _e.unit) != null ? _f : rawUnit) != null ? _g : "" : "";
- const min = getNormalizedResolvableNumericValue(hass, (_h = entityConfig == null ? void 0 : entityConfig.scale) == null ? void 0 : _h.min);
- const max = getNormalizedResolvableNumericValue(hass, (_i = entityConfig == null ? void 0 : entityConfig.scale) == null ? void 0 : _i.max);
- const safeMin = Number.isFinite(min) ? min : 0;
- const safeMax = Number.isFinite(max) ? max : 100;
+ const configuredUnit = (_e = entityConfig == null ? void 0 : entityConfig.formatting) == null ? void 0 : _e.unit;
+ const targetUnit = (_f = configuredUnit != null ? configuredUnit : rawUnit) != null ? _f : "";
+ const displayUnit = numericValue !== null ? targetUnit : "";
+ const { min: safeMin, max: safeMax } = getResolvedScale(hass, entityConfig == null ? void 0 : entityConfig.scale, previousScale);
const percent = numericValue !== null ? toScalePct(numericValue, safeMin, safeMax) : 0;
- const displayValue = numericValue === null ? rawState : formatNumericDisplay(numericValue, (_k = (_j = entityConfig == null ? void 0 : entityConfig.formatting) == null ? void 0 : _j.decimal) != null ? _k : null);
- const targetValue = ((_l = entityConfig == null ? void 0 : entityConfig.target_marker) == null ? void 0 : _l.enabled) === false ? null : getNormalizedResolvableNumericValue(hass, (_m = entityConfig == null ? void 0 : entityConfig.target_marker) == null ? void 0 : _m.source, safeMin, safeMax);
+ const decimal = (_h = (_g = entityConfig == null ? void 0 : entityConfig.formatting) == null ? void 0 : _g.decimal) != null ? _h : null;
+ const primaryPresentation = numericValue === null ? createTextPresentation(rawState) : createNumericPresentation(numericValue, displayUnit, decimal);
+ const targetValue = ((_i = entityConfig == null ? void 0 : entityConfig.target_marker) == null ? void 0 : _i.enabled) === false ? null : getNormalizedResolvableNumericValue(hass, (_j = entityConfig == null ? void 0 : entityConfig.target_marker) == null ? void 0 : _j.source, safeMin, safeMax);
const targetPercent = targetValue !== null ? toScalePct(targetValue, safeMin, safeMax) : null;
const targetVisible = targetValue !== null;
- const targetDisplay = targetValue !== null ? formatDisplayWithUnit(
- formatNumericDisplay(targetValue, (_o = (_n = entityConfig == null ? void 0 : entityConfig.formatting) == null ? void 0 : _n.decimal) != null ? _o : null),
- (_r = (_q = (_p = entityConfig == null ? void 0 : entityConfig.formatting) == null ? void 0 : _p.unit) != null ? _q : rawUnit) != null ? _r : ""
- ) : null;
- const baselineValue = ((_s = entityConfig == null ? void 0 : entityConfig.baseline) == null ? void 0 : _s.enabled) === false ? null : getNormalizedResolvableNumericValue(hass, (_t = entityConfig == null ? void 0 : entityConfig.baseline) == null ? void 0 : _t.at, safeMin, safeMax);
+ const targetDecimal = (_l = (_k = entityConfig == null ? void 0 : entityConfig.target_marker) == null ? void 0 : _k.label_decimal) != null ? _l : decimal;
+ const targetPresentation = targetValue !== null ? createNumericPresentation(targetValue, targetUnit, targetDecimal) : null;
+ const targetLabelPresentation = targetValue !== null ? createMarkerLabelPresentation(targetValue, targetUnit, (_n = (_m = entityConfig == null ? void 0 : entityConfig.target_marker) == null ? void 0 : _m.label_precision) != null ? _n : targetDecimal, {
+ text: (_o = entityConfig == null ? void 0 : entityConfig.target_marker) == null ? void 0 : _o.label_text,
+ showValue: (_p = entityConfig == null ? void 0 : entityConfig.target_marker) == null ? void 0 : _p.label_show_value,
+ showUnit: (_q = entityConfig == null ? void 0 : entityConfig.target_marker) == null ? void 0 : _q.label_show_unit
+ }) : null;
+ const baselineValue = ((_r = entityConfig == null ? void 0 : entityConfig.baseline) == null ? void 0 : _r.enabled) === false ? null : getNormalizedResolvableNumericValue(hass, (_s = entityConfig == null ? void 0 : entityConfig.baseline) == null ? void 0 : _s.at, safeMin, safeMax);
const baselinePercent = Number.isFinite(baselineValue) ? toScalePct(baselineValue, safeMin, safeMax) : null;
const baselineVisible = Number.isFinite(baselineValue);
- const peakState = getPeakState(
- entityId,
+ const legacyPeak = Number.isFinite(getFiniteNumber(peaks == null ? void 0 : peaks[entityId])) ? { value: getFiniteNumber(peaks == null ? void 0 : peaks[entityId]) } : null;
+ const peakState = getExtremumState(
numericValue,
safeMin,
safeMax,
- peaks,
+ (_t = extrema == null ? void 0 : extrema.peak) != null ? _t : legacyPeak,
+ "max",
((_u = entityConfig == null ? void 0 : entityConfig.peak_marker) == null ? void 0 : _u.show) === true
);
- const peakDisplay = peakState.visible ? formatNumericDisplay(peakState.value, (_w = (_v = entityConfig == null ? void 0 : entityConfig.formatting) == null ? void 0 : _v.decimal) != null ? _w : null) : null;
+ const floorState = getExtremumState(
+ numericValue,
+ safeMin,
+ safeMax,
+ extrema == null ? void 0 : extrema.floor,
+ "min",
+ ((_v = entityConfig == null ? void 0 : entityConfig.floor_marker) == null ? void 0 : _v.show) === true
+ );
+ const peakDecimal = (_x = (_w = entityConfig == null ? void 0 : entityConfig.peak_marker) == null ? void 0 : _w.label_decimal) != null ? _x : decimal;
+ const floorDecimal = (_z = (_y = entityConfig == null ? void 0 : entityConfig.floor_marker) == null ? void 0 : _y.label_decimal) != null ? _z : decimal;
+ const peakLabelPrecision = (_B = (_A = entityConfig == null ? void 0 : entityConfig.peak_marker) == null ? void 0 : _A.label_precision) != null ? _B : peakDecimal;
+ const floorLabelPrecision = (_D = (_C = entityConfig == null ? void 0 : entityConfig.floor_marker) == null ? void 0 : _C.label_precision) != null ? _D : floorDecimal;
+ const peakPresentation = peakState.visible ? createNumericPresentation(peakState.value, targetUnit, peakDecimal) : null;
+ const floorPresentation = floorState.visible ? createNumericPresentation(floorState.value, targetUnit, floorDecimal) : null;
+ const peakLabelPresentation = peakState.visible ? createMarkerLabelPresentation(peakState.value, targetUnit, peakLabelPrecision, {
+ text: (_E = entityConfig == null ? void 0 : entityConfig.peak_marker) == null ? void 0 : _E.label_text,
+ showValue: (_F = entityConfig == null ? void 0 : entityConfig.peak_marker) == null ? void 0 : _F.label_show_value,
+ showUnit: (_G = entityConfig == null ? void 0 : entityConfig.peak_marker) == null ? void 0 : _G.label_show_unit
+ }) : null;
+ const floorLabelPresentation = floorState.visible ? createMarkerLabelPresentation(floorState.value, targetUnit, floorLabelPrecision, {
+ text: (_H = entityConfig == null ? void 0 : entityConfig.floor_marker) == null ? void 0 : _H.label_text,
+ showValue: (_I = entityConfig == null ? void 0 : entityConfig.floor_marker) == null ? void 0 : _I.label_show_value,
+ showUnit: (_J = entityConfig == null ? void 0 : entityConfig.floor_marker) == null ? void 0 : _J.label_show_unit
+ }) : null;
+ const genericMarkers = ((_K = entityConfig == null ? void 0 : entityConfig.generic_markers) != null ? _K : []).filter((marker) => marker.accepted).map((marker) => {
+ var _a2, _b2, _c2, _d2, _e2;
+ const value = getNormalizedResolvableNumericValue(hass, marker.source, safeMin, safeMax);
+ const visible = Number.isFinite(value);
+ const markerPrecision = (_a2 = marker.label.precision) != null ? _a2 : decimal;
+ const labelStateObj = marker.label.entity ? (_b2 = hass == null ? void 0 : hass.states) == null ? void 0 : _b2[marker.label.entity] : null;
+ const rawLabelState = labelStateObj == null ? void 0 : labelStateObj.state;
+ const cleanLabelState = typeof rawLabelState === "string" ? rawLabelState.trim() : "";
+ const usableLabelState = labelStateObj && cleanLabelState && !["unknown", "unavailable"].includes(cleanLabelState.toLowerCase());
+ const labelValue = marker.label.entity ? usableLabelState ? (_c2 = getFiniteNumber(cleanLabelState)) != null ? _c2 : cleanLabelState : null : value;
+ const labelUnit = marker.label.entity ? usableLabelState ? (_e2 = (_d2 = labelStateObj == null ? void 0 : labelStateObj.attributes) == null ? void 0 : _d2.unit_of_measurement) != null ? _e2 : "" : "" : targetUnit;
+ const label = marker.label.show ? createMarkerLabelPresentation(labelValue, labelUnit, markerPrecision, {
+ text: marker.label.text,
+ showValue: marker.label.showValue,
+ showUnit: marker.label.showUnit
+ }) : null;
+ return {
+ id: marker.id,
+ value,
+ position: visible ? toScalePct(value, safeMin, safeMax) : null,
+ lane: marker.lane,
+ visible,
+ color: marker.color,
+ shape: marker.shape,
+ direction: marker.direction,
+ showMarker: marker.showMarker,
+ label,
+ labelVisible: marker.label.show
+ };
+ });
+ const markers = buildMarkerModels({
+ entityConfig,
+ targetValue,
+ targetPosition: targetPercent,
+ targetPresentation,
+ targetLabelPresentation,
+ targetVisible,
+ peakValue: peakState.value,
+ peakPosition: peakState.percent,
+ peakPresentation,
+ peakLabelPresentation,
+ peakVisible: peakState.visible,
+ floorValue: floorState.value,
+ floorPosition: floorState.percent,
+ floorPresentation,
+ floorLabelPresentation,
+ floorVisible: floorState.visible,
+ genericMarkers
+ });
return {
entityId,
- name: (_z = (_y = entityConfig == null ? void 0 : entityConfig.name) != null ? _y : (_x = entityState == null ? void 0 : entityState.attributes) == null ? void 0 : _x.friendly_name) != null ? _z : entityId,
- icon: (entityConfig == null ? void 0 : entityConfig.icon) === false ? false : (_C = (_B = entityConfig == null ? void 0 : entityConfig.icon) != null ? _B : (_A = entityState == null ? void 0 : entityState.attributes) == null ? void 0 : _A.icon) != null ? _C : getDefaultEntityIcon(entityState, entityId),
+ name: (_N = (_M = entityConfig == null ? void 0 : entityConfig.name) != null ? _M : (_L = entityState == null ? void 0 : entityState.attributes) == null ? void 0 : _L.friendly_name) != null ? _N : entityId,
+ icon: (entityConfig == null ? void 0 : entityConfig.icon) === false ? false : (_Q = (_P = entityConfig == null ? void 0 : entityConfig.icon) != null ? _P : (_O = entityState == null ? void 0 : entityState.attributes) == null ? void 0 : _O.icon) != null ? _Q : getDefaultEntityIcon(entityState, entityId),
state: rawState,
numericValue,
rawUnit,
- displayUnit,
min: safeMin,
max: safeMax,
percent,
- displayValue,
- unit: displayUnit,
- barColor: (_E = (_D = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _D.color) != null ? _E : null,
- fillStyle: (_G = (_F = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _F.fill_style) != null ? _G : null,
+ displayValue: primaryPresentation.number,
+ displayUnit: primaryPresentation.unit,
+ primaryPresentation,
+ unit: primaryPresentation.unit,
+ barColor: (_S = (_R = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _R.color) != null ? _S : null,
+ fillStyle: (_U = (_T = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _T.fill_style) != null ? _U : null,
target: targetValue,
targetPercent,
- targetDisplay,
+ targetDisplay: (_V = targetPresentation == null ? void 0 : targetPresentation.text) != null ? _V : null,
+ targetPresentation,
+ targetLabelPresentation,
targetVisible,
baseline: baselineValue,
baselinePercent,
baselineVisible,
peak: peakState.value,
peakPercent: peakState.percent,
- peakDisplay,
+ peakDisplay: (_W = peakPresentation == null ? void 0 : peakPresentation.number) != null ? _W : null,
+ peakPresentation,
+ peakLabelPresentation,
peakVisible: peakState.visible,
- segments: (_I = (_H = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _H.segments) != null ? _I : null,
- gradientStops: (_K = (_J = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _J.gradient_stops) != null ? _K : null,
+ floor: floorState.value,
+ floorPercent: floorState.percent,
+ floorDisplay: (_X = floorPresentation == null ? void 0 : floorPresentation.number) != null ? _X : null,
+ floorPresentation,
+ floorLabelPresentation,
+ floorVisible: floorState.visible,
+ markers,
+ markerLaneOccupancy: getMarkerLaneOccupancy(entityConfig),
+ markerLabelLaneOccupancy: getMarkerLabelLaneOccupancy(entityConfig),
+ segments: (_Z = (_Y = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _Y.segments) != null ? _Z : null,
+ gradientStops: (_$ = (__ = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : __.gradient_stops) != null ? _$ : null,
needle: getNeedleState(entityConfig, numericValue, safeMin, safeMax, baselinePercent),
classes: {
- labelPosition: (_N = (_M = (_L = entityConfig == null ? void 0 : entityConfig.layout) == null ? void 0 : _L.label) == null ? void 0 : _M.position) != null ? _N : "left",
- animated: ((_O = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _O.animated) !== false
+ labelPosition: (_ca = (_ba = (_aa = entityConfig == null ? void 0 : entityConfig.layout) == null ? void 0 : _aa.label) == null ? void 0 : _ba.position) != null ? _ca : "left",
+ animated: ((_da = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _da.animated) !== false
},
attributes: {
entity: entityId,
- baseHeight: (_Q = (_P = entityConfig == null ? void 0 : entityConfig.layout) == null ? void 0 : _P.height) != null ? _Q : 38,
- heightExplicit: ((_R = entityConfig == null ? void 0 : entityConfig.layout) == null ? void 0 : _R.height_explicit) === true,
- barAnimated: ((_S = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _S.animated) !== false
+ baseHeight: (_fa = (_ea = entityConfig == null ? void 0 : entityConfig.layout) == null ? void 0 : _ea.height) != null ? _fa : 38,
+ heightExplicit: ((_ga = entityConfig == null ? void 0 : entityConfig.layout) == null ? void 0 : _ga.height_explicit) === true,
+ barAnimated: ((_ha = entityConfig == null ? void 0 : entityConfig.bar) == null ? void 0 : _ha.animated) !== false
}
};
}
@@ -990,6 +1917,8 @@
"src/view-model/row-view-model.js"() {
init_resolve();
init_normalize();
+ init_format();
+ init_marker_view_model();
}
});
@@ -1004,6 +1933,20 @@
});
// src/card/SensorBarCard.js
+ function getRevealTransitionDuration(previousGeometry, nextGeometry) {
+ if (!previousGeometry || !nextGeometry || !Number.isFinite(previousGeometry.valuePercent) || !Number.isFinite(nextGeometry.valuePercent)) {
+ return 600;
+ }
+ const previousBaseline = Number.isFinite(previousGeometry.baselinePercent) ? previousGeometry.baselinePercent : 0;
+ const nextBaseline = Number.isFinite(nextGeometry.baselinePercent) ? nextGeometry.baselinePercent : 0;
+ const delta = Math.max(
+ Math.abs(nextGeometry.valuePercent - previousGeometry.valuePercent),
+ Math.abs(nextBaseline - previousBaseline)
+ );
+ const ordinaryDuration = Math.round(Math.min(600, Math.max(150, 600 - 15 * Math.max(0, delta - 5))));
+ const crossesBaseline = Number.isFinite(previousGeometry.baselinePercent) && Number.isFinite(nextGeometry.baselinePercent) && (previousGeometry.valuePercent - previousGeometry.baselinePercent) * (nextGeometry.valuePercent - nextGeometry.baselinePercent) < 0;
+ return crossesBaseline ? Math.min(ordinaryDuration, 300) : ordinaryDuration;
+ }
var SensorBarCard;
var init_SensorBarCard = __esm({
"src/card/SensorBarCard.js"() {
@@ -1011,7 +1954,10 @@
init_resolve();
init_validate();
init_row_view_model();
+ init_marker_view_model();
init_dom();
+ init_format();
+ init_extrema();
SensorBarCard = class extends HTMLElement {
static getConfigElement() {
return document.createElement("sensor-bar-card-plus-editor");
@@ -1024,7 +1970,11 @@
this._diagnostics = { warnings: [], errors: [] };
this._lastDiagnosticsSignature = null;
this._hass = null;
- this._peaks = {};
+ this._extrema = /* @__PURE__ */ new WeakMap();
+ this._rowScales = /* @__PURE__ */ new WeakMap();
+ this._rowGeneration = 0;
+ this._rowPresence = [];
+ this._leftModeResponsiveHistory = /* @__PURE__ */ new WeakMap();
this._rendered = false;
this._resizeObserver = null;
this._densityPassScheduled = false;
@@ -1032,14 +1982,20 @@
this._densityPassFrame = null;
this._densityPassRetries = 0;
this._boundWindowResize = () => this._schedulePostLayoutDensityPass();
+ this._markerHover = null;
+ this._boundMarkerPointerOver = (event) => this._handleMarkerPointerOver(event);
+ this._boundMarkerPointerOut = (event) => this._handleMarkerPointerOut(event);
this._ensureBaseDom();
}
connectedCallback() {
window.addEventListener("resize", this._boundWindowResize, { passive: true });
+ this._setupResizeObserver();
this._schedulePostLayoutDensityPass();
}
disconnectedCallback() {
+ this._rowGeneration += 1;
window.removeEventListener("resize", this._boundWindowResize);
+ this._clearMarkerHover();
this._disconnectResizeObserver();
if (this._densityPassFrame) {
cancelAnimationFrame(this._densityPassFrame);
@@ -1049,23 +2005,90 @@
this._densityPassDirty = false;
}
setConfig(config) {
+ var _a;
if (!config.entities && !config.entity) {
throw new Error("You must define entities or entity");
}
this._rendered = false;
+ this._rowGeneration += 1;
+ const previousConfig = this._config;
this._config = this.normalizeCardConfig(config);
- const activeEntityIds = new Set(
- (this._config.entities || []).map((entityConfig) => entityConfig.entity)
- );
- for (const entityId of Object.keys(this._peaks)) {
- if (!activeEntityIds.has(entityId)) {
- delete this._peaks[entityId];
- }
- }
+ this._rowScales = /* @__PURE__ */ new WeakMap();
+ this._reconcileRowHistory((_a = previousConfig == null ? void 0 : previousConfig.entities) != null ? _a : []);
this._diagnostics = validateNormalizedConfig(this._config);
this._logDiagnostics();
this._render();
}
+ _reconcileRowHistory(previousRows) {
+ var _a, _b, _c, _d;
+ const canonical = (value, key = "") => {
+ if (Array.isArray(value)) return value.map(canonical);
+ if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().filter((name) => !["severity", "segment_space", "label_precision_key"].includes(name)).map((name) => [name, canonical(value[name], name)]));
+ if (key === "fixed") return getNumericValue(null, value);
+ if (/color/i.test(key) && typeof value === "string" && /^#(?:[\da-f]{3}|[\da-f]{6})$/i.test(value.trim())) {
+ const hex = value.trim().slice(1).toLowerCase();
+ return `#${hex.length === 3 ? hex.split("").map((char) => char + char).join("") : hex}`;
+ }
+ return value;
+ };
+ const signature = (row) => {
+ var _a2;
+ return JSON.stringify(canonical({
+ entity: row.entity,
+ name: row.name,
+ icon: (_a2 = row.icon) != null ? _a2 : null,
+ layout: row.layout,
+ scale: Object.fromEntries(["min", "max"].map((key) => [key, {
+ ...row.scale[key],
+ fixed_explicit: row.scale[key].fixed_explicit !== false
+ }])),
+ bar: row.bar,
+ baseline: row.baseline,
+ formatting: row.formatting,
+ target_marker: row.target_marker,
+ peak_marker: row.peak_marker,
+ floor_marker: row.floor_marker,
+ generic_markers: row.generic_markers
+ }));
+ };
+ const unmatchedOld = new Set(previousRows);
+ const matches = /* @__PURE__ */ new Map();
+ const oldSignatures = new Map(previousRows.map((row) => [row, signature(row)]));
+ for (const row of this._config.entities) {
+ const key = signature(row);
+ const previous = [...unmatchedOld].find((old) => oldSignatures.get(old) === key);
+ if (!previous) continue;
+ matches.set(row, previous);
+ unmatchedOld.delete(previous);
+ }
+ const unmatchedNew = this._config.entities.filter((row) => !matches.has(row));
+ for (const row of unmatchedNew) {
+ const old = [...unmatchedOld].filter((previous) => previous.entity === row.entity);
+ const next = unmatchedNew.filter((current) => current.entity === row.entity);
+ if (old.length !== 1 || next.length !== 1) continue;
+ matches.set(row, old[0]);
+ unmatchedOld.delete(old[0]);
+ }
+ const extrema = /* @__PURE__ */ new WeakMap();
+ const responsive = /* @__PURE__ */ new WeakMap();
+ for (const [row, previous] of matches) {
+ const stored = this._extrema.get(previous);
+ const retained = {};
+ for (const key of ["peak", "floor"]) {
+ const before = previous[`${key}_marker`];
+ const after = row[`${key}_marker`];
+ if ((stored == null ? void 0 : stored[key]) && (before == null ? void 0 : before.show) === true && (after == null ? void 0 : after.show) === true && JSON.stringify(before.reset) === JSON.stringify(after.reset)) {
+ retained[key] = { ...stored[key] };
+ }
+ }
+ if (retained.peak || retained.floor) extrema.set(row, retained);
+ if (((_b = (_a = previous.layout) == null ? void 0 : _a.label) == null ? void 0 : _b.position) === "left" && ((_d = (_c = row.layout) == null ? void 0 : _c.label) == null ? void 0 : _d.position) === "left" && this._leftModeResponsiveHistory.has(previous)) {
+ responsive.set(row, this._leftModeResponsiveHistory.get(previous));
+ }
+ }
+ this._extrema = extrema;
+ this._leftModeResponsiveHistory = responsive;
+ }
_logDiagnostics() {
var _a;
const diagnostics = (_a = this._diagnostics) != null ? _a : { warnings: [], errors: [] };
@@ -1186,12 +2209,13 @@
set hass(hass) {
const oldHass = this._hass;
this._hass = hass;
+ if (!this._config.entities) return;
if (!oldHass) {
this._update();
return;
}
if (this._shouldUpdate(oldHass, hass)) {
- this._update();
+ this._update(oldHass);
}
}
// Merge global config with per-entity overrides
@@ -1205,6 +2229,38 @@
name: (_g = ecfg.name) != null ? _g : null
};
}
+ _getStateTimestamp(stateObj) {
+ var _a;
+ const rawTimestamp = (_a = stateObj == null ? void 0 : stateObj.last_updated) != null ? _a : stateObj == null ? void 0 : stateObj.last_changed;
+ const timestamp = rawTimestamp instanceof Date ? rawTimestamp.getTime() : Date.parse(String(rawTimestamp != null ? rawTimestamp : ""));
+ return Number.isFinite(timestamp) ? timestamp : Date.now();
+ }
+ _updateExtrema(entityCfg, normalizedEntity, stateObj) {
+ var _a, _b, _c;
+ const sample = getFiniteNumber(stateObj == null ? void 0 : stateObj.state);
+ if (!Number.isFinite(sample)) return;
+ const current = (_a = this._extrema.get(entityCfg)) != null ? _a : {};
+ const timestamp = this._getStateTimestamp(stateObj);
+ for (const key of ["peak", "floor"]) {
+ const marker = normalizedEntity == null ? void 0 : normalizedEntity[`${key}_marker`];
+ if ((marker == null ? void 0 : marker.show) !== true) {
+ delete current[key];
+ continue;
+ }
+ current[key] = updateExtremum(
+ (_b = current[key]) != null ? _b : null,
+ sample,
+ (_c = marker.reset) != null ? _c : { kind: "never" },
+ key === "floor" ? "min" : "max",
+ timestamp
+ );
+ }
+ if (current.peak || current.floor) {
+ this._extrema.set(entityCfg, current);
+ } else {
+ this._extrema.delete(entityCfg);
+ }
+ }
_getDefaultEntityIcon(stateObj, entityId = "") {
var _a, _b, _c;
const deviceClass = String((_b = (_a = stateObj == null ? void 0 : stateObj.attributes) == null ? void 0 : _a.device_class) != null ? _b : "").trim();
@@ -1240,7 +2296,7 @@
return (_c = domainIcons[domain]) != null ? _c : null;
}
_shouldUpdate(oldHass, newHass) {
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
if (!this._config || !this._config.entities) return true;
for (const entityCfg of this._config.entities) {
const ecfg = this._resolve(entityCfg);
@@ -1249,11 +2305,15 @@
(_b = (_a = ecfg.scale) == null ? void 0 : _a.min) == null ? void 0 : _b.entity,
(_d = (_c = ecfg.scale) == null ? void 0 : _c.max) == null ? void 0 : _d.entity,
(_f = (_e = ecfg.baseline) == null ? void 0 : _e.at) == null ? void 0 : _f.entity,
- (_h = (_g = ecfg.target_marker) == null ? void 0 : _g.source) == null ? void 0 : _h.entity
+ (_h = (_g = ecfg.target_marker) == null ? void 0 : _g.source) == null ? void 0 : _h.entity,
+ ...((_i = ecfg.generic_markers) != null ? _i : []).filter((marker) => marker.accepted).flatMap((marker) => {
+ var _a2, _b2;
+ return [(_a2 = marker.source) == null ? void 0 : _a2.entity, (_b2 = marker.label) == null ? void 0 : _b2.entity];
+ })
].filter(Boolean);
for (const ent of entitiesToWatch) {
- const oldState = (_i = oldHass.states[ent]) != null ? _i : null;
- const newState = (_j = newHass.states[ent]) != null ? _j : null;
+ const oldState = (_j = oldHass.states[ent]) != null ? _j : null;
+ const newState = (_k = newHass.states[ent]) != null ? _k : null;
if (oldState !== newState) {
return true;
}
@@ -1318,12 +2378,80 @@
if (!this.shadowRoot) return;
this.shadowRoot.querySelectorAll(".row[data-entity]").forEach((row) => {
this._positionTargetLabel(row);
+ this._positionMarkerValueLabel(row, ".peak-value-label", ".peak-marker");
+ this._positionMarkerValueLabel(row, ".floor-value-label", ".floor-marker");
+ this._positionGenericMarkerLabels(row);
+ });
+ }
+ _positionGenericMarkerLabels(row) {
+ var _a, _b;
+ ((_b = (_a = row.querySelectorAll) == null ? void 0 : _a.call(row, ".generic-value-label[data-marker-id]")) != null ? _b : []).forEach((label) => {
+ const id = label.dataset.markerId;
+ this._positionMarkerValueLabel(
+ row,
+ `.generic-value-label[data-marker-id="${id}"]`,
+ `.generic-marker[data-marker-id="${id}"]`
+ );
});
}
_positionTargetLabel(row) {
+ this._positionMarkerValueLabel(row, ".target-value-label", ".target-marker");
+ }
+ _getMarkerLabel(markerEl) {
+ var _a;
+ const row = markerEl == null ? void 0 : markerEl.closest(".row");
+ if (!row) return null;
+ if (markerEl.matches(".generic-marker[data-marker-id]")) {
+ const markerId = markerEl.dataset.markerId;
+ return (_a = [...row.querySelectorAll(".generic-value-label[data-marker-id]")].find((label) => label.dataset.markerId === markerId)) != null ? _a : null;
+ }
+ const labelSelector = markerEl.matches(".target-marker") ? ".target-value-label" : markerEl.matches(".peak-marker") ? ".peak-value-label" : markerEl.matches(".floor-marker") ? ".floor-value-label" : null;
+ return labelSelector ? row.querySelector(labelSelector) : null;
+ }
+ _getGenericMarkerForLabel(labelEl) {
+ var _a, _b;
+ const row = labelEl == null ? void 0 : labelEl.closest(".row");
+ const markerId = (_a = labelEl == null ? void 0 : labelEl.dataset) == null ? void 0 : _a.markerId;
+ if (!row || !markerId) return null;
+ return (_b = [...row.querySelectorAll(".generic-marker[data-marker-id]")].find((marker) => marker.dataset.markerId === markerId)) != null ? _b : null;
+ }
+ _setMarkerHover(markerEl) {
+ var _a;
+ const label = this._getMarkerLabel(markerEl);
+ if (!label || markerEl.style.display === "none" || getComputedStyle(label).visibility !== "visible") {
+ this._clearMarkerHover(markerEl);
+ return;
+ }
+ if (((_a = this._markerHover) == null ? void 0 : _a.label) === label) return;
+ this._clearMarkerHover();
+ label.dataset.markerHovered = "true";
+ this._markerHover = { marker: markerEl, label };
+ }
+ _clearMarkerHover(markerEl = null) {
+ if (!this._markerHover || markerEl && this._markerHover.marker !== markerEl) return;
+ delete this._markerHover.label.dataset.markerHovered;
+ this._markerHover = null;
+ }
+ _handleMarkerPointerOver(event) {
+ var _a, _b, _c;
+ if (event.pointerType === "touch") return;
+ const target = (_b = (_a = event.target) == null ? void 0 : _a.closest) == null ? void 0 : _b.call(_a, '.generic-value-label[data-show-marker="false"], .generic-marker, .target-marker, .peak-marker, .floor-marker');
+ const markerEl = ((_c = target == null ? void 0 : target.matches) == null ? void 0 : _c.call(target, ".generic-value-label")) ? this._getGenericMarkerForLabel(target) : target;
+ if (markerEl) this._setMarkerHover(markerEl);
+ }
+ _handleMarkerPointerOut(event) {
+ var _a, _b;
+ if (event.pointerType === "touch") return;
+ const target = (_b = (_a = event.target) == null ? void 0 : _a.closest) == null ? void 0 : _b.call(_a, '.generic-value-label[data-show-marker="false"], .generic-marker, .target-marker, .peak-marker, .floor-marker');
+ if (!target || target.contains(event.relatedTarget)) return;
+ const markerEl = target.matches(".generic-value-label") ? this._getGenericMarkerForLabel(target) : target;
+ if (!markerEl) return;
+ this._clearMarkerHover(markerEl);
+ }
+ _positionMarkerValueLabel(row, labelSelector, markerSelector) {
const track = row.querySelector(".bar-track");
- const label = row.querySelector(".target-value-label");
- const marker = row.querySelector(".target-marker");
+ const label = row.querySelector(labelSelector);
+ const marker = row.querySelector(markerSelector);
if (!track || !label || !marker) return;
if (marker.style.display === "none" || !label.textContent.trim()) {
this._setStyleIfChanged(label, "visibility", "hidden");
@@ -1472,7 +2600,7 @@
var _a, _b;
const safeMin = Number.isFinite(minValue) ? minValue : 0;
const safeMax = Number.isFinite(maxValue) ? maxValue : 100;
- const rawSegments = Array.isArray((_a = ecfg.bar) == null ? void 0 : _a.segments) ? ecfg.bar.segments : [];
+ const rawSegments = (Array.isArray((_a = ecfg.bar) == null ? void 0 : _a.segments) ? ecfg.bar.segments : []).filter((segment) => !(segment == null ? void 0 : segment.invalidBoundary));
if (((_b = ecfg.bar) == null ? void 0 : _b.segment_space) === "scale" || this._segmentsNeedBoundaryResolution(rawSegments)) {
const resolvedSegments = rawSegments.map((segment) => {
var _a2;
@@ -1602,6 +2730,9 @@
const range = safeMax - safeMin || 1;
return Math.min(100, Math.max(0, (value - safeMin) / range * 100));
}
+ _getRevealTransitionDuration(previousGeometry, nextGeometry) {
+ return getRevealTransitionDuration(previousGeometry, nextGeometry);
+ }
_resolveBaselinePct(ecfg, safeMin, safeMax) {
var _a, _b;
if (((_a = ecfg.baseline) == null ? void 0 : _a.enabled) === false) return null;
@@ -1610,11 +2741,7 @@
return this._toScalePct(baselineValue, safeMin, safeMax);
}
_formatNumericDisplay(rawVal, decimal = null) {
- if (!Number.isFinite(rawVal)) return String(rawVal);
- if (decimal !== null) {
- return parseFloat(rawVal.toFixed(decimal)).toLocaleString();
- }
- return rawVal.toLocaleString();
+ return formatNumericDisplay(rawVal, decimal);
}
_getNormalizedPercent(valuePct, baselinePct = null) {
const clampedValue = Math.min(100, Math.max(0, valuePct));
@@ -1818,6 +2945,7 @@
--sbcp-value-width: 60px;
--sbcp-bar-min-width: 56px;
--sbcp-target-label-font-size: 12px;
+ --sbcp-marker-label-lane-size: 15px;
--sbcp-inline-label-padding-x: 8px;
--sbcp-inline-label-padding-y: 2px;
--sbcp-inline-label-font-size: 12px;
@@ -1882,6 +3010,13 @@
padding: 2px 4px;
}
.row:last-child { margin-bottom: 0; }
+ .row[data-marker-label-lane-below="true"]:not(:last-child) {
+ margin-bottom: calc(10px + max(0px, var(--sbcp-marker-label-lane-size) - 12px));
+ }
+ /* Facing configured lanes overlap by 9px at the approved offsets; 10px leaves a 1px gap. */
+ .row[data-marker-label-lane-below="true"]:has(+ .row[data-marker-label-lane-above="true"]) {
+ margin-bottom: calc(10px + max(0px, var(--sbcp-marker-label-lane-size) - 12px) + 10px);
+ }
.row-stack {
--sbcp-row-height: 38px;
display: flex;
@@ -1909,6 +3044,12 @@
.top-right-value[data-active="true"] {
display: flex;
}
+ .above-line,
+ .hero-line,
+ .top-right-value {
+ position: relative;
+ z-index: 10;
+ }
.row:hover .bar-track { filter: brightness(0.95); transition: filter 0.15s; }
.main-line {
display: flex;
@@ -1916,6 +3057,9 @@
gap: var(--sbcp-main-gap);
min-width: 0;
}
+ .row[data-marker-label-lane-above="true"] .main-line:not(.hero-mode) {
+ margin-top: var(--sbcp-target-label-font-size);
+ }
.main-line[data-row-density="tight"] {
gap: calc(var(--sbcp-main-gap) - 1px);
}
@@ -1944,7 +3088,8 @@
.main-line.left-mode[data-hide-left-icon="true"] .icon-wrap,
.main-line.above-mode[data-hide-above-icon="true"] .icon-wrap,
.main-line.inside-mode[data-hide-inside-icon="true"] .icon-wrap,
- .main-line.inside-mode[data-priority-hide-inside-icon="true"] .icon-wrap {
+ .main-line.inside-mode[data-priority-hide-inside-icon="true"] .icon-wrap,
+ .main-line.off-mode[data-hide-off-icon="true"] .icon-wrap {
display: none;
}
.main-line.left-mode[data-left-density="normal"] {
@@ -1978,14 +3123,13 @@
color: var(--primary-text-color, #333);
line-height: 1;
}
- .main-line.left-mode[data-left-density="compressed"] .icon-wrap {
- display: none;
- }
ha-icon {
--mdc-icon-size: 20px;
display: block;
}
.label-left {
+ position: relative;
+ z-index: 10;
flex: 1 1 auto;
height: var(--sbcp-row-height);
min-width: 0;
@@ -2023,7 +3167,7 @@
position: absolute;
inset: 0;
pointer-events: none;
- transition: clip-path 0.6s cubic-bezier(0.4,0,0.2,1);
+ transition: clip-path var(--sbcp-reveal-duration, 600ms) cubic-bezier(0.4,0,0.2,1);
z-index: 1;
}
.bar-paint-layer {
@@ -2038,10 +3182,31 @@
.bar-fill-reveal.no-anim {
transition: none;
}
+ .baseline-indicator {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ transform: translateX(-50%);
+ background-color: var(--primary-text-color, currentColor);
+ opacity: 0.6;
+ pointer-events: none;
+ transition: left 0.6s cubic-bezier(0.4,0,0.2,1);
+ z-index: 3;
+ }
.row[data-bar-animated="false"] .bar-fill-reveal,
.row[data-bar-animated="false"] .needle-marker,
.row[data-bar-animated="false"] .target-marker,
.row[data-bar-animated="false"] .peak-marker,
+ .row[data-bar-animated="false"] .floor-marker,
+ .row[data-bar-animated="false"] .generic-marker,
+ .row[data-bar-animated="false"] .baseline-indicator,
+ .row[data-bar-animated="false"] .target-value-label,
+ .row[data-bar-animated="false"] .peak-value-label,
+ .row[data-bar-animated="false"] .floor-value-label,
+ .row[data-bar-animated="false"] .generic-value-label {
+ transition: none;
+ }
.row[data-bar-animated="false"] .target-value-label {
transition: none;
}
@@ -2055,7 +3220,7 @@
gap: 6px;
padding: 0 6px;
pointer-events: none;
- z-index: 8;
+ z-index: 10;
}
.bar-inner-label[data-inside-density="compact"] {
gap: 5px;
@@ -2107,15 +3272,22 @@
display: none;
}
.bar-inner-label .inside-value {
- flex: 0 1 auto;
+ flex: 0 0 auto;
min-width: 0;
- max-width: 56%;
+ max-width: 100%;
display: inline-flex;
align-items: baseline;
}
.bar-inner-label .inside-value[data-hide-value="true"] {
display: none;
}
+ .bar-inner-label[data-value-fit="compact"] {
+ padding: 0 2px;
+ }
+ .bar-inner-label .inside-value[data-value-fit="compact"] {
+ padding-left: 2px;
+ padding-right: 2px;
+ }
.main-line.inside-mode[data-hide-inside-icon="true"] .bar-inner-label .inside-value,
.main-line.inside-mode[data-priority-hide-inside-icon="true"] .bar-inner-label .inside-value,
.bar-inner-label[data-hide-name="true"] .inside-value,
@@ -2171,19 +3343,77 @@
.target-value-label {
position: absolute;
top: 100%;
- margin-top: 3px;
+ margin-top: 2px;
font-size: var(--sbcp-target-label-font-size);
line-height: 1;
- color: var(--secondary-text-color, #888);
+ color: var(--marker-color, var(--secondary-text-color, #888));
+ text-shadow:
+ 0 0 0.6px var(--marker-contrast-color),
+ 0 0 1.2px color-mix(in srgb, var(--marker-contrast-color) 78%, transparent);
+ background-color: var(--card-background-color, #fff);
+ padding: 0 2px;
+ border: 0;
+ border-radius: 2px;
+ box-shadow: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
pointer-events: none;
- z-index: 6;
+ z-index: 8;
visibility: hidden;
transition: left 0.6s cubic-bezier(0.4,0,0.2,1);
}
+ .peak-value-label,
+ .floor-value-label,
+ .generic-value-label {
+ position: absolute;
+ font-size: var(--sbcp-target-label-font-size);
+ line-height: 1;
+ color: var(--marker-color, var(--secondary-text-color, #888));
+ text-shadow:
+ 0 0 0.6px var(--marker-contrast-color),
+ 0 0 1.2px color-mix(in srgb, var(--marker-contrast-color) 78%, transparent);
+ background-color: var(--card-background-color, #fff);
+ padding: 0 2px;
+ border: 0;
+ border-radius: 2px;
+ box-shadow: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ box-sizing: border-box;
+ pointer-events: none;
+ z-index: 8;
+ visibility: hidden;
+ transition: left 0.6s cubic-bezier(0.4,0,0.2,1);
+ }
+ .generic-value-label[data-show-marker="false"] {
+ pointer-events: auto;
+ cursor: pointer;
+ }
+ .peak-value-label {
+ bottom: 100%;
+ margin-bottom: 1px;
+ }
+ .floor-value-label {
+ top: 100%;
+ margin-top: 2px;
+ }
+ .generic-value-label[data-lane="above"] {
+ bottom: 100%;
+ margin-bottom: 1px;
+ }
+ .generic-value-label[data-lane="below"] {
+ top: 100%;
+ margin-top: 2px;
+ }
+ .target-value-label[data-marker-hovered="true"],
+ .peak-value-label[data-marker-hovered="true"],
+ .floor-value-label[data-marker-hovered="true"],
+ .generic-value-label[data-marker-hovered="true"] {
+ z-index: 11;
+ }
.above-line {
display: grid;
grid-template-columns: var(--sbcp-icon-width) minmax(0, 1fr);
@@ -2217,6 +3447,10 @@
margin-bottom: 2px;
min-height: 16px;
}
+ .above-bar-label[data-hide-name="true"],
+ .above-bar-label[data-priority-hide-name="true"] {
+ gap: 0;
+ }
.above-bar-label-name {
flex: 1 1 auto;
min-width: 0;
@@ -2270,6 +3504,9 @@
overflow: hidden;
margin-bottom: clamp(2px, calc(var(--sbcp-row-height) * 0.08), 4px);
}
+ .row[data-marker-label-lane-above="false"] .hero-header {
+ margin-bottom: 0;
+ }
.hero-header[data-hide-name="true"] .hero-label,
.hero-header[data-priority-hide-name="true"] .hero-label {
display: none;
@@ -2374,7 +3611,7 @@
text-overflow: clip;
}
/* \u2500\u2500 Shared marker base \u2500\u2500 */
- .peak-marker, .target-marker {
+ .peak-marker, .target-marker, .floor-marker, .generic-marker {
position: absolute;
top: 0;
bottom: 0;
@@ -2385,12 +3622,46 @@
--marker-color: #888;
--marker-contrast-color: #f3f4f6;
}
+ .peak-marker .peak-inset,
+ .peak-marker .peak-outset,
+ .target-marker .target-inset,
+ .target-marker .target-outset,
+ .floor-marker .floor-inset,
+ .floor-marker .floor-outset,
+ .generic-marker .peak-inset,
+ .generic-marker .peak-outset,
+ .generic-marker .target-inset,
+ .generic-marker .target-outset {
+ pointer-events: auto;
+ }
+ .peak-marker .peak-inset:hover,
+ .peak-marker .peak-outset:hover,
+ .target-marker .target-inset:hover,
+ .target-marker .target-outset:hover,
+ .floor-marker .floor-inset:hover,
+ .floor-marker .floor-outset:hover,
+ .generic-marker .peak-inset:hover,
+ .generic-marker .peak-outset:hover,
+ .generic-marker .target-inset:hover,
+ .generic-marker .target-outset:hover,
+ .marker-shape-svg path[data-shape]:hover {
+ cursor: none;
+ }
.target-marker {
z-index: 6;
}
+ .floor-marker {
+ z-index: 6;
+ }
+ .generic-marker[data-lane="below"] {
+ z-index: 6;
+ }
.peak-marker {
z-index: 7;
}
+ .generic-marker[data-lane="above"] {
+ z-index: 7;
+ }
.needle-layer {
position: absolute;
inset: 0;
@@ -2424,7 +3695,13 @@
.peak-marker .peak-inset,
.peak-marker .peak-outset,
.target-marker .target-inset,
- .target-marker .target-outset {
+ .target-marker .target-outset,
+ .floor-marker .floor-inset,
+ .floor-marker .floor-outset,
+ .generic-marker[data-lane="above"] .peak-inset,
+ .generic-marker[data-lane="above"] .peak-outset,
+ .generic-marker[data-lane="below"] .target-inset,
+ .generic-marker[data-lane="below"] .target-outset {
position: absolute;
left: 50%;
transform: translateX(-50%);
@@ -2432,7 +3709,8 @@
height: 0;
}
/* Peak marker: large triangle intrudes into the bar, small one sits just above it. */
- .peak-marker .peak-inset {
+ .peak-marker .peak-inset,
+ .generic-marker[data-lane="above"] .peak-inset {
top: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
@@ -2442,7 +3720,14 @@
drop-shadow(0 0 1.2px var(--marker-contrast-color))
drop-shadow(0 0 3px color-mix(in srgb, var(--marker-contrast-color) 78%, transparent));
}
- .peak-marker .peak-outset {
+ .peak-marker[data-direction="outward"] .peak-inset,
+ .generic-marker[data-lane="above"][data-direction="outward"] .peak-inset {
+ border-top-width: 0;
+ border-top-color: transparent;
+ border-bottom: 11px solid var(--marker-color);
+ }
+ .peak-marker .peak-outset,
+ .generic-marker[data-lane="above"] .peak-outset {
top: -4px;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
@@ -2450,7 +3735,8 @@
z-index: 3;
}
/* Target marker: large triangle intrudes into the bar, small one sits just below it. */
- .target-marker .target-inset {
+ .target-marker .target-inset,
+ .generic-marker[data-lane="below"] .target-inset {
bottom: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
@@ -2460,15 +3746,144 @@
drop-shadow(0 0 1.2px var(--marker-contrast-color))
drop-shadow(0 0 3px color-mix(in srgb, var(--marker-contrast-color) 78%, transparent));
}
- .target-marker .target-outset {
+ .target-marker .target-outset,
+ .generic-marker[data-lane="below"] .target-outset {
bottom: -4px;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 4px solid var(--marker-color);
z-index: 3;
}
+ .floor-marker .floor-inset {
+ bottom: 0;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ border-bottom: 11px solid var(--marker-color);
+ z-index: 2;
+ filter:
+ drop-shadow(0 0 1.2px var(--marker-contrast-color))
+ drop-shadow(0 0 3px color-mix(in srgb, var(--marker-contrast-color) 78%, transparent));
+ }
+ .target-marker[data-direction="outward"] .target-inset,
+ .floor-marker[data-direction="outward"] .floor-inset,
+ .generic-marker[data-lane="below"][data-direction="outward"] .target-inset {
+ border-bottom-width: 0;
+ border-bottom-color: transparent;
+ border-top: 11px solid var(--marker-color);
+ }
+ .floor-marker .floor-outset {
+ bottom: -4px;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 4px solid var(--marker-color);
+ z-index: 3;
+ }
+ /* Shared non-triangle marker shapes. Triangle keeps the original CSS geometry. */
+ .marker-shape-svg {
+ display: none;
+ position: absolute;
+ left: 50%;
+ width: 16px;
+ height: 16px;
+ overflow: visible;
+ color: var(--marker-color);
+ pointer-events: none;
+ z-index: 2;
+ transform: translateX(-50%);
+ filter:
+ drop-shadow(0 0 1.2px var(--marker-contrast-color))
+ drop-shadow(0 0 3px color-mix(in srgb, var(--marker-contrast-color) 78%, transparent));
+ }
+ .peak-marker .marker-shape-svg {
+ top: 0;
+ }
+ .target-marker .marker-shape-svg {
+ bottom: 0;
+ }
+ .floor-marker .marker-shape-svg {
+ bottom: 0;
+ }
+ .generic-marker[data-lane="above"] .marker-shape-svg {
+ top: 0;
+ }
+ .generic-marker[data-lane="below"] .marker-shape-svg {
+ bottom: 0;
+ }
+ .marker-shape-svg[data-lane="above"] {
+ transform-origin: 50% 0;
+ }
+ .marker-shape-svg[data-lane="below"] {
+ transform-origin: 50% 100%;
+ }
+ .marker-shape-svg[data-shape="diamond"],
+ .marker-shape-svg[data-shape="arrow"],
+ .marker-shape-svg[data-shape="chevron"],
+ .marker-shape-svg[data-shape="pin"] {
+ transform: translateX(-50%) scale(0.75);
+ }
+ .marker-shape-svg[data-shape="circle"] {
+ transform: translateX(-50%) scale(0.64);
+ }
+ .peak-marker[data-shape]:not([data-shape="triangle"]) .peak-inset,
+ .peak-marker[data-shape]:not([data-shape="triangle"]) .peak-outset,
+ .target-marker[data-shape]:not([data-shape="triangle"]) .target-inset,
+ .target-marker[data-shape]:not([data-shape="triangle"]) .target-outset,
+ .floor-marker[data-shape]:not([data-shape="triangle"]) .floor-inset,
+ .floor-marker[data-shape]:not([data-shape="triangle"]) .floor-outset,
+ .generic-marker[data-shape]:not([data-shape="triangle"]) .peak-inset,
+ .generic-marker[data-shape]:not([data-shape="triangle"]) .peak-outset,
+ .generic-marker[data-shape]:not([data-shape="triangle"]) .target-inset,
+ .generic-marker[data-shape]:not([data-shape="triangle"]) .target-outset {
+ display: none;
+ }
+ .peak-marker[data-shape]:not([data-shape="triangle"]) .marker-shape-svg,
+ .target-marker[data-shape]:not([data-shape="triangle"]) .marker-shape-svg {
+ display: block;
+ }
+ .floor-marker[data-shape]:not([data-shape="triangle"]) .marker-shape-svg {
+ display: block;
+ }
+ .generic-marker[data-shape]:not([data-shape="triangle"]) .marker-shape-svg {
+ display: block;
+ }
+ .generic-marker[data-show-marker="false"][data-shape] .peak-inset,
+ .generic-marker[data-show-marker="false"][data-shape] .peak-outset,
+ .generic-marker[data-show-marker="false"][data-shape] .target-inset,
+ .generic-marker[data-show-marker="false"][data-shape] .target-outset,
+ .generic-marker[data-show-marker="false"][data-shape] .marker-shape-svg {
+ display: none;
+ }
+ .marker-shape-svg path {
+ display: none;
+ fill: currentColor;
+ }
+ .marker-shape-svg path[data-shape] {
+ pointer-events: visiblePainted;
+ }
+ .marker-shape-svg[data-shape="circle"] path[data-shape="circle"],
+ .marker-shape-svg[data-shape="diamond"] path[data-shape="diamond"],
+ .marker-shape-svg[data-shape="chevron"] path[data-shape="chevron"],
+ .marker-shape-svg[data-shape="arrow"] path[data-shape="arrow"],
+ .marker-shape-svg[data-shape="pin"] path[data-shape="pin"] {
+ display: block;
+ }
+ .marker-shape-svg[data-shape="chevron"] path[data-shape="chevron"] {
+ fill: none;
+ }
+ .marker-shape-svg[data-shape="chevron"][data-lane="below"][data-direction="inward"] .marker-shape-paths,
+ .marker-shape-svg[data-shape="arrow"][data-lane="below"][data-direction="inward"] .marker-shape-paths,
+ .marker-shape-svg[data-shape="pin"][data-lane="below"][data-direction="inward"] .marker-shape-paths,
+ .marker-shape-svg[data-shape="chevron"][data-lane="above"][data-direction="outward"] .marker-shape-paths,
+ .marker-shape-svg[data-shape="arrow"][data-lane="above"][data-direction="outward"] .marker-shape-paths,
+ .marker-shape-svg[data-shape="pin"][data-lane="above"][data-direction="outward"] .marker-shape-paths {
+ transform-box: view-box;
+ transform-origin: 0 0;
+ transform: translateY(16px) scaleY(-1);
+ }
.value-right {
+ position: relative;
+ z-index: 10;
--sbcp-value-extra-width: 0px;
flex: 0 0 calc(var(--sbcp-value-width) + var(--sbcp-value-extra-width));
width: calc(var(--sbcp-value-width) + var(--sbcp-value-extra-width));
@@ -2499,6 +3914,9 @@
overflow: hidden;
white-space: nowrap;
}
+ .main-line.off-mode .value-right {
+ flex-shrink: 1;
+ }
.value-right-text.has-unit {
gap: 2px;
}
@@ -2556,6 +3974,8 @@
`;
+ this.shadowRoot.addEventListener("pointerover", this._boundMarkerPointerOver);
+ this.shadowRoot.addEventListener("pointerout", this._boundMarkerPointerOut);
this._baseDomReady = true;
}
_render() {
@@ -2571,20 +3991,24 @@
titleEl.style.display = "none";
}
}
- this._disconnectResizeObserver();
- this._resizeObserver = new ResizeObserver(() => {
+ this._setupResizeObserver();
+ this._update();
+ this._schedulePostLayoutDensityPass();
+ }
+ _setupResizeObserver() {
+ if (!this.isConnected || this._resizeObserver) return;
+ const surface = this.shadowRoot.querySelector("ha-card");
+ const card = this.shadowRoot.querySelector(".card");
+ if (!surface || !card) return;
+ const observer = new ResizeObserver(() => {
+ if (!this.isConnected || this._resizeObserver !== observer) return;
this._applyCompactTier();
this._schedulePostLayoutDensityPass();
});
- const surface = this.shadowRoot.querySelector("ha-card");
- const card = this.shadowRoot.querySelector(".card");
- if (surface && card) {
- this._applyCompactTier();
- this._resizeObserver.observe(surface);
- this._resizeObserver.observe(this);
- }
- this._update();
- this._schedulePostLayoutDensityPass();
+ this._resizeObserver = observer;
+ this._applyCompactTier();
+ observer.observe(surface);
+ observer.observe(this);
}
_disconnectResizeObserver() {
if (!this._resizeObserver) return;
@@ -2612,13 +4036,17 @@
if (width < 360) return "compact";
return "normal";
}
- _classifyLeftDensity(width, currentDensity = "normal") {
+ _classifyLeftDensity(width, currentDensity = "normal", naturalLabelWidth = Number.POSITIVE_INFINITY) {
if (!this._isReliableWidth(width)) return currentDensity || "normal";
- if (width < 170) return "compressed";
- if (width < 210) return "dense";
- if (width < 255) return "tight";
- if (width < 320) return "compact";
- return "normal";
+ const densities = ["normal", "compact", "tight", "dense", "compressed"];
+ let density = "normal";
+ if (width < 170) density = "compressed";
+ else if (width < 210) density = "dense";
+ else if (width < 255) density = "tight";
+ else if (width < 320) density = "compact";
+ const currentIndex = densities.indexOf(density);
+ const relaxBy = Number.isFinite(naturalLabelWidth) ? naturalLabelWidth <= 44 && width >= 205 ? 2 : naturalLabelWidth <= 72 && width >= 185 ? 1 : 0 : 0;
+ return densities[Math.max(0, currentIndex - relaxBy)];
}
_classifyRowDensity(width, currentDensity = "normal") {
if (!this._isReliableWidth(width)) return currentDensity || "normal";
@@ -2674,7 +4102,6 @@
}
_applyLeftModeDensity() {
if (!this.shadowRoot) return;
- const densities = ["normal", "compact", "tight", "dense", "compressed"];
this.shadowRoot.querySelectorAll(".main-line.left-mode").forEach((mainLine) => {
const width = mainLine.getBoundingClientRect().width;
if (!this._isReliableWidth(width)) {
@@ -2682,21 +4109,10 @@
if (!mainLine.dataset.leftDensity) mainLine.dataset.leftDensity = "normal";
return;
}
- let density = this._classifyLeftDensity(width, mainLine.dataset.leftDensity);
const labelText = mainLine.querySelector(".label-left-text");
- const fullLabelWidth = labelText ? labelText.scrollWidth : Number.POSITIVE_INFINITY;
- const visibleLabelWidth = labelText ? labelText.clientWidth : Number.POSITIVE_INFINITY;
- const labelIsTruncated = labelText ? fullLabelWidth > visibleLabelWidth + 1 : false;
- const effectiveLabelWidth = labelIsTruncated ? visibleLabelWidth : fullLabelWidth;
- let relaxBy = 0;
- if (Number.isFinite(effectiveLabelWidth)) {
- if (effectiveLabelWidth <= 72 && width >= 185) relaxBy = 1;
- if (effectiveLabelWidth <= 44 && width >= 205) relaxBy = 2;
- }
- const currentIndex = densities.indexOf(density);
- if (currentIndex !== -1 && relaxBy > 0) {
- density = densities[Math.max(0, currentIndex - relaxBy)];
- }
+ const text = ((labelText == null ? void 0 : labelText.textContent) || "").trim();
+ const naturalLabelWidth = labelText ? this._measureTextWidthWithStyles(labelText, text) || labelText.scrollWidth : Number.POSITIVE_INFINITY;
+ const density = this._classifyLeftDensity(width, mainLine.dataset.leftDensity, naturalLabelWidth);
mainLine.dataset.leftDensity = density;
});
}
@@ -2709,70 +4125,78 @@
const nameEl = innerLabel.querySelector(".inside-name");
const valueEl = innerLabel.querySelector(".inside-value");
if (!track || !nameEl || !valueEl) return;
- const trackWidth = track.getBoundingClientRect().width;
- const valueDisplay = this._decodeDataAttr(valueEl.dataset.display || valueEl.textContent || "");
- const valueUnit = this._decodeDataAttr(valueEl.dataset.unit || ((_a = valueEl.querySelector(".inside-unit")) == null ? void 0 : _a.textContent) || "");
- const valueWidth = this._measureInsideValueMarkupWidth(valueEl, valueDisplay, valueUnit, false);
- const valueOnlyWidth = this._measureInsideValueMarkupWidth(valueEl, valueDisplay, valueUnit, true);
- let density = this._classifyInsideDensity(trackWidth, valueWidth);
- const rowWidth = typeof (mainLine == null ? void 0 : mainLine.getBoundingClientRect) === "function" ? mainLine.getBoundingClientRect().width : 0;
- const rowDensity = this._isReliableWidth(rowWidth) ? this._classifyRowDensity(rowWidth, ((_b = mainLine == null ? void 0 : mainLine.dataset) == null ? void 0 : _b.rowDensity) || "normal") : ((_c = mainLine == null ? void 0 : mainLine.dataset) == null ? void 0 : _c.rowDensity) || "normal";
- const iconWrap = (_e = (_d = mainLine == null ? void 0 : mainLine.querySelector) == null ? void 0 : _d.call(mainLine, ".icon-wrap")) != null ? _e : null;
- let hideIcon = rowDensity === "dense" || rowDensity === "compressed";
- const reclaimedWidth = iconWrap ? this._getLeftModeIconWidth(iconWrap, mainLine) + this._getLeftModeGap(mainLine) : 0;
- if (!hideIcon && valueWidth > this._getInsideValueVisibleCap(trackWidth, density)) {
- hideIcon = true;
- }
- if (iconWrap && hideIcon) {
- density = this._classifyInsideDensity(trackWidth + reclaimedWidth, valueWidth);
- }
- const effectiveTrackWidth = trackWidth + (hideIcon ? reclaimedWidth : 0);
- let hideName = false;
- if (rowDensity === "compressed") {
- density = "compressed";
- hideIcon = true;
- }
- const rawValueCap = this._getInsideValueVisibleCap(trackWidth, density);
- const innerPadding = this._getNumericStyleValue(innerLabel, "padding-left", 0) + this._getNumericStyleValue(innerLabel, "padding-right", 0);
- const valueCap = Math.max(0, rawValueCap - innerPadding);
- const hideUnit = !!valueUnit && valueWidth > valueCap;
- const hideValue = valueOnlyWidth > valueCap;
- const reservedValueWidth = hideValue ? 0 : Math.ceil(hideUnit ? valueOnlyWidth : valueWidth);
- const labelGap = !hideValue ? this._getNumericStyleValue(innerLabel, "gap", 0) : 0;
- if (!hideValue) {
- const nameText = (nameEl.textContent || "").trim();
- const nameFullWidth = nameEl.scrollWidth || ((_f = nameEl.getBoundingClientRect) == null ? void 0 : _f.call(nameEl).width) || this._measureTextWidthWithStyles(nameEl, nameText);
- if (!nameText || !Number.isFinite(nameFullWidth) || nameFullWidth <= 0) {
- hideName = density === "dense" || density === "compressed";
- } else {
- const usefulNameWidth = this._getInsideUsefulNameWidth(nameEl, nameText, nameFullWidth);
- const availableNameWidth = Math.max(
- 0,
- trackWidth - innerPadding - reservedValueWidth - labelGap
- );
+ const display = this._decodeDataAttr(valueEl.dataset.display || valueEl.textContent || "");
+ const unit = this._decodeDataAttr(valueEl.dataset.unit || "");
+ valueEl.dataset.valueFit = "normal";
+ innerLabel.dataset.valueFit = "normal";
+ const fullWidth = this._measureInsideValueMarkupWidth(valueEl, display, unit, false);
+ const numberWidth = this._measureInsideValueMarkupWidth(valueEl, display, unit, true);
+ const rowWidth = (_b = (_a = mainLine == null ? void 0 : mainLine.getBoundingClientRect) == null ? void 0 : _a.call(mainLine).width) != null ? _b : 0;
+ const rowDensity = this._isReliableWidth(rowWidth) ? this._classifyRowDensity(rowWidth, (_c = mainLine == null ? void 0 : mainLine.dataset) == null ? void 0 : _c.rowDensity) : ((_d = mainLine == null ? void 0 : mainLine.dataset) == null ? void 0 : _d.rowDensity) || "normal";
+ const iconWrap = (_f = (_e = mainLine == null ? void 0 : mainLine.querySelector) == null ? void 0 : _e.call(mainLine, ".icon-wrap")) != null ? _f : null;
+ const iconReserve = iconWrap ? this._getLeftModeIconWidth(iconWrap, mainLine) + this._getLeftModeGap(mainLine) : 0;
+ const minimum = this._getLeftModeBarMinWidth(mainLine);
+ const withIconWidth = this._isReliableWidth(rowWidth) ? Math.max(minimum, rowWidth - iconReserve) : track.getBoundingClientRect().width;
+ const withoutIconWidth = this._isReliableWidth(rowWidth) ? Math.max(minimum, rowWidth) : withIconWidth + iconReserve;
+ const nameText = (nameEl.textContent || "").trim();
+ const nameWidth = this._measureTextWidthWithStyles(nameEl, nameText) || nameEl.scrollWidth;
+ const candidate = (trackWidth) => {
+ const density = rowDensity === "compressed" ? "compressed" : this._classifyInsideDensity(trackWidth, fullWidth);
+ innerLabel.dataset.insideDensity = density;
+ innerLabel.dataset.valueFit = "normal";
+ valueEl.dataset.valueFit = "normal";
+ const padding = this._getNumericStyleValue(innerLabel, "padding-left", 0) + this._getNumericStyleValue(innerLabel, "padding-right", 0);
+ let cap = Math.max(0, trackWidth - padding);
+ const hideUnit = !!unit && fullWidth > cap;
+ let readingWidth = hideUnit ? numberWidth : fullWidth;
+ let valueFit = "normal";
+ if (readingWidth > cap) {
+ valueFit = "compact";
+ innerLabel.dataset.valueFit = valueFit;
+ valueEl.dataset.valueFit = valueFit;
+ cap = Math.max(0, trackWidth - this._getNumericStyleValue(innerLabel, "padding-left", 0) - this._getNumericStyleValue(innerLabel, "padding-right", 0));
+ readingWidth = this._measureInsideValueMarkupWidth(valueEl, display, unit, true);
+ }
+ const hideValue = readingWidth > cap;
+ const gap = hideValue ? 0 : this._getNumericStyleValue(innerLabel, "gap", 0);
+ const nameShare = density === "compact" ? 0.56 : density === "tight" ? 0.48 : 0.6;
+ const availableNameWidth = Math.max(
+ 0,
+ Math.min(cap * nameShare, cap - (hideValue ? 0 : Math.ceil(readingWidth)) - gap)
+ );
+ let hideName = density === "dense" || density === "compressed";
+ if (nameText && nameWidth > 0) {
+ const usefulWidth = this._getInsideUsefulNameWidth(nameEl, nameText, nameWidth);
const visibleChars = this._measureVisibleLabelCharacters(nameEl, nameText, availableNameWidth);
- const minUsefulChars = Math.min(4, nameText.length);
- const nameNeedsTruncation = nameFullWidth > availableNameWidth + 1;
- const hasUsefulRoom = availableNameWidth >= usefulNameWidth && (!nameNeedsTruncation || visibleChars >= minUsefulChars);
- hideName = !hasUsefulRoom;
+ hideName = availableNameWidth < usefulWidth || nameWidth > availableNameWidth + 1 && visibleChars < Math.min(4, nameText.length);
+ }
+ return {
+ density,
+ valueFit,
+ hideUnit,
+ hideValue,
+ hideName,
+ rank: hideValue ? 3 : valueFit === "compact" ? 2 : hideUnit ? 1 : 0
+ };
+ };
+ let hideIcon = rowDensity === "dense" || rowDensity === "compressed";
+ let chosen = candidate(hideIcon ? withoutIconWidth : withIconWidth);
+ if (iconWrap && !hideIcon) {
+ const withoutIcon = candidate(withoutIconWidth);
+ if (withoutIcon.rank < chosen.rank || withoutIcon.rank === chosen.rank && chosen.hideName && !withoutIcon.hideName) {
+ hideIcon = true;
+ chosen = withoutIcon;
}
}
- if (hideName && density === "normal") density = "dense";
- innerLabel.dataset.insideDensity = density;
- innerLabel.dataset.hideName = hideName ? "true" : "false";
- valueEl.dataset.hideUnit = hideUnit ? "true" : "false";
- valueEl.dataset.hideValue = hideValue ? "true" : "false";
- if (mainLine) {
- mainLine.dataset.hideInsideIcon = hideIcon ? "true" : "false";
- }
+ innerLabel.dataset.insideDensity = chosen.density;
+ innerLabel.dataset.valueFit = chosen.valueFit;
+ innerLabel.dataset.hideName = chosen.hideName ? "true" : "false";
+ valueEl.dataset.valueFit = chosen.valueFit;
+ valueEl.dataset.hideUnit = chosen.hideUnit ? "true" : "false";
+ valueEl.dataset.hideValue = chosen.hideValue ? "true" : "false";
+ if (mainLine) mainLine.dataset.hideInsideIcon = hideIcon ? "true" : "false";
});
}
- _getInsideValueVisibleCap(trackWidth, density) {
- if (density === "dense" || density === "compressed") {
- return trackWidth;
- }
- return trackWidth * 0.56;
- }
_classifyInsideDensity(trackWidth, valueWidth) {
if (trackWidth < Math.max(72, valueWidth + 12)) return "compressed";
if (trackWidth < valueWidth + 56) return "dense";
@@ -2795,7 +4219,7 @@
_applyAboveLabelDensity() {
if (!this.shadowRoot) return;
this.shadowRoot.querySelectorAll(".above-line, .hero-line").forEach((aboveLine) => {
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
const label = aboveLine.querySelector(".above-bar-label, .hero-header");
if (!label) return;
const isHeroLine = aboveLine.classList.contains("hero-line");
@@ -2830,31 +4254,41 @@
const rowStack = (_k = aboveLine.closest) == null ? void 0 : _k.call(aboveLine, ".row-stack");
const lineWidth = (_o = (_n = (_l = rowStack == null ? void 0 : rowStack.getBoundingClientRect) == null ? void 0 : _l.call(rowStack).width) != null ? _n : (_m = aboveLine.getBoundingClientRect) == null ? void 0 : _m.call(aboveLine).width) != null ? _o : width;
const spacerEl = typeof aboveLine.querySelector === "function" ? aboveLine.querySelector(".above-icon-spacer") : null;
- const spacerWidth = (_q = (_p = spacerEl == null ? void 0 : spacerEl.getBoundingClientRect) == null ? void 0 : _p.call(spacerEl).width) != null ? _q : 0;
+ const spacerWidth = mainIconVisible ? iconRect.width : 0;
const lineGap = spacerEl ? this._getNumericStyleValue(aboveLine, "gap", 0) : 0;
- const nameWidth = (_t = (_s = (_r = labelText.getBoundingClientRect) == null ? void 0 : _r.call(labelText).width) != null ? _s : labelText.clientWidth) != null ? _t : 0;
- const labelGap = this._getNumericStyleValue(label, "gap", this._getLeftModeGap(aboveLine));
+ const labelGap = this._getNumericStyleValue(aboveLine, "--sbcp-main-gap", this._getLeftModeGap(aboveLine));
const fullValueWidth = Math.ceil(this._measureValueMarkupWidth(valueEl, display, unit, false) + 2);
const valueOnlyWidth = Math.ceil(this._measureValueMarkupWidth(valueEl, display, unit, true) + 2);
const text = (labelText.textContent || "").trim();
- const visibleWidth = labelText.clientWidth;
- const fullWidth = labelText.scrollWidth;
- const visibleChars = this._measureVisibleLabelCharacters(labelText, text, visibleWidth);
- const labelIsUnhelpful = this._shouldHideLeftLabel(text, fullWidth, visibleWidth, visibleChars);
const spacerReserve = mainIconVisible && spacerWidth > 0 ? spacerWidth + lineGap : 0;
- const availableWithName = Math.max(0, lineWidth - spacerReserve - nameWidth - labelGap);
+ const nameWidth = this._measureTextWidthWithStyles(labelText, text) || labelText.scrollWidth || 0;
+ const withNameBudget = Math.max(0, lineWidth - spacerReserve - labelGap);
+ const fitsWithName = (valueWidth, budget = withNameBudget) => {
+ const visibleWidth = Math.max(0, budget - valueWidth);
+ const visibleChars = this._measureVisibleLabelCharacters(labelText, text, visibleWidth);
+ return !this._shouldHideLeftLabel(text, nameWidth, visibleWidth, visibleChars);
+ };
const availableValueOnly = Math.max(0, lineWidth);
- hideName = labelIsUnhelpful;
- if (!hideName && fullValueWidth <= availableWithName) {
+ let hideUnit = false;
+ if (fullValueWidth <= withNameBudget && fitsWithName(fullValueWidth)) {
+ hideName = false;
hideSpacer = !mainIconVisible;
- } else if (!hideName && valueOnlyWidth <= availableWithName) {
+ } else if (fullValueWidth <= availableValueOnly) {
+ hideName = !fitsWithName(fullValueWidth, lineWidth - labelGap);
+ hideSpacer = true;
+ } else if (valueOnlyWidth <= withNameBudget && fitsWithName(valueOnlyWidth)) {
+ hideName = false;
+ hideUnit = !!unit;
hideSpacer = !mainIconVisible;
+ } else if (fitsWithName(valueOnlyWidth, lineWidth - labelGap)) {
+ hideName = false;
+ hideSpacer = true;
+ hideUnit = !!unit;
} else {
hideName = true;
hideSpacer = true;
+ hideUnit = !!unit && fullValueWidth > availableValueOnly;
}
- const availableWidth = hideName ? availableValueOnly : availableWithName;
- const hideUnit = !!unit && fullValueWidth > availableWidth;
valueEl.dataset.hideUnit = hideUnit ? "true" : "false";
} else if (valueEl) {
valueEl.dataset.hideUnit = "false";
@@ -2981,6 +4415,26 @@
if (valueWidth("normal", false) <= availableWithoutLabel) return;
}
if (hasUnit) {
+ for (const fit of ["tight", "minimum"]) {
+ const readingWidth = valueWidth(fit, false);
+ if (readingWidth <= availableWithoutLabel) {
+ heroLine.dataset.heroValueFit = fit;
+ if (!labelHiddenByDensity && labelEl) {
+ const text = (labelEl.textContent || "").trim();
+ const nameWidth = this._measureTextWidthWithStyles(labelEl, text);
+ const availableName = Math.max(0, availableWithLabel - readingWidth);
+ if (!this._shouldHideLeftLabel(
+ text,
+ nameWidth,
+ availableName,
+ this._measureVisibleLabelCharacters(labelEl, text, availableName)
+ )) {
+ delete headerEl.dataset.priorityHideName;
+ }
+ }
+ return;
+ }
+ }
heroLine.dataset.hideHeroUnit = "true";
if (valueWidth("normal", true) <= availableWithoutLabel) return;
}
@@ -3019,17 +4473,13 @@
clone.style.textOverflow = "clip";
clone.style.whiteSpace = "nowrap";
clone.innerHTML = this._formatInsideValueMarkup(display, unit, hideUnit);
- layer.replaceChildren(clone);
- let extraWidth = 0;
- try {
- const style = getComputedStyle(valueEl);
- extraWidth += parseFloat(style.paddingLeft || "0") || 0;
- extraWidth += parseFloat(style.paddingRight || "0") || 0;
- extraWidth += parseFloat(style.borderLeftWidth || "0") || 0;
- extraWidth += parseFloat(style.borderRightWidth || "0") || 0;
- } catch (_err) {
- }
- return clone.getBoundingClientRect().width + extraWidth;
+ clone.removeAttribute("data-hide-value");
+ const wrapper = document.createElement("div");
+ wrapper.className = "bar-inner-label";
+ wrapper.style.cssText = "position:static;display:block;padding:0";
+ wrapper.appendChild(clone);
+ layer.replaceChildren(wrapper);
+ return clone.getBoundingClientRect().width;
}
_measureTextWidthWithStyles(sourceEl, text) {
var _a;
@@ -3082,7 +4532,7 @@
_applyValueWidthReservation() {
if (!this.shadowRoot) return;
this.shadowRoot.querySelectorAll(".value-right").forEach((valueEl) => {
- var _a, _b, _c;
+ var _a, _b, _c, _d, _e;
const display = this._decodeDataAttr(valueEl.dataset.display || "");
const unit = this._decodeDataAttr(valueEl.dataset.unit || "");
if (!display) {
@@ -3093,7 +4543,29 @@
if (!getStyle) return;
const style = getStyle(valueEl);
const baseWidth = parseFloat(style.getPropertyValue("--sbcp-value-width")) || valueEl.clientWidth || 0;
- const desiredWidth = Math.ceil(this._measureValueMarkupWidth(valueEl, display, unit, false) + 2);
+ const fullWidth = Math.ceil(this._measureValueMarkupWidth(valueEl, display, unit, false) + 2);
+ const mainLine = valueEl.closest(".main-line");
+ let desiredWidth = fullWidth;
+ if (mainLine == null ? void 0 : mainLine.classList.contains("off-mode")) {
+ const barWrap = mainLine.querySelector(".bar-wrap");
+ const iconWrap = mainLine.querySelector(".icon-wrap");
+ mainLine.dataset.hideOffIcon = "false";
+ const gap = parseFloat(getStyle(mainLine).gap) || 0;
+ const rowWidth = (_e = (_d = mainLine.getBoundingClientRect) == null ? void 0 : _d.call(mainLine).width) != null ? _e : 0;
+ const minimumRail = parseFloat(getStyle(barWrap).minWidth) || 0;
+ const iconVisible = iconWrap && getStyle(iconWrap).display !== "none";
+ const iconWidth = iconVisible ? this._getLeftModeIconWidth(iconWrap, mainLine) : 0;
+ const withoutIcon = Math.max(0, rowWidth - minimumRail - gap);
+ const withIcon = withoutIcon - (iconVisible ? iconWidth + gap : 0);
+ const numberWidth = Math.ceil(this._measureValueMarkupWidth(valueEl, display, unit, true) + 2);
+ const hideIcon = iconVisible && (fullWidth > withIcon && fullWidth <= withoutIcon || numberWidth > withIcon && numberWidth <= withoutIcon);
+ mainLine.dataset.hideOffIcon = hideIcon ? "true" : "false";
+ const availableWidth = hideIcon ? withoutIcon : withIcon;
+ if (availableWidth > 0) {
+ const readableWidth = fullWidth <= availableWidth ? fullWidth : numberWidth;
+ desiredWidth = Math.min(readableWidth, availableWidth);
+ }
+ }
const extraWidth = Math.max(0, desiredWidth - baseWidth);
valueEl.style.setProperty("--sbcp-value-extra-width", `${extraWidth}px`);
});
@@ -3197,8 +4669,27 @@
const fullMarkupWidth = Math.ceil(this._measureValueMarkupWidth(valueEl, display, unit, false) + 2);
return Math.max(reservedWidth, fullMarkupWidth);
}
+ _getStableLeftLabelMetrics(row, rowWidth = null) {
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
+ const mainLine = row == null ? void 0 : row.querySelector(".main-line");
+ const labelWrap = row == null ? void 0 : row.querySelector(".label-left");
+ const labelText = row == null ? void 0 : row.querySelector(".label-left-text");
+ if (!mainLine || !labelWrap || !labelText) return null;
+ const width = (_b = rowWidth != null ? rowWidth : (_a = mainLine.getBoundingClientRect) == null ? void 0 : _a.call(mainLine).width) != null ? _b : 0;
+ const text = (labelText.textContent || "").trim();
+ const naturalWidth = this._measureTextWidthWithStyles(labelText, text) || labelText.scrollWidth || 0;
+ const density = ((_c = mainLine.dataset) == null ? void 0 : _c.leftDensity) || "normal";
+ const labelShares = { normal: 0.25, compact: 0.22, tight: 0.19, dense: 0.16, compressed: 0.14 };
+ const entityConfig = (_e = (_d = this._config) == null ? void 0 : _d.entities) == null ? void 0 : _e.find((item) => {
+ var _a2;
+ return item.entity === ((_a2 = row.dataset) == null ? void 0 : _a2.entity);
+ });
+ const configuredWidth = entityConfig ? (_i = (_h = (_g = (_f = this._resolve(entityConfig)) == null ? void 0 : _f.layout) == null ? void 0 : _g.label) == null ? void 0 : _h.width) != null ? _i : 100 : 100;
+ const maximumWidth = width > 0 ? Math.min(configuredWidth, width * ((_j = labelShares[density]) != null ? _j : labelShares.normal)) : Math.min(naturalWidth, configuredWidth);
+ return { text, naturalWidth, maximumWidth, labelWidth: maximumWidth, labelText };
+ }
_estimateLeftModeWidthBudget(row) {
- var _a, _b, _c, _d, _e, _f;
+ var _a, _b, _c;
const mainLine = row == null ? void 0 : row.querySelector(".main-line");
if (!mainLine) return null;
const rowWidth = (_b = (_a = mainLine.getBoundingClientRect) == null ? void 0 : _a.call(mainLine).width) != null ? _b : 0;
@@ -3206,19 +4697,38 @@
const labelWrap = row.querySelector(".label-left");
const iconWrap = row.querySelector(".icon-wrap");
const valueEl = row.querySelector(".value-right");
- const labelMetrics = this._getLabelSacrificeMetrics(row, "left", { rowWidth });
- const labelWidth = ((_c = labelWrap == null ? void 0 : labelWrap.dataset) == null ? void 0 : _c.hidden) === "true" ? 0 : (_f = (_e = (_d = labelWrap == null ? void 0 : labelWrap.getBoundingClientRect) == null ? void 0 : _d.call(labelWrap).width) != null ? _e : labelMetrics == null ? void 0 : labelMetrics.labelWidth) != null ? _f : 0;
+ const labelMetrics = this._getStableLeftLabelMetrics(row, rowWidth);
+ const labelWidth = (_c = labelMetrics == null ? void 0 : labelMetrics.labelWidth) != null ? _c : 0;
const iconWidth = iconWrap ? this._getLeftModeIconWidth(iconWrap, mainLine) : 0;
const valueWidth = this._getReservedInlineValueWidth(valueEl);
const gap = this._getLeftModeGap(mainLine);
const barMinWidth = this._getLeftModeBarMinWidth(mainLine);
- const baseLabelVisible = !!labelWrap && labelWrap.dataset.hidden !== "true";
- const labelSacrificial = baseLabelVisible ? this._isLabelWorthSacrificing(row, "left", { rowWidth }) : true;
+ const baseLabelVisible = !!labelWrap;
const hasIcon = !!iconWrap;
+ const labelGapCount = hasIcon ? 2 : 1;
+ const labelAvailableWidth = Math.max(
+ 0,
+ rowWidth - (hasIcon ? iconWidth : 0) - barMinWidth - labelGapCount * gap
+ );
+ const candidateLabelWidth = Math.min(labelWidth, labelAvailableWidth);
+ const labelSacrificial = !labelMetrics || this._shouldHideLeftLabel(
+ labelMetrics.text,
+ labelMetrics.naturalWidth,
+ candidateLabelWidth,
+ this._measureVisibleLabelCharacters(labelMetrics.labelText, labelMetrics.text, candidateLabelWidth)
+ );
+ const withoutIconLabelWidth = Math.min(labelWidth, Math.max(0, rowWidth - barMinWidth - gap));
+ const labelSacrificialWithoutIcon = !labelMetrics || this._shouldHideLeftLabel(
+ labelMetrics.text,
+ labelMetrics.naturalWidth,
+ withoutIconLabelWidth,
+ this._measureVisibleLabelCharacters(labelMetrics.labelText, labelMetrics.text, withoutIconLabelWidth)
+ );
return {
rowWidth,
gap,
barMinWidth,
+ labelSacrificialWithoutIcon,
labelWidth: this._isReliableWidth(labelWidth, 0) ? labelWidth : 0,
iconWidth: this._isReliableWidth(iconWidth, 0) ? iconWidth : 0,
valueWidth: this._isReliableWidth(valueWidth, 0) ? valueWidth : 0,
@@ -3226,7 +4736,6 @@
labelSacrificial,
hasIcon,
mainLine,
- labelWrap,
iconWrap,
valueEl,
rowStack: row.querySelector(".row-stack")
@@ -3241,7 +4750,9 @@
const visibleItems = 1 + (showIcon ? 1 : 0) + (showLabel ? 1 : 0) + (showInlineValue ? 1 : 0);
const gapCount = Math.max(0, visibleItems - 1);
const reservedWidth = (showIcon ? effectiveBudget.iconWidth : 0) + (showLabel ? effectiveBudget.labelWidth : 0) + (showInlineValue ? effectiveBudget.valueWidth : 0) + gapCount * effectiveBudget.gap;
- const predictedBarWidth = Math.max(0, effectiveBudget.rowWidth - reservedWidth);
+ const remainingWidth = Math.max(0, effectiveBudget.rowWidth - reservedWidth);
+ const fits = remainingWidth >= effectiveBudget.barMinWidth;
+ const predictedBarWidth = fits ? remainingWidth : effectiveBudget.barMinWidth;
return {
rowWidth: effectiveBudget.rowWidth,
barWidth: predictedBarWidth,
@@ -3250,31 +4761,31 @@
showIcon,
showInlineValue,
reservedWidth,
- gapCount
+ gapCount,
+ fits
};
}
_getLeftModeCandidateStates(budget) {
- const states = [
- { hideLabel: false, topValue: false, hideIcon: false }
- ];
- if (budget == null ? void 0 : budget.labelSacrificial) {
- states.push({ hideLabel: true, topValue: false, hideIcon: false });
+ const states = [];
+ if (budget && !budget.labelSacrificial) {
+ states.push(
+ { hideLabel: false, topValue: false, hideIcon: false },
+ { hideLabel: false, topValue: true, hideIcon: false }
+ );
+ }
+ if (budget && !budget.labelSacrificialWithoutIcon) {
+ states.push(
+ { hideLabel: false, topValue: false, hideIcon: true },
+ { hideLabel: false, topValue: true, hideIcon: true }
+ );
}
states.push(
- { hideLabel: false, topValue: true, hideIcon: false },
- { hideLabel: true, topValue: true, hideIcon: false },
+ { hideLabel: true, topValue: false, hideIcon: false },
+ { hideLabel: true, topValue: false, hideIcon: true },
{ hideLabel: true, topValue: true, hideIcon: true }
);
return states;
}
- _chooseFirstPredictedLeftModeState(row, states, threshold, budget) {
- for (const state of states) {
- const predicted = this._predictLeftModeBarShareForState(row, state, budget);
- if (!predicted) continue;
- if (predicted.share >= threshold) return { ...state, predicted };
- }
- return null;
- }
_chooseFallbackPredictedLeftModeState(row, states, budget) {
let fallback = null;
for (const state of states) {
@@ -3284,27 +4795,32 @@
}
return fallback;
}
- _chooseLeftModeResponsiveState(row) {
- var _a, _b;
+ _chooseLeftModeResponsiveState(row, settled = true) {
+ var _a, _b, _c, _d;
const budget = this._estimateLeftModeWidthBudget(row);
if (!budget) return null;
const minimumBarShare = this._getMinimumBarShare();
const states = this._getLeftModeCandidateStates(budget);
- const previousTopValue = ((_b = (_a = budget.rowStack) == null ? void 0 : _a.dataset) == null ? void 0 : _b.forceTopValue) === "true";
- const inlineStates = states.filter((state) => !state.topValue);
+ const entityCfg = (_b = this._config.entities) == null ? void 0 : _b[Number((_a = row == null ? void 0 : row.dataset) == null ? void 0 : _a.rowIndex)];
+ const previousTopValue = entityCfg && this._leftModeResponsiveHistory.has(entityCfg) ? this._leftModeResponsiveHistory.get(entityCfg) : ((_d = (_c = budget.rowStack) == null ? void 0 : _c.dataset) == null ? void 0 : _d.forceTopValue) === "true";
const topStates = states.filter((state) => state.topValue);
const enableShare = this._getTopValueEnableShare();
const disableShare = this._getTopValueDisableShare();
- const inlineChoice = previousTopValue ? this._chooseFirstPredictedLeftModeState(row, inlineStates, disableShare, budget) : this._chooseFirstPredictedLeftModeState(row, inlineStates, enableShare, budget);
- if (inlineChoice) return inlineChoice;
- const topChoice = this._chooseFirstPredictedLeftModeState(row, topStates, minimumBarShare, budget) || this._chooseFallbackPredictedLeftModeState(row, topStates, budget);
- return topChoice;
+ for (const state of states) {
+ const threshold = state.topValue || settled ? minimumBarShare : previousTopValue ? disableShare : enableShare;
+ const predicted = this._predictLeftModeBarShareForState(row, state, budget);
+ if ((predicted == null ? void 0 : predicted.fits) && predicted.share >= threshold) return { ...state, predicted };
+ }
+ return this._chooseFallbackPredictedLeftModeState(row, topStates, budget);
}
_applyLeftModeResponsiveState(row, state) {
+ var _a, _b;
const mainLine = row == null ? void 0 : row.querySelector(".main-line");
const rowStack = row == null ? void 0 : row.querySelector(".row-stack");
const leftLabel = row == null ? void 0 : row.querySelector(".label-left");
if (!mainLine || !rowStack) return;
+ const entityCfg = (_b = this._config.entities) == null ? void 0 : _b[Number((_a = row == null ? void 0 : row.dataset) == null ? void 0 : _a.rowIndex)];
+ if (entityCfg) this._leftModeResponsiveHistory.set(entityCfg, (state == null ? void 0 : state.topValue) === true);
delete rowStack.dataset.forceTopValue;
delete mainLine.dataset.hideLeftIcon;
if (leftLabel) delete leftLabel.dataset.priorityHidden;
@@ -3423,7 +4939,7 @@
if (!metrics || !metrics.text) return false;
return this._shouldHideLeftLabel(metrics.text, metrics.fullWidth, metrics.visibleWidth, metrics.visibleChars);
}
- _ensureMinimumBarShare(rows = null) {
+ _ensureMinimumBarShare(rows = null, leftWidths = null) {
if (!this.shadowRoot) return;
const targetRows = rows || this.shadowRoot.querySelectorAll(".row[data-entity]");
const minimumBarShare = this._getMinimumBarShare();
@@ -3433,8 +4949,10 @@
const mode = mainLine.classList.contains("left-mode") ? "left" : mainLine.classList.contains("above-mode") ? "above" : mainLine.classList.contains("inside-mode") ? "inside" : "other";
if (mode === "other") return;
if (mode === "left") {
- const state = this._chooseLeftModeResponsiveState(row);
+ const settled = !leftWidths || leftWidths.get(mainLine) === mainLine.getBoundingClientRect().width;
+ const state = this._chooseLeftModeResponsiveState(row, settled);
if (state) this._applyLeftModeResponsiveState(row, state);
+ if (!settled) this._schedulePostLayoutDensityPass();
return;
}
this._clearMinimumBarShareOverrides(row);
@@ -3525,44 +5043,61 @@
_applyLeftLabelUsefulness() {
if (!this.shadowRoot) return;
this.shadowRoot.querySelectorAll(".main-line.left-mode").forEach((mainLine) => {
+ var _a;
const labelWrap = mainLine.querySelector(".label-left");
const labelText = mainLine.querySelector(".label-left-text");
if (!labelWrap || !labelText) return;
- labelWrap.dataset.hidden = "false";
- const text = (labelText.textContent || "").trim();
- const fullWidth = labelText.scrollWidth;
- const visibleWidth = labelText.clientWidth;
- const visibleChars = this._measureVisibleLabelCharacters(labelText, text, visibleWidth);
- const shouldHide = this._shouldHideLeftLabel(text, fullWidth, visibleWidth, visibleChars);
- labelWrap.dataset.hidden = shouldHide ? "true" : "false";
+ const row = ((_a = mainLine.closest) == null ? void 0 : _a.call(mainLine, ".row")) || {
+ dataset: {},
+ querySelector: (selector) => selector === ".main-line" ? mainLine : selector === ".label-left" ? labelWrap : selector === ".label-left-text" ? labelText : null
+ };
+ const metrics = this._getStableLeftLabelMetrics(row);
+ if (!metrics) return;
+ const visibleChars = this._measureVisibleLabelCharacters(metrics.labelText, metrics.text, metrics.labelWidth);
+ labelWrap.dataset.hidden = this._shouldHideLeftLabel(
+ metrics.text,
+ metrics.naturalWidth,
+ metrics.labelWidth,
+ visibleChars
+ ) ? "true" : "false";
});
}
_runPostLayoutPasses(rows = null) {
+ const generation = this._rowGeneration;
requestAnimationFrame(() => {
+ var _a;
+ if (!this.isConnected || generation !== this._rowGeneration) return;
this._applyRowDensity();
this._applyLeftModeDensity();
this._applyAboveLabelDensity();
this._applyHeroValueFit();
this._applyInsideLabelDensity();
this._applyValueWidthReservation();
+ const leftWidths = new Map(
+ [...((_a = this.shadowRoot) == null ? void 0 : _a.querySelectorAll(".main-line.left-mode")) || []].map((mainLine) => [mainLine, mainLine.getBoundingClientRect().width])
+ );
requestAnimationFrame(() => {
- var _a;
+ var _a2;
+ if (!this.isConnected || generation !== this._rowGeneration) return;
this._applyAdaptiveRowHeight();
this._applyValueVisibility();
this._applyLeftLabelUsefulness();
this._applyTopRightValueLayout();
- this._ensureMinimumBarShare(rows);
+ this._ensureMinimumBarShare(rows, leftWidths);
this._applyTopRightValueLayout();
this._applyLeftLabelUsefulness();
- const targetRows = rows || ((_a = this.shadowRoot) == null ? void 0 : _a.querySelectorAll(".row[data-entity]")) || [];
+ const targetRows = rows || ((_a2 = this.shadowRoot) == null ? void 0 : _a2.querySelectorAll(".row[data-entity]")) || [];
targetRows.forEach((row) => {
this._positionTargetLabel(row);
+ this._positionMarkerValueLabel(row, ".peak-value-label", ".peak-marker");
+ this._positionMarkerValueLabel(row, ".floor-value-label", ".floor-marker");
+ this._positionGenericMarkerLabels(row);
});
});
});
}
_isTightUnit(unit) {
- return ["h", "m", "s"].includes(String(unit || "").trim());
+ return isTightUnit(unit);
}
_encodeDataAttr(value) {
return encodeURIComponent(String(value != null ? value : ""));
@@ -3635,6 +5170,14 @@
const contrastS = Math.max(40, Math.min(100, s));
return `hsl(${Math.round(h)} ${Math.round(contrastS)}% ${Math.round(contrastL)}%)`;
}
+ _getEffectiveMarkerColor(marker) {
+ var _a;
+ return (_a = marker == null ? void 0 : marker.color) != null ? _a : "#888888";
+ }
+ _getMarkerLabelColorStyle(marker) {
+ const color = this._getEffectiveMarkerColor(marker);
+ return `--marker-color:${color};--marker-contrast-color:${this._getMarkerContrastColor(color)};`;
+ }
_getNeedleBorderColor(color) {
const rgb = this._parseColorToRgb(color);
if (!rgb) return "#000000";
@@ -3646,9 +5189,7 @@
return luminance < 0.22 ? "#ffffff" : "#000000";
}
_formatDisplayWithUnit(display, unit) {
- if (!unit) return String(display);
- const cleanUnit = String(unit);
- return `${display}${this._isTightUnit(cleanUnit) ? "" : " "}${cleanUnit}`;
+ return formatDisplayWithUnit(display, unit);
}
_formatRightValueMarkup(display, unit, hideUnit = false) {
const escapedDisplay = escapeHtml(display);
@@ -3672,21 +5213,146 @@
const unitModeClass = this._isTightUnit(cleanUnit) ? "tight-unit" : "has-unit";
return `${escapedDisplay}${escapedUnit}`;
}
- _buildRow(entityCfg, stateDisplay, unit, pct, color, peakPct, peakDisplay, targetPct, targetDisplay, peakColor, targetColor, minValue, maxValue) {
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
- const ecfg = this._resolve(entityCfg);
- const stateObj = (_c = (_b = (_a = this._hass) == null ? void 0 : _a.states) == null ? void 0 : _b[entityCfg.entity]) != null ? _c : null;
- const rowViewModel = stateObj ? buildRowViewModel({
+ _getRowMarkerModels(rowViewModel, ecfg, peakPct, peakDisplay, targetPct, targetDisplay, peakColor, targetColor) {
+ if (rowViewModel == null ? void 0 : rowViewModel.markers) {
+ return rowViewModel.markers.map((marker) => {
+ var _a, _b;
+ if (marker.type === "target") {
+ return {
+ ...marker,
+ position: targetPct === void 0 ? marker.position : targetPct,
+ visible: targetPct !== null && targetPct !== void 0,
+ color: targetColor || marker.color,
+ label: (_a = marker.label) != null ? _a : targetDisplay === null ? null : { text: targetDisplay }
+ };
+ }
+ if (marker.type === "floor") {
+ return marker;
+ }
+ if (marker.type !== "peak") return marker;
+ return {
+ ...marker,
+ position: peakPct === void 0 ? marker.position : peakPct,
+ visible: peakPct !== null && peakPct !== void 0 && ecfg.peak_marker.show === true,
+ color: peakColor || marker.color,
+ label: (_b = marker.label) != null ? _b : peakDisplay === null ? null : { text: peakDisplay }
+ };
+ });
+ }
+ const markers = buildMarkerModels({
+ entityConfig: ecfg,
+ targetPosition: targetPct,
+ targetPresentation: targetDisplay === null ? null : { text: targetDisplay },
+ targetVisible: Number.isFinite(targetPct),
+ peakPosition: peakPct,
+ peakPresentation: peakDisplay === null ? null : { number: peakDisplay },
+ peakVisible: Number.isFinite(peakPct)
+ });
+ const targetMarker = this._getMarkerModel(markers, "target");
+ const peakMarker = this._getMarkerModel(markers, "peak");
+ if (targetMarker && targetColor) targetMarker.color = targetColor;
+ if (peakMarker && peakColor) peakMarker.color = peakColor;
+ return markers;
+ }
+ _getMarkerModel(markers, type) {
+ var _a;
+ return (_a = markers.find((marker) => marker.id === type || marker.type === type)) != null ? _a : null;
+ }
+ _renderMarker(marker) {
+ var _a, _b, _c, _d, _e, _f, _g;
+ if (!marker) return "";
+ const position = Number.isFinite(marker.position) ? marker.position : 0;
+ const color = this._getEffectiveMarkerColor(marker);
+ const contrastColor = this._getMarkerContrastColor(color);
+ const display = marker.visible ? "" : "none";
+ const defaultShape = marker.type === "target" ? "diamond" : marker.type === "generic" ? "circle" : "triangle";
+ const shape = normalizeMarkerShape(marker.shape, defaultShape);
+ const lane = (_a = marker.lane) != null ? _a : marker.type === "peak" ? "above" : "below";
+ const shapePaths = `