diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 58bc5c4..b5d260d 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -1,48 +1,67 @@ -let e="en";const t={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.all_targets":"All","notification.event_bus_target":"Event Bus (HA event bus)","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","error.panel_offline":"SPAN Panel unreachable","error.panel_reconnected":"SPAN Panel reconnected","error.panel_offline_named":"{name} unreachable","error.panel_reconnected_named":"{name} reconnected","error.discovery_failed":"Unable to connect to SPAN Panel","error.relay_failed":"Unable to toggle relay","error.shedding_failed":"Unable to update shedding priority","error.threshold_failed":"Unable to save threshold","error.graph_horizon_failed":"Unable to update graph time horizon","error.favorites_fetch_failed":"Unable to load favorites","error.favorites_toggle_failed":"Unable to update favorite","error.history_failed":"Unable to load historical data","error.monitoring_failed":"Unable to load monitoring status","error.graph_settings_failed":"Unable to load graph settings","error.areas_failed":"Area assignments may be out of sync","error.retry":"Retry","card.connecting":"Connecting to SPAN Panel...","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.configure_subdevice":"Configure device","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.favorites_subtitle":"Favorites","sidepanel.global_default":"Global Default","sidepanel.list_view_columns":"List View Columns","sidepanel.columns":"Columns","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.favorite":"Favorite","sidepanel.save_to_favorites":"Save to favorites","panel.favorites":"Favorites","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (bus de eventos de HA)","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","error.panel_offline":"SPAN Panel inaccesible","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inaccesible","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"No se puede conectar al SPAN Panel","error.relay_failed":"No se pudo cambiar el relé","error.shedding_failed":"No se pudo actualizar la prioridad de desconexión","error.threshold_failed":"No se pudo guardar el umbral","error.graph_horizon_failed":"No se pudo actualizar el horizonte temporal del gráfico","error.favorites_fetch_failed":"No se pudieron cargar los favoritos","error.favorites_toggle_failed":"No se pudo actualizar el favorito","error.history_failed":"No se pudieron cargar los datos históricos","error.monitoring_failed":"No se pudo cargar el estado de monitoreo","error.graph_settings_failed":"No se pudo cargar la configuración del gráfico","error.areas_failed":"Las asignaciones de áreas pueden estar desincronizadas","error.retry":"Reintentar","card.connecting":"Conectando al SPAN Panel...","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.favorites_subtitle":"Favoritos","sidepanel.global_default":"Predeterminado Global","sidepanel.list_view_columns":"Columnas de la lista","sidepanel.columns":"Columnas","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Guardar en favoritos","panel.favorites":"Favoritos","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (bus d'événements HA)","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","error.panel_offline":"SPAN Panel inaccessible","error.panel_reconnected":"SPAN Panel reconnecté","error.panel_offline_named":"{name} inaccessible","error.panel_reconnected_named":"{name} reconnecté","error.discovery_failed":"Impossible de se connecter au SPAN Panel","error.relay_failed":"Impossible de basculer le relais","error.shedding_failed":"Impossible de mettre à jour la priorité de délestage","error.threshold_failed":"Impossible d'enregistrer le seuil","error.graph_horizon_failed":"Impossible de mettre à jour l'horizon temporel du graphique","error.favorites_fetch_failed":"Impossible de charger les favoris","error.favorites_toggle_failed":"Impossible de mettre à jour le favori","error.history_failed":"Impossible de charger les données historiques","error.monitoring_failed":"Impossible de charger l'état de surveillance","error.graph_settings_failed":"Impossible de charger les paramètres du graphique","error.areas_failed":"Les affectations de zones peuvent être désynchronisées","error.retry":"Réessayer","card.connecting":"Connexion au SPAN Panel...","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.configure_subdevice":"Configurer l'appareil","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.favorites_subtitle":"Favoris","sidepanel.global_default":"Défaut Global","sidepanel.list_view_columns":"Colonnes de la liste","sidepanel.columns":"Colonnes","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.favorite":"Favori","sidepanel.save_to_favorites":"Enregistrer dans les favoris","panel.favorites":"Favoris","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.all_targets":"すべて","notification.event_bus_target":"イベントバス (HAイベントバス)","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","error.panel_offline":"SPANパネルに接続できません","error.panel_reconnected":"SPANパネルが再接続されました","error.panel_offline_named":"{name}に接続できません","error.panel_reconnected_named":"{name}が再接続されました","error.discovery_failed":"SPANパネルへの接続に失敗しました","error.relay_failed":"リレーの切り替えに失敗しました","error.shedding_failed":"シェディング優先度の更新に失敗しました","error.threshold_failed":"しきい値の保存に失敗しました","error.graph_horizon_failed":"グラフの時間範囲の更新に失敗しました","error.favorites_fetch_failed":"お気に入りの読み込みに失敗しました","error.favorites_toggle_failed":"お気に入りの更新に失敗しました","error.history_failed":"履歴データの読み込みに失敗しました","error.monitoring_failed":"監視ステータスの読み込みに失敗しました","error.graph_settings_failed":"グラフ設定の読み込みに失敗しました","error.areas_failed":"エリア割り当てが同期されていない可能性があります","error.retry":"再試行","card.connecting":"SPANパネルに接続中...","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.configure_subdevice":"デバイスを設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.favorites_subtitle":"お気に入り","sidepanel.global_default":"グローバルデフォルト","sidepanel.list_view_columns":"リスト表示の列数","sidepanel.columns":"列","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.favorite":"お気に入り","sidepanel.save_to_favorites":"お気に入りに保存","panel.favorites":"お気に入り","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (barramento de eventos do HA)","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","error.panel_offline":"SPAN Panel inacessível","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inacessível","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"Não foi possível conectar ao SPAN Panel","error.relay_failed":"Não foi possível alternar o relé","error.shedding_failed":"Não foi possível atualizar a prioridade de desligamento","error.threshold_failed":"Não foi possível salvar o limite","error.graph_horizon_failed":"Não foi possível atualizar o horizonte temporal do gráfico","error.favorites_fetch_failed":"Não foi possível carregar os favoritos","error.favorites_toggle_failed":"Não foi possível atualizar o favorito","error.history_failed":"Não foi possível carregar os dados históricos","error.monitoring_failed":"Não foi possível carregar o status de monitoramento","error.graph_settings_failed":"Não foi possível carregar as configurações do gráfico","error.areas_failed":"As atribuições de áreas podem estar fora de sincronização","error.retry":"Tentar novamente","card.connecting":"Conectando ao SPAN Panel...","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.favorites_subtitle":"Favoritos","sidepanel.global_default":"Padrão Global","sidepanel.list_view_columns":"Colunas da Lista","sidepanel.columns":"Colunas","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Salvar nos favoritos","panel.favorites":"Favoritos","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};function n(n){e=n&&t[n]?n:"en"}function i(n){return t[e]?.[n]??t.en?.[n]??n}function s(n,i){return(t[e]?.[n]??t.en?.[n]??n).replace(/\{(\w+)\}/g,(e,t)=>Object.prototype.hasOwnProperty.call(i,t)?i[t]:`{${t}}`)}const o="power",r="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},l="span_panel",c="CLOSED",d="pv",h="bess",p="evse",u="sub_",g=500,f={power:{entityRole:"power",label:()=>i("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>i("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},_={soc:{entityRole:"soc",label:()=>i("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>i("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:f.power},m={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>i("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>i("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>i("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>i("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>i("shedding.unknown")}},v="#ff9800";function b(e,t,n,i){var s,o=arguments.length,r=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)r=Reflect.decorate(e,t,n,i);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(r=(o<3?s(r):o>3?s(t,n,r):s(t,n))||r);return o>3&&r&&Object.defineProperty(t,n,r),r}"function"==typeof SuppressedError&&SuppressedError; +let t="en";const e={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.all_targets":"All","notification.event_bus_target":"Event Bus (HA event bus)","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","error.panel_offline":"SPAN Panel unreachable","error.panel_reconnected":"SPAN Panel reconnected","error.panel_offline_named":"{name} unreachable","error.panel_reconnected_named":"{name} reconnected","error.discovery_failed":"Unable to connect to SPAN Panel","error.relay_failed":"Unable to toggle relay","error.shedding_failed":"Unable to update shedding priority","error.threshold_failed":"Unable to save threshold","error.graph_horizon_failed":"Unable to update graph time horizon","error.favorites_fetch_failed":"Unable to load favorites","error.favorites_toggle_failed":"Unable to update favorite","error.history_failed":"Unable to load historical data","error.monitoring_failed":"Unable to load monitoring status","error.graph_settings_failed":"Unable to load graph settings","error.areas_failed":"Area assignments may be out of sync","error.retry":"Retry","card.connecting":"Connecting to SPAN Panel...","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.configure_subdevice":"Configure device","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.favorites_subtitle":"Favorites","sidepanel.global_default":"Global Default","sidepanel.list_view_columns":"List View Columns","sidepanel.columns":"Columns","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.favorite":"Favorite","sidepanel.save_to_favorites":"Save to favorites","panel.favorites":"Favorites","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (bus de eventos de HA)","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","error.panel_offline":"SPAN Panel inaccesible","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inaccesible","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"No se puede conectar al SPAN Panel","error.relay_failed":"No se pudo cambiar el relé","error.shedding_failed":"No se pudo actualizar la prioridad de desconexión","error.threshold_failed":"No se pudo guardar el umbral","error.graph_horizon_failed":"No se pudo actualizar el horizonte temporal del gráfico","error.favorites_fetch_failed":"No se pudieron cargar los favoritos","error.favorites_toggle_failed":"No se pudo actualizar el favorito","error.history_failed":"No se pudieron cargar los datos históricos","error.monitoring_failed":"No se pudo cargar el estado de monitoreo","error.graph_settings_failed":"No se pudo cargar la configuración del gráfico","error.areas_failed":"Las asignaciones de áreas pueden estar desincronizadas","error.retry":"Reintentar","card.connecting":"Conectando al SPAN Panel...","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.favorites_subtitle":"Favoritos","sidepanel.global_default":"Predeterminado Global","sidepanel.list_view_columns":"Columnas de la lista","sidepanel.columns":"Columnas","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Guardar en favoritos","panel.favorites":"Favoritos","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (bus d'événements HA)","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","error.panel_offline":"SPAN Panel inaccessible","error.panel_reconnected":"SPAN Panel reconnecté","error.panel_offline_named":"{name} inaccessible","error.panel_reconnected_named":"{name} reconnecté","error.discovery_failed":"Impossible de se connecter au SPAN Panel","error.relay_failed":"Impossible de basculer le relais","error.shedding_failed":"Impossible de mettre à jour la priorité de délestage","error.threshold_failed":"Impossible d'enregistrer le seuil","error.graph_horizon_failed":"Impossible de mettre à jour l'horizon temporel du graphique","error.favorites_fetch_failed":"Impossible de charger les favoris","error.favorites_toggle_failed":"Impossible de mettre à jour le favori","error.history_failed":"Impossible de charger les données historiques","error.monitoring_failed":"Impossible de charger l'état de surveillance","error.graph_settings_failed":"Impossible de charger les paramètres du graphique","error.areas_failed":"Les affectations de zones peuvent être désynchronisées","error.retry":"Réessayer","card.connecting":"Connexion au SPAN Panel...","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.configure_subdevice":"Configurer l'appareil","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.favorites_subtitle":"Favoris","sidepanel.global_default":"Défaut Global","sidepanel.list_view_columns":"Colonnes de la liste","sidepanel.columns":"Colonnes","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.favorite":"Favori","sidepanel.save_to_favorites":"Enregistrer dans les favoris","panel.favorites":"Favoris","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.all_targets":"すべて","notification.event_bus_target":"イベントバス (HAイベントバス)","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","error.panel_offline":"SPANパネルに接続できません","error.panel_reconnected":"SPANパネルが再接続されました","error.panel_offline_named":"{name}に接続できません","error.panel_reconnected_named":"{name}が再接続されました","error.discovery_failed":"SPANパネルへの接続に失敗しました","error.relay_failed":"リレーの切り替えに失敗しました","error.shedding_failed":"シェディング優先度の更新に失敗しました","error.threshold_failed":"しきい値の保存に失敗しました","error.graph_horizon_failed":"グラフの時間範囲の更新に失敗しました","error.favorites_fetch_failed":"お気に入りの読み込みに失敗しました","error.favorites_toggle_failed":"お気に入りの更新に失敗しました","error.history_failed":"履歴データの読み込みに失敗しました","error.monitoring_failed":"監視ステータスの読み込みに失敗しました","error.graph_settings_failed":"グラフ設定の読み込みに失敗しました","error.areas_failed":"エリア割り当てが同期されていない可能性があります","error.retry":"再試行","card.connecting":"SPANパネルに接続中...","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.configure_subdevice":"デバイスを設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.favorites_subtitle":"お気に入り","sidepanel.global_default":"グローバルデフォルト","sidepanel.list_view_columns":"リスト表示の列数","sidepanel.columns":"列","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.favorite":"お気に入り","sidepanel.save_to_favorites":"お気に入りに保存","panel.favorites":"お気に入り","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (barramento de eventos do HA)","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","error.panel_offline":"SPAN Panel inacessível","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inacessível","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"Não foi possível conectar ao SPAN Panel","error.relay_failed":"Não foi possível alternar o relé","error.shedding_failed":"Não foi possível atualizar a prioridade de desligamento","error.threshold_failed":"Não foi possível salvar o limite","error.graph_horizon_failed":"Não foi possível atualizar o horizonte temporal do gráfico","error.favorites_fetch_failed":"Não foi possível carregar os favoritos","error.favorites_toggle_failed":"Não foi possível atualizar o favorito","error.history_failed":"Não foi possível carregar os dados históricos","error.monitoring_failed":"Não foi possível carregar o status de monitoramento","error.graph_settings_failed":"Não foi possível carregar as configurações do gráfico","error.areas_failed":"As atribuições de áreas podem estar fora de sincronização","error.retry":"Tentar novamente","card.connecting":"Conectando ao SPAN Panel...","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.favorites_subtitle":"Favoritos","sidepanel.global_default":"Padrão Global","sidepanel.list_view_columns":"Colunas da Lista","sidepanel.columns":"Colunas","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Salvar nos favoritos","panel.favorites":"Favoritos","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};function n(n){t=n&&e[n]?n:"en"}function i(n){return e[t]?.[n]??e.en?.[n]??n}function r(n,i){return(e[t]?.[n]??e.en?.[n]??n).replace(/\{(\w+)\}/g,(t,e)=>Object.prototype.hasOwnProperty.call(i,e)?i[e]:`{${e}}`)}const o="power",a="5m",s={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},l="span_panel",c="CLOSED",u="pv",h="bess",d="evse",p="sub_",f=500,g={power:{entityRole:"power",label:()=>i("metric.power"),unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>{const e=Math.abs(t);return e>=1e3?(e/1e3).toFixed(1):e<10&&e>0?e.toFixed(1):String(Math.round(e))}},current:{entityRole:"current",label:()=>i("metric.current"),unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},v={soc:{entityRole:"soc",label:()=>i("metric.soc"),unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>i("metric.soe"),unit:()=>"kWh",format:t=>t.toFixed(1)},power:g.power},m={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>i("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>i("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>i("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>i("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>i("shedding.unknown")}};var y=function(t,e){return y=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},y(t,e)};function _(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}y(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}function x(t,e,n,i){var r,o=arguments.length,a=o<3?e:null===i?i=Object.getOwnPropertyDescriptor(e,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(t,e,n,i);else for(var s=t.length-1;s>=0;s--)(r=t[s])&&(a=(o<3?r(a):o>3?r(e,n,a):r(e,n))||a);return o>3&&a&&Object.defineProperty(e,n,a),a}"function"==typeof SuppressedError&&SuppressedError; /** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const y=globalThis,w=y.ShadowRoot&&(void 0===y.ShadyCSS||y.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,x=Symbol(),S=new WeakMap;let C=class{constructor(e,t,n){if(this._$cssResult$=!0,n!==x)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(w&&void 0===e){const n=void 0!==t&&1===t.length;n&&(e=S.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&S.set(t,e))}return e}toString(){return this.cssText}};const $=e=>new C("string"==typeof e?e:e+"",void 0,x),E=w?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const n of e.cssRules)t+=n.cssText;return $(t)})(e):e,{is:k,defineProperty:z,getOwnPropertyDescriptor:P,getOwnPropertyNames:A,getOwnPropertySymbols:N,getPrototypeOf:M}=Object,L=globalThis,I=L.trustedTypes,D=I?I.emptyScript:"",T=L.reactiveElementPolyfillSupport,H=(e,t)=>e,O={toAttribute(e,t){switch(t){case Boolean:e=e?D:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},F=(e,t)=>!k(e,t),R={attribute:!0,type:String,converter:O,reflect:!1,useDefault:!1,hasChanged:F}; +const b=globalThis,w=b.ShadowRoot&&(void 0===b.ShadyCSS||b.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,S=Symbol(),C=new WeakMap;let M=class{constructor(t,e,n){if(this._$cssResult$=!0,n!==S)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(w&&void 0===t){const n=void 0!==e&&1===e.length;n&&(t=C.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),n&&C.set(e,t))}return t}toString(){return this.cssText}};const k=t=>new M("string"==typeof t?t:t+"",void 0,S),T=(t,...e)=>{const n=1===t.length?t[0]:e.reduce((e,n,i)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+t[i+1],t[0]);return new M(n,t,S)},D=w?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const n of t.cssRules)e+=n.cssText;return k(e)})(t):t,{is:I,defineProperty:A,getOwnPropertyDescriptor:P,getOwnPropertyNames:L,getOwnPropertySymbols:E,getPrototypeOf:z}=Object,O=globalThis,N=O.trustedTypes,R=N?N.emptyScript:"",H=O.reactiveElementPolyfillSupport,B=(t,e)=>t,F={toAttribute(t,e){switch(e){case Boolean:t=t?R:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let n=t;switch(e){case Boolean:n=null!==t;break;case Number:n=null===t?null:Number(t);break;case Object:case Array:try{n=JSON.parse(t)}catch(t){n=null}}return n}},$=(t,e)=>!I(t,e),V={attribute:!0,type:String,converter:F,reflect:!1,useDefault:!1,hasChanged:$}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */Symbol.metadata??=Symbol("metadata"),L.litPropertyMetadata??=new WeakMap;let j=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=R){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const n=Symbol(),i=this.getPropertyDescriptor(e,n,t);void 0!==i&&z(this.prototype,e,i)}}static getPropertyDescriptor(e,t,n){const{get:i,set:s}=P(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const o=i?.call(this);s?.call(this,t),this.requestUpdate(e,o,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??R}static _$Ei(){if(this.hasOwnProperty(H("elementProperties")))return;const e=M(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(H("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(H("properties"))){const e=this.properties,t=[...A(e),...N(e)];for(const n of t)this.createProperty(n,e[n])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,n]of t)this.elementProperties.set(e,n)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const n=this._$Eu(e,t);void 0!==n&&this._$Eh.set(n,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const n=new Set(e.flat(1/0).reverse());for(const e of n)t.unshift(E(e))}else void 0!==e&&t.push(E(e));return t}static _$Eu(e,t){const n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const n of t.keys())this.hasOwnProperty(n)&&(e.set(n,this[n]),delete this[n]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(w)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const n of t){const t=document.createElement("style"),i=y.litNonce;void 0!==i&&t.setAttribute("nonce",i),t.textContent=n.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,n){this._$AK(e,n)}_$ET(e,t){const n=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,n);if(void 0!==i&&!0===n.reflect){const s=(void 0!==n.converter?.toAttribute?n.converter:O).toAttribute(t,n.type);this._$Em=e,null==s?this.removeAttribute(i):this.setAttribute(i,s),this._$Em=null}}_$AK(e,t){const n=this.constructor,i=n._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=n.getPropertyOptions(i),s="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:O;this._$Em=i;const o=s.fromAttribute(t,e.type);this[i]=o??this._$Ej?.get(i)??o,this._$Em=null}}requestUpdate(e,t,n,i=!1,s){if(void 0!==e){const o=this.constructor;if(!1===i&&(s=this[e]),n??=o.getPropertyOptions(e),!((n.hasChanged??F)(s,t)||n.useDefault&&n.reflect&&s===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:n,reflect:i,wrapped:s},o){n&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==s||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||n||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,n]of e){const{wrapped:e}=n,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,n,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};j.elementStyles=[],j.shadowRootOptions={mode:"open"},j[H("elementProperties")]=new Map,j[H("finalized")]=new Map,T?.({ReactiveElement:j}),(L.reactiveElementVersions??=[]).push("2.1.2"); + */Symbol.metadata??=Symbol("metadata"),O.litPropertyMetadata??=new WeakMap;let W=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=V){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const n=Symbol(),i=this.getPropertyDescriptor(t,n,e);void 0!==i&&A(this.prototype,t,i)}}static getPropertyDescriptor(t,e,n){const{get:i,set:r}=P(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:i,set(e){const o=i?.call(this);r?.call(this,e),this.requestUpdate(t,o,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??V}static _$Ei(){if(this.hasOwnProperty(B("elementProperties")))return;const t=z(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(B("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(B("properties"))){const t=this.properties,e=[...L(t),...E(t)];for(const n of e)this.createProperty(n,t[n])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,n]of e)this.elementProperties.set(t,n)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const n=this._$Eu(t,e);void 0!==n&&this._$Eh.set(n,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const n=new Set(t.flat(1/0).reverse());for(const t of n)e.unshift(D(t))}else void 0!==t&&e.push(D(t));return e}static _$Eu(t,e){const n=e.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const n of e.keys())this.hasOwnProperty(n)&&(t.set(n,this[n]),delete this[n]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((t,e)=>{if(w)t.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const n of e){const e=document.createElement("style"),i=b.litNonce;void 0!==i&&e.setAttribute("nonce",i),e.textContent=n.cssText,t.appendChild(e)}})(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,n){this._$AK(t,n)}_$ET(t,e){const n=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,n);if(void 0!==i&&!0===n.reflect){const r=(void 0!==n.converter?.toAttribute?n.converter:F).toAttribute(e,n.type);this._$Em=t,null==r?this.removeAttribute(i):this.setAttribute(i,r),this._$Em=null}}_$AK(t,e){const n=this.constructor,i=n._$Eh.get(t);if(void 0!==i&&this._$Em!==i){const t=n.getPropertyOptions(i),r="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:F;this._$Em=i;const o=r.fromAttribute(e,t.type);this[i]=o??this._$Ej?.get(i)??o,this._$Em=null}}requestUpdate(t,e,n,i=!1,r){if(void 0!==t){const o=this.constructor;if(!1===i&&(r=this[t]),n??=o.getPropertyOptions(t),!((n.hasChanged??$)(r,e)||n.useDefault&&n.reflect&&r===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,n))))return;this.C(t,e,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:n,reflect:i,wrapped:r},o){n&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,o??e??this[t]),!0!==r||void 0!==o)||(this._$AL.has(t)||(this.hasUpdated||n||(e=void 0),this._$AL.set(t,e)),!0===i&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,n]of t){const{wrapped:t}=n,i=this[e];!0!==t||this._$AL.has(e)||void 0===i||this.C(e,void 0,n,i)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};W.elementStyles=[],W.shadowRootOptions={mode:"open"},W[B("elementProperties")]=new Map,W[B("finalized")]=new Map,H?.({ReactiveElement:W}),(O.reactiveElementVersions??=[]).push("2.1.2"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const q=globalThis,U=e=>e,W=q.trustedTypes,B=W?W.createPolicy("lit-html",{createHTML:e=>e}):void 0,G="$lit$",V=`lit$${Math.random().toFixed(9).slice(2)}$`,Q="?"+V,K=`<${Q}>`,J=document,X=()=>J.createComment(""),Z=e=>null===e||"object"!=typeof e&&"function"!=typeof e,Y=Array.isArray,ee="[ \t\n\f\r]",te=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ne=/-->/g,ie=/>/g,se=RegExp(`>|${ee}(?:([^\\s"'>=/]+)(${ee}*=${ee}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),oe=/'/g,re=/"/g,ae=/^(?:script|style|textarea|title)$/i,le=(e=>(t,...n)=>({_$litType$:e,strings:t,values:n}))(1),ce=Symbol.for("lit-noChange"),de=Symbol.for("lit-nothing"),he=new WeakMap,pe=J.createTreeWalker(J,129);function ue(e,t){if(!Y(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==B?B.createHTML(t):t}const ge=(e,t)=>{const n=e.length-1,i=[];let s,o=2===t?"":3===t?"":"",r=te;for(let t=0;t"===l[0]?(r=s??te,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?se:'"'===l[3]?re:oe):r===re||r===oe?r=se:r===ne||r===ie?r=te:(r=se,s=void 0);const h=r===se&&e[t+1].startsWith("/>")?" ":"";o+=r===te?n+K:c>=0?(i.push(a),n.slice(0,c)+G+n.slice(c)+V+h):n+V+(-2===c?t:h)}return[ue(e,o+(e[n]||"")+(2===t?"":3===t?"":"")),i]};class fe{constructor({strings:e,_$litType$:t},n){let i;this.parts=[];let s=0,o=0;const r=e.length-1,a=this.parts,[l,c]=ge(e,t);if(this.el=fe.createElement(l,n),pe.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(i=pe.nextNode())&&a.length0){i.textContent=W?W.emptyScript:"";for(let n=0;nY(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==de&&Z(this._$AH)?this._$AA.nextSibling.data=e:this.T(J.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:n}=e,i="number"==typeof n?this._$AC(e):(void 0===n.el&&(n.el=fe.createElement(ue(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new me(i,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=he.get(e.strings);return void 0===t&&he.set(e.strings,t=new fe(e)),t}k(e){Y(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let n,i=0;for(const s of e)i===t.length?t.push(n=new ve(this.O(X()),this.O(X()),this,this.options)):n=t[i],n._$AI(s),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=de}_$AI(e,t=this,n,i){const s=this.strings;let o=!1;if(void 0===s)e=_e(this,e,t,0),o=!Z(e)||e!==this._$AH&&e!==ce,o&&(this._$AH=e);else{const i=e;let r,a;for(e=s[0],r=0;rt,q=U.trustedTypes,j=q?q.createPolicy("lit-html",{createHTML:t=>t}):void 0,X="$lit$",Y=`lit$${Math.random().toFixed(9).slice(2)}$`,Z="?"+Y,K=`<${Z}>`,Q=document,J=()=>Q.createComment(""),tt=t=>null===t||"object"!=typeof t&&"function"!=typeof t,et=Array.isArray,nt="[ \t\n\f\r]",it=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,rt=/-->/g,ot=/>/g,at=RegExp(`>|${nt}(?:([^\\s"'>=/]+)(${nt}*=${nt}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),st=/'/g,lt=/"/g,ct=/^(?:script|style|textarea|title)$/i,ut=t=>(e,...n)=>({_$litType$:t,strings:e,values:n}),ht=ut(1),dt=ut(2),pt=Symbol.for("lit-noChange"),ft=Symbol.for("lit-nothing"),gt=new WeakMap,vt=Q.createTreeWalker(Q,129);function mt(t,e){if(!et(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==j?j.createHTML(e):e}const yt=(t,e)=>{const n=t.length-1,i=[];let r,o=2===e?"":3===e?"":"",a=it;for(let e=0;e"===l[0]?(a=r??it,c=-1):void 0===l[1]?c=-2:(c=a.lastIndex-l[2].length,s=l[1],a=void 0===l[3]?at:'"'===l[3]?lt:st):a===lt||a===st?a=at:a===rt||a===ot?a=it:(a=at,r=void 0);const h=a===at&&t[e+1].startsWith("/>")?" ":"";o+=a===it?n+K:c>=0?(i.push(s),n.slice(0,c)+X+n.slice(c)+Y+h):n+Y+(-2===c?e:h)}return[mt(t,o+(t[n]||"")+(2===e?"":3===e?"":"")),i]};class _t{constructor({strings:t,_$litType$:e},n){let i;this.parts=[];let r=0,o=0;const a=t.length-1,s=this.parts,[l,c]=yt(t,e);if(this.el=_t.createElement(l,n),vt.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(i=vt.nextNode())&&s.length0){i.textContent=q?q.emptyScript:"";for(let n=0;net(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==ft&&tt(this._$AH)?this._$AA.nextSibling.data=t:this.T(Q.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:n}=t,i="number"==typeof n?this._$AC(t):(void 0===n.el&&(n.el=_t.createElement(mt(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===i)this._$AH.p(e);else{const t=new bt(i,this),n=t.u(this.options);t.p(e),this.T(n),this._$AH=t}}_$AC(t){let e=gt.get(t.strings);return void 0===e&>.set(t.strings,e=new _t(t)),e}k(t){et(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let n,i=0;for(const r of t)i===e.length?e.push(n=new wt(this.O(J()),this.O(J()),this,this.options)):n=e[i],n._$AI(r),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=ft}_$AI(t,e=this,n,i){const r=this.strings;let o=!1;if(void 0===r)t=xt(this,t,e,0),o=!tt(t)||t!==this._$AH&&t!==pt,o&&(this._$AH=t);else{const i=t;let a,s;for(t=r[0],a=0;a{const i=n?.renderBefore??t;let s=i._$litPart$;if(void 0===s){const e=n?.renderBefore??null;i._$litPart$=s=new ve(t.insertBefore(X(),e),e,void 0,n??{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ce}}Ee._$litElement$=!0,Ee.finalized=!0,$e.litElementHydrateSupport?.({LitElement:Ee});const ke=$e.litElementPolyfillSupport;ke?.({LitElement:Ee}),($e.litElementVersions??=[]).push("4.2.2"); + */class At extends W{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=((t,e,n)=>{const i=n?.renderBefore??e;let r=i._$litPart$;if(void 0===r){const t=n?.renderBefore??null;i._$litPart$=r=new wt(e.insertBefore(J(),t),t,void 0,n??{})}return r._$AI(t),r})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return pt}}At._$litElement$=!0,At.finalized=!0,It.litElementHydrateSupport?.({LitElement:At});const Pt=It.litElementPolyfillSupport;Pt?.({LitElement:At}),(It.litElementVersions??=[]).push("4.2.2"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const ze=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)},Pe={attribute:!0,type:String,converter:O,reflect:!1,hasChanged:F},Ae=(e=Pe,t,n)=>{const{kind:i,metadata:s}=n;let o=globalThis.litPropertyMetadata.get(s);if(void 0===o&&globalThis.litPropertyMetadata.set(s,o=new Map),"setter"===i&&((e=Object.create(e)).wrapped=!0),o.set(n.name,e),"accessor"===i){const{name:i}=n;return{set(n){const s=t.get.call(this);t.set.call(this,n),this.requestUpdate(i,s,e,!0,n)},init(t){return void 0!==t&&this.C(i,void 0,e,t),t}}}if("setter"===i){const{name:i}=n;return function(n){const s=this[i];t.call(this,n),this.requestUpdate(i,s,e,!0,n)}}throw Error("Unsupported decorator location: "+i)}; +const Lt={attribute:!0,type:String,converter:F,reflect:!1,hasChanged:$},Et=(t=Lt,e,n)=>{const{kind:i,metadata:r}=n;let o=globalThis.litPropertyMetadata.get(r);if(void 0===o&&globalThis.litPropertyMetadata.set(r,o=new Map),"setter"===i&&((t=Object.create(t)).wrapped=!0),o.set(n.name,t),"accessor"===i){const{name:i}=n;return{set(n){const r=e.get.call(this);e.set.call(this,n),this.requestUpdate(i,r,t,!0,n)},init(e){return void 0!==e&&this.C(i,void 0,t,e),e}}}if("setter"===i){const{name:i}=n;return function(n){const r=this[i];e.call(this,n),this.requestUpdate(i,r,t,!0,n)}}throw Error("Unsupported decorator location: "+i)}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function Ne(e){return(t,n)=>"object"==typeof n?Ae(e,t,n):((e,t,n)=>{const i=t.hasOwnProperty(n);return t.constructor.createProperty(n,e),i?Object.getOwnPropertyDescriptor(t,n):void 0})(e,t,n)} + */function zt(t){return(e,n)=>"object"==typeof n?Et(t,e,n):((t,e,n)=>{const i=e.hasOwnProperty(n);return e.constructor.createProperty(n,t),i?Object.getOwnPropertyDescriptor(e,n):void 0})(t,e,n)} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function Me(e){return Ne({...e,state:!0,attribute:!1})}const Le={"&":"&","<":"<",">":">",'"':""","'":"'"};function Ie(e){return String(e).replace(/[&<>"']/g,e=>Le[e]??e)}const De="span_panel_list_columns";function Te(){try{const e=localStorage.getItem(De);if(!e)return 1;const t=parseInt(e,10);return 1===t||2===t||3===t?t:1}catch{return 1}}function He(e){try{localStorage.setItem(De,String(e))}catch{}}function Oe(e,t,n={}){const s=Ie(e.device_name||i("header.default_name")),o=Ie(e.serial||""),r=Ie(e.firmware||""),a="current"===(t.chart_metric||"power"),l=!1!==n.showSwitches;return`\n
\n
\n
\n

${s}

\n ${o}\n \n ${l?`
\n ${Ie(i("header.enable_switches"))}\n
\n \n
\n
`:""}\n
\n ${function(e,t){const n="current"===(t.chart_metric||"power"),s=!!e.panel_entities?.site_power,o=!!e.panel_entities?.dsm_state,r=!!e.panel_entities?.current_power,a=!!e.panel_entities?.feedthrough_power,l=!!e.panel_entities?.pv_power,c=!!e.panel_entities?.battery_level;return`\n
\n ${s?`\n
\n ${i("header.site")}\n
\n 0\n ${n?"A":"kW"}\n
\n
`:""}\n ${o?`\n
\n ${i("header.grid")}\n
\n --\n
\n
`:""}\n ${r?`\n
\n ${i("header.upstream")}\n
\n --\n ${n?"A":"kW"}\n
\n
`:""}\n ${a?`\n
\n ${i("header.downstream")}\n
\n --\n ${n?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${i("header.solar")}\n
\n --\n ${n?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${i("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n `}(e,t)}\n
\n
\n
\n ${r}\n
\n \n \n
\n
\n
\n ${Object.entries(m).filter(([e])=>"unknown"!==e).map(([,e])=>{const t=Ie(e.icon),n=Ie(e.color),i=Ie(e.label());let s;return s=e.icon2?``:e.textLabel?`${Ie(e.textLabel)}`:``,`
${s}${i}
`}).join("")}\n
\n
\n
\n `}const Fe=f.power;function Re(e){return Fe.unit(e)}function je(e){return(e<0?"-":"")+Fe.format(e)}function qe(e){return(Math.abs(e)/1e3).toFixed(1)}function Ue(e){return Math.ceil(e/2)}function We(e){return e%2==0?1:0}function Be(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return Ue(t)===Ue(n)?"row-span":We(t)===We(n)?"col-span":"row-span"}function Ge(e){const t=e.chart_metric??o;return f[t]??f[o]}function Ve(e,t){const n=function(e){return Ge(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}function Qe(e){return new Promise(t=>setTimeout(t,e))}class Ke{constructor(e){this._store=e}async callWS(e,t,n){const i=n?.retries??3,s=n?.errorId??`ws:${String(t.type??"unknown")}`;return this._withRetry(()=>e.callWS(t),i,s,n?.errorMessage)}async callService(e,t,n,i,s,o){const r=o?.retries??3,a=o?.errorId??`svc:${t}.${n}`;return this._withRetry(()=>e.callService(t,n,i,s),r,a,o?.errorMessage)}async _withRetry(e,t,n,s){if(this._store.hasAnyPanelOffline())try{const t=await e();return this._store.remove(n),t}catch(e){const t=e instanceof Error?e:new Error(String(e));throw this._store.add({key:n,level:"error",message:s??i("error.panel_offline"),persistent:!1}),t}let o;for(let i=0;i<=t;i++)try{const t=await e();return this._store.remove(n),t}catch(e){if(o=e instanceof Error?e:new Error(String(e)),i{try{const n={};t&&(n.config_entry_id=t);const o={type:"call_service",domain:l,service:"get_monitoring_status",service_data:n,return_response:!0},r=this._retry?await this._retry.callWS(e,o,{errorId:"fetch:monitoring",errorMessage:i("error.monitoring_failed")}):await e.callWS(o),a=r?.response??null;return s===this._generation&&(this._status=a,this._lastFetch=Date.now()),a}catch(e){return console.warn("SPAN Panel: monitoring status fetch failed",e),s===this._generation&&(this._status=null),this._retry||this._errorStore?.add({key:"fetch:monitoring",level:"warning",message:i("error.monitoring_failed"),persistent:!1}),null}finally{this._inflight?.gen===s&&(this._inflight=null)}})();return this._inflight={gen:s,promise:o},o}invalidate(){this._lastFetch=0,this._generation++}get status(){return this._status}clear(){this._status=null,this._lastFetch=0,this._generation++}}class Xe{constructor(){this._caches=new Map,this._errorStore=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e;for(const t of this._caches.values())t.errorStore=e}async fetchOne(e,t){let n=this._caches.get(t);return n||(n=new Je,n.errorStore=this._errorStore,this._caches.set(t,n)),n.fetch(e,t)}invalidate(){for(const e of this._caches.values())e.invalidate()}clear(){this._caches.clear()}}function Ze(e,t){return e?.circuits?e.circuits[t]??null:null}function Ye(e){return!!e&&void 0!==e.continuous_threshold_pct}function et(e,t,n,i){const s=[];return n||s.push("circuit-off"),i&&s.push("circuit-producer"),function(e){return!!e&&null!=e.over_threshold_since}(t)&&s.push("circuit-alert"),Ye(t)&&s.push("circuit-custom-monitoring"),s.join(" ")}function tt(e,t,n,s,o,r,a,l,h,p=!1){const u=t.entities?.power,g=u?r.states[u]:null,f=g&&parseFloat(g.state)||0,_=t.device_type===d||f<0,b=t.entities?.switch,y=b?r.states[b]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===c,x=t.breaker_rating_a,S=x?`${Math.round(x)}A`:"",C=Ie(t.name||i("grid.unknown")),$=Ge(a);let E;if("current"===$.entityRole){const e=t.entities?.current,n=e?r.states[e]:null,i=n&&parseFloat(n.state)||0;E=`${$.format(i)}A`}else E=`${je(f)}${Re(f)}`;const k=h||"unknown";let z="";if("unknown"!==k){const e=m[k]??m.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"},t=Ie(e.label()),n=Ie(e.icon),i=Ie(e.color);if(e.icon2){z=`\n \n \n `}else if(e.textLabel){z=`\n \n ${Ie(e.textLabel)}\n `}else z=``}const P=l&&Ye(l)?v:"#555",A=``;let N="",M=l?.utilization_pct??null;if(null==M&&t.breaker_rating_a){const e=t.entities?.current,n=e?r.states[e]:null,i=n?Math.abs(parseFloat(n.state)||0):0;M=Math.round(i/t.breaker_rating_a*1e3)/10}if(null!=M){N=`=80?"utilization-warning":"utilization-normal"}">${Math.round(M)}%`}return`\n
\n
\n
\n ${S?`${S}`:""}\n ${N}\n ${C}\n
\n
\n \n ${E}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${i(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${z}\n ${A}\n
\n
\n
\n `}function nt(e,t){return`\n
\n \n
\n `}const it={names:["power","battery power"],suffixes:["_power"]},st={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},ot={names:["state of energy"],suffixes:["_soe_kwh"]},rt={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function at(e,t){if(!e.entities)return null;for(const[n,i]of Object.entries(e.entities)){if("sensor"!==i.domain)continue;const e=(i.original_name??"").toLowerCase();if(t.names.some(t=>e===t))return n;if(i.unique_id&&t.suffixes.some(e=>i.unique_id.endsWith(e)))return n}return null}function lt(e){return at(e,it)}function ct(e){return at(e,st)}function dt(e){return at(e,ot)}function ht(e){return at(e,rt)}function pt(e,t,n,i){const s=n.visible_sub_entities||{};let o="";if(!e.entities)return o;for(const[n,r]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const a=t.states[n];if(!a)continue;let l=r.original_name||a.attributes.friendly_name||n;const c=e.name||"";let d;if(l.startsWith(c+" ")&&(l=l.slice(c.length+1)),t.formatEntityState)d=t.formatEntityState(a);else{d=a.state;const e=a.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(a.attributes.unit_of_measurement||"")){const e=parseFloat(a.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${Ie(l)}:\n ${Ie(d)}\n
\n `}return o}function ut(e,t,n,s,o,r){if(n){const t=[{key:`${u}${e}_soc`,title:i("subdevice.soc"),available:!!o},{key:`${u}${e}_soe`,title:i("subdevice.soe"),available:!!r},{key:`${u}${e}_power`,title:i("subdevice.power"),available:!!s}].filter(e=>e.available);return`\n
\n ${t.map(e=>`\n
\n
${Ie(e.title)}
\n
\n
\n `).join("")}\n
\n `}return s?`
`:""}function gt(e){const t=void 0!==e.history_days||void 0!==e.history_hours||void 0!==e.history_minutes,n=60*(60*(24*(t&&parseInt(String(e.history_days))||0)+(t&&parseInt(String(e.history_hours))||0))+(t?parseInt(String(e.history_minutes))||0:5))*1e3;return Math.max(n,6e4)}function ft(e){const t=a[e];return t?t.ms:a[r].ms}function _t(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function mt(e){return Math.max(500,Math.floor(e/5e3))}function vt(e,t,n,i,s,o){e.has(t)||e.set(t,[]);const r=e.get(t);r.push({time:i,value:n});const a=r.findIndex(e=>e.time>=s);a>0?r.splice(0,a):-1===a&&(r.length=0),r.length>o&&r.splice(0,r.length-o)}function bt(e,t,n=500){if(0===e.length)return e;e.sort((e,t)=>e.time-t.time);const i=[e[0]];for(let t=1;t=n&&i.push(e[t]);return i.length>t&&i.splice(0,i.length-t),i}async function yt(e,t,n,i,s){const o=new Date(Date.now()-i).toISOString(),r=i/36e5>72?"hour":"5minute",a=await e.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:t,period:r,types:["mean"]});for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const o=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&o.push({time:n,value:t})}if(o.length>0){const e=s.get(i)||[],t=[...o,...e];t.sort((e,t)=>e.time-t.time),s.set(i,t)}}}async function wt(e,t,n,i,s){const o=new Date(Date.now()-i).toISOString(),r=await e.callWS({type:"history/history_during_period",start_time:o,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),a=_t(i),l=mt(i);for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const o=[];for(const e of t){const t=parseFloat(e.s);if(!Number.isFinite(t))continue;const n=1e3*(e.lu||e.lc||0);n>0&&o.push({time:n,value:t})}if(o.length>0){const e=s.get(i)||[],t=[...o,...e];s.set(i,bt(t,a,l))}}}function xt(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:lt(i)};i.type===h&&(e.soc=ct(i),e.soe=dt(i));for(const[i,s]of Object.entries(e))s&&t.push({entityId:s,key:`${u}${n}_${i}`,devId:n})}return t}async function St(e,t,n,i,s,o){if(!t||!e)return;const r=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=Ve(i,n);if(!t)continue;let o;o=s&&s.has(e)?ft(s.get(e)):gt(n),r.has(o)||r.set(o,{entityIds:[],uuidByEntity:new Map});const a=r.get(o);a.entityIds.push(t),a.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:s}of xt(t)){let t;t=o&&o.has(s)?ft(o.get(s)):gt(n),r.has(t)||r.set(t,{entityIds:[],uuidByEntity:new Map});const a=r.get(t);a.entityIds.push(e),a.uuidByEntity.set(e,i)}const a=[];for(const[t,n]of r){if(0===n.entityIds.length)continue;t>2592e5?a.push(yt(e,n.entityIds,n.uuidByEntity,t,i)):a.push(wt(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(a)}function Ct(e,t,n,i,s,r,a,l,c){const{options:d,series:h}=function(e,t,n,i,s,r=!1){n||(n=f[o]);const a=i?"140, 160, 220":"77, 217, 175",l=`rgb(${a})`,c=Date.now(),d=c-t,h=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=(e??[]).filter(e=>e.time>=d).map(e=>[e.time,Math.abs(e.value)]),u=[{type:"line",data:p,showSymbol:!1,smooth:!1,...r?{}:{step:"end"},lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.18)`},{offset:1,color:`rgba(${a}, 0.18)`}]}},itemStyle:{color:l}}],g=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):0,_={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:g<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};h?(_.min=n.fixedMin,_.max=n.fixedMax):g<1&&(_.min=0,_.max=1),s&&"current"===n.entityRole&&(_.min=0,_.max=Math.ceil(1.25*s),u.push({type:"line",data:[[d,.8*s],[c,.8*s]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),u.push({type:"line",data:[[d,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const m={xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:_,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:e=>{if(!e||0===e.length)return"";const t=e[0],i=new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}),s=parseFloat(t.value[1].toFixed(2));return`
${i}
${n.format(s)} ${n.unit(s)}
`}},animation:!1};return{options:m,series:u}}(n,i,s,r,l,c),p=a??120;e.style.minHeight=p+"px";let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.hass=t,e.innerHTML="",e.appendChild(u));const g=e.clientHeight;u.height=(g>0?g:p)+"px",u.hass=t,u.options=d,u.data=h}function $t(e){return"function"==typeof globalThis.CSS?.escape?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function Et(e,t,n,i,s){const o=e.querySelector(".panel-stats");o&&function(e,t,n,i,s){const o="current"===(i.chart_metric||"power"),r=e.querySelector(".stat-consumption .stat-value"),a=e.querySelector(".stat-consumption .stat-unit");if(o){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,s=i?parseFloat(i.attributes?.amperage):NaN;r&&(r.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),a&&(a.textContent="A")}else{let e=s;const i=n.panel_entities?.site_power;if(i){const n=t.states[i];n&&(e=Math.abs(parseFloat(n.state)||0))}r&&(r.textContent=qe(e)),a&&(a.textContent="kW")}const l=e.querySelector(".stat-upstream .stat-value"),c=e.querySelector(".stat-upstream .stat-unit");if(l){const e=n.panel_entities?.current_power,i=e?t.states[e]:null;if(o){const e=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",c&&(c.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=qe(e),c&&(c.textContent="kW")}}const d=e.querySelector(".stat-downstream .stat-value"),h=e.querySelector(".stat-downstream .stat-unit");if(d){const e=n.panel_entities?.feedthrough_power,i=e?t.states[e]:null;if(o){const e=i?parseFloat(i.attributes?.amperage):NaN;d.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",h&&(h.textContent="A")}else{const e=i?Math.abs(parseFloat(i.state)||0):0;d.textContent=qe(e),h&&(h.textContent="kW")}}const p=e.querySelector(".stat-solar .stat-value"),u=e.querySelector(".stat-solar .stat-unit");if(p){const e=n.panel_entities?.pv_power,i=e?t.states[e]:null;if(o){const e=i?parseFloat(i.attributes?.amperage):NaN;p.textContent=Number.isFinite(e)?Math.abs(e).toFixed(1):"--",u&&(u.textContent="A")}else{if(i){const e=Math.abs(parseFloat(i.state)||0);p.textContent=qe(e)}else p.textContent="--";u&&(u.textContent="kW")}}const g=e.querySelector(".stat-battery .stat-value");if(g){const e=n.panel_entities?.battery_level,i=e?t.states[e]:null;i&&(g.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const f=e.querySelector(".stat-grid-state .stat-value");if(f){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;f.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}(o,t,n,i,s)}class kt{get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new Ke(e):null}constructor(){this._errorStore=null,this._retry=null,this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};t&&(n.config_entry_id=t);const s={type:"call_service",domain:l,service:"get_graph_settings",service_data:n,return_response:!0},o=this._retry?await this._retry.callWS(e,s,{errorId:"fetch:graph_settings",errorMessage:i("error.graph_settings_failed")}):await e.callWS(s);this._settings=o?.response??null,this._lastFetch=Date.now()}catch(e){console.warn("SPAN Panel: graph settings fetch failed",e),this._settings=null,this._retry||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:i("error.graph_settings_failed"),persistent:!1})}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}function zt(e,t){if(!e)return r;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??r}function Pt(e,t){if(!e)return r;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??r}class At{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new Je,this.monitoringMultiCache=new Xe,this.graphSettingsCache=new kt,this._errorStore=null,this._hass=null,this._topology=null,this._config=null,this._configEntryId=null,this._favRefs=null,this._perPanelInfo=new Map,this._panelFavorites=null,this._showMonitoring=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._resizeObserver=null,this._lastWidth=0,this._resizeDebounce=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this.monitoringCache.errorStore=e,this.graphSettingsCache.errorStore=e,this.monitoringMultiCache.errorStore=e}get hass(){return this._hass}set hass(e){this._hass=e}get topology(){return this._topology}get config(){return this._config}set showMonitoring(e){this._showMonitoring=e}init(e,t,n,i){this._topology=e,this._config=t,this._hass=n,this._configEntryId=i}setFavoriteRefs(e){this._favRefs=e}clearFavoriteRefs(){this._favRefs=null}setPanelFavorites(e){this._panelFavorites=e}setFavoritesPerPanelInfo(e){this._perPanelInfo=e??new Map}get _inFavoritesView(){return null!==this._favRefs}setConfig(e){this._config=e}buildHorizonMaps(e){if(this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits))this.horizonMap.set(t,zt(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,Pt(e,t))}async fetchAndBuildHorizonMaps(){try{this._favRefs?await this._buildFavoritesHorizonMaps():(await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings))}catch(e){console.warn("SPAN Panel: graph settings fetch failed",e),this.graphSettingsCache.errorStore||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:i("error.graph_settings_failed"),persistent:!1})}}async fetchMergedMonitoringStatus(e){if(!this._hass||0===e.length)return null;const t=this._hass;return function(e){let t=!1;const n={},i={};for(const s of e)s&&(t=!0,s.circuits&&Object.assign(n,s.circuits),s.mains&&Object.assign(i,s.mains));return t?{circuits:n,mains:i}:null}(await Promise.all(e.map(e=>this.monitoringMultiCache.fetchOne(t,e))))}async _buildFavoritesHorizonMaps(){if(!this._hass||!this._favRefs||!this._topology)return;const e=new Set;for(const t of Object.values(this._favRefs))t.configEntryId&&e.add(t.configEntryId);const t=new Map;await Promise.all(Array.from(e).map(async e=>{t.set(e,await this._fetchGraphSettingsFresh(e))})),this.horizonMap.clear(),this.subDeviceHorizonMap.clear();for(const e of Object.keys(this._topology.circuits)){const n=this._favRefs[e],i=n?.configEntryId?t.get(n.configEntryId)??null:null,s=n?.targetId??e;this.horizonMap.set(e,zt(i,s))}if(this._topology.sub_devices)for(const e of Object.keys(this._topology.sub_devices)){const n=this._favRefs[e],i=n?.configEntryId?t.get(n.configEntryId)??null:null,s=n?.targetId??e;this.subDeviceHorizonMap.set(e,Pt(i,s))}}async loadHistory(){await St(this._hass,this._topology,this._config,this.powerHistory,this.horizonMap,this.subDeviceHorizonMap)}recordSamples(){if(!this._topology||!this._hass||!this._config)return;const e=Date.now();for(const[t,n]of Object.entries(this._topology.circuits)){const i=this.horizonMap.get(t)??r;if(!a[i]?.useRealtime)continue;const s=Ve(n,this._config);if(!s)continue;const o=this._hass.states[s];if(!o)continue;const l=parseFloat(o.state);if(isNaN(l))continue;const c=ft(i),d=_t(c),h=mt(c),p=e-c,u=this.powerHistory.get(t)??[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time0&&this._topology)for(const{key:e,devId:t}of xt(this._topology))n.has(t)&&s.add(e);const o=new Map;try{await St(this._hass,this._topology,this._config,o,t,n);for(const e of t.keys()){const t=o.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}for(const e of s){const t=o.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}this.updateDOM(e)}catch(e){console.warn("SPAN Panel: history refresh failed",e),this._errorStore?.add({key:"fetch:history",level:"warning",message:i("error.history_failed"),persistent:!1})}}updateDOM(e){this._hass&&this._topology&&this._config&&(function(e,t,n,s,o,r){if(!e||!n||!t)return;const a=gt(s);let l=0;for(const[,e]of Object.entries(n.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],s=i&&parseFloat(i.state)||0;e.device_type!==d&&(l+=Math.abs(s))}Et(e,t,n,s,l);const h=Ge(s),p="current"===h.entityRole;for(const[s,l]of Object.entries(n.circuits)){const n=e.querySelector(`.circuit-slot[data-uuid="${$t(s)}"]`);if(!n)continue;const u=l.entities?.power,g=u?t.states[u]:null,f=g&&parseFloat(g.state)||0,_=l.device_type===d||f<0,v=l.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(g?.attributes?.relay_state||l.relay_state)===c,w=n.querySelector(".power-value");if(w)if(p){const e=l.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${h.format(i)}A`}else w.innerHTML=`${je(f)}${Re(f)}`;const x=n.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=x.querySelector(".toggle-label");e&&(e.textContent=i(y?"grid.on":"grid.off"))}let S;if(n.classList.toggle("circuit-off",!y),n.classList.toggle("circuit-producer",_),l.always_on)S="always_on";else{const e=l.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const C=m[S]??m.unknown,$=n.querySelector(".shedding-icon");$&&($.setAttribute("icon",C.icon),$.style.color=C.color,$.title=C.label());const E=n.querySelector(".shedding-icon-secondary");E&&(C.icon2?(E.setAttribute("icon",C.icon2),E.style.color=C.color,E.style.display=""):E.style.display="none");const k=n.querySelector(".shedding-label");k&&(C.textLabel?(k.textContent=C.textLabel,k.style.color=C.color,k.style.display=""):k.style.display="none");const z=n.querySelector(".chart-container");if(z){const e=o.get(s)||[],i=n.classList.contains("circuit-col-span")?200:100,c=r?.has(s)?ft(r.get(s)):a,p=l.device_type===d;Ct(z,t,e,c,h,_,i,l.breaker_rating_a??void 0,p)}}}(e,this._hass,this._topology,this._config,this.powerHistory,this.horizonMap),function(e,t,n,i,s,o){if(!n.sub_devices)return;const r=gt(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${$t(i)}"]`);if(!n)continue;const l=lt(a);if(l){const e=t.states[l],i=e&&parseFloat(e.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${je(i)} ${Re(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey;if(!n)continue;const a=s.get(n)||[];let l=_.power;n.endsWith("_soc")?l=_.soc:n.endsWith("_soe")&&(l=_.soe);const c=!!e.closest(".bess-chart-col");Ct(e,t,a,o?.has(i)?ft(o.get(i)):r,l,!1,c?120:150,void 0,n.endsWith("_soc")||n.endsWith("_soe"))}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${$t(e)}"]`);if(!i)continue;const s=t.states[e];if(s){let e;if(t.formatEntityState)e=t.formatEntityState(s);else{e=s.state;const t=s.attributes.unit_of_measurement||"";t&&(e+=" "+t)}if("Wh"===(s.attributes.unit_of_measurement||"")){const t=parseFloat(s.state);isNaN(t)||(e=(t/1e3).toFixed(1)+" kWh")}i.textContent=e}}}}(e,this._hass,this._topology,this._config,this.powerHistory,this.subDeviceHorizonMap))}async onGraphSettingsChanged(e){if(this._hass){this._favRefs?await this._buildFavoritesHorizonMaps():(this.graphSettingsCache.invalidate(),await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings)),this.powerHistory.clear();try{await this.loadHistory()}catch{}this.updateDOM(e)}}onToggleClick(e,t){const n=e.target,s=n?.closest(".toggle-pill");if(!s)return;const o=t.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const r=s.closest("[data-uuid]");if(!r||!this._topology||!this._hass)return;const a=r.dataset.uuid;if(!a)return;const l=this._topology.circuits[a];if(!l)return;const c=l.entities?.switch;if(!c)return;const d=this._hass.states[c];if(!d)return void console.warn("SPAN Panel: switch entity not found:",c);const h="on"===d.state?"turn_off":"turn_on";this._hass.callService("switch",h,{},{entity_id:c}).catch(e=>{console.warn("SPAN Panel: switch service call failed",e),this._errorStore?.add({key:"service:relay",level:"error",message:i("error.relay_failed"),persistent:!1})})}async onGearClick(e,t){const n=e.target,i=n?.closest(".gear-icon");if(!i)return;const s=t.querySelector("span-side-panel");if(!s||!this._hass)return;if(s.hass=this._hass,s.errorStore=this.errorStore,i.classList.contains("panel-gear")){if(this._inFavoritesView){const e=await this._buildFavoritesSections();if(0===e.length)return;return void s.open({favoritesMode:!0,perPanelSections:e})}return await this.graphSettingsCache.fetch(this._hass,this._configEntryId),void s.open({panelMode:!0,topology:this._topology,graphSettings:this.graphSettingsCache.settings,showFavorites:null!==this._panelFavorites,favoritePanelDeviceId:this._panelFavorites?.panelDeviceId,favoriteCircuitUuids:this._panelFavorites?.circuitUuids,favoriteSubDeviceIds:this._panelFavorites?.subDeviceIds,configEntryId:this._configEntryId})}const o=i.dataset.uuid;if(o&&this._topology){const e=this._topology.circuits[o];if(e){const t=this._favRefs?.[o]??null,n=t&&"circuit"===t.kind?t.targetId:o,i=t?.configEntryId??this._configEntryId;let a,l;t?[a,l]=await Promise.all([this._fetchGraphSettingsFresh(i),this._fetchMonitoringStatusFresh(i)]):(await Promise.all([this.graphSettingsCache.fetch(this._hass,i),this.monitoringCache.fetch(this._hass,i)]),a=this.graphSettingsCache.settings,l=this.monitoringCache.status);const c=e.entities?.current??e.entities?.power,d=c?l?.circuits?.[c]??null:null,h=a?.global_horizon??r,p=a?.circuits?.[n],u=p?{...p,globalHorizon:h}:{horizon:h,has_override:!1,globalHorizon:h},g=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,f=null!==t||(this._panelFavorites?.circuitUuids.has(n)??!1),_=this._inFavoritesView||null!==this._panelFavorites;return void s.open({...e,uuid:n,monitoringInfo:d,showMonitoring:this._showMonitoring,graphHorizonInfo:u,showFavorites:_,favoritePanelDeviceId:g,isFavorite:f,configEntryId:i})}}const a=i.dataset.subdevId;if(a&&this._topology?.sub_devices?.[a]){const e=this._topology.sub_devices[a],t=this._favRefs?.[a]??null,n=t&&"sub_device"===t.kind?t.targetId:a,i=t?.configEntryId??this._configEntryId;let o;t?o=await this._fetchGraphSettingsFresh(i):(await this.graphSettingsCache.fetch(this._hass,i),o=this.graphSettingsCache.settings);const l=o?.global_horizon??r,c=o?.sub_devices?.[n],d=c?{...c,globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l},h=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,p=null!==t||(this._panelFavorites?.subDeviceIds.has(n)??!1),u=this._inFavoritesView||null!==this._panelFavorites;s.open({subDeviceMode:!0,subDeviceId:n,name:e.name??n,deviceType:e.type??"",entities:e.entities,graphHorizonInfo:d,showFavorites:u,favoritePanelDeviceId:h,isFavorite:p,configEntryId:i})}}async _buildFavoritesSections(){if(!this._hass||!this._favRefs)return[];const e=function(e,t){const n=new Map;for(const i of Object.values(e)){if("circuit"!==i.kind)continue;const e=t.get(i.panelDeviceId);if(void 0===e)continue;let s=n.get(i.panelDeviceId);void 0===s&&(s={panelDeviceId:i.panelDeviceId,panelName:e.panelName,topology:e.topology,configEntryId:e.configEntryId,favoriteCircuitUuids:new Set},n.set(i.panelDeviceId,s)),s.favoriteCircuitUuids.add(i.targetId)}return Array.from(n.values()).sort((e,t)=>e.panelName.localeCompare(t.panelName))}(this._favRefs,this._perPanelInfo);if(0===e.length)return[];return await Promise.all(e.map(async e=>({panelDeviceId:e.panelDeviceId,panelName:e.panelName,topology:e.topology,graphSettings:await this._fetchGraphSettingsFresh(e.configEntryId),favoriteCircuitUuids:e.favoriteCircuitUuids,configEntryId:e.configEntryId})))}async _fetchGraphSettingsFresh(e){if(!this._hass)return null;try{const t={};e&&(t.config_entry_id=e);const n={type:"call_service",domain:l,service:"get_graph_settings",service_data:t,return_response:!0},s=this._errorStore?new Ke(this._errorStore):null,o=s?await s.callWS(this._hass,n,{errorId:"fetch:graph_settings",errorMessage:i("error.graph_settings_failed")}):await this._hass.callWS(n);return o?.response??null}catch(e){return console.warn("SPAN Panel: fresh graph settings fetch failed",e),null}}async _fetchMonitoringStatusFresh(e){if(!this._hass)return null;try{const t={};e&&(t.config_entry_id=e);const n={type:"call_service",domain:l,service:"get_monitoring_status",service_data:t,return_response:!0},s=this._errorStore?new Ke(this._errorStore):null,o=s?await s.callWS(this._hass,n,{errorId:"fetch:monitoring",errorMessage:i("error.monitoring_failed")}):await this._hass.callWS(n),r=o?.response;return r?{circuits:r.circuits,mains:r.mains}:null}catch(e){return console.warn("SPAN Panel: fresh monitoring status fetch failed",e),null}}bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n||!i)return;let s=!1,o=0,r=0;const a=t=>{e.classList.contains("confirmed")||(s=!0,o=t-n.offsetLeft,r=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!s)return;const t=Math.max(2,Math.min(e-o,r));n.style.left=t+"px"},c=()=>{if(!s)return;s=!1;(n.offsetLeft-2)/r>=.9?(n.style.left=r+"px",e.classList.add("confirmed"),n.querySelector("ha-icon")?.setAttribute("icon","mdi:lock-open"),i.textContent=e.dataset.textOn??"",t&&t.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",e=>{e.preventDefault(),a(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),a(e.touches[0].clientX)},{passive:!1}),e.addEventListener("touchmove",e=>l(e.touches[0].clientX),{passive:!0}),e.addEventListener("touchend",c),e.addEventListener("touchcancel",c),e.addEventListener("click",()=>{e.classList.contains("confirmed")&&(e.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("ha-icon")?.setAttribute("icon","mdi:lock"),i.textContent=e.dataset.textOff??"",t&&t.classList.add("switches-disabled"))})}startIntervals(e,t){this._updateInterval=setInterval(()=>{this.recordSamples(),this.updateDOM(e),t&&t()},1e3),this._recorderRefreshInterval=setInterval(()=>{this.refreshRecorderData(e)},3e4)}stopIntervals(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this.cleanupResizeObserver()}setupResizeObserver(e,t){this.cleanupResizeObserver(),t&&(this._lastWidth=t.clientWidth,this._resizeObserver=new ResizeObserver(t=>{const n=t[0];if(!n)return;const i=n.contentRect.width;Math.abs(i-this._lastWidth)<5||(this._lastWidth=i,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const t of e.querySelectorAll(".chart-container")){const e=t.querySelector("ha-chart-base");e&&e.remove()}this.updateDOM(e)},150))}),this._resizeObserver.observe(t))}cleanupResizeObserver(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}reset(){this.powerHistory.clear(),this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),this.monitoringCache.clear(),this.monitoringMultiCache.clear(),this.graphSettingsCache.clear()}}function Nt(e=""){const t=e?` value="${Ie(e)}"`:"",n=e?"":"display:none;";return`\n
\n \n \n
\n `}function Mt(e,t,n,s,o,r,a){const l=t.entities?.power,d=l?n.states[l]:null,h=d&&parseFloat(d.state)||0,p=t.entities?.switch,u=p?n.states[p]:null,g=u?"on"===u.state:(d?.attributes?.relay_state||t.relay_state)===c,f=t.breaker_rating_a,_=f?`${Math.round(f)}A`:"",b=Ie(t.name||i("grid.unknown")),y=Ge(s),w="current"===y.entityRole;let x;if(g)if(w){const e=t.entities?.current,i=e?n.states[e]:null,s=i&&parseFloat(i.state)||0;x=`${y.format(s)}A`}else x=`${je(h)}${Re(h)}`;else x="";const S=r||"unknown";let C="";if("unknown"!==S){const e=m[S]??m.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};C=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}let $="",E=o?.utilization_pct??null;if(null==E&&t.breaker_rating_a){const e=t.entities?.current,i=e?n.states[e]:null,s=i?Math.abs(parseFloat(i.state)||0):0;E=Math.round(s/t.breaker_rating_a*1e3)/10}if(null!=E){$=`=80?"utilization-warning":"utilization-normal"}">${Math.round(E)}%`}const k=!!o&&Ye(o)?v:"#555",z=``,P=!1!==t.is_user_controllable&&!!t.entities?.switch?`
\n ${i(g?"grid.on":"grid.off")}\n \n
`:`${g?"ON":"OFF"}`;return`\n
\n ${_?`${_}`:""}\n ${$}\n ${b}\n ${C}\n ${P}\n \n ${x}\n \n ${z}\n \n
\n `}function Lt(e,t,n,i,s){const o=t.entities?.power,r=o?n.states[o]:null,a=r&&parseFloat(r.state)||0,l=t.device_type===d||a<0,h=t.entities?.switch,p=h?n.states[h]:null,u=et(0,s,p?"on"===p.state:(r?.attributes?.relay_state||t.relay_state)===c,l),g=Ie(e);return`\n
\n
\n
\n
\n
\n `}function It(e){return`
${Ie(e)}
`}function Dt(e,t,n){const i=e.entities?.switch,s=i?t.states[i]:null,o=e.entities?.power,r=o?t.states[o]:null,a=s?"on"===s.state:(r?.attributes?.relay_state||e.relay_state)===c;let l;if("current"===(n.chart_metric||"power")){const n=e.entities?.current,i=n?t.states[n]:null;l=i?Math.abs(parseFloat(i.state)||0):0}else l=r?Math.abs(parseFloat(r.state)||0):0;return{isOn:a,value:l}}function Tt(e,t){if(e.always_on)return"always_on";const n=e.entities?.select,i=n?t.states[n]:null;return i?i.state:"unknown"}function Ht(e,t,n,i){const s=Dt(e,n,i),o=Dt(t,n,i);return s.isOn&&!o.isOn?-1:!s.isOn&&o.isOn?1:o.value-s.value}function Ot(e,t,n){return e.sort((e,i)=>Ht(e[1],i[1],t,n))}function Ft(e){return e.entities?.current??e.entities?.power??""}class Rt{constructor(e){this._expandedUuids=new Set,this._searchQuery="",this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null,this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null,this._viewName=null,this._columns=1,this._ctrl=e}setColumns(e){const t=Math.max(1,Math.min(3,Math.floor(e)));this._columns=t}setInitialExpansion(e){this._expandedUuids=new Set(e)}setInitialSearchQuery(e){this._searchQuery=e}setViewName(e){this._viewName=e}renderActivityView(e,t,n,i,s,o){this._unbindEvents(),this._hass=t,this._topology=n,this._config=i,this._monitoringStatus=s;const r=Ot(Object.entries(n.circuits),t,i);let a=o+Nt(this._searchQuery);a+=`
`;for(const[e,n]of r){const o=Ze(s,Ft(n)),r=Tt(n,t),l=this._expandedUuids.has(e);a+=`
`,a+=Mt(e,n,t,i,o,r,l),l&&(a+=Lt(e,n,t,0,o)),a+="
"}a+="
",a+="",e.innerHTML=a;const l=e.querySelector("span-side-panel");l&&(l.hass=t,l.errorStore=this._ctrl.errorStore),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}renderAreaView(e,t,n,s,o,r){this._unbindEvents(),this._hass=t,this._topology=n,this._config=s,this._monitoringStatus=o;const a=i("list.unassigned_area"),l=new Map;for(const[e,t]of Object.entries(n.circuits)){const n=t.area??a,i=l.get(n);i?i.push([e,t]):l.set(n,[[e,t]])}const c=[...l.keys()].sort((e,t)=>e===a?1:t===a?-1:e.localeCompare(t));let d=r+Nt(this._searchQuery);d+=`
`;for(const e of c){const n=l.get(e);if(!n)continue;const i=Ot(n,t,s);d+=It(e);for(const[e,n]of i){const i=Ze(o,Ft(n)),r=Tt(n,t),a=this._expandedUuids.has(e);d+=`
`,d+=Mt(e,n,t,s,i,r,a),a&&(d+=Lt(e,n,t,0,i)),d+="
"}}d+="
",d+="",e.innerHTML=d;const h=e.querySelector("span-side-panel");h&&(h.hass=t,h.errorStore=this._ctrl.errorStore),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}updateCollapsedRows(e,t,n,s){const o=Ge(s),r="current"===o.entityRole,a=e.querySelectorAll(".list-row[data-row-uuid]");for(const e of a){const a=e.dataset.rowUuid;if(!a)continue;const l=n.circuits[a];if(!l)continue;const{isOn:c,value:d}=Dt(l,t,s),h=e.querySelector(".list-power-value");if(h)if(c)if(r)h.innerHTML=`${o.format(d)}A`;else{const e=l.entities?.power,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;h.innerHTML=`${je(i)}${Re(i)}`}else h.innerHTML="";const p=e.querySelector(".toggle-pill");if(p){p.classList.toggle("toggle-on",c),p.classList.toggle("toggle-off",!c);const e=p.querySelector(".toggle-label");e&&(e.textContent=i(c?"grid.on":"grid.off"))}const u=e.querySelector(".list-status-badge");u&&(u.textContent=c?"ON":"OFF",u.classList.toggle("list-status-on",c),u.classList.toggle("list-status-off",!c)),e.classList.toggle("circuit-off",!c)}!function(e,t,n,i){const s=e.querySelector(".list-view");if(s)for(const e of function(e,t){let n={anchor:null,units:[]};const i=[n];for(const s of[...e.children])if(s.classList.contains("area-header"))n={anchor:s,units:[]},i.push(n);else if(s.classList.contains("list-cell")){const e=s.dataset.cellUuid,i=e?t.circuits[e]:void 0;e&&i&&n.units.push({cell:s,uuid:e,circuit:i})}return i}(s,n)){if(e.units.length<2)continue;const n=[...e.units].sort((e,n)=>Ht(e.circuit,n.circuit,t,i));if(!n.some((t,n)=>t.uuid!==e.units[n].uuid))continue;let o=e.anchor;for(const e of n)o?o.after(e.cell):s.prepend(e.cell),o=e.cell}}(e,t,n,s)}stop(){this._unbindEvents(),null===this._viewName&&(this._expandedUuids.clear(),this._searchQuery=""),this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null}_dispatchFavoritesViewState(){if(!this._viewName||!this._container)return;const e={view:this._viewName,expanded:[...this._expandedUuids],searchQuery:this._searchQuery};this._container.dispatchEvent(new CustomEvent("favorites-view-state-changed",{detail:e,bubbles:!0,composed:!0}))}_bindEvents(e){this._container=e,this._clickHandler=t=>{const n=t.target;if(!n)return;const i=n.closest(".list-expand-toggle");if(i){const e=i.dataset.expandUuid;return void(e&&this._toggleExpand(e))}if(n.closest(".gear-icon"))return void this._ctrl.onGearClick(t,e);if(n.closest(".toggle-pill"))return void this._ctrl.onToggleClick(t,e);if(n.closest(".list-search-clear")){const t=e.querySelector(".list-search");return void(t&&(t.value="",t.dispatchEvent(new Event("input",{bubbles:!0}))))}const s=n.closest(".unit-btn");if(s){const t=s.dataset.unit;t&&e.dispatchEvent(new CustomEvent("unit-changed",{detail:t,bubbles:!0,composed:!0}))}},this._inputHandler=t=>{const n=t.target;n&&n.classList.contains("list-search")&&(this._searchQuery=n.value.toLowerCase(),this._applyFilter(e),this._dispatchFavoritesViewState())},this._graphSettingsHandler=()=>{this._ctrl.onGraphSettingsChanged(e).then(()=>{this._ctrl.updateDOM(e)}).catch(()=>{})},e.addEventListener("click",this._clickHandler),e.addEventListener("input",this._inputHandler),e.addEventListener("graph-settings-changed",this._graphSettingsHandler);const t=e.querySelector(".slide-confirm");t&&(this._ctrl.bindSlideConfirm(t,e),e.classList.add("switches-disabled"))}_unbindEvents(){this._container&&(this._clickHandler&&this._container.removeEventListener("click",this._clickHandler),this._inputHandler&&this._container.removeEventListener("input",this._inputHandler),this._graphSettingsHandler&&this._container.removeEventListener("graph-settings-changed",this._graphSettingsHandler)),this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null}_applyFilter(e){const t=e.querySelector(".list-search-clear");t&&(t.style.display=this._searchQuery?"":"none");const n=e.querySelectorAll(".list-cell[data-cell-uuid]");for(const e of n){const t=e.querySelector(".list-circuit-name"),n=(t?.textContent?.toLowerCase()??"").includes(this._searchQuery);e.style.display=n?"":"none"}const i=e.querySelectorAll(".area-header");for(const e of i){let t=!1,n=e.nextElementSibling;for(;n&&!n.classList.contains("area-header");){if(n.classList.contains("list-cell")&&"none"!==n.style.display){t=!0;break}n=n.nextElementSibling}e.style.display=t?"":"none"}}_toggleExpand(e){if(!(this._container&&this._hass&&this._topology&&this._config))return;const t=$t(e),n=this._container.querySelector(`.list-cell[data-cell-uuid="${t}"]`);if(!n)return;const i=n.querySelector(`.list-row[data-row-uuid="${t}"]`),s=n.querySelector(`.list-expand-toggle[data-expand-uuid="${t}"]`);if(i){if(this._expandedUuids.has(e)){this._expandedUuids.delete(e);const o=n.querySelector(`.list-expanded-content[data-expanded-uuid="${t}"]`);o&&o.remove(),s&&s.classList.remove("expanded"),i.classList.remove("list-row-expanded")}else{this._expandedUuids.add(e);const t=this._topology.circuits[e];if(!t)return;const n=Ze(this._monitoringStatus,Ft(t)),o=Lt(e,t,this._hass,this._config,n);i.insertAdjacentHTML("afterend",o),s&&s.classList.add("expanded"),i.classList.add("list-row-expanded"),this._ctrl.updateDOM(this._container)}this._dispatchFavoritesViewState()}}}async function jt(e,t){const[n,i,s]=await Promise.all([e.callWS({type:"config/area_registry/list"}),e.callWS({type:"config/entity_registry/list"}),e.callWS({type:"config/device_registry/list"})]),o=new Map;for(const e of n)o.set(e.area_id,e.name);const r=new Map;for(const e of i)e.area_id&&r.set(e.entity_id,e.area_id);const a=new Map;for(const e of s)a.set(e.id,e.area_id);let l;if(t.device_id){const e=a.get(t.device_id);e&&(l=o.get(e))}for(const e of Object.values(t.circuits)){let t;for(const n of Object.values(e.entities)){if(!n)continue;const e=r.get(n);if(e){t=o.get(e);break}}t||(t=l),e.area=t}}class qt{constructor(){this._persistent=new Map,this._transient=null,this._transientTimer=null,this._subscribers=new Set,this._watchedPanels=new Map}add(e){const t={...e,timestamp:Date.now()};if(t.persistent)this._persistent.set(t.key,t);else{this._clearTransient(),this._transient=t;const e=t.ttl??5e3;this._transientTimer=setTimeout(()=>{this._transient=null,this._transientTimer=null,this._notify()},e)}this._notify()}remove(e){if(this._persistent.has(e))return this._persistent.delete(e),void this._notify();this._transient?.key===e&&(this._clearTransient(),this._notify())}clear(e){void 0===e?(this._persistent.clear(),this._clearTransient(),this._watchedPanels.clear()):!0===e.persistent?this._persistent.clear():!1===e.persistent&&this._clearTransient(),this._notify()}get active(){const e=[...this._persistent.values()];return null!==this._transient&&e.push(this._transient),e}hasPersistent(e){return this._persistent.has(e)}hasAnyPanelOffline(){for(const e of this._persistent.keys())if("panel-offline"===e||e.startsWith("panel-offline:"))return!0;return!1}subscribe(e){return this._subscribers.add(e),()=>{this._subscribers.delete(e)}}watchPanelStatus(e){this.watchPanelStatuses([{entityId:e,panelName:null}])}watchPanelStatuses(e){const t=this._watchedPanels,n=new Map;for(const i of e){const e=t.get(i.entityId);n.set(i.entityId,{panelName:i.panelName??null,wasOffline:e?.wasOffline??!1})}const i=this._isSingleUnnamed(t),s=this._isSingleUnnamed(n);for(const e of t.keys()){n.has(e)&&i===s||this._persistent.delete(this._offlineKey(e,i))}this._watchedPanels=n,this._notify()}clearPanelStatusWatch(){if(0===this._watchedPanels.size)return;const e=this._isSingleUnnamed(this._watchedPanels);for(const t of this._watchedPanels.keys())this._persistent.delete(this._offlineKey(t,e));this._watchedPanels.clear(),this._notify()}updateHass(e){if(0===this._watchedPanels.size)return;const t=this._isSingleUnnamed(this._watchedPanels);for(const[n,o]of this._watchedPanels){const r=e.states[n]?.state,a="on"===r,l=this._offlineKey(n,t),c=this._reconnectKey(n,t);if(a){const e=o.wasOffline;o.wasOffline=!1,this.remove(l),e&&this.add({key:c,level:"info",message:null===o.panelName?i("error.panel_reconnected"):s("error.panel_reconnected_named",{name:o.panelName}),persistent:!1})}else o.wasOffline=!0,this.hasPersistent(l)||this.add({key:l,level:"error",message:null===o.panelName?i("error.panel_offline"):s("error.panel_offline_named",{name:o.panelName}),persistent:!0})}}dispose(){this._clearTransient(),this._persistent.clear(),this._subscribers.clear(),this._watchedPanels.clear()}_isSingleUnnamed(e){if(1!==e.size)return!1;for(const t of e.values())return null===t.panelName;return!1}_offlineKey(e,t){return t?"panel-offline":`panel-offline:${e}`}_reconnectKey(e,t){return t?"panel-reconnected":`panel-reconnected:${e}`}_clearTransient(){null!==this._transientTimer&&(clearTimeout(this._transientTimer),this._transientTimer=null),this._transient=null}_notify(){for(const e of this._subscribers)try{e()}catch(e){console.warn("SPAN Panel: error-store subscriber threw",e)}}}function Ut(e){let t=0;for(const n of Object.values(e))if(n)for(const e of n.tabs)e>t&&(t=e);return t>0?t+t%2:0}function Wt(e){return e?{id:e.id,name:e.name,name_by_user:e.name_by_user,config_entries:e.config_entries,identifiers:e.identifiers,via_device_id:e.via_device_id,sw_version:e.sw_version,model:e.model}:null}const Bt="favorites-changed";async function Gt(e,t,n={}){const i=await e.callWS({type:"call_service",domain:l,service:t,service_data:n,return_response:!0});return i?.response??null}const Vt=Object.keys(m).filter(e=>"unknown"!==e&&"always_on"!==e);class Qt extends HTMLElement{constructor(){super(),this.errorStore=null,this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}disconnectedCallback(){this._clearDebounceTimers(),this._config=null}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open",""),this.setAttribute("data-mode",this._modeFor(e))}close(){this._clearDebounceTimers(),this.removeAttribute("open"),this.removeAttribute("data-mode"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_clearDebounceTimers(){for(const e of Object.keys(this._debounceTimers))clearTimeout(this._debounceTimers[e]);this._debounceTimers={}}_modeFor(e){return e.favoritesMode?"favorites":e.panelMode?"panel":e.subDeviceMode?"subDevice":"circuit"}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;if(!t)return;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .unit-toggle {\n display: inline-flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .unit-btn:last-child {\n border-right: none;\n }\n .unit-btn:hover:not(.unit-active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .fav-heart {\n background: none;\n border: 1px solid var(--divider-color, #e0e0e0);\n color: var(--secondary-text-color, #727272);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n font-size: 0.9em;\n margin-right: 6px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n }\n .fav-heart.active {\n color: var(--primary-color, #03a9f4);\n border-color: var(--primary-color, #03a9f4);\n }\n .fav-heart:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .fav-heart ha-icon {\n --mdc-icon-size: 16px;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const s=document.createElement("div");s.className="panel",t.appendChild(s),e.favoritesMode?this._renderFavoritesMode(s):e.panelMode?this._renderPanelMode(s):e.subDeviceMode?this._renderSubDeviceMode(s,e):this._renderCircuitMode(s,e)}_renderPanelMode(e){const t=this._config,n=this._createHeader(i("sidepanel.graph_settings"),i("sidepanel.global_defaults"));e.appendChild(n);const s=document.createElement("div");s.className="panel-body";const o=t.graphSettings,l=t.topology,c=o?.global_horizon??r,d=o?.circuits??{};s.appendChild(this._buildListColumnsSection());const h=document.createElement("div");h.className="section";const p=document.createElement("div");p.className="section-label",p.textContent=i("sidepanel.graph_horizon"),h.appendChild(p);const u=document.createElement("div");u.className="field-row";const f=document.createElement("span");f.className="field-label",f.textContent=i("sidepanel.global_default"),u.appendChild(f);const _=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e;const n=`horizon.${e}`,s=i(n);t.textContent=s!==n?s:e,e===c&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{const e={horizon:_.value};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("set_graph_time_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),u.appendChild(_),h.appendChild(u),s.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=i("sidepanel.circuit_scales"),e.appendChild(n);const o=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,i]of o){const s=this._buildPanelModeCircuitRow(n,i,d[n],c,t.configEntryId??null,t.showFavorites??!1,t.favoritePanelDeviceId,t.favoriteCircuitUuids);e.appendChild(s)}s.appendChild(e)}const m=o?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=i("sidepanel.subdevice_scales"),e.appendChild(n);const o=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of o){const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");if(r.className="field-label",r.textContent=s.name||n,r.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",o.appendChild(r),t.showFavorites&&t.favoritePanelDeviceId){const e=this._buildSubDeviceFavoriteHeart(s.entities,t.favoriteSubDeviceIds?.has(n)??!1);e&&o.appendChild(e)}const l=m[n]||{horizon:c,has_override:!1},d=l.has_override?l.horizon:c,h=document.createElement("select");h.dataset.subdevId=n;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e;const n=`horizon.${e}`,s=i(n);t.textContent=s!==n?s:e,e===d&&(t.selected=!0),h.appendChild(t)}if(h.addEventListener("change",()=>{this._debounce(`subdev-${n}`,g,()=>{const e={subdevice_id:n,horizon:h.value};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("set_subdevice_graph_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})})}),o.appendChild(h),l.has_override){const e=document.createElement("button");e.textContent="↺",e.title=i("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{const s={subdevice_id:n};t.configEntryId&&(s.config_entry_id=t.configEntryId),this._callDomainService("clear_subdevice_graph_horizon",s).then(()=>{h.value=c,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),o.appendChild(e)}e.appendChild(o)}s.appendChild(e)}e.appendChild(s)}_buildPanelModeCircuitRow(e,t,n,s,o,r,l,c){const d=document.createElement("div");d.className="field-row";const h=document.createElement("span");if(h.className="field-label",h.textContent=t.name||e,h.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",d.appendChild(h),r&&l){const n=this._buildFavoriteHeart(t.entities,c?.has(e)??!1);n&&d.appendChild(n)}const p=n||{horizon:s,has_override:!1},u=p.has_override?p.horizon:s,f=document.createElement("select");f.dataset.uuid=e;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e;const n=`horizon.${e}`,s=i(n);t.textContent=s!==n?s:e,e===u&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{this._debounce(`circuit-${e}`,g,()=>{const t={circuit_id:e,horizon:f.value};o&&(t.config_entry_id=o),this._callDomainService("set_circuit_graph_horizon",t).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})})}),d.appendChild(f),p.has_override){const t=document.createElement("button");t.textContent="↺",t.title=i("sidepanel.reset_to_global"),Object.assign(t.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),t.addEventListener("click",()=>{const n={circuit_id:e};o&&(n.config_entry_id=o),this._callDomainService("clear_circuit_graph_horizon",n).then(()=>{f.value=s,t.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),d.appendChild(t)}return d}_renderFavoritesMode(e){const t=this._config,n=this._createHeader(i("sidepanel.graph_settings"),i("sidepanel.favorites_subtitle"));e.appendChild(n);const s=document.createElement("div");s.className="panel-body",s.appendChild(this._buildListColumnsSection());for(const e of t.perPanelSections)s.appendChild(this._buildFavoritesPanelSection(e));e.appendChild(s)}_buildFavoritesPanelSection(e){const t=document.createElement("div");t.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=e.panelName,t.appendChild(n);const i=e.graphSettings?.global_horizon??r,s=e.graphSettings?.circuits??{},o=function(e){const t=e.circuits??{};return Object.entries(t).map(([e,t])=>({uuid:e,circuit:t})).sort((e,t)=>(e.circuit.name||"").localeCompare(t.circuit.name||""))}(e.topology);for(const{uuid:n,circuit:r}of o){const o=this._buildPanelModeCircuitRow(n,r,s[n],i,e.configEntryId,!0,e.panelDeviceId,e.favoriteCircuitUuids);t.appendChild(o)}return t}_renderCircuitMode(e,t){const n=`${Ie(String(t.breaker_rating_a))}A · ${Ie(String(t.voltage))}V · Tabs [${Ie(String(t.tabs))}]`,i=this._createHeader(Ie(t.name),n);e.appendChild(i);const s=document.createElement("div");s.className="panel-body",e.appendChild(s),this._renderRelaySection(s,t),t.showFavorites&&this._renderFavoriteSection(s,t),this._renderSheddingSection(s,t),this._renderGraphHorizonSection(s,t),t.showMonitoring&&this._renderMonitoringSection(s,t)}_favoriteEntityId(e){return e?.current??e?.power??null}_subDeviceFavoriteEntityId(e){if(!e)return null;let t=null;for(const[n,i]of Object.entries(e)){if("sensor"===i.domain)return n;t||(t=n)}return t}_buildSubDeviceFavoriteHeart(e,t){const n=this._subDeviceFavoriteEntityId(e);return n?this._buildHeartButton(n,t):null}_buildListColumnsSection(){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=i("sidepanel.list_view_columns"),e.appendChild(t);const n=document.createElement("div");n.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=i("sidepanel.columns"),n.appendChild(s);const o=Te(),r=document.createElement("div");r.className="unit-toggle";for(const e of[1,2,3]){const t=document.createElement("button");t.type="button",t.className="unit-btn"+(e===o?" unit-active":""),t.dataset.columns=String(e),t.textContent=String(e),t.addEventListener("click",()=>{He(e);for(const e of r.querySelectorAll(".unit-btn"))e.classList.toggle("unit-active",e===t);this.dispatchEvent(new CustomEvent("list-columns-changed",{detail:e,bubbles:!0,composed:!0}))}),r.appendChild(t)}return n.appendChild(r),e.appendChild(n),e}_buildFavoriteHeart(e,t){const n=this._favoriteEntityId(e);return n?this._buildHeartButton(n,t):(console.warn("SPAN Panel: circuit has no current/power sensor; favorite heart suppressed"),null)}_buildHeartButton(e,t){const n=document.createElement("button");n.type="button",n.className=t?"fav-heart active":"fav-heart",n.dataset.role="fav-heart",n.title=i("sidepanel.save_to_favorites"),n.setAttribute("role","switch"),n.setAttribute("aria-checked",String(t)),n.setAttribute("aria-label",i("sidepanel.save_to_favorites"));const s=document.createElement("ha-icon");return s.setAttribute("icon",t?"mdi:heart":"mdi:heart-outline"),n.appendChild(s),n.addEventListener("click",t=>{t.stopPropagation(),this._toggleFavoriteEntity(n,s,e).catch(()=>{})}),n}async _toggleFavoriteEntity(e,t,n){if(!this._hass)return;const s=e.classList.contains("active"),o=!s;e.classList.toggle("active",o),t.setAttribute("icon",o?"mdi:heart":"mdi:heart-outline"),e.setAttribute("aria-checked",String(o));try{o?await async function(e,t){const n=await Gt(e,"add_favorite",{entity_id:t});return document.dispatchEvent(new CustomEvent(Bt)),n?.favorites??{}}(this._hass,n):await async function(e,t){const n=await Gt(e,"remove_favorite",{entity_id:t});return document.dispatchEvent(new CustomEvent(Bt)),n?.favorites??{}}(this._hass,n)}catch(n){throw e.classList.toggle("active",s),t.setAttribute("icon",s?"mdi:heart":"mdi:heart-outline"),e.setAttribute("aria-checked",String(s)),console.warn("SPAN Panel: favorite toggle failed",n),this.errorStore?.add({key:"service:favorites",level:"error",message:i("error.favorites_toggle_failed"),persistent:!1}),n}}_renderFavoriteSection(e,t){const n=this._favoriteEntityId(t.entities);n&&this._appendFavoriteHeartSection(e,n,!0===t.isFavorite)}_appendFavoriteHeartSection(e,t,n){const s=document.createElement("div");s.className="section",s.innerHTML=``;const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=i("sidepanel.save_to_favorites"),o.appendChild(r),o.appendChild(this._buildHeartButton(t,n)),s.appendChild(o),e.appendChild(s)}_renderSubDeviceMode(e,t){const n=this._createHeader(Ie(t.name),Ie(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i),t.showFavorites&&this._renderSubDeviceFavoriteSection(i,t),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceFavoriteSection(e,t){const n=this._subDeviceFavoriteEntityId(t.entities);n&&this._appendFavoriteHeartSection(e,n,!0===t.isFavorite)}_renderSubDeviceHorizonSection(e,t){const n=document.createElement("div");n.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=i("sidepanel.graph_horizon"),n.appendChild(s);const o=t.graphHorizonInfo,l=!0===o?.has_override,c=o?.horizon||r,d=o?.globalHorizon||r,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:i("sidepanel.global")}];for(const e of Object.keys(a))p.push({key:e,label:e});const u=l?c:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:n}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=n,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const n={subdevice_id:t.subDeviceId};t.configEntryId&&(n.config_entry_id=t.configEntryId),"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",n).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})):(g(e),this._callDomainService("set_subdevice_graph_horizon",{...n,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})}))}),h.appendChild(s)}n.appendChild(h),e.appendChild(n)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div"),s=Ie(e),o=Ie(t);i.innerHTML=`
${s}
`+(o?`
${o}
`:"");const r=document.createElement("button");return r.className="close-btn",r.innerHTML="✕",r.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(r),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("sidepanel.breaker");const r=document.createElement("ha-switch");r.dataset.role="relay-toggle";const a=t.entities.switch,l=this._hass?.states?.[a]?.state;"on"===l&&r.setAttribute("checked",""),r.addEventListener("change",()=>{const e=r.hasAttribute("checked")||r.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:a}).catch(e=>{console.warn("SPAN Panel: relay toggle failed",e),this.errorStore?.add({key:"service:relay",level:"error",message:i("error.relay_failed"),persistent:!1})})}),s.appendChild(o),s.appendChild(r),n.appendChild(s),e.appendChild(n)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("sidepanel.priority_label");const r=document.createElement("select");r.dataset.role="shedding-select";const a=t.entities.select,l=this._hass?.states?.[a]?.state||"";for(const e of Vt){const t=m[e];if(!t)continue;const n=document.createElement("option");n.value=e,n.textContent=i(`shedding.select.${e}`)||t.label(),e===l&&(n.selected=!0),r.appendChild(n)}r.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:r.value}).catch(e=>{console.warn("SPAN Panel: shedding update failed",e),this.errorStore?.add({key:"service:shedding",level:"error",message:i("error.shedding_failed"),persistent:!1})})}),s.appendChild(o),s.appendChild(r),n.appendChild(s),e.appendChild(n)}_renderGraphHorizonSection(e,t){const n=document.createElement("div");n.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=i("sidepanel.graph_horizon"),n.appendChild(s);const o=t.graphHorizonInfo,l=!0===o?.has_override,c=o?.horizon||r,d=o?.globalHorizon||r,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:i("sidepanel.global")}];for(const e of Object.keys(a))p.push({key:e,label:e});const u=l?c:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:n}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=n,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const n={circuit_id:t.uuid};t.configEntryId&&(n.config_entry_id=t.configEntryId),"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",n).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})):(g(e),this._callDomainService("set_circuit_graph_horizon",{...n,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})}))}),h.appendChild(s)}n.appendChild(h),e.appendChild(n)}_renderMonitoringSection(e,t){const n=document.createElement("div");n.className="section";const s=document.createElement("div");s.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent=i("sidepanel.monitoring"),o.style.margin="0";const r=document.createElement("ha-switch");r.dataset.role="monitoring-toggle";const a=t.monitoringInfo,l=null!=a&&!1!==a.monitoring_enabled;l&&r.setAttribute("checked",""),s.appendChild(o),s.appendChild(r),n.appendChild(s);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",n.appendChild(c);const d=!0===a?.has_override,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,c.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=a?.continuous_threshold_pct??80,g=a?.spike_threshold_pct??100,f=a?.window_duration_m??15,_=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(i("sidepanel.continuous_pct"),"continuous",u,t)),p.appendChild(this._createThresholdRow(i("sidepanel.spike_pct"),"spike",g,t)),p.appendChild(this._createDurationRow(i("sidepanel.window_duration"),"window-m",f,1,180,"m",t)),p.appendChild(this._createDurationRow(i("sidepanel.cooldown"),"cooldown-m",_,1,180,"m",t)),c.appendChild(p),r.addEventListener("change",()=>{const e=r.checked;c.style.display=e?"block":"none";const n={circuit_id:t.entities?.power||t.uuid,monitoring_enabled:e};t.configEntryId&&(n.config_entry_id=t.configEntryId),this._callDomainService("set_circuit_threshold",n).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})});const m=h.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e={circuit_id:t.entities?.power||t.uuid};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("clear_circuit_threshold",e).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})}});e.appendChild(n)}_createThresholdRow(e,t,n,s){const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=e;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${t}`,a.addEventListener("input",()=>{this._debounce(`threshold-${t}`,g,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),n=e.querySelector('[data-role="threshold-spike"]'),o=e.querySelector('[data-role="threshold-window-m"]'),r=e.querySelector('[data-role="threshold-cooldown-m"]'),a={circuit_id:s.entities?.power||s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:o?Number(o.value):void 0,cooldown_duration_m:r?Number(r.value):void 0};s.configEntryId&&(a.config_entry_id=s.configEntryId),this._callDomainService("set_circuit_threshold",a).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})})}),o.appendChild(r),o.appendChild(a),o}_createDurationRow(e,t,n,s,o,r,a,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const h=document.createElement("div"),p=document.createElement("input");p.type="number",p.min=String(s),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${t}`,l&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=r,h.appendChild(p),h.appendChild(u),l||p.addEventListener("input",()=>{this._debounce(`threshold-${t}`,g,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),n=e.querySelector('[data-role="threshold-spike"]'),s=e.querySelector('[data-role="threshold-window-m"]'),o={circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:s?Number(s.value):void 0};a.configEntryId&&(o.config_entry_id=a.configEntryId),this._callDomainService("set_circuit_threshold",o).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})})}),c.appendChild(d),c.appendChild(h),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(!e.subDeviceMode&&!e.favoritesMode){if(e.entities?.switch){const t=this.shadowRoot?.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot?.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._hass?this._hass.callWS({type:"call_service",domain:l,service:e,service_data:t}):Promise.resolve()}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}try{customElements.get("span-side-panel")||customElements.define("span-side-panel",Qt)}catch{}let Kt=class extends Ee{constructor(){super(...arguments),this._store=null,this._unsub=null,this._errors=[]}set store(e){if(this._store===e)return;this._unsub?.(),this._unsub=null,this._store=e,this._errors=e.active;const t=e;this._unsub=e.subscribe(()=>{this._errors=t.active})}connectedCallback(){if(super.connectedCallback(),this._store&&!this._unsub){const e=this._store;this._errors=e.active,this._unsub=e.subscribe(()=>{this._errors=e.active})}}disconnectedCallback(){super.disconnectedCallback(),this._unsub?.(),this._unsub=null}render(){return 0===this._errors.length?de:le`${this._errors.map(e=>le` - + `}return ht` +
(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)}
- +
- `}updated(e){if(e.has("hass")&&this.hass&&(n(this.hass.language),this._ctrl.hass=this.hass,this._errorStore.updateHass(this.hass),this._config.device_id))if(this._discovered||this._discovering){if(this._discovered){this._ctrl.recordSamples(),this._ctrl.updateDOM(this._root);const e=this._root.querySelector("span-side-panel");e&&(e.hass=this.hass,e.errorStore=this._errorStore)}this._discovered&&"panel"!==this._activeTab&&this._topology&&this._listCtrl.updateCollapsedRows(this._root,this.hass,this._topology,this._config)}else this._startDiscovery()}async _startDiscovery(){this._discovering||(this._discovering=!0,await this._discoverTopology(),this._errorStore.hasPersistent("discovery-failed")?this._discovering=!1:(this._discovered=!0,this._discovering=!1,this._ctrl.init(this._topology,this._config,this.hass,this._configEntryId),this._topology?.panel_entities?.panel_status&&(this._errorStore.watchPanelStatus(this._topology.panel_entities.panel_status),this._errorStore.updateHass(this.hass)),this._topology&&(this._areaSubscribing=!0,async function(e,t,n,s){if(!e.connection)return()=>{};const o=async()=>{try{const i=new Map;for(const[e,n]of Object.entries(t.circuits))i.set(e,n.area);await jt(e,t);for(const[e,s]of Object.entries(t.circuits))if(s.area!==i.get(e))return void n()}catch(e){console.warn("[span-panel] area registry update failed:",e),s?.add({key:"fetch:areas",level:"warning",message:i("error.areas_failed"),persistent:!1})}},[r,a]=await Promise.all([e.connection.subscribeEvents(o,"entity_registry_updated"),e.connection.subscribeEvents(o,"area_registry_updated")]);return()=>{r(),a()}}(this.hass,this._topology,()=>{"area"===this._activeTab&&this._discovered&&this._populateCardContent()},this._errorStore).then(e=>{this._areaSubscribing?this._areaUnsub=e:e()}).catch(e=>{this._areaSubscribing=!1,console.warn("SPAN Panel: area subscription failed",e),this._errorStore.add({key:"subscribe:area",level:"warning",message:i("error.areas_failed"),persistent:!1})})),await this.updateComplete,this._populateCardContent(),this._loadHistory(),this._ctrl.monitoringCache.fetch(this.hass,this._configEntryId).then(()=>{this._discovered&&this._ctrl.updateDOM(this._root)})))}async _discoverTopology(){if(!this.hass)return;const e=new Ke(this._errorStore);try{const t=await async function(e,t,n){if(!t)throw new Error(i("card.device_not_found"));const s={type:`${l}/panel_topology`,device_id:t},o=n?await n.callWS(e,s,{errorId:"fetch:topology"}):await e.callWS(s),r=o.panel_size??Ut(o.circuits);if(!r)throw new Error(i("card.topology_error"));const a={type:"config/device_registry/list"},c=Wt((n?await n.callWS(e,a,{errorId:"fetch:topology"}):await e.callWS(a)).find(e=>e.id===t));return await jt(e,o),{topology:o,panelDevice:c,panelSize:r}}(this.hass,this._config.device_id,e);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(t){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",t);try{const t=await async function(e,t,n){const s={type:"config/device_registry/list"},o={type:"config/entity_registry/list"},[r,a]=await Promise.all([n?n.callWS(e,s,{errorId:"fetch:topology"}):e.callWS(s),n?n.callWS(e,o,{errorId:"fetch:topology"}):e.callWS(o)]),c=Wt(r.find(e=>e.id===t));if(!c)return{topology:null,panelDevice:null,panelSize:0};const d=a.filter(e=>e.device_id===t),h=r.filter(e=>e.via_device_id===t),p=new Set(h.map(e=>e.id)),u=a.filter(e=>void 0!==e.device_id&&p.has(e.device_id)),g={},f=c.name_by_user??c.name??"";for(const t of[...d,...u]){const n=e.states[t.entity_id];if(!n)continue;const i=n.attributes,s=i.tabs;if("string"!=typeof s||!s.startsWith("tabs ["))continue;const o=s.slice(6,-1);let r;if(r=o.includes(":")?o.split(":").map(Number):[Number(o)],!r.every(Number.isFinite))continue;const a=t.unique_id.split("_");let l=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(t)){l=t;break}}if(!l)continue;let c=("string"==typeof i.friendly_name?i.friendly_name:void 0)??t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}f&&c.startsWith(f+" ")&&(c=c.slice(f.length+1));const d=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,""),h="number"==typeof i.voltage?i.voltage:2===r.length?240:120,p={power:t.entity_id,switch:`switch.${d}_breaker`,breaker_rating:`sensor.${d}_breaker_rating`};g[l]={tabs:r,name:c,voltage:h,device_type:"string"==typeof i.device_type?i.device_type:"circuit",relay_state:"string"==typeof i.relay_state?i.relay_state:"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:p}}let _="";if(c.identifiers)for(const e of c.identifiers){if(!Array.isArray(e)||e.length<2)continue;const[t,n]=e;t===l&&"string"==typeof n&&(_=n)}let m=0;for(const t of d){const n=e.states[t.entity_id];if(n&&"number"==typeof n.attributes.panel_size){m=n.attributes.panel_size;break}}if(m||(m=Ut(g)),!m)throw new Error(i("card.panel_size_error"));const v={};for(const t of h){const n=a.filter(e=>e.device_id===t.id),i=(t.model??"").toLowerCase(),s=i.includes("battery")||(t.identifiers??[]).some(e=>e[1].toLowerCase().includes("bess")),o=i.includes("drive")||(t.identifiers??[]).some(e=>e[1].toLowerCase().includes("evse")),r={};for(const t of n){const n=t.entity_id.split(".")[0],i=e.states[t.entity_id],s=i?.attributes?.friendly_name;r[t.entity_id]={domain:n??"",original_name:"string"==typeof s?s:t.entity_id}}v[t.id]={name:t.name_by_user??t.name??"",type:s?"bess":o?"evse":"unknown",entities:r}}const b={serial:_,firmware:c.sw_version??"",panel_size:m,device_id:t,device_name:c.name_by_user??c.name??i("header.default_name"),circuits:g,sub_devices:v};return await jt(e,b),{topology:b,panelDevice:c,panelSize:m}}(this.hass,this._config.device_id,e);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._errorStore.add({key:"discovery-failed",level:"error",message:i("error.discovery_failed"),persistent:!0,retryFn:()=>{this._errorStore.remove("discovery-failed"),this._startDiscovery()}})}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this.hass){this._historyLoaded=!0,await this._ctrl.fetchAndBuildHorizonMaps();try{await this._ctrl.loadHistory(),this._ctrl.updateDOM(this._root)}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_populateCardContent(){const e=this._root.querySelector("#card-content");if(!(e&&this.hass&&this._topology&&this._panelSize))return;const t=this._root.querySelector("#card-tabs");if(t){const e=[{id:"panel",label:i("tab.by_panel"),icon:"mdi:view-dashboard"},{id:"activity",label:i("tab.by_activity"),icon:"mdi:sort-descending"},{id:"area",label:i("tab.by_area"),icon:"mdi:home-group"}];t.innerHTML=(n=e,s=this._activeTab,o=this._config.tab_style??"text",`
${n.map(e=>{const t=e.id===s?" active":"",n=Ie(e.id);return"icon"===o?``:``}).join("")}
`),this._tabBarCleanup&&(this._tabBarCleanup(),this._tabBarCleanup=null),this._tabBarCleanup=function(e,t){const n=e=>{const n=e.target.closest(".shared-tab");if(n){const e=n.dataset.tab;e&&t(e)}};return e.addEventListener("click",n),()=>{e.removeEventListener("click",n)}}(t,e=>{["panel","activity","area"].includes(e)&&(this._activeTab=e,this._listCtrl.stop(),this._populateCardContent())})}var n,s,o;if("panel"===this._activeTab){const t=Math.ceil(this._panelSize/2),n=Oe(this._topology,this._config),s=this._ctrl.monitoringCache.status,o=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),n=Object.values(e.mains??{}),s=[...t,...n],o=s.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=80&&e.utilization_pct<100).length,r=s.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=100).length,a=s.filter(e=>e.has_override).length;return`\n
\n ✓ ${i("status.monitoring")} · ${t.length} ${i("status.circuits")} · ${n.length} ${i("status.mains")}\n \n ${o>0?`${o} ${i(o>1?"status.warnings":"status.warning")}`:""}\n ${r>0?`${r} ${i(r>1?"status.alerts":"status.alert")}`:""}\n ${a>0?`${a} ${i(a>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,s){const o=new Map,r=new Set;for(const[t,n]of Object.entries(e.circuits)){const e=n.tabs;if(!e||0===e.length)continue;const i=Math.min(...e),s=1===e.length?"single":Be(e)??"single";o.set(i,{uuid:t,circuit:n,layout:s});for(const t of e)r.add(t)}const a=new Set,l=new Set;for(const[e,t]of o)if("col-span"===t.layout){const n=t.circuit.tabs,i=Ue(Math.max(...n));0===We(e)?a.add(i):l.add(i)}function c(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=s?Ze(s,t??""):null;let o;if(e.circuit.always_on)o="always_on";else{const t=e.circuit.entities?.select;o=t&&n.states[t]?n.states[t].state:"unknown"}return{monInfo:i,sheddingPriority:o}}let d="";for(let e=1;e<=t;e++){const t=2*e-1,s=2*e,h=o.get(t),p=o.get(s);if(d+=`
${t}
`,h&&"row-span"===h.layout){const{monInfo:t,sheddingPriority:o}=c(h);d+=tt(h.uuid,h.circuit,e,"2 / 4","row-span",n,i,t,o),d+=`
${s}
`;continue}if(!a.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(t)||(d+=nt(e,"2"));else{const{monInfo:t,sheddingPriority:s}=c(h);d+=tt(h.uuid,h.circuit,e,"2",h.layout,n,i,t,s)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(s)||(d+=nt(e,"3"));else{const{monInfo:t,sheddingPriority:s}=c(p);d+=tt(p.uuid,p.circuit,e,"3",p.layout,n,i,t,s)}d+=`
${s}
`}return d}(this._topology,t,this.hass,this._config,s),a=function(e,t,n){const s=!1!==n.show_battery,o=!1!==n.show_evse;if(!e.sub_devices)return"";const r=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===h&&!s||e.type===p&&!o));if(0===r.length)return"";const a=r.filter(([,e])=>e.type===p).length;let l=0,c="";for(const[e,s]of r){const o=s.type===p?i("subdevice.ev_charger"):s.type===h?i("subdevice.battery"):i("subdevice.fallback"),r=lt(s),d=r?t.states[r]:void 0,u=d&&parseFloat(d.state)||0,g=s.type===h,f=s.type===p,_=g?ct(s):null,m=g?dt(s):null,v=g?ht(s):null,b=pt(s,t,n,new Set([r,_,m,v].filter(e=>null!==e))),y=ut(e,0,g,r,_,m);let w="";g?w="sub-device-bess":f&&(l++,l===a&&a%2==1&&(w="sub-device-full")),c+=`\n
\n
\n ${Ie(o)}\n ${Ie(s.name||"")}\n ${r?`${je(u)} ${Re(u)}`:""}\n \n
\n ${y}\n ${b}\n
\n `}return c}(this._topology,this.hass,this._config);e.innerHTML=`\n ${n}\n ${o}\n ${a?`
${a}
`:""}\n ${!1!==this._config.show_panel?`
${r}
`:""}\n `;const l=e.querySelector(".slide-confirm");if(l){const e=this._root.querySelector("ha-card");this._ctrl.bindSlideConfirm(l,e),e&&e.classList.add("switches-disabled")}const c=this._root.querySelector("span-side-panel");c&&(c.hass=this.hass,c.errorStore=this._errorStore),this._ctrl.recordSamples(),this._ctrl.updateDOM(this._root),this._ctrl.setupResizeObserver(this._root,this._root.querySelector("ha-card"))}else if("activity"===this._activeTab){e.innerHTML="";const t=Oe(this._topology,this._config);this._listCtrl.setColumns(Te()),this._listCtrl.renderActivityView(e,this.hass,this._topology,this._config,this._ctrl.monitoringCache.status,t),this._ctrl.updateDOM(this._root)}else if("area"===this._activeTab){e.innerHTML="";const t=Oe(this._topology,this._config);this._listCtrl.setColumns(Te()),this._listCtrl.renderAreaView(e,this.hass,this._topology,this._config,this._ctrl.monitoringCache.status,t),this._ctrl.updateDOM(this._root)}}_onCardClick(e){if("panel"!==this._activeTab)return;const t=e.target;if(!t)return;const n=t.closest(".unit-btn");if(n)return void this._onUnitToggle(n);if(t.closest(".toggle-pill"))return void this._ctrl.onToggleClick(e,this._root);t.closest(".gear-icon")&&this._ctrl.onGearClick(e,this._root)}async _onUnitToggle(e){const t=e.dataset.unit;t&&t!==(this._config.chart_metric??"power")&&(this._config={...this._config,chart_metric:t},this._ctrl.setConfig(this._config),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._ctrl.powerHistory.clear(),this._historyLoaded=!1,this._populateCardContent(),await this._loadHistory(),this._ctrl.updateDOM(this._root))}async _onListUnitChanged(e){const t=e.detail;t&&t!==(this._config.chart_metric??"power")&&(this._config={...this._config,chart_metric:t},this._ctrl.setConfig(this._config),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._ctrl.powerHistory.clear(),this._historyLoaded=!1,this._populateCardContent(),await this._loadHistory(),this._ctrl.updateDOM(this._root))}_onGraphSettingsChanged(){this._ctrl.onGraphSettingsChanged(this._root)}_onListColumnsChanged(e){const t=e.detail;"number"!=typeof t||1!==t&&2!==t&&3!==t||"activity"!==this._activeTab&&"area"!==this._activeTab||this._populateCardContent()}_onSidePanelClosed(){this._ctrl.monitoringCache.invalidate(),this._ctrl.graphSettingsCache.invalidate()}_renderPreview(){const e=Jt.map(e=>le` + `}updated(t){if(t.has("hass")&&this.hass&&(n(this.hass.language),this._ctrl.hass=this.hass,this._errorStore.updateHass(this.hass),this._config.device_id))if(this._discovered||this._discovering){if(this._discovered){this._ctrl.recordSamples(),this._ctrl.updateDOM(this._root);const t=this._root.querySelector("span-side-panel");t&&(t.hass=this.hass,t.errorStore=this._errorStore)}this._discovered&&"panel"!==this._activeTab&&this._topology&&this._listCtrl.updateCollapsedRows(this._root,this.hass,this._topology,this._config)}else this._startDiscovery()}async _startDiscovery(){this._discovering||(this._discovering=!0,await this._discoverTopology(),this._errorStore.hasPersistent("discovery-failed")?this._discovering=!1:(this._discovered=!0,this._discovering=!1,this._ctrl.init(this._topology,this._config,this.hass,this._configEntryId),this._topology?.panel_entities?.panel_status&&(this._errorStore.watchPanelStatus(this._topology.panel_entities.panel_status),this._errorStore.updateHass(this.hass)),this._topology&&(this._areaSubscribing=!0,async function(t,e,n,r){if(!t.connection)return()=>{};const o=async()=>{try{const i=new Map;for(const[t,n]of Object.entries(e.circuits))i.set(t,n.area);await Kk(t,e);for(const[t,r]of Object.entries(e.circuits))if(r.area!==i.get(t))return void n()}catch(t){console.warn("[span-panel] area registry update failed:",t),r?.add({key:"fetch:areas",level:"warning",message:i("error.areas_failed"),persistent:!1})}},[a,s]=await Promise.all([t.connection.subscribeEvents(o,"entity_registry_updated"),t.connection.subscribeEvents(o,"area_registry_updated")]);return()=>{a(),s()}}(this.hass,this._topology,()=>{"area"===this._activeTab&&this._discovered&&this._populateCardContent()},this._errorStore).then(t=>{this._areaSubscribing?this._areaUnsub=t:t()}).catch(t=>{this._areaSubscribing=!1,console.warn("SPAN Panel: area subscription failed",t),this._errorStore.add({key:"subscribe:area",level:"warning",message:i("error.areas_failed"),persistent:!1})})),await this.updateComplete,this._populateCardContent(),this._loadHistory(),this._ctrl.monitoringCache.fetch(this.hass,this._configEntryId).then(()=>{this._discovered&&this._ctrl.updateDOM(this._root)})))}async _discoverTopology(){if(!this.hass)return;const t=new Qt(this._errorStore);try{const e=await async function(t,e,n){if(!e)throw new Error(i("card.device_not_found"));const r={type:`${l}/panel_topology`,device_id:e},o=n?await n.callWS(t,r,{errorId:"fetch:topology"}):await t.callWS(r),a=o.panel_size??Jk(o.circuits);if(!a)throw new Error(i("card.topology_error"));const s={type:"config/device_registry/list"},c=tT((n?await n.callWS(t,s,{errorId:"fetch:topology"}):await t.callWS(s)).find(t=>t.id===e));return await Kk(t,o),{topology:o,panelDevice:c,panelSize:a}}(this.hass,this._config.device_id,t);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(e){console.error("SPAN Panel: topology fetch failed, falling back to entity discovery",e);try{const e=await async function(t,e,n){const r={type:"config/device_registry/list"},o={type:"config/entity_registry/list"},[a,s]=await Promise.all([n?n.callWS(t,r,{errorId:"fetch:topology"}):t.callWS(r),n?n.callWS(t,o,{errorId:"fetch:topology"}):t.callWS(o)]),c=tT(a.find(t=>t.id===e));if(!c)return{topology:null,panelDevice:null,panelSize:0};const u=s.filter(t=>t.device_id===e),h=a.filter(t=>t.via_device_id===e),d=new Set(h.map(t=>t.id)),p=s.filter(t=>void 0!==t.device_id&&d.has(t.device_id)),f={},g=c.name_by_user??c.name??"";for(const e of[...u,...p]){const n=t.states[e.entity_id];if(!n)continue;const i=n.attributes,r=i.tabs;if("string"!=typeof r||!r.startsWith("tabs ["))continue;const o=r.slice(6,-1);let a;if(a=o.includes(":")?o.split(":").map(Number):[Number(o)],!a.every(Number.isFinite))continue;const s=e.unique_id.split("_");let l=null;for(let t=2;t=16&&/^[a-f0-9]+$/i.test(e)){l=e;break}}if(!l)continue;let c=("string"==typeof i.friendly_name?i.friendly_name:void 0)??e.entity_id;for(const t of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(t)){c=c.slice(0,-t.length);break}g&&c.startsWith(g+" ")&&(c=c.slice(g.length+1));const u=e.entity_id.replace(/^sensor\./,"").replace(/_power$/,""),h="number"==typeof i.voltage?i.voltage:2===a.length?240:120,d={power:e.entity_id,switch:`switch.${u}_breaker`,breaker_rating:`sensor.${u}_breaker_rating`};f[l]={tabs:a,name:c,voltage:h,device_type:"string"==typeof i.device_type?i.device_type:"circuit",relay_state:"string"==typeof i.relay_state?i.relay_state:"UNKNOWN",is_user_controllable:!0,breaker_rating_a:null,entities:d}}let v="";if(c.identifiers)for(const t of c.identifiers){if(!Array.isArray(t)||t.length<2)continue;const[e,n]=t;e===l&&"string"==typeof n&&(v=n)}let m=0;for(const e of u){const n=t.states[e.entity_id];if(n&&"number"==typeof n.attributes.panel_size){m=n.attributes.panel_size;break}}if(m||(m=Jk(f)),!m)throw new Error(i("card.panel_size_error"));const y={};for(const e of h){const n=s.filter(t=>t.device_id===e.id),i=(e.model??"").toLowerCase(),r=i.includes("battery")||(e.identifiers??[]).some(t=>t[1].toLowerCase().includes("bess")),o=i.includes("drive")||(e.identifiers??[]).some(t=>t[1].toLowerCase().includes("evse")),a={};for(const e of n){const n=e.entity_id.split(".")[0],i=t.states[e.entity_id],r=i?.attributes?.friendly_name;a[e.entity_id]={domain:n??"",original_name:"string"==typeof r?r:e.entity_id}}y[e.id]={name:e.name_by_user??e.name??"",type:r?"bess":o?"evse":"unknown",entities:a}}const _={serial:v,firmware:c.sw_version??"",panel_size:m,device_id:e,device_name:c.name_by_user??c.name??i("header.default_name"),circuits:f,sub_devices:y};return await Kk(t,_),{topology:_,panelDevice:c,panelSize:m}}(this.hass,this._config.device_id,t);this._topology=e.topology,this._panelDevice=e.panelDevice,this._panelSize=e.panelSize}catch(t){console.error("SPAN Panel: fallback discovery also failed",t),this._errorStore.add({key:"discovery-failed",level:"error",message:i("error.discovery_failed"),persistent:!0,retryFn:()=>{this._errorStore.remove("discovery-failed"),this._startDiscovery()}})}}}async _loadHistory(){if(!this._historyLoaded&&this._topology&&this.hass){this._historyLoaded=!0,await this._ctrl.fetchAndBuildHorizonMaps();try{await this._ctrl.loadHistory(),this._ctrl.updateDOM(this._root)}catch(t){console.warn("SPAN Panel: history fetch failed, charts will populate live",t)}}}_populateCardContent(){const t=this._root.querySelector("#card-content");if(!(t&&this.hass&&this._topology&&this._panelSize))return;const e=this._root.querySelector("#card-tabs");if(e){const t=[{id:"panel",label:i("tab.by_panel"),icon:"mdi:view-dashboard"},{id:"activity",label:i("tab.by_activity"),icon:"mdi:sort-descending"},{id:"area",label:i("tab.by_area"),icon:"mdi:home-group"}];e.innerHTML=(n=t,r=this._activeTab,o=this._config.tab_style??"text",`
${n.map(t=>{const e=t.id===r?" active":"",n=Rt(t.id);return"icon"===o?``:``}).join("")}
`),this._tabBarCleanup&&(this._tabBarCleanup(),this._tabBarCleanup=null),this._tabBarCleanup=function(t,e){const n=t=>{const n=t.target.closest(".shared-tab");if(n){const t=n.dataset.tab;t&&e(t)}};return t.addEventListener("click",n),()=>{t.removeEventListener("click",n)}}(e,t=>{["panel","activity","area"].includes(t)&&(this._activeTab=t,this._listCtrl.stop(),this._populateCardContent())})}var n,r,o;if("panel"===this._activeTab){const e=Math.ceil(this._panelSize/2),n=$t(this._topology,this._config),r=this._ctrl.monitoringCache.status,o=function(t){if(!t)return"";const e=Object.values(t.circuits??{}),n=Object.values(t.mains??{}),r=[...e,...n],o=r.filter(t=>void 0!==t.utilization_pct&&t.utilization_pct>=80&&t.utilization_pct<100).length,a=r.filter(t=>void 0!==t.utilization_pct&&t.utilization_pct>=100).length,s=r.filter(t=>t.has_override).length;return`\n
\n ✓ ${i("status.monitoring")} · ${e.length} ${i("status.circuits")} · ${n.length} ${i("status.mains")}\n \n ${o>0?`${o} ${i(o>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${i(a>1?"status.alerts":"status.alert")}`:""}\n ${s>0?`${s} ${i(s>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(r),a=function(t,e,n,i,r){const o=new Map,a=new Set;for(const[e,n]of Object.entries(t.circuits)){const t=n.tabs;if(!t||0===t.length)continue;const i=Math.min(...t),r=1===t.length?"single":Xt(t)??"single";o.set(i,{uuid:e,circuit:n,layout:r});for(const e of t)a.add(e)}const s=new Set,l=new Set;for(const[t,e]of o)if("col-span"===e.layout){const n=e.circuit.tabs,i=qt(Math.max(...n));0===jt(t)?s.add(i):l.add(i)}function c(t){const e=t.circuit.entities?.current??t.circuit.entities?.power,i=r?ee(r,e??""):null;let o;if(t.circuit.always_on)o="always_on";else{const e=t.circuit.entities?.select;o=e&&n.states[e]?n.states[e].state:"unknown"}return{monInfo:i,sheddingPriority:o}}let u="";for(let t=1;t<=e;t++){const e=2*t-1,r=2*t,h=o.get(e),d=o.get(r);if(u+=`
${e}
`,h&&"row-span"===h.layout){const{monInfo:e,sheddingPriority:o}=c(h);u+=ie(h.uuid,h.circuit,t,"2 / 5","row-span",n,i,e,o),u+=`
${r}
`;continue}if(!s.has(t))if(!h||"col-span"!==h.layout&&"single"!==h.layout)a.has(e)||(u+=re(t,"2"));else{const{monInfo:e,sheddingPriority:r}=c(h);u+=ie(h.uuid,h.circuit,t,"2",h.layout,n,i,e,r)}if(!l.has(t))if(!d||"col-span"!==d.layout&&"single"!==d.layout)a.has(r)||(u+=re(t,"4"));else{const{monInfo:e,sheddingPriority:r}=c(d);u+=ie(d.uuid,d.circuit,t,"4",d.layout,n,i,e,r)}u+=`
${r}
`}return u}(this._topology,e,this.hass,this._config,r),s=function(t,e,n){const r=!1!==n.show_battery,o=!1!==n.show_evse;if(!t.sub_devices)return"";const a=Object.entries(t.sub_devices).filter(([,t])=>!(t.type===h&&!r||t.type===d&&!o));if(0===a.length)return"";const s=a.filter(([,t])=>t.type===d).length;let l=0,c="";for(const[t,r]of a){const o=r.type===d?i("subdevice.ev_charger"):r.type===h?i("subdevice.battery"):i("subdevice.fallback"),a=ue(r),u=a?e.states[a]:void 0,p=u&&parseFloat(u.state)||0,f=r.type===h,g=r.type===d,v=f?he(r):null,m=f?de(r):null,y=f?pe(r):null,_=fe(r,e,n,new Set([a,v,m,y].filter(t=>null!==t))),x=ge(t,0,f,a,v,m);let b="";f?b="sub-device-bess":g&&(l++,l===s&&s%2==1&&(b="sub-device-full")),c+=`\n
\n
\n ${Rt(o)}\n ${Rt(r.name||"")}\n ${a?`${Ut(p)} ${Wt(p)}`:""}\n \n
\n ${x}\n ${_}\n
\n `}return c}(this._topology,this.hass,this._config);t.innerHTML=`\n ${n}\n ${o}\n ${s?`
${s}
`:""}\n ${!1!==this._config.show_panel?`
${a}
`:""}\n `;const l=t.querySelector(".slide-confirm");if(l){const t=this._root.querySelector(".span-card");this._ctrl.bindSlideConfirm(l,t),t&&t.classList.add("switches-disabled")}const c=this._root.querySelector("span-side-panel");c&&(c.hass=this.hass,c.errorStore=this._errorStore),this._ctrl.recordSamples(),this._ctrl.updateDOM(this._root),this._ctrl.setupResizeObserver(this._root,this._root.querySelector(".span-card"))}else if("activity"===this._activeTab){t.innerHTML="";const e=$t(this._topology,this._config);this._listCtrl.setColumns(Bt()),this._listCtrl.renderActivityView(t,this.hass,this._topology,this._config,this._ctrl.monitoringCache.status,e),this._ctrl.updateDOM(this._root)}else if("area"===this._activeTab){t.innerHTML="";const e=$t(this._topology,this._config);this._listCtrl.setColumns(Bt()),this._listCtrl.renderAreaView(t,this.hass,this._topology,this._config,this._ctrl.monitoringCache.status,e),this._ctrl.updateDOM(this._root)}}_onCardClick(t){if("panel"!==this._activeTab)return;const e=t.target;if(!e)return;const n=e.closest(".unit-btn");if(n)return void this._onUnitToggle(n);if(e.closest(".toggle-pill"))return void this._ctrl.onToggleClick(t,this._root);e.closest(".gear-icon")&&this._ctrl.onGearClick(t,this._root)}async _onUnitToggle(t){const e=t.dataset.unit;e&&e!==(this._config.chart_metric??"power")&&(this._config={...this._config,chart_metric:e},this._ctrl.setConfig(this._config),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._ctrl.powerHistory.clear(),this._historyLoaded=!1,this._populateCardContent(),await this._loadHistory(),this._ctrl.updateDOM(this._root))}async _onListUnitChanged(t){const e=t.detail;e&&e!==(this._config.chart_metric??"power")&&(this._config={...this._config,chart_metric:e},this._ctrl.setConfig(this._config),this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config},bubbles:!0,composed:!0})),this._ctrl.powerHistory.clear(),this._historyLoaded=!1,this._populateCardContent(),await this._loadHistory(),this._ctrl.updateDOM(this._root))}_onGraphSettingsChanged(){this._ctrl.onGraphSettingsChanged(this._root)}_onListColumnsChanged(t){const e=t.detail;"number"!=typeof e||1!==e&&2!==e&&3!==e||"activity"!==this._activeTab&&"area"!==this._activeTab||this._populateCardContent()}_onSidePanelClosed(){this._ctrl.monitoringCache.invalidate(),this._ctrl.graphSettingsCache.invalidate()}_renderPreview(){const t=uT.map(t=>ht`
- ${e.name} + ${t.name} ${e.watts}W${t.watts}W
- +
- `);return le` - + `);return ht` +
SPAN Panel Live Power
-
${e}
+
${t}
${i("card.no_device")}
- - `}};Xt.styles=$('\n :host {\n --span-accent: var(--primary-color, #4dd9af);\n }\n\n ha-card {\n padding: 24px;\n background: var(--card-background-color, #1c1c1c);\n color: var(--primary-text-color, #e0e0e0);\n border-radius: var(--ha-card-border-radius, 12px);\n border: var(--ha-card-border-width, 1px) solid var(--ha-card-border-color, var(--divider-color, #333));\n box-shadow: var(--ha-card-box-shadow, none);\n }\n\n .panel-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: flex-start;\n gap: 8px 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .header-left { flex: 1 1 300px; min-width: 0; }\n .header-center { flex: 0 0 auto; }\n .header-right { flex: 0 1 auto; min-width: 0; }\n\n .panel-identity {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 8px 12px;\n margin-bottom: 12px;\n }\n\n .panel-title {\n font-size: 1.8em;\n font-weight: 700;\n margin: 0;\n color: var(--primary-text-color, #fff);\n }\n\n .panel-serial {\n font-size: 0.85em;\n color: var(--secondary-text-color, #999);\n font-family: monospace;\n }\n\n .panel-stats {\n display: flex;\n flex-wrap: wrap;\n gap: 16px 32px;\n }\n\n /* Favorites view header: gear + slide-to-arm + right-anchored legend/W-A cluster. */\n .favorites-summary {\n padding: 8px 24px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n align-items: center;\n gap: 12px;\n }\n /* Override the generic .gear-icon { margin-left: auto } rule so the\n favorites gear stays flush-left instead of floating to the right of\n the flex row (same idea as .panel-identity .panel-gear does for\n real-panel headers). */\n .favorites-summary .favorites-gear {\n margin-left: 0;\n }\n /* Right-anchored cluster wrapping the shedding legend + W/A unit toggle.\n margin-left:auto moved here from .favorites-summary-unit-toggle so the\n legend and toggle cluster together, matching the real-panel header\n layout. */\n .favorites-summary-right {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 16px;\n }\n .favorites-subdevices-section {\n padding: 8px 16px 0;\n }\n\n /* Favorites view: responsive grid of per-contributing-panel status cards. */\n .favorites-panel-stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n gap: 12px;\n padding: 12px 24px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .favorites-panel-card {\n background: var(--secondary-background-color, rgba(255, 255, 255, 0.04));\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n padding: 10px 14px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n .favorites-panel-card-title {\n font-size: 0.85em;\n font-weight: 600;\n color: var(--primary-text-color);\n opacity: 0.85;\n }\n .favorites-panel-card .panel-stats {\n gap: 10px 20px;\n }\n .favorites-panel-card .stat-value {\n font-size: 1.15em;\n }\n\n .stat { display: flex; flex-direction: column; }\n .stat-label { font-size: 0.8em; color: var(--secondary-text-color, #999); margin-bottom: 2px; }\n .stat-row { display: flex; align-items: baseline; gap: 2px; }\n .stat-value { font-size: 1.5em; font-weight: 700; color: var(--primary-text-color, #fff); }\n .stat-unit { font-size: 0.7em; font-weight: 400; color: var(--secondary-text-color, #999); }\n\n .header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; padding-top: 8px; }\n .header-right-top { display: flex; gap: 20px; align-items: center; }\n .meta-item { font-size: 0.8em; color: var(--secondary-text-color, #999); }\n\n .shedding-legend { display: flex; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }\n .shedding-legend-item { display: inline-flex; align-items: center; gap: 3px; }\n .shedding-legend-item ha-icon { --mdc-icon-size: 16px; }\n .shedding-legend-secondary { --mdc-icon-size: 12px; opacity: 0.8; }\n .shedding-legend-text { font-size: 9px; font-weight: 600; }\n .shedding-legend-label { font-size: 0.7em; color: var(--secondary-text-color, #999); }\n\n .panel-gear {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color);\n opacity: 0.6;\n padding: 4px;\n margin-left: 8px;\n vertical-align: middle;\n }\n .panel-gear:hover { opacity: 1; }\n .header-center {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding-top: 8px;\n }\n .panel-identity .panel-gear {\n margin-left: 0;\n }\n .slide-confirm {\n position: relative;\n display: inline-flex;\n align-items: center;\n width: 160px;\n height: 28px;\n border-radius: 14px;\n background: color-mix(in srgb, var(--primary-color, #4dd9af) 20%, var(--secondary-background-color, #333));\n vertical-align: middle;\n overflow: hidden;\n user-select: none;\n touch-action: none;\n }\n .slide-confirm-text {\n position: absolute;\n width: 100%;\n text-align: center;\n font-size: 0.65em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n pointer-events: none;\n z-index: 0;\n }\n .slide-confirm-knob {\n position: absolute;\n left: 2px;\n top: 2px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--secondary-text-color, #666);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n z-index: 1;\n transition: none;\n }\n .slide-confirm-knob ha-icon {\n --mdc-icon-size: 14px;\n color: var(--card-background-color, #1c1c1c);\n }\n .slide-confirm-knob.snapping {\n transition: left 0.25s ease;\n }\n .slide-confirm.confirmed {\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n }\n .slide-confirm.confirmed .slide-confirm-text {\n color: var(--state-active-color, var(--span-accent));\n }\n .slide-confirm.confirmed .slide-confirm-knob {\n background: var(--state-active-color, var(--span-accent));\n }\n .switches-disabled .toggle-pill {\n opacity: 0.3;\n pointer-events: none;\n }\n .unit-toggle {\n display: inline-flex;\n background: var(--secondary-background-color, #333);\n border-radius: 6px;\n overflow: hidden;\n margin-left: 8px;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n background: none;\n color: var(--secondary-text-color);\n font-size: 0.75em;\n font-weight: 600;\n cursor: pointer;\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #4dd9af);\n color: var(--text-primary-color, #000);\n }\n\n .monitoring-summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 6px 16px;\n font-size: 0.8em;\n background: rgba(76, 175, 80, 0.1);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n }\n .monitoring-active { color: #4caf50; }\n .monitoring-counts { display: flex; gap: 12px; }\n .count-warning { color: #ff9800; }\n .count-alert { color: #f44336; }\n .count-overrides { color: var(--secondary-text-color); }\n\n .panel-grid {\n display: grid;\n grid-template-columns: 28px 1fr 1fr 28px;\n gap: 8px;\n align-items: stretch;\n /* Mark the grid as a size-query container so individual cells can\n fold to a name-on-row-1 layout based on the grid\'s rendered\n width (see .circuit-slot @container rule below). Viewport-based\n media queries would mis-fire here because cells are half-width\n of the grid, not half-width of the viewport. */\n container-type: inline-size;\n }\n\n .tab-label {\n display: flex;\n align-items: center;\n font-size: 0.85em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n user-select: none;\n }\n .tab-left { justify-content: flex-start; }\n .tab-right { justify-content: flex-end; }\n\n .circuit-slot {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px 20px;\n min-height: 140px;\n transition: opacity 0.3s;\n position: relative;\n overflow: hidden;\n }\n\n .circuit-col-span { min-height: 280px; }\n .circuit-row-span { border-left: 3px solid var(--span-accent); }\n .circuit-off .circuit-name,\n .circuit-off .breaker-badge,\n .circuit-off .power-value,\n .circuit-off .chart-container { opacity: 0.35; }\n .circuit-off .toggle-pill,\n .circuit-off .gear-icon { opacity: 1; }\n\n .circuit-empty {\n opacity: 0.2;\n min-height: 60px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-style: dashed;\n }\n .empty-label { color: var(--secondary-text-color, #999); font-size: 0.85em; }\n\n .circuit-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 6px;\n gap: 8px;\n }\n\n .circuit-info { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }\n\n .breaker-badge {\n background: color-mix(in srgb, var(--span-accent) 15%, transparent);\n color: var(--span-accent);\n font-size: 0.7em;\n font-weight: 700;\n padding: 2px 7px;\n border-radius: 4px;\n white-space: nowrap;\n border: 1px solid color-mix(in srgb, var(--span-accent) 25%, transparent);\n flex-shrink: 0;\n }\n\n .circuit-name {\n font-size: 0.9em;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--primary-text-color, #e0e0e0);\n }\n\n .circuit-controls { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }\n\n /* Narrow-cell fold for By Panel breaker cells. At full width the\n cell packs breaker + utilization + name on the left of .circuit-header\n and power + toggle on the right, with .circuit-status (shedding +\n gear) below. Each cell is half the .panel-grid width, so truncation\n kicks in once the grid is narrower than ~760px (cell ~340px) —\n well before any viewport-based media query would fire. Query the\n grid\'s own width via a container query and, once crossed, collapse\n the nested flex containers into a single grid so the name gets the\n whole first row and all readings/controls/gear drop to a second row.\n \'display: contents\' on the wrappers removes them from the layout\n tree so the leaf elements can be placed by the outer grid. */\n @container (max-width: 760px) {\n .circuit-slot {\n display: grid;\n grid-template-columns: auto auto auto 1fr auto auto auto;\n grid-template-areas:\n "name name name name name name name"\n "badge util shed . status power gear"\n "chart chart chart chart chart chart chart";\n row-gap: 6px;\n column-gap: 8px;\n }\n .circuit-slot > .circuit-header,\n .circuit-slot > .circuit-status,\n .circuit-header > .circuit-info,\n .circuit-header > .circuit-controls {\n display: contents;\n }\n .circuit-slot .circuit-name {\n grid-area: name;\n justify-self: start;\n }\n .circuit-slot .breaker-badge {\n grid-area: badge;\n }\n .circuit-slot .utilization {\n grid-area: util;\n }\n .circuit-slot .shedding-icon,\n .circuit-slot .shedding-composite {\n grid-area: shed;\n }\n .circuit-slot .toggle-pill {\n grid-area: status;\n justify-self: end;\n }\n .circuit-slot .power-value {\n grid-area: power;\n justify-self: end;\n }\n .circuit-slot .gear-icon.circuit-gear {\n grid-area: gear;\n justify-self: end;\n }\n .circuit-slot > .chart-container {\n grid-area: chart;\n }\n }\n\n .power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .power-value strong { font-weight: 700; font-size: 1.1em; }\n .power-unit { font-size: 0.8em; font-weight: 400; color: var(--secondary-text-color, #999); margin-left: 1px; }\n .circuit-producer .power-value strong { color: var(--info-color, #4fc3f7); }\n\n .toggle-pill {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 4px;\n border-radius: 10px;\n cursor: pointer;\n font-size: 0.65em;\n font-weight: 600;\n transition: background 0.2s;\n user-select: none;\n min-width: 40px;\n }\n .toggle-on {\n padding-left: 6px;\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n color: var(--state-active-color, var(--span-accent));\n }\n .toggle-off {\n padding-right: 6px;\n background: color-mix(in srgb, var(--secondary-text-color) 15%, transparent);\n color: var(--secondary-text-color, #999);\n }\n .toggle-knob {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n transition: background 0.2s, margin 0.2s;\n }\n .toggle-on .toggle-knob {\n background: var(--state-active-color, var(--span-accent));\n margin-left: auto;\n }\n .toggle-off .toggle-knob {\n background: var(--secondary-text-color, #999);\n margin-right: auto;\n order: -1;\n }\n\n .circuit-status {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-top: 4px;\n padding: 0 4px;\n }\n .shedding-icon { opacity: 0.8; cursor: default; }\n .shedding-composite {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n }\n .shedding-icon-secondary { opacity: 0.8; }\n .shedding-label {\n font-size: 10px;\n font-weight: 600;\n opacity: 0.8;\n }\n .gear-icon {\n background: none;\n border: none;\n cursor: pointer;\n padding: 2px;\n opacity: 0.6;\n transition: opacity 0.2s;\n margin-left: auto;\n }\n .gear-icon:hover { opacity: 1; }\n .utilization {\n font-size: 0.75em;\n font-weight: 600;\n }\n .utilization-normal { color: #4caf50; }\n .utilization-warning { color: #ff9800; }\n .utilization-alert { color: #f44336; }\n .circuit-alert {\n border-color: #f44336 !important;\n box-shadow: 0 0 8px rgba(244, 67, 54, 0.3);\n }\n .circuit-custom-monitoring {\n border-left: 3px solid #ff9800;\n }\n\n .chart-container {\n width: 100%;\n aspect-ratio: 4 / 1;\n margin-top: 4px;\n overflow: hidden;\n min-width: 0;\n }\n\n .sub-devices {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 12px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .sub-device {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px;\n }\n .sub-device-bess,\n .sub-device-full {\n grid-column: 1 / -1;\n }\n\n .sub-device-header { display: flex; gap: 10px; align-items: baseline; margin-bottom: 8px; }\n .sub-device-type { font-size: 0.7em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--span-accent); }\n .sub-device-name { font-size: 0.85em; color: var(--secondary-text-color, #999); flex: 1; }\n .sub-power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .sub-power-value strong { font-weight: 700; font-size: 1.1em; }\n .sub-device .chart-container { margin-bottom: 8px; aspect-ratio: auto; }\n\n .bess-charts {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(0, 1fr));\n gap: 12px;\n margin-bottom: 10px;\n }\n .bess-chart-col { min-width: 0; }\n .bess-chart-title {\n font-size: 0.75em;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--secondary-text-color, #999);\n margin-bottom: 4px;\n }\n .bess-chart-col .chart-container { aspect-ratio: auto; }\n .sub-entity { display: flex; gap: 6px; padding: 3px 0; font-size: 0.85em; }\n .sub-entity-name { color: var(--secondary-text-color, #999); }\n .sub-entity-value { font-weight: 500; color: var(--primary-text-color, #e0e0e0); }\n\n /* ── Shared tab bar ────────────────────────────────────── */\n\n .shared-tab-bar {\n display: flex;\n gap: 0;\n margin-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .shared-tab {\n padding: 8px 16px;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 500;\n color: var(--primary-text-color);\n opacity: 0.6;\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n transition: opacity 0.15s;\n }\n\n .shared-tab:hover {\n opacity: 0.85;\n }\n\n .shared-tab.active {\n opacity: 1;\n border-bottom-color: var(--span-accent);\n }\n\n /* ── List view search ──────────────────────────────────── */\n\n .list-search-container {\n margin-bottom: 12px;\n position: relative;\n }\n\n .list-search {\n width: 100%;\n padding: 8px 36px 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--secondary-background-color, #2a2a2a);\n color: var(--primary-text-color);\n font-size: 0.9em;\n box-sizing: border-box;\n outline: none;\n }\n\n .list-search:focus {\n border-color: var(--span-accent);\n }\n\n .list-search-clear {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 2px;\n display: flex;\n align-items: center;\n opacity: 0.7;\n }\n\n .list-search-clear:hover {\n opacity: 1;\n }\n\n .list-unit-toggle {\n display: inline-flex;\n margin-bottom: 12px;\n }\n\n /* ── List rows ─────────────────────────────────────────── */\n\n .list-view {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n /* Each circuit is wrapped in a .list-cell so the row + its optional\n expanded chart stay together. In single-column flex mode the cell\n just stacks naturally. In multi-column grid mode the cell becomes\n one grid item, so the chart is always in the same column as its\n row. Area headers (rendered as siblings, not inside a cell) span\n all columns via their inline "grid-column: 1 / -1". */\n .list-cell {\n display: flex;\n flex-direction: column;\n min-width: 0;\n }\n .list-view[data-columns="2"],\n .list-view[data-columns="3"] {\n display: grid;\n grid-template-columns: repeat(var(--list-cols), minmax(0, 1fr));\n gap: 6px 8px;\n flex-direction: initial;\n }\n /* On narrow viewports a 2/3-column list would squeeze rows into an\n unreadable shape, so force stacking regardless of user preference. */\n @media (max-width: 599px) {\n .list-view[data-columns="2"],\n .list-view[data-columns="3"] {\n display: flex;\n flex-direction: column;\n }\n }\n\n .list-row {\n display: flex;\n align-items: center;\n padding: 12px 16px;\n gap: 10px;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.15s;\n }\n\n .list-row:hover {\n background: var(--secondary-background-color, #2a2a2a);\n }\n\n .list-row.circuit-off {\n opacity: 0.5;\n }\n\n .list-row.list-row-expanded {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-color: transparent;\n }\n\n .list-circuit-name {\n flex: 1;\n color: var(--primary-text-color);\n font-size: 0.9em;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .list-status-badge {\n font-size: 0.75em;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n\n .list-status-on {\n color: #4dd9af;\n }\n\n .list-status-off {\n color: #f44336;\n }\n\n .list-power-value {\n font-size: 0.9em;\n font-weight: 600;\n flex-shrink: 0;\n /* No min-width / text-align:right: the old 70px right-aligned\n cell left a visible blank column for short readings (e.g.\n "1.3A" in a 70px slot), which robbed horizontal space from\n .list-circuit-name on narrow rows. Let the value hug the\n preceding relay control and size to its content so the freed\n width flows back into the flex:1 name column. */\n }\n\n .list-expand-toggle {\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 4px;\n transition: transform 0.2s;\n display: flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .list-expand-toggle.expanded {\n transform: rotate(180deg);\n }\n\n .list-row .gear-icon {\n background: transparent;\n border: none;\n padding: 2px;\n cursor: pointer;\n color: #555;\n display: inline-flex;\n align-items: center;\n }\n .list-row .gear-icon:hover {\n color: var(--primary-text-color);\n }\n\n /* Narrow-viewport fold: the flat flex row truncates the circuit\n name heavily once breaker + utilization + shedding + toggle +\n power + gear + chevron are all competing for width. Switch to a\n two-row grid so the name gets the full width (paired only with\n the expand chevron), and the badges/controls/reading/gear drop\n to a secondary row underneath. Named areas keep the CSS readable\n despite the flat HTML child order. */\n @media (max-width: 520px) {\n .list-row {\n display: grid;\n grid-template-columns: auto auto auto 1fr auto auto auto;\n grid-template-areas:\n "name name name name name name chevron"\n "badge util shed . status power gear";\n row-gap: 6px;\n column-gap: 8px;\n }\n .list-row > .list-circuit-name {\n grid-area: name;\n justify-self: start;\n }\n .list-row > .list-expand-toggle {\n grid-area: chevron;\n }\n .list-row > .breaker-badge {\n grid-area: badge;\n }\n .list-row > .utilization {\n grid-area: util;\n }\n .list-row > .shedding-icon,\n .list-row > .shedding-composite {\n grid-area: shed;\n }\n .list-row > .toggle-pill,\n .list-row > .list-status-badge {\n grid-area: status;\n }\n .list-row > .list-power-value {\n grid-area: power;\n justify-self: end;\n }\n .list-row > .gear-icon.circuit-gear {\n grid-area: gear;\n justify-self: end;\n }\n }\n\n /* ── Expanded circuit content ──────────────────────────── */\n\n .list-expanded-content {\n padding: 0;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n border-radius: 0 0 8px 8px;\n margin-top: -6px;\n margin-bottom: 2px;\n }\n\n .circuit-slot.circuit-chart-only {\n border: none;\n margin: 0;\n background: none;\n padding: 8px 12px;\n min-height: 0;\n }\n\n /* ── Area headers ──────────────────────────────────────── */\n\n .area-header {\n padding: 16px 12px 6px;\n font-weight: 600;\n font-size: 0.85em;\n color: var(--secondary-text-color);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n /* ── No results ────────────────────────────────────────── */\n\n .list-no-results {\n padding: 24px;\n text-align: center;\n color: var(--secondary-text-color);\n }\n\n'),b([Ne({attribute:!1})],Xt.prototype,"hass",void 0),b([Me()],Xt.prototype,"_config",void 0),b([Me()],Xt.prototype,"_discovered",void 0),b([Me()],Xt.prototype,"_discovering",void 0),b([Me()],Xt.prototype,"_topology",void 0),b([Me()],Xt.prototype,"_activeTab",void 0),Xt=b([ze("span-panel-card")],Xt);class Zt extends HTMLElement{constructor(){super(...arguments),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1,this._panelSelect=null,this._daysInput=null,this._hoursInput=null,this._minsInput=null,this._metricSelect=null,this._checkboxes={},this._entityContainers={},this._tabStyleSelect=null}setConfig(e){this._config={...e},this._updateControls()}set hass(e){this._hass=e,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const e=await this._hass.callWS({type:"config/device_registry/list"});this._panels=e.filter(e=>(e.identifiers??[]).some(e=>e[0]===l)&&!e.via_device_id).map(e=>{const t=(e.identifiers??[]).find(e=>e[0]===l)?.[1]??"",n=e.name_by_user??e.name??i("editor.panel_label");return{device_id:e.id,label:`${n} (${t})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const e=document.createElement("div");e.style.padding="16px";const t="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(e,t,n,i),this._buildTimeWindow(e,t,n,i),this._buildMetricSelector(e,t,n,i),this._buildTabStyleSelector(e,t,n,i),this._buildSectionCheckboxes(e,n,i),this.appendChild(e),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const r=document.createElement("label");r.textContent=i("editor.panel_label"),r.style.cssText=n;const a=document.createElement("select");a.style.cssText=t;const l=document.createElement("option");if(l.value="",l.textContent=i("editor.select_panel"),a.appendChild(l),this._panels)for(const e of this._panels){const t=document.createElement("option");t.value=e.device_id,t.textContent=e.label,e.device_id===this._config.device_id&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._panelSelect=a}_buildTimeWindow(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const r=document.createElement("label");r.textContent=i("editor.chart_window"),r.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=t+"width: 70px; cursor: text;",c=(e,t,n,i)=>{const s=document.createElement("div");s.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=t,o.max=n,o.value=String(e),o.style.cssText=l;const r=document.createElement("span");return r.textContent=i,r.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(r),{wrap:s,input:o}},d=parseInt(String(this._config.history_days))||0,h=parseInt(String(this._config.history_hours))||0,p=parseInt(String(this._config.history_minutes))||0,u=c(d,"0","30",i("editor.days")),g=c(h,"0","23",i("editor.hours")),f=c(p,"0","59",i("editor.minutes")),_=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(f.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",_),g.input.addEventListener("change",_),f.input.addEventListener("change",_),a.appendChild(u.wrap),a.appendChild(g.wrap),a.appendChild(f.wrap),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=f.input}_buildMetricSelector(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const r=document.createElement("label");r.textContent=i("editor.chart_metric"),r.style.cssText=n;const a=document.createElement("select");a.style.cssText=t,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._metricSelect=a}_buildTabStyleSelector(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const r=document.createElement("label");r.textContent=i("editor.tab_style"),r.style.cssText=n;const a=document.createElement("select");a.style.cssText=t;const l=[{value:"text",text:i("editor.tab_style_text")},{value:"icon",text:i("editor.tab_style_icon")}];for(const e of l){const t=document.createElement("option");t.value=e.value,t.textContent=e.text,e.value===(this._config.tab_style??"text")&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._config={...this._config,tab_style:a.value},this._fireConfigChanged()}),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._tabStyleSelect=a}_buildSectionCheckboxes(e,t,n){const s=document.createElement("div");s.style.cssText=n;const o=document.createElement("label");o.textContent=i("editor.visible_sections"),o.style.cssText=t,s.appendChild(o);const r=[{key:"show_panel",label:i("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:i("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:i("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of r){const t=document.createElement("div");t.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[e.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=e.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",t.appendChild(n),t.appendChild(i),s.appendChild(t),this._checkboxes[e.key]=n;let o=null;e.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",s.appendChild(o),this._entityContainers[e.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(s)}_isChartEntity(e,t,n){const i=(t.original_name??"").toLowerCase(),s=t.unique_id??"";if("power"===i||"battery power"===i||s.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||s.endsWith("_battery_level")||s.endsWith("_battery_percentage"))return!0;if("state of energy"===i||s.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||s.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(e){const t=this._config.visible_sub_entities??{};for(const[,n]of Object.entries(e)){const e=n.type?this._entityContainers[n.type]:void 0;if(e&&(e.innerHTML="",n.entities))for(const[i,s]of Object.entries(n.entities)){if("sensor"===s.domain&&this._isChartEntity(i,s,n.type??""))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const r=document.createElement("input");r.type="checkbox",r.checked=!0===t[i],r.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const a=document.createElement("span");let l=s.original_name??i;const c=n.name??"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),a.textContent=l,a.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(r),o.appendChild(a),e.appendChild(o),r.addEventListener("change",()=>{const e={...this._config.visible_sub_entities??{}};r.checked?e[i]=!0:delete e[i],this._config={...this._config,visible_sub_entities:e},this._fireConfigChanged()})}}}async _discoverAvailableRoles(e){if(this._hass&&e)try{const t=await this._hass.callWS({type:`${l}/panel_topology`,device_id:e}),n=new Set;for(const e of Object.values(t.circuits??{}))for(const t of Object.keys(e.entities??{}))n.add(t);this._availableRoles=n,this._populateMetricSelect(),t.sub_devices&&this._populateEntityCheckboxes(t.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const e=this._metricSelect;if(!e)return;const t=this._config.chart_metric??o;e.innerHTML="";for(const[n,i]of Object.entries(f)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=n,s.textContent=i.label(),n===t&&(s.selected=!0),e.appendChild(s)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id??""),this._daysInput&&(this._daysInput.value=String(parseInt(String(this._config.history_days))||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(String(this._config.history_hours))||0)),this._minsInput&&(this._minsInput.value=String(parseInt(String(this._config.history_minutes))||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric??o),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e];this._tabStyleSelect&&(this._tabStyleSelect.value=this._config.tab_style??"text")}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}try{customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",Zt)}catch{}window.customCards=window.customCards??[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.9.4 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;"); +
+ `}};hT.styles=k('\n :host {\n --span-accent: var(--primary-color, #4dd9af);\n }\n\n /* Card shell — replaces . Theme variables (--ha-card-*) are\n stable HA contracts (not the deprecated component APIs flagged by the\n 2026.4 frontend blog), so they stay in place to keep visual parity\n with the rest of HA\'s dashboards. */\n .span-card {\n display: block;\n padding: 24px;\n background: var(--card-background-color, #1c1c1c);\n color: var(--primary-text-color, #e0e0e0);\n border-radius: var(--ha-card-border-radius, 12px);\n border: var(--ha-card-border-width, 1px) solid var(--ha-card-border-color, var(--divider-color, #333));\n box-shadow: var(--ha-card-box-shadow, none);\n }\n\n .panel-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: flex-start;\n gap: 8px 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .header-left { flex: 1 1 300px; min-width: 0; }\n .header-center { flex: 0 0 auto; }\n .header-right { flex: 0 1 auto; min-width: 0; }\n\n .panel-identity {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 8px 12px;\n margin-bottom: 12px;\n }\n\n .panel-title {\n font-size: 1.8em;\n font-weight: 700;\n margin: 0;\n color: var(--primary-text-color, #fff);\n }\n\n .panel-serial {\n font-size: 0.85em;\n color: var(--secondary-text-color, #999);\n font-family: monospace;\n }\n\n .panel-stats {\n display: flex;\n flex-wrap: wrap;\n gap: 16px 32px;\n }\n\n /* Favorites view header: gear + slide-to-arm + right-anchored legend/W-A cluster. */\n .favorites-summary {\n padding: 8px 24px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n align-items: center;\n gap: 12px;\n }\n /* Override the generic .gear-icon { margin-left: auto } rule so the\n favorites gear stays flush-left instead of floating to the right of\n the flex row (same idea as .panel-identity .panel-gear does for\n real-panel headers). */\n .favorites-summary .favorites-gear {\n margin-left: 0;\n }\n /* Right-anchored cluster wrapping the shedding legend + W/A unit toggle.\n margin-left:auto moved here from .favorites-summary-unit-toggle so the\n legend and toggle cluster together, matching the real-panel header\n layout. */\n .favorites-summary-right {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 16px;\n }\n .favorites-subdevices-section {\n padding: 8px 16px 0;\n }\n\n /* Favorites view: responsive grid of per-contributing-panel status cards. */\n .favorites-panel-stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n gap: 12px;\n padding: 12px 24px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .favorites-panel-card {\n background: var(--secondary-background-color, rgba(255, 255, 255, 0.04));\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n padding: 10px 14px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n .favorites-panel-card-title {\n font-size: 0.85em;\n font-weight: 600;\n color: var(--primary-text-color);\n opacity: 0.85;\n }\n .favorites-panel-card .panel-stats {\n gap: 10px 20px;\n }\n .favorites-panel-card .stat-value {\n font-size: 1.15em;\n }\n\n .stat { display: flex; flex-direction: column; }\n .stat-label { font-size: 0.8em; color: var(--secondary-text-color, #999); margin-bottom: 2px; }\n .stat-row { display: flex; align-items: baseline; gap: 2px; }\n .stat-value { font-size: 1.5em; font-weight: 700; color: var(--primary-text-color, #fff); }\n .stat-unit { font-size: 0.7em; font-weight: 400; color: var(--secondary-text-color, #999); }\n\n .header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; padding-top: 8px; }\n .header-right-top { display: flex; gap: 20px; align-items: center; }\n .meta-item { font-size: 0.8em; color: var(--secondary-text-color, #999); }\n\n .shedding-legend { display: flex; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }\n .shedding-legend-item { display: inline-flex; align-items: center; gap: 3px; }\n .shedding-legend-item span-icon { --mdc-icon-size: 16px; }\n .shedding-legend-secondary { --mdc-icon-size: 12px; opacity: 0.8; }\n .shedding-legend-text { font-size: 9px; font-weight: 600; }\n .shedding-legend-label { font-size: 0.7em; color: var(--secondary-text-color, #999); }\n\n .panel-gear {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color);\n opacity: 0.6;\n padding: 4px;\n margin-left: 8px;\n vertical-align: middle;\n }\n .panel-gear:hover { opacity: 1; }\n .header-center {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding-top: 8px;\n }\n .panel-identity .panel-gear {\n margin-left: 0;\n }\n .slide-confirm {\n position: relative;\n display: inline-flex;\n align-items: center;\n width: 160px;\n height: 28px;\n border-radius: 14px;\n background: color-mix(in srgb, var(--primary-color, #4dd9af) 20%, var(--secondary-background-color, #333));\n vertical-align: middle;\n overflow: hidden;\n user-select: none;\n touch-action: none;\n }\n .slide-confirm-text {\n position: absolute;\n width: 100%;\n text-align: center;\n font-size: 0.65em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n pointer-events: none;\n z-index: 0;\n }\n .slide-confirm-knob {\n position: absolute;\n left: 2px;\n top: 2px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--secondary-text-color, #666);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n z-index: 1;\n transition: none;\n }\n .slide-confirm-knob span-icon {\n --mdc-icon-size: 14px;\n color: var(--card-background-color, #1c1c1c);\n }\n .slide-confirm-knob.snapping {\n transition: left 0.25s ease;\n }\n .slide-confirm.confirmed {\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n }\n .slide-confirm.confirmed .slide-confirm-text {\n color: var(--state-active-color, var(--span-accent));\n }\n .slide-confirm.confirmed .slide-confirm-knob {\n background: var(--state-active-color, var(--span-accent));\n }\n .switches-disabled .toggle-pill {\n opacity: 0.3;\n pointer-events: none;\n }\n .unit-toggle {\n display: inline-flex;\n background: var(--secondary-background-color, #333);\n border-radius: 6px;\n overflow: hidden;\n margin-left: 8px;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n background: none;\n color: var(--secondary-text-color);\n font-size: 0.75em;\n font-weight: 600;\n cursor: pointer;\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #4dd9af);\n color: var(--text-primary-color, #000);\n }\n\n .monitoring-summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 6px 16px;\n font-size: 0.8em;\n background: rgba(76, 175, 80, 0.1);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n }\n .monitoring-active { color: #4caf50; }\n .monitoring-counts { display: flex; gap: 12px; }\n .count-warning { color: #ff9800; }\n .count-alert { color: #f44336; }\n .count-overrides { color: var(--secondary-text-color); }\n\n .panel-grid {\n display: grid;\n /* Five columns: left tab label, left cell, explicit 8px spacer,\n right cell, right tab label. Spacer is in-band rather than a\n column-gap so we can keep inter-cell space without paying an\n equal gap between each cell and its tab label. The tab columns\n are sized to fit a 2-digit breaker number (the font is 0.85em\n of the panel body ≈ 14px glyph width). */\n grid-template-columns: 14px 1fr 8px 1fr 14px;\n column-gap: 0;\n row-gap: 8px;\n align-items: stretch;\n }\n\n .tab-label {\n display: flex;\n align-items: center;\n font-size: 0.85em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n user-select: none;\n }\n .tab-left { justify-content: flex-start; }\n .tab-right { justify-content: flex-end; }\n\n .circuit-slot {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px 20px;\n min-height: 140px;\n transition: opacity 0.3s;\n position: relative;\n overflow: hidden;\n }\n\n .circuit-col-span { min-height: 280px; }\n .circuit-row-span { border-left: 3px solid var(--span-accent); }\n .circuit-off .circuit-name,\n .circuit-off .breaker-badge,\n .circuit-off .power-value,\n .circuit-off .chart-container { opacity: 0.35; }\n .circuit-off .toggle-pill,\n .circuit-off .gear-icon { opacity: 1; }\n\n .circuit-empty {\n opacity: 0.2;\n min-height: 60px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-style: dashed;\n }\n .empty-label { color: var(--secondary-text-color, #999); font-size: 0.85em; }\n\n .circuit-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 6px;\n gap: 8px;\n }\n\n .circuit-info { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }\n\n .breaker-badge {\n background: color-mix(in srgb, var(--span-accent) 15%, transparent);\n color: var(--span-accent);\n font-size: 0.7em;\n font-weight: 700;\n padding: 2px 3px;\n border-radius: 4px;\n white-space: nowrap;\n border: 1px solid color-mix(in srgb, var(--span-accent) 25%, transparent);\n flex-shrink: 0;\n }\n\n .circuit-name {\n font-size: 0.9em;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--primary-text-color, #e0e0e0);\n }\n\n .circuit-controls { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }\n\n /* Truncation-driven fold for By Panel breaker cells. The .is-folded\n class is added/removed by the JS observer in\n src/core/truncation-fold.ts when the .circuit-name actually\n ellipsizes. Pixel thresholds can\'t get this right because name\n length varies wildly per circuit (e.g. "Spa" vs\n "Commissioned PV System") — only measuring the live name vs its\n container catches the exact moment of truncation.\n\n When folded the nested flex wrappers (.circuit-header,\n .circuit-info, .circuit-controls, .circuit-status) collapse via\n \'display: contents\' so the leaf elements participate directly in\n the outer grid: name gets the whole first row, readings/controls/\n gear drop to a second row, chart stays as the full-width third. */\n .circuit-slot.is-folded {\n display: grid;\n /* Columns: badges + relay-toggle pack tight on the left, slack\n absorbed by the 1fr column between the relay and the power\n reading, keeping power + gear pinned to the right edge. The\n previous layout placed the slack between the shedding icon and\n the relay, which read as wasted padding the user pointed out. */\n grid-template-columns: auto auto auto auto 1fr auto auto;\n /* Rows: name and controls sized to content; chart absorbs any\n extra cell height. Without the explicit 1fr on row 3, a tall\n cell (e.g. .circuit-col-span\'s 280px min-height for 240V\n double-pole breakers) distributes excess space equally across\n all three rows via the default align-content:stretch, which\n pushes the chart down and vertically inflates the badge and\n relay toggle to fill the controls row. */\n grid-template-rows: auto auto 1fr;\n grid-template-areas:\n "name name name name name name name"\n "badge util shed status . power gear"\n "chart chart chart chart chart chart chart";\n row-gap: 6px;\n column-gap: 8px;\n }\n .circuit-slot.is-folded > .circuit-header,\n .circuit-slot.is-folded > .circuit-status,\n .circuit-slot.is-folded > .circuit-header > .circuit-info,\n .circuit-slot.is-folded > .circuit-header > .circuit-controls {\n display: contents;\n }\n .circuit-slot.is-folded .circuit-name {\n grid-area: name;\n justify-self: start;\n }\n .circuit-slot.is-folded .breaker-badge {\n grid-area: badge;\n }\n .circuit-slot.is-folded .utilization {\n grid-area: util;\n }\n .circuit-slot.is-folded .shedding-icon,\n .circuit-slot.is-folded .shedding-composite {\n grid-area: shed;\n }\n .circuit-slot.is-folded .toggle-pill {\n grid-area: status;\n justify-self: end;\n }\n .circuit-slot.is-folded .power-value {\n grid-area: power;\n justify-self: end;\n }\n .circuit-slot.is-folded .gear-icon.circuit-gear {\n grid-area: gear;\n justify-self: end;\n }\n .circuit-slot.is-folded > .chart-container {\n grid-area: chart;\n }\n\n .power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .power-value strong { font-weight: 700; font-size: 1.1em; }\n .power-unit { font-size: 0.8em; font-weight: 400; color: var(--secondary-text-color, #999); margin-left: 1px; }\n .circuit-producer .power-value strong { color: var(--info-color, #4fc3f7); }\n\n .toggle-pill {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 4px;\n border-radius: 10px;\n cursor: pointer;\n font-size: 0.65em;\n font-weight: 600;\n transition: background 0.2s;\n user-select: none;\n min-width: 40px;\n }\n .toggle-on {\n padding-left: 6px;\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n color: var(--state-active-color, var(--span-accent));\n }\n .toggle-off {\n padding-right: 6px;\n background: color-mix(in srgb, var(--secondary-text-color) 15%, transparent);\n color: var(--secondary-text-color, #999);\n }\n .toggle-knob {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n transition: background 0.2s, margin 0.2s;\n }\n .toggle-on .toggle-knob {\n background: var(--state-active-color, var(--span-accent));\n margin-left: auto;\n }\n .toggle-off .toggle-knob {\n background: var(--secondary-text-color, #999);\n margin-right: auto;\n order: -1;\n }\n\n .circuit-status {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-top: 4px;\n padding: 0 4px;\n }\n .shedding-icon { opacity: 0.8; cursor: default; }\n .shedding-composite {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n }\n .shedding-icon-secondary { opacity: 0.8; }\n .shedding-label {\n font-size: 10px;\n font-weight: 600;\n opacity: 0.8;\n }\n .gear-icon {\n background: none;\n border: none;\n cursor: pointer;\n padding: 2px;\n opacity: 0.6;\n transition: opacity 0.2s;\n margin-left: auto;\n }\n .gear-icon:hover { opacity: 1; }\n .utilization {\n font-size: 0.75em;\n font-weight: 600;\n }\n .utilization-normal { color: #4caf50; }\n .utilization-warning { color: #ff9800; }\n .utilization-alert { color: #f44336; }\n .circuit-alert {\n border-color: #f44336 !important;\n box-shadow: 0 0 8px rgba(244, 67, 54, 0.3);\n }\n .chart-container {\n width: 100%;\n aspect-ratio: 4 / 1;\n margin-top: 4px;\n overflow: hidden;\n min-width: 0;\n }\n\n .sub-devices {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 12px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .sub-device {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px;\n }\n .sub-device-bess,\n .sub-device-full {\n grid-column: 1 / -1;\n }\n\n .sub-device-header { display: flex; gap: 10px; align-items: baseline; margin-bottom: 8px; }\n .sub-device-type { font-size: 0.7em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--span-accent); }\n .sub-device-name { font-size: 0.85em; color: var(--secondary-text-color, #999); flex: 1; }\n .sub-power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .sub-power-value strong { font-weight: 700; font-size: 1.1em; }\n .sub-device .chart-container { margin-bottom: 8px; aspect-ratio: auto; }\n\n .bess-charts {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(0, 1fr));\n gap: 12px;\n margin-bottom: 10px;\n }\n .bess-chart-col { min-width: 0; }\n .bess-chart-title {\n font-size: 0.75em;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--secondary-text-color, #999);\n margin-bottom: 4px;\n }\n .bess-chart-col .chart-container { aspect-ratio: auto; }\n .sub-entity { display: flex; gap: 6px; padding: 3px 0; font-size: 0.85em; }\n .sub-entity-name { color: var(--secondary-text-color, #999); }\n .sub-entity-value { font-weight: 500; color: var(--primary-text-color, #e0e0e0); }\n\n /* ── Shared tab bar ────────────────────────────────────── */\n\n .shared-tab-bar {\n display: flex;\n gap: 0;\n margin-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .shared-tab {\n padding: 8px 16px;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 500;\n color: var(--primary-text-color);\n opacity: 0.6;\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n transition: opacity 0.15s;\n }\n\n .shared-tab:hover {\n opacity: 0.85;\n }\n\n .shared-tab.active {\n opacity: 1;\n border-bottom-color: var(--span-accent);\n }\n\n /* ── List view search ──────────────────────────────────── */\n\n .list-search-container {\n margin-bottom: 12px;\n position: relative;\n }\n\n .list-search {\n width: 100%;\n padding: 8px 36px 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--secondary-background-color, #2a2a2a);\n color: var(--primary-text-color);\n font-size: 0.9em;\n box-sizing: border-box;\n outline: none;\n }\n\n .list-search:focus {\n border-color: var(--span-accent);\n }\n\n .list-search-clear {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 2px;\n display: flex;\n align-items: center;\n opacity: 0.7;\n }\n\n .list-search-clear:hover {\n opacity: 1;\n }\n\n .list-unit-toggle {\n display: inline-flex;\n margin-bottom: 12px;\n }\n\n /* ── List rows ─────────────────────────────────────────── */\n\n .list-view {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n /* Each circuit is wrapped in a .list-cell so the row + its optional\n expanded chart stay together. In single-column flex mode the cell\n just stacks naturally. In multi-column grid mode the cell becomes\n one grid item, so the chart is always in the same column as its\n row. Area headers (rendered as siblings, not inside a cell) span\n all columns via their inline "grid-column: 1 / -1". */\n .list-cell {\n display: flex;\n flex-direction: column;\n min-width: 0;\n }\n .list-view[data-columns="2"],\n .list-view[data-columns="3"] {\n display: grid;\n grid-template-columns: repeat(var(--list-cols), minmax(0, 1fr));\n gap: 6px 8px;\n flex-direction: initial;\n }\n /* On narrow viewports a 2/3-column list would squeeze rows into an\n unreadable shape, so force stacking regardless of user preference. */\n @media (max-width: 599px) {\n .list-view[data-columns="2"],\n .list-view[data-columns="3"] {\n display: flex;\n flex-direction: column;\n }\n }\n\n .list-row {\n display: flex;\n align-items: center;\n padding: 12px 16px;\n gap: 10px;\n /* min-width: 0 lets the row shrink below the sum of its\n non-shrinking children when its parent .list-cell is in a\n narrow CSS-grid track (multi-column list mode). Without this\n the row would maintain its intrinsic min-content width and\n overflow the cell, leaving the name unshrunk and the\n truncation-fold observer with no signal to react to. */\n min-width: 0;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.15s;\n }\n\n .list-row:hover {\n background: var(--secondary-background-color, #2a2a2a);\n }\n\n .list-row.circuit-off {\n opacity: 0.5;\n }\n\n .list-row.list-row-expanded {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-color: transparent;\n }\n\n .list-circuit-name {\n flex: 1;\n color: var(--primary-text-color);\n font-size: 0.9em;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .list-status-badge {\n font-size: 0.75em;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n\n .list-status-on {\n color: #4dd9af;\n }\n\n .list-status-off {\n color: #f44336;\n }\n\n .list-power-value {\n font-size: 0.9em;\n font-weight: 600;\n flex-shrink: 0;\n /* No min-width / text-align:right: the old 70px right-aligned\n cell left a visible blank column for short readings (e.g.\n "1.3A" in a 70px slot), which robbed horizontal space from\n .list-circuit-name on narrow rows. Let the value hug the\n preceding relay control and size to its content so the freed\n width flows back into the flex:1 name column. */\n }\n\n .list-expand-toggle {\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 4px;\n transition: transform 0.2s;\n display: flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .list-expand-toggle.expanded {\n transform: rotate(180deg);\n }\n\n .list-row .gear-icon {\n background: transparent;\n border: none;\n padding: 2px;\n cursor: pointer;\n color: #555;\n display: inline-flex;\n align-items: center;\n }\n .list-row .gear-icon:hover {\n color: var(--primary-text-color);\n }\n\n /* Truncation-driven fold for list rows. The .is-folded class is\n added/removed by the JS observer in src/core/truncation-fold.ts\n when the .list-circuit-name actually ellipsizes — pixel breakpoints\n can\'t track this because name length varies wildly per circuit\n ("Spa" vs "Commissioned PV System") and any single threshold\n misfires for the other end of the range. Switch to a two-row grid\n so the name gets the full width (paired only with the expand\n chevron) and the badges/controls/reading/gear drop to a secondary\n row underneath. Named areas keep the CSS readable despite the flat\n HTML child order. */\n .list-row.is-folded {\n display: grid;\n /* Row 1: name spans the row up to the chevron at the trailing\n column. Row 2: badge + util + shed + relay-toggle pack left,\n the 1fr column absorbs slack between the relay and the power\n reading, power + gear stay pinned to the right edge. The\n earlier layout placed the slack between the shedding icon and\n the relay, which the user flagged as wasted padding. */\n grid-template-columns: auto auto auto auto 1fr auto auto;\n grid-template-areas:\n "name name name name name name chevron"\n "badge util shed status . power gear";\n row-gap: 6px;\n column-gap: 8px;\n }\n .list-row.is-folded > .list-circuit-name {\n grid-area: name;\n justify-self: start;\n }\n .list-row.is-folded > .list-expand-toggle {\n grid-area: chevron;\n }\n .list-row.is-folded > .breaker-badge {\n grid-area: badge;\n }\n .list-row.is-folded > .utilization {\n grid-area: util;\n }\n .list-row.is-folded > .shedding-icon,\n .list-row.is-folded > .shedding-composite {\n grid-area: shed;\n }\n .list-row.is-folded > .toggle-pill,\n .list-row.is-folded > .list-status-badge {\n grid-area: status;\n }\n .list-row.is-folded > .list-power-value {\n grid-area: power;\n justify-self: end;\n }\n .list-row.is-folded > .gear-icon.circuit-gear {\n grid-area: gear;\n justify-self: end;\n }\n\n /* ── Expanded circuit content ──────────────────────────── */\n\n .list-expanded-content {\n padding: 0;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n border-radius: 0 0 8px 8px;\n margin-top: -6px;\n margin-bottom: 2px;\n }\n\n .circuit-slot.circuit-chart-only {\n border: none;\n margin: 0;\n background: none;\n padding: 8px 12px;\n min-height: 0;\n }\n\n /* ── Area headers ──────────────────────────────────────── */\n\n .area-header {\n padding: 16px 12px 6px;\n font-weight: 600;\n font-size: 0.85em;\n color: var(--secondary-text-color);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n /* ── No results ────────────────────────────────────────── */\n\n .list-no-results {\n padding: 24px;\n text-align: center;\n color: var(--secondary-text-color);\n }\n\n'),x([zt({attribute:!1})],hT.prototype,"hass",void 0),x([Ot()],hT.prototype,"_config",void 0),x([Ot()],hT.prototype,"_discovered",void 0),x([Ot()],hT.prototype,"_discovering",void 0),x([Ot()],hT.prototype,"_topology",void 0),x([Ot()],hT.prototype,"_activeTab",void 0),hT=x([(t=>(e,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(t,e)}):customElements.define(t,e)})("span-panel-card")],hT);class dT extends HTMLElement{constructor(){super(...arguments),this._config={},this._hass=null,this._panels=null,this._availableRoles=null,this._built=!1,this._panelSelect=null,this._daysInput=null,this._hoursInput=null,this._minsInput=null,this._metricSelect=null,this._checkboxes={},this._entityContainers={},this._tabStyleSelect=null}setConfig(t){this._config={...t},this._updateControls()}set hass(t){this._hass=t,this._panels?this._built||this._buildEditor():this._discoverPanels()}async _discoverPanels(){if(!this._hass)return;const t=await this._hass.callWS({type:"config/device_registry/list"});this._panels=t.filter(t=>(t.identifiers??[]).some(t=>t[0]===l)&&!t.via_device_id).map(t=>{const e=(t.identifiers??[]).find(t=>t[0]===l)?.[1]??"",n=t.name_by_user??t.name??i("editor.panel_label");return{device_id:t.id,label:`${n} (${e})`}}),this._buildEditor()}_buildEditor(){this.innerHTML="",this._built=!0;const t=document.createElement("div");t.style.padding="16px";const e="\n width: 100%;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--card-background-color, var(--secondary-background-color, #1c1c1c));\n color: var(--primary-text-color, #e0e0e0);\n font-size: 1em;\n cursor: pointer;\n appearance: auto;\n box-sizing: border-box;\n ",n="display: block; font-weight: 500; margin-bottom: 8px; color: var(--primary-text-color);",i="margin-bottom: 16px;";this._buildPanelSelector(t,e,n,i),this._buildTimeWindow(t,e,n,i),this._buildMetricSelector(t,e,n,i),this._buildTabStyleSelector(t,e,n,i),this._buildSectionCheckboxes(t,n,i),this.appendChild(t),this._populateMetricSelect(),this._config.device_id&&this._discoverAvailableRoles(this._config.device_id)}_buildPanelSelector(t,e,n,r){const o=document.createElement("div");o.style.cssText=r;const a=document.createElement("label");a.textContent=i("editor.panel_label"),a.style.cssText=n;const s=document.createElement("select");s.style.cssText=e;const l=document.createElement("option");if(l.value="",l.textContent=i("editor.select_panel"),s.appendChild(l),this._panels)for(const t of this._panels){const e=document.createElement("option");e.value=t.device_id,e.textContent=t.label,t.device_id===this._config.device_id&&(e.selected=!0),s.appendChild(e)}s.addEventListener("change",()=>{this._config={...this._config,device_id:s.value},this._fireConfigChanged(),this._discoverAvailableRoles(s.value)}),o.appendChild(a),o.appendChild(s),t.appendChild(o),this._panelSelect=s}_buildTimeWindow(t,e,n,r){const o=document.createElement("div");o.style.cssText=r;const a=document.createElement("label");a.textContent=i("editor.chart_window"),a.style.cssText=n;const s=document.createElement("div");s.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=e+"width: 70px; cursor: text;",c=(t,e,n,i)=>{const r=document.createElement("div");r.style.cssText="display: flex; align-items: center; gap: 6px;";const o=document.createElement("input");o.type="number",o.min=e,o.max=n,o.value=String(t),o.style.cssText=l;const a=document.createElement("span");return a.textContent=i,a.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",r.appendChild(o),r.appendChild(a),{wrap:r,input:o}},u=parseInt(String(this._config.history_days))||0,h=parseInt(String(this._config.history_hours))||0,d=parseInt(String(this._config.history_minutes))||0,p=c(u,"0","30",i("editor.days")),f=c(h,"0","23",i("editor.hours")),g=c(d,"0","59",i("editor.minutes")),v=()=>{this._config={...this._config,history_days:parseInt(p.input.value)||0,history_hours:parseInt(f.input.value)||0,history_minutes:parseInt(g.input.value)||0},this._fireConfigChanged()};p.input.addEventListener("change",v),f.input.addEventListener("change",v),g.input.addEventListener("change",v),s.appendChild(p.wrap),s.appendChild(f.wrap),s.appendChild(g.wrap),o.appendChild(a),o.appendChild(s),t.appendChild(o),this._daysInput=p.input,this._hoursInput=f.input,this._minsInput=g.input}_buildMetricSelector(t,e,n,r){const o=document.createElement("div");o.style.cssText=r;const a=document.createElement("label");a.textContent=i("editor.chart_metric"),a.style.cssText=n;const s=document.createElement("select");s.style.cssText=e,s.addEventListener("change",()=>{this._config={...this._config,chart_metric:s.value},this._fireConfigChanged()}),o.appendChild(a),o.appendChild(s),t.appendChild(o),this._metricSelect=s}_buildTabStyleSelector(t,e,n,r){const o=document.createElement("div");o.style.cssText=r;const a=document.createElement("label");a.textContent=i("editor.tab_style"),a.style.cssText=n;const s=document.createElement("select");s.style.cssText=e;const l=[{value:"text",text:i("editor.tab_style_text")},{value:"icon",text:i("editor.tab_style_icon")}];for(const t of l){const e=document.createElement("option");e.value=t.value,e.textContent=t.text,t.value===(this._config.tab_style??"text")&&(e.selected=!0),s.appendChild(e)}s.addEventListener("change",()=>{this._config={...this._config,tab_style:s.value},this._fireConfigChanged()}),o.appendChild(a),o.appendChild(s),t.appendChild(o),this._tabStyleSelect=s}_buildSectionCheckboxes(t,e,n){const r=document.createElement("div");r.style.cssText=n;const o=document.createElement("label");o.textContent=i("editor.visible_sections"),o.style.cssText=e,r.appendChild(o);const a=[{key:"show_panel",label:i("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:i("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:i("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const t of a){const e=document.createElement("div");e.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; cursor: pointer;";const n=document.createElement("input");n.type="checkbox",n.checked=!1!==this._config[t.key],n.style.cssText="width: 18px; height: 18px; cursor: pointer; accent-color: var(--primary-color);";const i=document.createElement("span");i.textContent=t.label,i.style.cssText="font-size: 0.9em; color: var(--primary-text-color); cursor: pointer;",e.appendChild(n),e.appendChild(i),r.appendChild(e),this._checkboxes[t.key]=n;let o=null;t.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",r.appendChild(o),this._entityContainers[t.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[t.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}t.appendChild(r)}_isChartEntity(t,e,n){const i=(e.original_name??"").toLowerCase(),r=e.unique_id??"";if("power"===i||"battery power"===i||r.endsWith("_power"))return!0;if("bess"===n){if("battery level"===i||"battery percentage"===i||r.endsWith("_battery_level")||r.endsWith("_battery_percentage"))return!0;if("state of energy"===i||r.endsWith("_soe_kwh"))return!0;if("nameplate capacity"===i||r.endsWith("_nameplate_capacity"))return!0}return!1}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities??{};for(const[,n]of Object.entries(t)){const t=n.type?this._entityContainers[n.type]:void 0;if(t&&(t.innerHTML="",n.entities))for(const[i,r]of Object.entries(n.entities)){if("sensor"===r.domain&&this._isChartEntity(i,r,n.type??""))continue;const o=document.createElement("div");o.style.cssText="display: flex; align-items: center; gap: 8px; margin-bottom: 5px; cursor: pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!0===e[i],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");let l=r.original_name??i;const c=n.name??"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),s.textContent=l,s.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(a),o.appendChild(s),t.appendChild(o),a.addEventListener("change",()=>{const t={...this._config.visible_sub_entities??{}};a.checked?t[i]=!0:delete t[i],this._config={...this._config,visible_sub_entities:t},this._fireConfigChanged()})}}}async _discoverAvailableRoles(t){if(this._hass&&t)try{const e=await this._hass.callWS({type:`${l}/panel_topology`,device_id:t}),n=new Set;for(const t of Object.values(e.circuits??{}))for(const e of Object.keys(t.entities??{}))n.add(e);this._availableRoles=n,this._populateMetricSelect(),e.sub_devices&&this._populateEntityCheckboxes(e.sub_devices)}catch{this._availableRoles=null,this._populateMetricSelect()}}_populateMetricSelect(){const t=this._metricSelect;if(!t)return;const e=this._config.chart_metric??o;t.innerHTML="";for(const[n,i]of Object.entries(g)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const r=document.createElement("option");r.value=n,r.textContent=i.label(),n===e&&(r.selected=!0),t.appendChild(r)}}_updateControls(){if(this._panelSelect&&(this._panelSelect.value=this._config.device_id??""),this._daysInput&&(this._daysInput.value=String(parseInt(String(this._config.history_days))||0)),this._hoursInput&&(this._hoursInput.value=String(parseInt(String(this._config.history_hours))||0)),this._minsInput&&(this._minsInput.value=String(parseInt(String(this._config.history_minutes))||0)),this._metricSelect&&(this._metricSelect.value=this._config.chart_metric??o),this._checkboxes)for(const[t,e]of Object.entries(this._checkboxes))e.checked=!1!==this._config[t];this._tabStyleSelect&&(this._tabStyleSelect.value=this._config.tab_style??"text")}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}try{customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",dT)}catch{}window.customCards=window.customCards??[],window.customCards.push({type:"span-panel-card",name:"SPAN Panel",description:"Physical panel layout with live power charts matching the SPAN frontend",preview:!0}),console.warn("%c SPAN-PANEL-CARD %c v0.9.4 ","background: var(--primary-color, #4dd9af); color: var(--text-primary-color, #000); font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;"); diff --git a/dist/span-panel.js b/dist/span-panel.js index 67b29a8..cf30277 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -1,58 +1,58 @@ -let e="en";const t={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.all_targets":"All","notification.event_bus_target":"Event Bus (HA event bus)","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","error.panel_offline":"SPAN Panel unreachable","error.panel_reconnected":"SPAN Panel reconnected","error.panel_offline_named":"{name} unreachable","error.panel_reconnected_named":"{name} reconnected","error.discovery_failed":"Unable to connect to SPAN Panel","error.relay_failed":"Unable to toggle relay","error.shedding_failed":"Unable to update shedding priority","error.threshold_failed":"Unable to save threshold","error.graph_horizon_failed":"Unable to update graph time horizon","error.favorites_fetch_failed":"Unable to load favorites","error.favorites_toggle_failed":"Unable to update favorite","error.history_failed":"Unable to load historical data","error.monitoring_failed":"Unable to load monitoring status","error.graph_settings_failed":"Unable to load graph settings","error.areas_failed":"Area assignments may be out of sync","error.retry":"Retry","card.connecting":"Connecting to SPAN Panel...","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.configure_subdevice":"Configure device","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.favorites_subtitle":"Favorites","sidepanel.global_default":"Global Default","sidepanel.list_view_columns":"List View Columns","sidepanel.columns":"Columns","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.favorite":"Favorite","sidepanel.save_to_favorites":"Save to favorites","panel.favorites":"Favorites","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (bus de eventos de HA)","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","error.panel_offline":"SPAN Panel inaccesible","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inaccesible","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"No se puede conectar al SPAN Panel","error.relay_failed":"No se pudo cambiar el relé","error.shedding_failed":"No se pudo actualizar la prioridad de desconexión","error.threshold_failed":"No se pudo guardar el umbral","error.graph_horizon_failed":"No se pudo actualizar el horizonte temporal del gráfico","error.favorites_fetch_failed":"No se pudieron cargar los favoritos","error.favorites_toggle_failed":"No se pudo actualizar el favorito","error.history_failed":"No se pudieron cargar los datos históricos","error.monitoring_failed":"No se pudo cargar el estado de monitoreo","error.graph_settings_failed":"No se pudo cargar la configuración del gráfico","error.areas_failed":"Las asignaciones de áreas pueden estar desincronizadas","error.retry":"Reintentar","card.connecting":"Conectando al SPAN Panel...","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.favorites_subtitle":"Favoritos","sidepanel.global_default":"Predeterminado Global","sidepanel.list_view_columns":"Columnas de la lista","sidepanel.columns":"Columnas","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Guardar en favoritos","panel.favorites":"Favoritos","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (bus d'événements HA)","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","error.panel_offline":"SPAN Panel inaccessible","error.panel_reconnected":"SPAN Panel reconnecté","error.panel_offline_named":"{name} inaccessible","error.panel_reconnected_named":"{name} reconnecté","error.discovery_failed":"Impossible de se connecter au SPAN Panel","error.relay_failed":"Impossible de basculer le relais","error.shedding_failed":"Impossible de mettre à jour la priorité de délestage","error.threshold_failed":"Impossible d'enregistrer le seuil","error.graph_horizon_failed":"Impossible de mettre à jour l'horizon temporel du graphique","error.favorites_fetch_failed":"Impossible de charger les favoris","error.favorites_toggle_failed":"Impossible de mettre à jour le favori","error.history_failed":"Impossible de charger les données historiques","error.monitoring_failed":"Impossible de charger l'état de surveillance","error.graph_settings_failed":"Impossible de charger les paramètres du graphique","error.areas_failed":"Les affectations de zones peuvent être désynchronisées","error.retry":"Réessayer","card.connecting":"Connexion au SPAN Panel...","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.configure_subdevice":"Configurer l'appareil","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.favorites_subtitle":"Favoris","sidepanel.global_default":"Défaut Global","sidepanel.list_view_columns":"Colonnes de la liste","sidepanel.columns":"Colonnes","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.favorite":"Favori","sidepanel.save_to_favorites":"Enregistrer dans les favoris","panel.favorites":"Favoris","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.all_targets":"すべて","notification.event_bus_target":"イベントバス (HAイベントバス)","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","error.panel_offline":"SPANパネルに接続できません","error.panel_reconnected":"SPANパネルが再接続されました","error.panel_offline_named":"{name}に接続できません","error.panel_reconnected_named":"{name}が再接続されました","error.discovery_failed":"SPANパネルへの接続に失敗しました","error.relay_failed":"リレーの切り替えに失敗しました","error.shedding_failed":"シェディング優先度の更新に失敗しました","error.threshold_failed":"しきい値の保存に失敗しました","error.graph_horizon_failed":"グラフの時間範囲の更新に失敗しました","error.favorites_fetch_failed":"お気に入りの読み込みに失敗しました","error.favorites_toggle_failed":"お気に入りの更新に失敗しました","error.history_failed":"履歴データの読み込みに失敗しました","error.monitoring_failed":"監視ステータスの読み込みに失敗しました","error.graph_settings_failed":"グラフ設定の読み込みに失敗しました","error.areas_failed":"エリア割り当てが同期されていない可能性があります","error.retry":"再試行","card.connecting":"SPANパネルに接続中...","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.configure_subdevice":"デバイスを設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.favorites_subtitle":"お気に入り","sidepanel.global_default":"グローバルデフォルト","sidepanel.list_view_columns":"リスト表示の列数","sidepanel.columns":"列","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.favorite":"お気に入り","sidepanel.save_to_favorites":"お気に入りに保存","panel.favorites":"お気に入り","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (barramento de eventos do HA)","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","error.panel_offline":"SPAN Panel inacessível","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inacessível","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"Não foi possível conectar ao SPAN Panel","error.relay_failed":"Não foi possível alternar o relé","error.shedding_failed":"Não foi possível atualizar a prioridade de desligamento","error.threshold_failed":"Não foi possível salvar o limite","error.graph_horizon_failed":"Não foi possível atualizar o horizonte temporal do gráfico","error.favorites_fetch_failed":"Não foi possível carregar os favoritos","error.favorites_toggle_failed":"Não foi possível atualizar o favorito","error.history_failed":"Não foi possível carregar os dados históricos","error.monitoring_failed":"Não foi possível carregar o status de monitoramento","error.graph_settings_failed":"Não foi possível carregar as configurações do gráfico","error.areas_failed":"As atribuições de áreas podem estar fora de sincronização","error.retry":"Tentar novamente","card.connecting":"Conectando ao SPAN Panel...","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.favorites_subtitle":"Favoritos","sidepanel.global_default":"Padrão Global","sidepanel.list_view_columns":"Colunas da Lista","sidepanel.columns":"Colunas","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Salvar nos favoritos","panel.favorites":"Favoritos","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};function n(n){return t[e]?.[n]??t.en?.[n]??n}function i(n,i){return(t[e]?.[n]??t.en?.[n]??n).replace(/\{(\w+)\}/g,(e,t)=>Object.prototype.hasOwnProperty.call(i,t)?i[t]:`{${t}}`)}const s="power",o="5m",r={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},a="span_panel",l="CLOSED",c="pv",d="bess",h="evse",u="sub_",p=500,g={power:{entityRole:"power",label:()=>n("metric.power"),unit:e=>Math.abs(e)>=1e3?"kW":"W",format:e=>{const t=Math.abs(e);return t>=1e3?(t/1e3).toFixed(1):t<10&&t>0?t.toFixed(1):String(Math.round(t))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},_={soc:{entityRole:"soc",label:()=>n("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>n("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:g.power},f={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}},v="#ff9800";function m(e,t,n,i){var s,o=arguments.length,r=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)r=Reflect.decorate(e,t,n,i);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(r=(o<3?s(r):o>3?s(t,n,r):s(t,n))||r);return o>3&&r&&Object.defineProperty(t,n,r),r}"function"==typeof SuppressedError&&SuppressedError; +let t="en";const e={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","monitoring.heading":"Monitoring","monitoring.global_settings":"Global Settings","monitoring.enabled":"Enabled","monitoring.continuous":"Continuous (%)","monitoring.spike":"Spike (%)","monitoring.window":"Window (min)","monitoring.cooldown":"Cooldown (min)","monitoring.monitored_points":"Monitored Points","monitoring.col.name":"Name","monitoring.col.continuous":"Continuous","monitoring.col.spike":"Spike","monitoring.col.window":"Window","monitoring.col.cooldown":"Cooldown","monitoring.all_none":"All / None","monitoring.reset":"Reset","notification.heading":"Notification Settings","notification.targets":"Notify Targets","notification.none_selected":"None selected","notification.no_targets":"No notify targets found","notification.all_targets":"All","notification.event_bus_target":"Event Bus (HA event bus)","notification.priority":"Priority","notification.priority.default":"Default","notification.priority.passive":"Passive","notification.priority.active":"Active","notification.priority.time_sensitive":"Time-sensitive","notification.priority.critical":"Critical","notification.hint.critical":"Overrides silent/DND","notification.hint.time_sensitive":"Breaks through Focus","notification.hint.passive":"Delivers silently","notification.hint.active":"Standard delivery","notification.title_template":"Title Template","notification.message_template":"Message Template","notification.placeholders":"Placeholders:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","error.panel_offline":"SPAN Panel unreachable","error.panel_reconnected":"SPAN Panel reconnected","error.panel_offline_named":"{name} unreachable","error.panel_reconnected_named":"{name} reconnected","error.discovery_failed":"Unable to connect to SPAN Panel","error.relay_failed":"Unable to toggle relay","error.shedding_failed":"Unable to update shedding priority","error.threshold_failed":"Unable to save threshold","error.graph_horizon_failed":"Unable to update graph time horizon","error.favorites_fetch_failed":"Unable to load favorites","error.favorites_toggle_failed":"Unable to update favorite","error.history_failed":"Unable to load historical data","error.monitoring_failed":"Unable to load monitoring status","error.graph_settings_failed":"Unable to load graph settings","error.areas_failed":"Area assignments may be out of sync","error.retry":"Retry","card.connecting":"Connecting to SPAN Panel...","settings.heading":"Settings","settings.description":"General integration settings (entity naming, device prefix, circuit numbers) are managed through the integration's options flow.","settings.open_link":"Open SPAN Panel Integration Settings","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Panel monitoring settings","header.graph_settings":"Graph time horizon settings","header.site":"Site","header.grid":"Grid","header.upstream":"Upstream","header.downstream":"Downstream","header.solar":"Solar","header.battery":"Battery","header.toggle_units":"Toggle Watts / Amps","header.enable_switches":"Enable Switches","header.switches_enabled":"Switches Enabled","grid.unknown":"Unknown","grid.configure":"Configure circuit","grid.configure_subdevice":"Configure device","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"EV Charger","subdevice.battery":"Battery","subdevice.fallback":"Sub-device","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Power","sidepanel.graph_settings":"Graph Settings","sidepanel.global_defaults":"Global defaults for all circuits","sidepanel.favorites_subtitle":"Favorites","sidepanel.global_default":"Global Default","sidepanel.list_view_columns":"List View Columns","sidepanel.columns":"Columns","sidepanel.circuit_scales":"Circuit Graph Scales","sidepanel.subdevice_scales":"Sub-Device Graph Scales","sidepanel.reset_to_global":"Reset to global default","sidepanel.relay":"Relay","sidepanel.breaker":"Breaker","sidepanel.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","sidepanel.monitoring":"Monitoring","sidepanel.global":"Global","sidepanel.custom":"Custom","sidepanel.continuous_pct":"Continuous %","sidepanel.spike_pct":"Spike %","sidepanel.window_duration":"Window duration","sidepanel.cooldown":"Cooldown","sidepanel.favorite":"Favorite","sidepanel.save_to_favorites":"Save to favorites","panel.favorites":"Favorites","status.monitoring":"Monitoring","status.circuits":"circuits","status.mains":"mains","status.warning":"warning","status.warnings":"warnings","status.alert":"alert","status.alerts":"alerts","status.override":"override","status.overrides":"overrides","card.no_device":"Open the card editor and select your SPAN Panel device.","card.device_not_found":"Panel device not found. Check device_id in card config.","card.topology_error":"Topology response missing panel_size and no circuits found. Update the SPAN Panel integration.","card.panel_size_error":"Could not determine panel_size. No circuits found and no panel_size attribute. Update the SPAN Panel integration.","editor.panel_label":"SPAN Panel","editor.select_panel":"Select a panel...","editor.chart_window":"Chart time window","editor.days":"days","editor.hours":"hours","editor.minutes":"minutes","editor.chart_metric":"Chart metric","editor.visible_sections":"Visible sections","editor.panel_circuits":"Panel circuits","editor.battery_bess":"Battery (BESS)","editor.ev_charger_evse":"EV Charger (EVSE)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","monitoring.heading":"Monitoreo","monitoring.global_settings":"Configuración Global","monitoring.enabled":"Activado","monitoring.continuous":"Continuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Ventana (min)","monitoring.cooldown":"Enfriamiento (min)","monitoring.monitored_points":"Puntos Monitoreados","monitoring.col.name":"Nombre","monitoring.col.continuous":"Continuo","monitoring.col.spike":"Pico","monitoring.col.window":"Ventana","monitoring.col.cooldown":"Enfriamiento","monitoring.all_none":"Todos / Ninguno","monitoring.reset":"Restablecer","notification.heading":"Configuración de Notificaciones","notification.targets":"Destinos de Notificación","notification.none_selected":"Ninguno seleccionado","notification.no_targets":"No se encontraron destinos de notificación","notification.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (bus de eventos de HA)","notification.priority":"Prioridad","notification.priority.default":"Predeterminado","notification.priority.passive":"Pasivo","notification.priority.active":"Activo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Anula silencio/No molestar","notification.hint.time_sensitive":"Atraviesa el modo Concentración","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega estándar","notification.title_template":"Plantilla de Título","notification.message_template":"Plantilla de Mensaje","notification.placeholders":"Variables:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","error.panel_offline":"SPAN Panel inaccesible","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inaccesible","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"No se puede conectar al SPAN Panel","error.relay_failed":"No se pudo cambiar el relé","error.shedding_failed":"No se pudo actualizar la prioridad de desconexión","error.threshold_failed":"No se pudo guardar el umbral","error.graph_horizon_failed":"No se pudo actualizar el horizonte temporal del gráfico","error.favorites_fetch_failed":"No se pudieron cargar los favoritos","error.favorites_toggle_failed":"No se pudo actualizar el favorito","error.history_failed":"No se pudieron cargar los datos históricos","error.monitoring_failed":"No se pudo cargar el estado de monitoreo","error.graph_settings_failed":"No se pudo cargar la configuración del gráfico","error.areas_failed":"Las asignaciones de áreas pueden estar desincronizadas","error.retry":"Reintentar","card.connecting":"Conectando al SPAN Panel...","settings.heading":"Configuración","settings.description":"La configuración general de la integración (nombres de entidades, prefijo de dispositivo, números de circuito) se administra a través del flujo de opciones de la integración.","settings.open_link":"Abrir Configuración de Integración SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Configuración de monitoreo del panel","header.graph_settings":"Configuración del horizonte temporal del gráfico","header.site":"Sitio","header.grid":"Red","header.upstream":"Aguas arriba","header.downstream":"Aguas abajo","header.solar":"Solar","header.battery":"Batería","header.toggle_units":"Alternar Watts / Amperios","header.enable_switches":"Habilitar Interruptores","header.switches_enabled":"Interruptores Habilitados","grid.unknown":"Desconocido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Enc","grid.off":"Apag","subdevice.ev_charger":"Cargador EV","subdevice.battery":"Batería","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potencia","sidepanel.graph_settings":"Configuración de Gráficos","sidepanel.global_defaults":"Valores predeterminados globales para todos los circuitos","sidepanel.favorites_subtitle":"Favoritos","sidepanel.global_default":"Predeterminado Global","sidepanel.list_view_columns":"Columnas de la lista","sidepanel.columns":"Columnas","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Restablecer al valor global","sidepanel.relay":"Relé","sidepanel.breaker":"Interruptor","sidepanel.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","sidepanel.monitoring":"Monitoreo","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Continuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duración de ventana","sidepanel.cooldown":"Enfriamiento","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Guardar en favoritos","panel.favorites":"Favoritos","status.monitoring":"Monitoreo","status.circuits":"circuitos","status.mains":"alimentación","status.warning":"advertencia","status.warnings":"advertencias","status.alert":"alerta","status.alerts":"alertas","status.override":"anulación","status.overrides":"anulaciones","card.no_device":"Abra el editor de tarjeta y seleccione su dispositivo SPAN Panel.","card.device_not_found":"Dispositivo de panel no encontrado. Verifique device_id en la configuración de la tarjeta.","card.topology_error":"La respuesta de topología no contiene panel_size y no se encontraron circuitos. Actualice la integración SPAN Panel.","card.panel_size_error":"No se pudo determinar panel_size. No se encontraron circuitos ni atributo panel_size. Actualice la integración SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Seleccione un panel...","editor.chart_window":"Ventana de tiempo del gráfico","editor.days":"días","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica del gráfico","editor.visible_sections":"Secciones visibles","editor.panel_circuits":"Circuitos del panel","editor.battery_bess":"Batería (BESS)","editor.ev_charger_evse":"Cargador EV (EVSE)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","monitoring.heading":"Surveillance","monitoring.global_settings":"Paramètres Globaux","monitoring.enabled":"Activé","monitoring.continuous":"Continu (%)","monitoring.spike":"Pic (%)","monitoring.window":"Fenêtre (min)","monitoring.cooldown":"Refroidissement (min)","monitoring.monitored_points":"Points Surveillés","monitoring.col.name":"Nom","monitoring.col.continuous":"Continu","monitoring.col.spike":"Pic","monitoring.col.window":"Fenêtre","monitoring.col.cooldown":"Refroidissement","monitoring.all_none":"Tous / Aucun","monitoring.reset":"Réinitialiser","notification.heading":"Paramètres de Notification","notification.targets":"Cibles de Notification","notification.none_selected":"Aucune sélection","notification.no_targets":"Aucune cible de notification trouvée","notification.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (bus d'événements HA)","notification.priority":"Priorité","notification.priority.default":"Par défaut","notification.priority.passive":"Passif","notification.priority.active":"Actif","notification.priority.time_sensitive":"Urgent","notification.priority.critical":"Critique","notification.hint.critical":"Outrepasse silencieux/NPD","notification.hint.time_sensitive":"Traverse le mode Concentration","notification.hint.passive":"Livraison silencieuse","notification.hint.active":"Livraison standard","notification.title_template":"Modèle de Titre","notification.message_template":"Modèle de Message","notification.placeholders":"Variables :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","error.panel_offline":"SPAN Panel inaccessible","error.panel_reconnected":"SPAN Panel reconnecté","error.panel_offline_named":"{name} inaccessible","error.panel_reconnected_named":"{name} reconnecté","error.discovery_failed":"Impossible de se connecter au SPAN Panel","error.relay_failed":"Impossible de basculer le relais","error.shedding_failed":"Impossible de mettre à jour la priorité de délestage","error.threshold_failed":"Impossible d'enregistrer le seuil","error.graph_horizon_failed":"Impossible de mettre à jour l'horizon temporel du graphique","error.favorites_fetch_failed":"Impossible de charger les favoris","error.favorites_toggle_failed":"Impossible de mettre à jour le favori","error.history_failed":"Impossible de charger les données historiques","error.monitoring_failed":"Impossible de charger l'état de surveillance","error.graph_settings_failed":"Impossible de charger les paramètres du graphique","error.areas_failed":"Les affectations de zones peuvent être désynchronisées","error.retry":"Réessayer","card.connecting":"Connexion au SPAN Panel...","settings.heading":"Paramètres","settings.description":"Les paramètres généraux de l'intégration (noms d'entités, préfixe de l'appareil, numéros de circuit) sont gérés via le flux d'options de l'intégration.","settings.open_link":"Ouvrir les Paramètres d'Intégration SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Paramètres de surveillance du panneau","header.graph_settings":"Paramètres d'horizon temporel du graphique","header.site":"Site","header.grid":"Réseau","header.upstream":"Amont","header.downstream":"Aval","header.solar":"Solaire","header.battery":"Batterie","header.toggle_units":"Basculer Watts / Ampères","header.enable_switches":"Activer les interrupteurs","header.switches_enabled":"Interrupteurs activés","grid.unknown":"Inconnu","grid.configure":"Configurer le circuit","grid.configure_subdevice":"Configurer l'appareil","grid.on":"On","grid.off":"Off","subdevice.ev_charger":"Chargeur VE","subdevice.battery":"Batterie","subdevice.fallback":"Sous-appareil","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Puissance","sidepanel.graph_settings":"Paramètres des Graphiques","sidepanel.global_defaults":"Valeurs par défaut globales pour tous les circuits","sidepanel.favorites_subtitle":"Favoris","sidepanel.global_default":"Défaut Global","sidepanel.list_view_columns":"Colonnes de la liste","sidepanel.columns":"Colonnes","sidepanel.circuit_scales":"Échelles des Graphiques de Circuits","sidepanel.subdevice_scales":"Échelles des Graphiques de Sous-Appareils","sidepanel.reset_to_global":"Réinitialiser à la valeur globale","sidepanel.relay":"Relais","sidepanel.breaker":"Disjoncteur","sidepanel.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","sidepanel.monitoring":"Surveillance","sidepanel.global":"Global","sidepanel.custom":"Personnalisé","sidepanel.continuous_pct":"Continu %","sidepanel.spike_pct":"Pic %","sidepanel.window_duration":"Durée de fenêtre","sidepanel.cooldown":"Refroidissement","sidepanel.favorite":"Favori","sidepanel.save_to_favorites":"Enregistrer dans les favoris","panel.favorites":"Favoris","status.monitoring":"Surveillance","status.circuits":"circuits","status.mains":"alimentation","status.warning":"avertissement","status.warnings":"avertissements","status.alert":"alerte","status.alerts":"alertes","status.override":"remplacement","status.overrides":"remplacements","card.no_device":"Ouvrez l'éditeur de carte et sélectionnez votre appareil SPAN Panel.","card.device_not_found":"Appareil de panneau introuvable. Vérifiez device_id dans la configuration de la carte.","card.topology_error":"La réponse de topologie ne contient pas panel_size et aucun circuit trouvé. Mettez à jour l'intégration SPAN Panel.","card.panel_size_error":"Impossible de déterminer panel_size. Aucun circuit trouvé et aucun attribut panel_size. Mettez à jour l'intégration SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Sélectionnez un panneau...","editor.chart_window":"Fenêtre de temps du graphique","editor.days":"jours","editor.hours":"heures","editor.minutes":"minutes","editor.chart_metric":"Métrique du graphique","editor.visible_sections":"Sections visibles","editor.panel_circuits":"Circuits du panneau","editor.battery_bess":"Batterie (BESS)","editor.ev_charger_evse":"Chargeur VE (EVSE)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","monitoring.heading":"モニタリング","monitoring.global_settings":"グローバル設定","monitoring.enabled":"有効","monitoring.continuous":"継続 (%)","monitoring.spike":"スパイク (%)","monitoring.window":"ウィンドウ (分)","monitoring.cooldown":"クールダウン (分)","monitoring.monitored_points":"監視ポイント","monitoring.col.name":"名前","monitoring.col.continuous":"継続","monitoring.col.spike":"スパイク","monitoring.col.window":"ウィンドウ","monitoring.col.cooldown":"クールダウン","monitoring.all_none":"全選択 / 全解除","monitoring.reset":"リセット","notification.heading":"通知設定","notification.targets":"通知先","notification.none_selected":"未選択","notification.no_targets":"通知先が見つかりません","notification.all_targets":"すべて","notification.event_bus_target":"イベントバス (HAイベントバス)","notification.priority":"優先度","notification.priority.default":"デフォルト","notification.priority.passive":"パッシブ","notification.priority.active":"アクティブ","notification.priority.time_sensitive":"緊急","notification.priority.critical":"重大","notification.hint.critical":"サイレント/おやすみモードを無視","notification.hint.time_sensitive":"集中モードを突破","notification.hint.passive":"サイレント配信","notification.hint.active":"標準配信","notification.title_template":"タイトルテンプレート","notification.message_template":"メッセージテンプレート","notification.placeholders":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","error.panel_offline":"SPANパネルに接続できません","error.panel_reconnected":"SPANパネルが再接続されました","error.panel_offline_named":"{name}に接続できません","error.panel_reconnected_named":"{name}が再接続されました","error.discovery_failed":"SPANパネルへの接続に失敗しました","error.relay_failed":"リレーの切り替えに失敗しました","error.shedding_failed":"シェディング優先度の更新に失敗しました","error.threshold_failed":"しきい値の保存に失敗しました","error.graph_horizon_failed":"グラフの時間範囲の更新に失敗しました","error.favorites_fetch_failed":"お気に入りの読み込みに失敗しました","error.favorites_toggle_failed":"お気に入りの更新に失敗しました","error.history_failed":"履歴データの読み込みに失敗しました","error.monitoring_failed":"監視ステータスの読み込みに失敗しました","error.graph_settings_failed":"グラフ設定の読み込みに失敗しました","error.areas_failed":"エリア割り当てが同期されていない可能性があります","error.retry":"再試行","card.connecting":"SPANパネルに接続中...","settings.heading":"設定","settings.description":"統合の一般設定(エンティティ名、デバイスプレフィックス、回路番号)は統合のオプションフローで管理されます。","settings.open_link":"SPAN Panel統合設定を開く","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"パネルモニタリング設定","header.graph_settings":"グラフ時間範囲設定","header.site":"サイト","header.grid":"グリッド","header.upstream":"上流","header.downstream":"下流","header.solar":"ソーラー","header.battery":"バッテリー","header.toggle_units":"ワット/アンペア切り替え","header.enable_switches":"スイッチを有効化","header.switches_enabled":"スイッチ有効","grid.unknown":"不明","grid.configure":"回路を設定","grid.configure_subdevice":"デバイスを設定","grid.on":"オン","grid.off":"オフ","subdevice.ev_charger":"EV充電器","subdevice.battery":"バッテリー","subdevice.fallback":"サブデバイス","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"電力","sidepanel.graph_settings":"グラフ設定","sidepanel.global_defaults":"全回路のグローバルデフォルト","sidepanel.favorites_subtitle":"お気に入り","sidepanel.global_default":"グローバルデフォルト","sidepanel.list_view_columns":"リスト表示の列数","sidepanel.columns":"列","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.favorite":"お気に入り","sidepanel.save_to_favorites":"お気に入りに保存","panel.favorites":"お気に入り","status.monitoring":"モニタリング","status.circuits":"回路","status.mains":"主電源","status.warning":"警告","status.warnings":"警告","status.alert":"アラート","status.alerts":"アラート","status.override":"上書き","status.overrides":"上書き","card.no_device":"カードエディタを開いてSPAN Panelデバイスを選択してください。","card.device_not_found":"パネルデバイスが見つかりません。カード設定のdevice_idを確認してください。","card.topology_error":"トポロジー応答にpanel_sizeがなく、回路が見つかりません。SPAN Panel統合を更新してください。","card.panel_size_error":"panel_sizeを判定できません。回路がpanel_size属性が見つかりません。SPAN Panel統合を更新してください。","editor.panel_label":"SPAN Panel","editor.select_panel":"パネルを選択...","editor.chart_window":"グラフ時間ウィンドウ","editor.days":"日","editor.hours":"時間","editor.minutes":"分","editor.chart_metric":"グラフ指標","editor.visible_sections":"表示セクション","editor.panel_circuits":"パネル回路","editor.battery_bess":"バッテリー (BESS)","editor.ev_charger_evse":"EV充電器 (EVSE)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","monitoring.heading":"Monitoramento","monitoring.global_settings":"Configurações Globais","monitoring.enabled":"Ativado","monitoring.continuous":"Contínuo (%)","monitoring.spike":"Pico (%)","monitoring.window":"Janela (min)","monitoring.cooldown":"Resfriamento (min)","monitoring.monitored_points":"Pontos Monitorados","monitoring.col.name":"Nome","monitoring.col.continuous":"Contínuo","monitoring.col.spike":"Pico","monitoring.col.window":"Janela","monitoring.col.cooldown":"Resfriamento","monitoring.all_none":"Todos / Nenhum","monitoring.reset":"Redefinir","notification.heading":"Configurações de Notificação","notification.targets":"Destinos de Notificação","notification.none_selected":"Nenhum selecionado","notification.no_targets":"Nenhum destino de notificação encontrado","notification.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (barramento de eventos do HA)","notification.priority":"Prioridade","notification.priority.default":"Padrão","notification.priority.passive":"Passivo","notification.priority.active":"Ativo","notification.priority.time_sensitive":"Urgente","notification.priority.critical":"Crítico","notification.hint.critical":"Substitui silencioso/Não perturbar","notification.hint.time_sensitive":"Atravessa o modo Foco","notification.hint.passive":"Entrega silenciosa","notification.hint.active":"Entrega padrão","notification.title_template":"Modelo de Título","notification.message_template":"Modelo de Mensagem","notification.placeholders":"Variáveis:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","error.panel_offline":"SPAN Panel inacessível","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inacessível","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"Não foi possível conectar ao SPAN Panel","error.relay_failed":"Não foi possível alternar o relé","error.shedding_failed":"Não foi possível atualizar a prioridade de desligamento","error.threshold_failed":"Não foi possível salvar o limite","error.graph_horizon_failed":"Não foi possível atualizar o horizonte temporal do gráfico","error.favorites_fetch_failed":"Não foi possível carregar os favoritos","error.favorites_toggle_failed":"Não foi possível atualizar o favorito","error.history_failed":"Não foi possível carregar os dados históricos","error.monitoring_failed":"Não foi possível carregar o status de monitoramento","error.graph_settings_failed":"Não foi possível carregar as configurações do gráfico","error.areas_failed":"As atribuições de áreas podem estar fora de sincronização","error.retry":"Tentar novamente","card.connecting":"Conectando ao SPAN Panel...","settings.heading":"Configurações","settings.description":"As configurações gerais da integração (nomes de entidades, prefixo do dispositivo, números de circuito) são gerenciadas através do fluxo de opções da integração.","settings.open_link":"Abrir Configurações de Integração SPAN Panel","horizon.5m":"5 Minutes","horizon.1h":"1 Hour","horizon.1d":"1 Day","horizon.1w":"1 Week","horizon.1M":"1 Month","settings.graph_horizon_heading":"Graph Time Horizon","settings.graph_horizon_description":"Default time window for all circuit graphs. Individual circuits can override this in their settings panel.","settings.global_default":"Global Default","settings.default_scale":"Default Scale","settings.circuit_graph_scales":"Circuit Graph Scales","settings.col.circuit":"Circuit","settings.col.scale":"Scale","sidepanel.graph_horizon":"Graph Time Horizon","header.default_name":"SPAN Panel","header.monitoring_settings":"Configurações de monitoramento do painel","header.graph_settings":"Configurações do horizonte temporal do gráfico","header.site":"Local","header.grid":"Rede","header.upstream":"Montante","header.downstream":"Jusante","header.solar":"Solar","header.battery":"Bateria","header.toggle_units":"Alternar Watts / Amperes","header.enable_switches":"Ativar Interruptores","header.switches_enabled":"Interruptores Ativados","grid.unknown":"Desconhecido","grid.configure":"Configurar circuito","grid.configure_subdevice":"Configurar dispositivo","grid.on":"Lig","grid.off":"Des","subdevice.ev_charger":"Carregador VE","subdevice.battery":"Bateria","subdevice.fallback":"Sub-dispositivo","subdevice.soc":"SoC","subdevice.soe":"SoE","subdevice.power":"Potência","sidepanel.graph_settings":"Configurações de Gráficos","sidepanel.global_defaults":"Padrões globais para todos os circuitos","sidepanel.favorites_subtitle":"Favoritos","sidepanel.global_default":"Padrão Global","sidepanel.list_view_columns":"Colunas da Lista","sidepanel.columns":"Colunas","sidepanel.circuit_scales":"Escalas de Gráficos de Circuitos","sidepanel.subdevice_scales":"Escalas de Gráficos de Sub-Dispositivos","sidepanel.reset_to_global":"Redefinir para o padrão global","sidepanel.relay":"Relé","sidepanel.breaker":"Disjuntor","sidepanel.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","sidepanel.monitoring":"Monitoramento","sidepanel.global":"Global","sidepanel.custom":"Personalizado","sidepanel.continuous_pct":"Contínuo %","sidepanel.spike_pct":"Pico %","sidepanel.window_duration":"Duração da janela","sidepanel.cooldown":"Resfriamento","sidepanel.favorite":"Favorito","sidepanel.save_to_favorites":"Salvar nos favoritos","panel.favorites":"Favoritos","status.monitoring":"Monitoramento","status.circuits":"circuitos","status.mains":"alimentação","status.warning":"aviso","status.warnings":"avisos","status.alert":"alerta","status.alerts":"alertas","status.override":"substituição","status.overrides":"substituições","card.no_device":"Abra o editor do cartão e selecione seu dispositivo SPAN Panel.","card.device_not_found":"Dispositivo do painel não encontrado. Verifique device_id na configuração do cartão.","card.topology_error":"A resposta de topologia não contém panel_size e nenhum circuito encontrado. Atualize a integração SPAN Panel.","card.panel_size_error":"Não foi possível determinar panel_size. Nenhum circuito encontrado e nenhum atributo panel_size. Atualize a integração SPAN Panel.","editor.panel_label":"SPAN Panel","editor.select_panel":"Selecione um painel...","editor.chart_window":"Janela de tempo do gráfico","editor.days":"dias","editor.hours":"horas","editor.minutes":"minutos","editor.chart_metric":"Métrica do gráfico","editor.visible_sections":"Seções visíveis","editor.panel_circuits":"Circuitos do painel","editor.battery_bess":"Bateria (BESS)","editor.ev_charger_evse":"Carregador VE (EVSE)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};function n(n){return e[t]?.[n]??e.en?.[n]??n}function i(n,i){return(e[t]?.[n]??e.en?.[n]??n).replace(/\{(\w+)\}/g,(t,e)=>Object.prototype.hasOwnProperty.call(i,e)?i[e]:`{${e}}`)}const r="power",o="5m",a={"5m":{ms:3e5,refreshMs:1e3,useRealtime:!0},"1h":{ms:36e5,refreshMs:3e4,useRealtime:!1},"1d":{ms:864e5,refreshMs:6e4,useRealtime:!1},"1w":{ms:6048e5,refreshMs:6e4,useRealtime:!1},"1M":{ms:2592e6,refreshMs:6e4,useRealtime:!1}},s="span_panel",l="CLOSED",c="pv",u="bess",h="evse",d="sub_",p=500,f={power:{entityRole:"power",label:()=>n("metric.power"),unit:t=>Math.abs(t)>=1e3?"kW":"W",format:t=>{const e=Math.abs(t);return e>=1e3?(e/1e3).toFixed(1):e<10&&e>0?e.toFixed(1):String(Math.round(e))}},current:{entityRole:"current",label:()=>n("metric.current"),unit:()=>"A",format:t=>Math.abs(t).toFixed(1)}},g={soc:{entityRole:"soc",label:()=>n("metric.soc"),unit:()=>"%",format:t=>String(Math.round(t)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>n("metric.soe"),unit:()=>"kWh",format:t=>t.toFixed(1)},power:f.power},v={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>n("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>n("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>n("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>n("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>n("shedding.unknown")}};var m=function(t,e){return m=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},m(t,e)};function y(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}m(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}function _(t,e,n,i){var r,o=arguments.length,a=o<3?e:null===i?i=Object.getOwnPropertyDescriptor(e,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(t,e,n,i);else for(var s=t.length-1;s>=0;s--)(r=t[s])&&(a=(o<3?r(a):o>3?r(e,n,a):r(e,n))||a);return o>3&&a&&Object.defineProperty(e,n,a),a}"function"==typeof SuppressedError&&SuppressedError; /** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const b=globalThis,y=b.ShadowRoot&&(void 0===b.ShadyCSS||b.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,w=Symbol(),x=new WeakMap;let S=class{constructor(e,t,n){if(this._$cssResult$=!0,n!==w)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(y&&void 0===e){const n=void 0!==t&&1===t.length;n&&(e=x.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&x.set(t,e))}return e}toString(){return this.cssText}};const $=e=>new S("string"==typeof e?e:e+"",void 0,w),C=(e,...t)=>{const n=1===e.length?e[0]:t.reduce((t,n,i)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+e[i+1],e[0]);return new S(n,e,w)},k=y?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const n of e.cssRules)t+=n.cssText;return $(t)})(e):e,{is:P,defineProperty:E,getOwnPropertyDescriptor:z,getOwnPropertyNames:A,getOwnPropertySymbols:N,getPrototypeOf:M}=Object,I=globalThis,T=I.trustedTypes,D=T?T.emptyScript:"",F=I.reactiveElementPolyfillSupport,L=(e,t)=>e,H={toAttribute(e,t){switch(t){case Boolean:e=e?D:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},O=(e,t)=>!P(e,t),R={attribute:!0,type:String,converter:H,reflect:!1,useDefault:!1,hasChanged:O}; +const b=globalThis,x=b.ShadowRoot&&(void 0===b.ShadyCSS||b.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,w=Symbol(),S=new WeakMap;let C=class{constructor(t,e,n){if(this._$cssResult$=!0,n!==w)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(x&&void 0===t){const n=void 0!==e&&1===e.length;n&&(t=S.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),n&&S.set(e,t))}return t}toString(){return this.cssText}};const k=t=>new C("string"==typeof t?t:t+"",void 0,w),M=(t,...e)=>{const n=1===t.length?t[0]:e.reduce((e,n,i)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+t[i+1],t[0]);return new C(n,t,w)},T=x?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const n of t.cssRules)e+=n.cssText;return k(e)})(t):t,{is:I,defineProperty:D,getOwnPropertyDescriptor:A,getOwnPropertyNames:P,getOwnPropertySymbols:L,getPrototypeOf:E}=Object,z=globalThis,N=z.trustedTypes,O=N?N.emptyScript:"",R=z.reactiveElementPolyfillSupport,$=(t,e)=>t,H={toAttribute(t,e){switch(e){case Boolean:t=t?O:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let n=t;switch(e){case Boolean:n=null!==t;break;case Number:n=null===t?null:Number(t);break;case Object:case Array:try{n=JSON.parse(t)}catch(t){n=null}}return n}},F=(t,e)=>!I(t,e),B={attribute:!0,type:String,converter:H,reflect:!1,useDefault:!1,hasChanged:F}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */Symbol.metadata??=Symbol("metadata"),I.litPropertyMetadata??=new WeakMap;let j=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=R){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const n=Symbol(),i=this.getPropertyDescriptor(e,n,t);void 0!==i&&E(this.prototype,e,i)}}static getPropertyDescriptor(e,t,n){const{get:i,set:s}=z(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const o=i?.call(this);s?.call(this,t),this.requestUpdate(e,o,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??R}static _$Ei(){if(this.hasOwnProperty(L("elementProperties")))return;const e=M(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(L("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(L("properties"))){const e=this.properties,t=[...A(e),...N(e)];for(const n of t)this.createProperty(n,e[n])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,n]of t)this.elementProperties.set(e,n)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const n=this._$Eu(e,t);void 0!==n&&this._$Eh.set(n,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const n=new Set(e.flat(1/0).reverse());for(const e of n)t.unshift(k(e))}else void 0!==e&&t.push(k(e));return t}static _$Eu(e,t){const n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const n of t.keys())this.hasOwnProperty(n)&&(e.set(n,this[n]),delete this[n]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(y)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const n of t){const t=document.createElement("style"),i=b.litNonce;void 0!==i&&t.setAttribute("nonce",i),t.textContent=n.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,n){this._$AK(e,n)}_$ET(e,t){const n=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,n);if(void 0!==i&&!0===n.reflect){const s=(void 0!==n.converter?.toAttribute?n.converter:H).toAttribute(t,n.type);this._$Em=e,null==s?this.removeAttribute(i):this.setAttribute(i,s),this._$Em=null}}_$AK(e,t){const n=this.constructor,i=n._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=n.getPropertyOptions(i),s="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:H;this._$Em=i;const o=s.fromAttribute(t,e.type);this[i]=o??this._$Ej?.get(i)??o,this._$Em=null}}requestUpdate(e,t,n,i=!1,s){if(void 0!==e){const o=this.constructor;if(!1===i&&(s=this[e]),n??=o.getPropertyOptions(e),!((n.hasChanged??O)(s,t)||n.useDefault&&n.reflect&&s===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:n,reflect:i,wrapped:s},o){n&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==s||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||n||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,n]of e){const{wrapped:e}=n,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,n,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};j.elementStyles=[],j.shadowRootOptions={mode:"open"},j[L("elementProperties")]=new Map,j[L("finalized")]=new Map,F?.({ReactiveElement:j}),(I.reactiveElementVersions??=[]).push("2.1.2"); + */Symbol.metadata??=Symbol("metadata"),z.litPropertyMetadata??=new WeakMap;let V=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=B){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const n=Symbol(),i=this.getPropertyDescriptor(t,n,e);void 0!==i&&D(this.prototype,t,i)}}static getPropertyDescriptor(t,e,n){const{get:i,set:r}=A(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:i,set(e){const o=i?.call(this);r?.call(this,e),this.requestUpdate(t,o,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??B}static _$Ei(){if(this.hasOwnProperty($("elementProperties")))return;const t=E(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty($("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty($("properties"))){const t=this.properties,e=[...P(t),...L(t)];for(const n of e)this.createProperty(n,t[n])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,n]of e)this.elementProperties.set(t,n)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const n=this._$Eu(t,e);void 0!==n&&this._$Eh.set(n,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const n=new Set(t.flat(1/0).reverse());for(const t of n)e.unshift(T(t))}else void 0!==t&&e.push(T(t));return e}static _$Eu(t,e){const n=e.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const n of e.keys())this.hasOwnProperty(n)&&(t.set(n,this[n]),delete this[n]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((t,e)=>{if(x)t.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const n of e){const e=document.createElement("style"),i=b.litNonce;void 0!==i&&e.setAttribute("nonce",i),e.textContent=n.cssText,t.appendChild(e)}})(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,n){this._$AK(t,n)}_$ET(t,e){const n=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,n);if(void 0!==i&&!0===n.reflect){const r=(void 0!==n.converter?.toAttribute?n.converter:H).toAttribute(e,n.type);this._$Em=t,null==r?this.removeAttribute(i):this.setAttribute(i,r),this._$Em=null}}_$AK(t,e){const n=this.constructor,i=n._$Eh.get(t);if(void 0!==i&&this._$Em!==i){const t=n.getPropertyOptions(i),r="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:H;this._$Em=i;const o=r.fromAttribute(e,t.type);this[i]=o??this._$Ej?.get(i)??o,this._$Em=null}}requestUpdate(t,e,n,i=!1,r){if(void 0!==t){const o=this.constructor;if(!1===i&&(r=this[t]),n??=o.getPropertyOptions(t),!((n.hasChanged??F)(r,e)||n.useDefault&&n.reflect&&r===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,n))))return;this.C(t,e,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:n,reflect:i,wrapped:r},o){n&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,o??e??this[t]),!0!==r||void 0!==o)||(this._$AL.has(t)||(this.hasUpdated||n||(e=void 0),this._$AL.set(t,e)),!0===i&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,n]of t){const{wrapped:t}=n,i=this[e];!0!==t||this._$AL.has(e)||void 0===i||this.C(e,void 0,n,i)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};V.elementStyles=[],V.shadowRootOptions={mode:"open"},V[$("elementProperties")]=new Map,V[$("finalized")]=new Map,R?.({ReactiveElement:V}),(z.reactiveElementVersions??=[]).push("2.1.2"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const q=globalThis,U=e=>e,W=q.trustedTypes,G=W?W.createPolicy("lit-html",{createHTML:e=>e}):void 0,V="$lit$",B=`lit$${Math.random().toFixed(9).slice(2)}$`,Q="?"+B,K=`<${Q}>`,J=document,X=()=>J.createComment(""),Z=e=>null===e||"object"!=typeof e&&"function"!=typeof e,Y=Array.isArray,ee="[ \t\n\f\r]",te=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ne=/-->/g,ie=/>/g,se=RegExp(`>|${ee}(?:([^\\s"'>=/]+)(${ee}*=${ee}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),oe=/'/g,re=/"/g,ae=/^(?:script|style|textarea|title)$/i,le=(e=>(t,...n)=>({_$litType$:e,strings:t,values:n}))(1),ce=Symbol.for("lit-noChange"),de=Symbol.for("lit-nothing"),he=new WeakMap,ue=J.createTreeWalker(J,129);function pe(e,t){if(!Y(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==G?G.createHTML(t):t}const ge=(e,t)=>{const n=e.length-1,i=[];let s,o=2===t?"":3===t?"":"",r=te;for(let t=0;t"===l[0]?(r=s??te,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?se:'"'===l[3]?re:oe):r===re||r===oe?r=se:r===ne||r===ie?r=te:(r=se,s=void 0);const h=r===se&&e[t+1].startsWith("/>")?" ":"";o+=r===te?n+K:c>=0?(i.push(a),n.slice(0,c)+V+n.slice(c)+B+h):n+B+(-2===c?t:h)}return[pe(e,o+(e[n]||"")+(2===t?"":3===t?"":"")),i]};class _e{constructor({strings:e,_$litType$:t},n){let i;this.parts=[];let s=0,o=0;const r=e.length-1,a=this.parts,[l,c]=ge(e,t);if(this.el=_e.createElement(l,n),ue.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(i=ue.nextNode())&&a.length0){i.textContent=W?W.emptyScript:"";for(let n=0;nY(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==de&&Z(this._$AH)?this._$AA.nextSibling.data=e:this.T(J.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:n}=e,i="number"==typeof n?this._$AC(e):(void 0===n.el&&(n.el=_e.createElement(pe(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new ve(i,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=he.get(e.strings);return void 0===t&&he.set(e.strings,t=new _e(e)),t}k(e){Y(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let n,i=0;for(const s of e)i===t.length?t.push(n=new me(this.O(X()),this.O(X()),this,this.options)):n=t[i],n._$AI(s),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=de}_$AI(e,t=this,n,i){const s=this.strings;let o=!1;if(void 0===s)e=fe(this,e,t,0),o=!Z(e)||e!==this._$AH&&e!==ce,o&&(this._$AH=e);else{const i=e;let r,a;for(e=s[0],r=0;rt,G=W.trustedTypes,q=G?G.createPolicy("lit-html",{createHTML:t=>t}):void 0,j="$lit$",X=`lit$${Math.random().toFixed(9).slice(2)}$`,Y="?"+X,Z=`<${Y}>`,K=document,Q=()=>K.createComment(""),J=t=>null===t||"object"!=typeof t&&"function"!=typeof t,tt=Array.isArray,et="[ \t\n\f\r]",nt=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,it=/-->/g,rt=/>/g,ot=RegExp(`>|${et}(?:([^\\s"'>=/]+)(${et}*=${et}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),at=/'/g,st=/"/g,lt=/^(?:script|style|textarea|title)$/i,ct=t=>(e,...n)=>({_$litType$:t,strings:e,values:n}),ut=ct(1),ht=ct(2),dt=Symbol.for("lit-noChange"),pt=Symbol.for("lit-nothing"),ft=new WeakMap,gt=K.createTreeWalker(K,129);function vt(t,e){if(!tt(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==q?q.createHTML(e):e}const mt=(t,e)=>{const n=t.length-1,i=[];let r,o=2===e?"":3===e?"":"",a=nt;for(let e=0;e"===l[0]?(a=r??nt,c=-1):void 0===l[1]?c=-2:(c=a.lastIndex-l[2].length,s=l[1],a=void 0===l[3]?ot:'"'===l[3]?st:at):a===st||a===at?a=ot:a===it||a===rt?a=nt:(a=ot,r=void 0);const h=a===ot&&t[e+1].startsWith("/>")?" ":"";o+=a===nt?n+Z:c>=0?(i.push(s),n.slice(0,c)+j+n.slice(c)+X+h):n+X+(-2===c?e:h)}return[vt(t,o+(t[n]||"")+(2===e?"":3===e?"":"")),i]};class yt{constructor({strings:t,_$litType$:e},n){let i;this.parts=[];let r=0,o=0;const a=t.length-1,s=this.parts,[l,c]=mt(t,e);if(this.el=yt.createElement(l,n),gt.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(i=gt.nextNode())&&s.length0){i.textContent=G?G.emptyScript:"";for(let n=0;ntt(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==pt&&J(this._$AH)?this._$AA.nextSibling.data=t:this.T(K.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:n}=t,i="number"==typeof n?this._$AC(t):(void 0===n.el&&(n.el=yt.createElement(vt(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===i)this._$AH.p(e);else{const t=new bt(i,this),n=t.u(this.options);t.p(e),this.T(n),this._$AH=t}}_$AC(t){let e=ft.get(t.strings);return void 0===e&&ft.set(t.strings,e=new yt(t)),e}k(t){tt(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let n,i=0;for(const r of t)i===e.length?e.push(n=new xt(this.O(Q()),this.O(Q()),this,this.options)):n=e[i],n._$AI(r),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=pt}_$AI(t,e=this,n,i){const r=this.strings;let o=!1;if(void 0===r)t=_t(this,t,e,0),o=!J(t)||t!==this._$AH&&t!==dt,o&&(this._$AH=t);else{const i=t;let a,s;for(t=r[0],a=0;a{const i=n?.renderBefore??t;let s=i._$litPart$;if(void 0===s){const e=n?.renderBefore??null;i._$litPart$=s=new me(t.insertBefore(X(),e),e,void 0,n??{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ce}};ke._$litElement$=!0,ke.finalized=!0,Ce.litElementHydrateSupport?.({LitElement:ke});const Pe=Ce.litElementPolyfillSupport;Pe?.({LitElement:ke}),(Ce.litElementVersions??=[]).push("4.2.2"); + */let Dt=class extends V{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=((t,e,n)=>{const i=n?.renderBefore??e;let r=i._$litPart$;if(void 0===r){const t=n?.renderBefore??null;i._$litPart$=r=new xt(e.insertBefore(Q(),t),t,void 0,n??{})}return r._$AI(t),r})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return dt}};Dt._$litElement$=!0,Dt.finalized=!0,It.litElementHydrateSupport?.({LitElement:Dt});const At=It.litElementPolyfillSupport;At?.({LitElement:Dt}),(It.litElementVersions??=[]).push("4.2.2"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const Ee=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)},ze={attribute:!0,type:String,converter:H,reflect:!1,hasChanged:O},Ae=(e=ze,t,n)=>{const{kind:i,metadata:s}=n;let o=globalThis.litPropertyMetadata.get(s);if(void 0===o&&globalThis.litPropertyMetadata.set(s,o=new Map),"setter"===i&&((e=Object.create(e)).wrapped=!0),o.set(n.name,e),"accessor"===i){const{name:i}=n;return{set(n){const s=t.get.call(this);t.set.call(this,n),this.requestUpdate(i,s,e,!0,n)},init(t){return void 0!==t&&this.C(i,void 0,e,t),t}}}if("setter"===i){const{name:i}=n;return function(n){const s=this[i];t.call(this,n),this.requestUpdate(i,s,e,!0,n)}}throw Error("Unsupported decorator location: "+i)}; +const Pt={attribute:!0,type:String,converter:H,reflect:!1,hasChanged:F},Lt=(t=Pt,e,n)=>{const{kind:i,metadata:r}=n;let o=globalThis.litPropertyMetadata.get(r);if(void 0===o&&globalThis.litPropertyMetadata.set(r,o=new Map),"setter"===i&&((t=Object.create(t)).wrapped=!0),o.set(n.name,t),"accessor"===i){const{name:i}=n;return{set(n){const r=e.get.call(this);e.set.call(this,n),this.requestUpdate(i,r,t,!0,n)},init(e){return void 0!==e&&this.C(i,void 0,t,e),e}}}if("setter"===i){const{name:i}=n;return function(n){const r=this[i];e.call(this,n),this.requestUpdate(i,r,t,!0,n)}}throw Error("Unsupported decorator location: "+i)}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function Ne(e){return(t,n)=>"object"==typeof n?Ae(e,t,n):((e,t,n)=>{const i=t.hasOwnProperty(n);return t.constructor.createProperty(n,e),i?Object.getOwnPropertyDescriptor(t,n):void 0})(e,t,n)} + */function Et(t){return(e,n)=>"object"==typeof n?Lt(t,e,n):((t,e,n)=>{const i=e.hasOwnProperty(n);return e.constructor.createProperty(n,t),i?Object.getOwnPropertyDescriptor(e,n):void 0})(t,e,n)} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function Me(e){return Ne({...e,state:!0,attribute:!1})} + */function zt(t){return Et({...t,state:!0,attribute:!1})} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */const Ie=2;class Te{constructor(e){}get _$AU(){return this._$AM._$AU}_$AT(e,t,n){this._$Ct=e,this._$AM=t,this._$Ci=n}_$AS(e,t){return this.update(e,t)}update(e,t){return this.render(...t)}} + */const Nt=2;class Ot{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,n){this._$Ct=t,this._$AM=e,this._$Ci=n}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */class De extends Te{constructor(e){if(super(e),this.it=de,e.type!==Ie)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(e){if(e===de||null==e)return this._t=void 0,this.it=e;if(e===ce)return e;if("string"!=typeof e)throw Error(this.constructor.directiveName+"() called with a non-string value");if(e===this.it)return this._t;this.it=e;const t=[e];return t.raw=t,this._t={_$litType$:this.constructor.resultType,strings:t,values:[]}}}De.directiveName="unsafeHTML",De.resultType=1;const Fe=(e=>(...t)=>({_$litDirective$:e,values:t}))(De);class Le{constructor(){this._persistent=new Map,this._transient=null,this._transientTimer=null,this._subscribers=new Set,this._watchedPanels=new Map}add(e){const t={...e,timestamp:Date.now()};if(t.persistent)this._persistent.set(t.key,t);else{this._clearTransient(),this._transient=t;const e=t.ttl??5e3;this._transientTimer=setTimeout(()=>{this._transient=null,this._transientTimer=null,this._notify()},e)}this._notify()}remove(e){if(this._persistent.has(e))return this._persistent.delete(e),void this._notify();this._transient?.key===e&&(this._clearTransient(),this._notify())}clear(e){void 0===e?(this._persistent.clear(),this._clearTransient(),this._watchedPanels.clear()):!0===e.persistent?this._persistent.clear():!1===e.persistent&&this._clearTransient(),this._notify()}get active(){const e=[...this._persistent.values()];return null!==this._transient&&e.push(this._transient),e}hasPersistent(e){return this._persistent.has(e)}hasAnyPanelOffline(){for(const e of this._persistent.keys())if("panel-offline"===e||e.startsWith("panel-offline:"))return!0;return!1}subscribe(e){return this._subscribers.add(e),()=>{this._subscribers.delete(e)}}watchPanelStatus(e){this.watchPanelStatuses([{entityId:e,panelName:null}])}watchPanelStatuses(e){const t=this._watchedPanels,n=new Map;for(const i of e){const e=t.get(i.entityId);n.set(i.entityId,{panelName:i.panelName??null,wasOffline:e?.wasOffline??!1})}const i=this._isSingleUnnamed(t),s=this._isSingleUnnamed(n);for(const e of t.keys()){n.has(e)&&i===s||this._persistent.delete(this._offlineKey(e,i))}this._watchedPanels=n,this._notify()}clearPanelStatusWatch(){if(0===this._watchedPanels.size)return;const e=this._isSingleUnnamed(this._watchedPanels);for(const t of this._watchedPanels.keys())this._persistent.delete(this._offlineKey(t,e));this._watchedPanels.clear(),this._notify()}updateHass(e){if(0===this._watchedPanels.size)return;const t=this._isSingleUnnamed(this._watchedPanels);for(const[s,o]of this._watchedPanels){const r=e.states[s]?.state,a="on"===r,l=this._offlineKey(s,t),c=this._reconnectKey(s,t);if(a){const e=o.wasOffline;o.wasOffline=!1,this.remove(l),e&&this.add({key:c,level:"info",message:null===o.panelName?n("error.panel_reconnected"):i("error.panel_reconnected_named",{name:o.panelName}),persistent:!1})}else o.wasOffline=!0,this.hasPersistent(l)||this.add({key:l,level:"error",message:null===o.panelName?n("error.panel_offline"):i("error.panel_offline_named",{name:o.panelName}),persistent:!0})}}dispose(){this._clearTransient(),this._persistent.clear(),this._subscribers.clear(),this._watchedPanels.clear()}_isSingleUnnamed(e){if(1!==e.size)return!1;for(const t of e.values())return null===t.panelName;return!1}_offlineKey(e,t){return t?"panel-offline":`panel-offline:${e}`}_reconnectKey(e,t){return t?"panel-reconnected":`panel-reconnected:${e}`}_clearTransient(){null!==this._transientTimer&&(clearTimeout(this._transientTimer),this._transientTimer=null),this._transient=null}_notify(){for(const e of this._subscribers)try{e()}catch(e){console.warn("SPAN Panel: error-store subscriber threw",e)}}}const He={"&":"&","<":"<",">":">",'"':""","'":"'"};function Oe(e){return String(e).replace(/[&<>"']/g,e=>He[e]??e)}const Re="span_panel_list_columns";function je(){try{const e=localStorage.getItem(Re);if(!e)return 1;const t=parseInt(e,10);return 1===t||2===t||3===t?t:1}catch{return 1}}function qe(e){try{localStorage.setItem(Re,String(e))}catch{}}function Ue(e){return new Promise(t=>setTimeout(t,e))}class We{constructor(e){this._store=e}async callWS(e,t,n){const i=n?.retries??3,s=n?.errorId??`ws:${String(t.type??"unknown")}`;return this._withRetry(()=>e.callWS(t),i,s,n?.errorMessage)}async callService(e,t,n,i,s,o){const r=o?.retries??3,a=o?.errorId??`svc:${t}.${n}`;return this._withRetry(()=>e.callService(t,n,i,s),r,a,o?.errorMessage)}async _withRetry(e,t,i,s){if(this._store.hasAnyPanelOffline())try{const t=await e();return this._store.remove(i),t}catch(e){const t=e instanceof Error?e:new Error(String(e));throw this._store.add({key:i,level:"error",message:s??n("error.panel_offline"),persistent:!1}),t}let o;for(let n=0;n<=t;n++)try{const t=await e();return this._store.remove(i),t}catch(e){if(o=e instanceof Error?e:new Error(String(e)),n{try{const t={type:"call_service",domain:a,service:"get_favorites",service_data:{},return_response:!0},s=this._retry?await this._retry.callWS(e,t,{errorId:"fetch:favorites",errorMessage:n("error.favorites_fetch_failed")}):await e.callWS(t),o=s?.response?.favorites??{};return i===this._generation&&(this._map=o,this._lastFetch=Date.now()),o}catch(e){return console.warn("SPAN Panel: favorites fetch failed",e),this._retry||this._errorStore?.add({key:"fetch:favorites",level:"warning",message:n("error.favorites_fetch_failed"),persistent:!1}),this._map??{}}finally{this._inflight?.gen===i&&(this._inflight=null)}})();return this._inflight={gen:i,promise:s},s}invalidate(){this._lastFetch=0,this._generation++}clear(){this._map=null,this._lastFetch=0,this._generation++}get map(){return this._map??{}}}function Qe(e){for(const t of Object.values(e)){if((t.circuits?.length??0)>0)return!0;if((t.sub_devices?.length??0)>0)return!0}return!1}const Ke=Object.keys(f).filter(e=>"unknown"!==e&&"always_on"!==e);class Je extends HTMLElement{constructor(){super(),this.errorStore=null,this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(e){this._hass=e,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}disconnectedCallback(){this._clearDebounceTimers(),this._config=null}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open",""),this.setAttribute("data-mode",this._modeFor(e))}close(){this._clearDebounceTimers(),this.removeAttribute("open"),this.removeAttribute("data-mode"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_clearDebounceTimers(){for(const e of Object.keys(this._debounceTimers))clearTimeout(this._debounceTimers[e]);this._debounceTimers={}}_modeFor(e){return e.favoritesMode?"favorites":e.panelMode?"panel":e.subDeviceMode?"subDevice":"circuit"}_render(){const e=this._config;if(!e)return;const t=this.shadowRoot;if(!t)return;t.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .unit-toggle {\n display: inline-flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .unit-btn:last-child {\n border-right: none;\n }\n .unit-btn:hover:not(.unit-active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .fav-heart {\n background: none;\n border: 1px solid var(--divider-color, #e0e0e0);\n color: var(--secondary-text-color, #727272);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n font-size: 0.9em;\n margin-right: 6px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n }\n .fav-heart.active {\n color: var(--primary-color, #03a9f4);\n border-color: var(--primary-color, #03a9f4);\n }\n .fav-heart:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .fav-heart ha-icon {\n --mdc-icon-size: 16px;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const s=document.createElement("div");s.className="panel",t.appendChild(s),e.favoritesMode?this._renderFavoritesMode(s):e.panelMode?this._renderPanelMode(s):e.subDeviceMode?this._renderSubDeviceMode(s,e):this._renderCircuitMode(s,e)}_renderPanelMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.global_defaults"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body";const a=t.graphSettings,l=t.topology,c=a?.global_horizon??o,d=a?.circuits??{};s.appendChild(this._buildListColumnsSection());const h=document.createElement("div");h.className="section";const u=document.createElement("div");u.className="section-label",u.textContent=n("sidepanel.graph_horizon"),h.appendChild(u);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=n("sidepanel.global_default"),g.appendChild(_);const f=document.createElement("select");for(const e of Object.keys(r)){const t=document.createElement("option");t.value=e;const i=`horizon.${e}`,s=n(i);t.textContent=s!==i?s:e,e===c&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{const e={horizon:f.value};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("set_graph_time_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})}),g.appendChild(f),h.appendChild(g),s.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const i=document.createElement("div");i.className="section-label",i.textContent=n("sidepanel.circuit_scales"),e.appendChild(i);const o=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,i]of o){const s=this._buildPanelModeCircuitRow(n,i,d[n],c,t.configEntryId??null,t.showFavorites??!1,t.favoritePanelDeviceId,t.favoriteCircuitUuids);e.appendChild(s)}s.appendChild(e)}const v=a?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const i=document.createElement("div");i.className="section-label",i.textContent=n("sidepanel.subdevice_scales"),e.appendChild(i);const o=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[i,s]of o){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");if(a.className="field-label",a.textContent=s.name||i,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",o.appendChild(a),t.showFavorites&&t.favoritePanelDeviceId){const e=this._buildSubDeviceFavoriteHeart(s.entities,t.favoriteSubDeviceIds?.has(i)??!1);e&&o.appendChild(e)}const l=v[i]||{horizon:c,has_override:!1},d=l.has_override?l.horizon:c,h=document.createElement("select");h.dataset.subdevId=i;for(const e of Object.keys(r)){const t=document.createElement("option");t.value=e;const i=`horizon.${e}`,s=n(i);t.textContent=s!==i?s:e,e===d&&(t.selected=!0),h.appendChild(t)}if(h.addEventListener("change",()=>{this._debounce(`subdev-${i}`,p,()=>{const e={subdevice_id:i,horizon:h.value};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("set_subdevice_graph_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})})}),o.appendChild(h),l.has_override){const e=document.createElement("button");e.textContent="↺",e.title=n("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{const s={subdevice_id:i};t.configEntryId&&(s.config_entry_id=t.configEntryId),this._callDomainService("clear_subdevice_graph_horizon",s).then(()=>{h.value=c,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})}),o.appendChild(e)}e.appendChild(o)}s.appendChild(e)}e.appendChild(s)}_buildPanelModeCircuitRow(e,t,i,s,o,a,l,c){const d=document.createElement("div");d.className="field-row";const h=document.createElement("span");if(h.className="field-label",h.textContent=t.name||e,h.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",d.appendChild(h),a&&l){const n=this._buildFavoriteHeart(t.entities,c?.has(e)??!1);n&&d.appendChild(n)}const u=i||{horizon:s,has_override:!1},g=u.has_override?u.horizon:s,_=document.createElement("select");_.dataset.uuid=e;for(const e of Object.keys(r)){const t=document.createElement("option");t.value=e;const i=`horizon.${e}`,s=n(i);t.textContent=s!==i?s:e,e===g&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._debounce(`circuit-${e}`,p,()=>{const t={circuit_id:e,horizon:_.value};o&&(t.config_entry_id=o),this._callDomainService("set_circuit_graph_horizon",t).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})})}),d.appendChild(_),u.has_override){const t=document.createElement("button");t.textContent="↺",t.title=n("sidepanel.reset_to_global"),Object.assign(t.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),t.addEventListener("click",()=>{const i={circuit_id:e};o&&(i.config_entry_id=o),this._callDomainService("clear_circuit_graph_horizon",i).then(()=>{_.value=s,t.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})}),d.appendChild(t)}return d}_renderFavoritesMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.favorites_subtitle"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body",s.appendChild(this._buildListColumnsSection());for(const e of t.perPanelSections)s.appendChild(this._buildFavoritesPanelSection(e));e.appendChild(s)}_buildFavoritesPanelSection(e){const t=document.createElement("div");t.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=e.panelName,t.appendChild(n);const i=e.graphSettings?.global_horizon??o,s=e.graphSettings?.circuits??{},r=function(e){const t=e.circuits??{};return Object.entries(t).map(([e,t])=>({uuid:e,circuit:t})).sort((e,t)=>(e.circuit.name||"").localeCompare(t.circuit.name||""))}(e.topology);for(const{uuid:n,circuit:o}of r){const r=this._buildPanelModeCircuitRow(n,o,s[n],i,e.configEntryId,!0,e.panelDeviceId,e.favoriteCircuitUuids);t.appendChild(r)}return t}_renderCircuitMode(e,t){const n=`${Oe(String(t.breaker_rating_a))}A · ${Oe(String(t.voltage))}V · Tabs [${Oe(String(t.tabs))}]`,i=this._createHeader(Oe(t.name),n);e.appendChild(i);const s=document.createElement("div");s.className="panel-body",e.appendChild(s),this._renderRelaySection(s,t),t.showFavorites&&this._renderFavoriteSection(s,t),this._renderSheddingSection(s,t),this._renderGraphHorizonSection(s,t),t.showMonitoring&&this._renderMonitoringSection(s,t)}_favoriteEntityId(e){return e?.current??e?.power??null}_subDeviceFavoriteEntityId(e){if(!e)return null;let t=null;for(const[n,i]of Object.entries(e)){if("sensor"===i.domain)return n;t||(t=n)}return t}_buildSubDeviceFavoriteHeart(e,t){const n=this._subDeviceFavoriteEntityId(e);return n?this._buildHeartButton(n,t):null}_buildListColumnsSection(){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.list_view_columns"),e.appendChild(t);const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.columns"),i.appendChild(s);const o=je(),r=document.createElement("div");r.className="unit-toggle";for(const e of[1,2,3]){const t=document.createElement("button");t.type="button",t.className="unit-btn"+(e===o?" unit-active":""),t.dataset.columns=String(e),t.textContent=String(e),t.addEventListener("click",()=>{qe(e);for(const e of r.querySelectorAll(".unit-btn"))e.classList.toggle("unit-active",e===t);this.dispatchEvent(new CustomEvent("list-columns-changed",{detail:e,bubbles:!0,composed:!0}))}),r.appendChild(t)}return i.appendChild(r),e.appendChild(i),e}_buildFavoriteHeart(e,t){const n=this._favoriteEntityId(e);return n?this._buildHeartButton(n,t):(console.warn("SPAN Panel: circuit has no current/power sensor; favorite heart suppressed"),null)}_buildHeartButton(e,t){const i=document.createElement("button");i.type="button",i.className=t?"fav-heart active":"fav-heart",i.dataset.role="fav-heart",i.title=n("sidepanel.save_to_favorites"),i.setAttribute("role","switch"),i.setAttribute("aria-checked",String(t)),i.setAttribute("aria-label",n("sidepanel.save_to_favorites"));const s=document.createElement("ha-icon");return s.setAttribute("icon",t?"mdi:heart":"mdi:heart-outline"),i.appendChild(s),i.addEventListener("click",t=>{t.stopPropagation(),this._toggleFavoriteEntity(i,s,e).catch(()=>{})}),i}async _toggleFavoriteEntity(e,t,i){if(!this._hass)return;const s=e.classList.contains("active"),o=!s;e.classList.toggle("active",o),t.setAttribute("icon",o?"mdi:heart":"mdi:heart-outline"),e.setAttribute("aria-checked",String(o));try{o?await async function(e,t){const n=await Ve(e,"add_favorite",{entity_id:t});return document.dispatchEvent(new CustomEvent(Ge)),n?.favorites??{}}(this._hass,i):await async function(e,t){const n=await Ve(e,"remove_favorite",{entity_id:t});return document.dispatchEvent(new CustomEvent(Ge)),n?.favorites??{}}(this._hass,i)}catch(i){throw e.classList.toggle("active",s),t.setAttribute("icon",s?"mdi:heart":"mdi:heart-outline"),e.setAttribute("aria-checked",String(s)),console.warn("SPAN Panel: favorite toggle failed",i),this.errorStore?.add({key:"service:favorites",level:"error",message:n("error.favorites_toggle_failed"),persistent:!1}),i}}_renderFavoriteSection(e,t){const n=this._favoriteEntityId(t.entities);n&&this._appendFavoriteHeartSection(e,n,!0===t.isFavorite)}_appendFavoriteHeartSection(e,t,i){const s=document.createElement("div");s.className="section",s.innerHTML=``;const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=n("sidepanel.save_to_favorites"),o.appendChild(r),o.appendChild(this._buildHeartButton(t,i)),s.appendChild(o),e.appendChild(s)}_renderSubDeviceMode(e,t){const n=this._createHeader(Oe(t.name),Oe(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i),t.showFavorites&&this._renderSubDeviceFavoriteSection(i,t),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceFavoriteSection(e,t){const n=this._subDeviceFavoriteEntityId(t.entities);n&&this._appendFavoriteHeartSection(e,n,!0===t.isFavorite)}_renderSubDeviceHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const a=t.graphHorizonInfo,l=!0===a?.has_override,c=a?.horizon||o,d=a?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(r))u.push({key:e,label:e});const p=l?c:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=i,s.classList.toggle("active",e===p),s.classList.toggle("referenced","global"===p&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const i={subdevice_id:t.subDeviceId};t.configEntryId&&(i.config_entry_id=t.configEntryId),"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",i).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})):(g(e),this._callDomainService("set_subdevice_graph_horizon",{...i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})}))}),h.appendChild(s)}i.appendChild(h),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div"),s=Oe(e),o=Oe(t);i.innerHTML=`
${s}
`+(o?`
${o}
`:"");const r=document.createElement("button");return r.className="close-btn",r.innerHTML="✕",r.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(r),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=n("sidepanel.breaker");const r=document.createElement("ha-switch");r.dataset.role="relay-toggle";const a=t.entities.switch,l=this._hass?.states?.[a]?.state;"on"===l&&r.setAttribute("checked",""),r.addEventListener("change",()=>{const e=r.hasAttribute("checked")||r.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:a}).catch(e=>{console.warn("SPAN Panel: relay toggle failed",e),this.errorStore?.add({key:"service:relay",level:"error",message:n("error.relay_failed"),persistent:!1})})}),s.appendChild(o),s.appendChild(r),i.appendChild(s),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=n("sidepanel.priority_label");const r=document.createElement("select");r.dataset.role="shedding-select";const a=t.entities.select,l=this._hass?.states?.[a]?.state||"";for(const e of Ke){const t=f[e];if(!t)continue;const i=document.createElement("option");i.value=e,i.textContent=n(`shedding.select.${e}`)||t.label(),e===l&&(i.selected=!0),r.appendChild(i)}r.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:r.value}).catch(e=>{console.warn("SPAN Panel: shedding update failed",e),this.errorStore?.add({key:"service:shedding",level:"error",message:n("error.shedding_failed"),persistent:!1})})}),s.appendChild(o),s.appendChild(r),i.appendChild(s),e.appendChild(i)}_renderGraphHorizonSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=n("sidepanel.graph_horizon"),i.appendChild(s);const a=t.graphHorizonInfo,l=!0===a?.has_override,c=a?.horizon||o,d=a?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const u=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(r))u.push({key:e,label:e});const p=l?c:"global",g=e=>{for(const t of h.querySelectorAll(".horizon-segment")){const n=t.dataset.horizon;t.classList.toggle("active",n===e),t.classList.toggle("referenced","global"===e&&n===d)}};for(const{key:e,label:i}of u){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=i,s.classList.toggle("active",e===p),s.classList.toggle("referenced","global"===p&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const i={circuit_id:t.uuid};t.configEntryId&&(i.config_entry_id=t.configEntryId),"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",i).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})):(g(e),this._callDomainService("set_circuit_graph_horizon",{...i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})}))}),h.appendChild(s)}i.appendChild(h),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent=n("sidepanel.monitoring"),o.style.margin="0";const r=document.createElement("ha-switch");r.dataset.role="monitoring-toggle";const a=t.monitoringInfo,l=null!=a&&!1!==a.monitoring_enabled;l&&r.setAttribute("checked",""),s.appendChild(o),s.appendChild(r),i.appendChild(s);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=!0===a?.has_override,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,c.appendChild(h);const u=document.createElement("div");u.dataset.role="threshold-fields",u.style.display=d?"block":"none";const p=a?.continuous_threshold_pct??80,g=a?.spike_threshold_pct??100,_=a?.window_duration_m??15,f=a?.cooldown_duration_m??15;u.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",p,t)),u.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),u.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",_,1,180,"m",t)),u.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(u),r.addEventListener("change",()=>{const e=r.checked;c.style.display=e?"block":"none";const i={circuit_id:t.entities?.power||t.uuid,monitoring_enabled:e};t.configEntryId&&(i.config_entry_id=t.configEntryId),this._callDomainService("set_circuit_threshold",i).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})});const v=h.querySelectorAll('input[type="radio"]');for(const e of v)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(u.style.display=i?"block":"none",!i&&e.checked){const e={circuit_id:t.entities?.power||t.uuid};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("clear_circuit_threshold",e).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})}});e.appendChild(i)}_createThresholdRow(e,t,i,s){const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=e;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(i),a.dataset.role=`threshold-${t}`,a.addEventListener("input",()=>{this._debounce(`threshold-${t}`,p,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),i=e.querySelector('[data-role="threshold-spike"]'),o=e.querySelector('[data-role="threshold-window-m"]'),r=e.querySelector('[data-role="threshold-cooldown-m"]'),a={circuit_id:s.entities?.power||s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:o?Number(o.value):void 0,cooldown_duration_m:r?Number(r.value):void 0};s.configEntryId&&(a.config_entry_id=s.configEntryId),this._callDomainService("set_circuit_threshold",a).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})})}),o.appendChild(r),o.appendChild(a),o}_createDurationRow(e,t,i,s,o,r,a,l=!1){const c=document.createElement("div");c.className="field-row";const d=document.createElement("span");d.className="field-label",d.textContent=e;const h=document.createElement("div"),u=document.createElement("input");u.type="number",u.min=String(s),u.max=String(o),u.value=String(i),u.dataset.role=`threshold-${t}`,l&&(u.disabled=!0);const g=document.createElement("span");return g.textContent=r,h.appendChild(u),h.appendChild(g),l||u.addEventListener("input",()=>{this._debounce(`threshold-${t}`,p,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),i=e.querySelector('[data-role="threshold-spike"]'),s=e.querySelector('[data-role="threshold-window-m"]'),o={circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:i?Number(i.value):void 0,window_duration_m:s?Number(s.value):void 0};a.configEntryId&&(o.config_entry_id=a.configEntryId),this._callDomainService("set_circuit_threshold",o).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})})}),c.appendChild(d),c.appendChild(h),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(!e.subDeviceMode&&!e.favoritesMode){if(e.entities?.switch){const t=this.shadowRoot?.querySelector('[data-role="relay-toggle"]');if(t){const n=this._hass?.states?.[e.entities.switch]?.state;"on"===n?t.setAttribute("checked",""):t.removeAttribute("checked")}}if(e.entities?.select){const t=this.shadowRoot?.querySelector('[data-role="shedding-select"]');if(t){const n=this._hass?.states?.[e.entities.select]?.state||"";t.value=n}}}}_callService(e,t,n){return this._hass?Promise.resolve(this._hass.callService(e,t,n)):Promise.resolve()}_callDomainService(e,t){return this._hass?this._hass.callWS({type:"call_service",domain:a,service:e,service_data:t}):Promise.resolve()}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}try{customElements.get("span-side-panel")||customElements.define("span-side-panel",Je)}catch{}let Xe=class extends ke{constructor(){super(...arguments),this._store=null,this._unsub=null,this._errors=[]}set store(e){if(this._store===e)return;this._unsub?.(),this._unsub=null,this._store=e,this._errors=e.active;const t=e;this._unsub=e.subscribe(()=>{this._errors=t.active})}connectedCallback(){if(super.connectedCallback(),this._store&&!this._unsub){const e=this._store;this._errors=e.active,this._unsub=e.subscribe(()=>{this._errors=e.active})}}disconnectedCallback(){super.disconnectedCallback(),this._unsub?.(),this._unsub=null}render(){return 0===this._errors.length?de:le`${this._errors.map(e=>le` - - `}_onPanelChange(e){const t=e.target;this._selectedPanelId=t.value,localStorage.setItem("span_panel_selected",t.value),this._isFavoritesView&&"dashboard"===this._activeTab&&(this._activeTab="activity"),this._areaSubscribing=!1,this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null)}get _isFavoritesView(){return this._selectedPanelId===yn}_onTabClick(e){const t=e.target.closest(".shared-tab");if(!t)return;const n=t.dataset.tab;n&&n!==this._activeTab&&(this._activeTab=n,this._isFavoritesView&&"dashboard"!==n&&(this._favoritesViewState.activeTab=n,mn(this._favoritesViewState)))}_onTabContentClick(e){const t=e.target.closest(".unit-btn");if(t){const e=t.dataset.unit;if(!e||e===this._chartMetric)return;return this._chartMetric=e,void localStorage.setItem("span_panel_metric",e)}}_onSidePanelClosed(){if("dashboard"===this._activeTab){const e=this._dashboardTab._ctrl;e.monitoringCache.invalidate(),e.graphSettingsCache.invalidate()}this._listDashCtrl.monitoringMultiCache.invalidate(),this._pendingTabRender&&(this._pendingTabRender=!1,this._scheduleTabRender())}_onUnitChanged(e){const t=e.detail;t&&t!==this._chartMetric&&(this._chartMetric=t,localStorage.setItem("span_panel_metric",t))}_onListColumnsChanged(e){const t=e.detail;"number"!=typeof t||1!==t&&2!==t&&3!==t||t===this._listColumns||(this._listColumns=t,qe(t))}_onGraphSettingsChanged(){if("dashboard"===this._activeTab){const e=this._root.getElementById("tab-content");if(e){this._dashboardTab._ctrl.onGraphSettingsChanged(e)}}}_onNavigateTab(e){const t=e.detail;t&&(this._activeTab=t)}_onFavoritesViewStateChangedEvent(e){if(!this._isFavoritesView)return;const t=e.detail;if(!t)return;const n=this._favoritesViewState;n.activeTab=t.view;const i=this._listDashCtrl.topology,s=i?.circuits;s&&Object.keys(s).length>0?n.expanded[t.view]=t.expanded.filter(e=>e in s):n.expanded[t.view]=t.expanded,n.searchQuery=t.searchQuery,this._persistFavoritesViewStateTimer&&clearTimeout(this._persistFavoritesViewStateTimer),this._persistFavoritesViewStateTimer=setTimeout(()=>{this._persistFavoritesViewStateTimer=null,mn(n)},250)}_subscribeDeviceRegistry(){!this._deviceRegistryUnsub&&this.hass?.connection&&(this._deviceRegistryUnsub=this.hass.connection.subscribeEvents(()=>this._refreshPanels(),"device_registry_updated"))}_unsubscribeDeviceRegistry(){this._deviceRegistryUnsub&&(this._deviceRegistryUnsub.then(e=>e()),this._deviceRegistryUnsub=null)}async _refreshPanels(){if(!this.hass||!this._discovered)return;const e=(await this.hass.callWS({type:"config/device_registry/list"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id),t=this._panels.filter(e=>e.id!==yn),n=new Map(t.map(e=>[e.id,e])),i=new Set(e.map(e=>e.id)),s=n.size!==i.size||[...n.keys()].some(e=>!i.has(e)),o=!s&&e.some(e=>{const t=n.get(e.id);return!!t&&(t.name!==e.name||t.name_by_user!==e.name_by_user)});if((s||o)&&(this._panels=this._buildPanelList(e,this._favorites),!this._panels.some(e=>e.id===this._selectedPanelId)&&this._panels.length>0)){const t=e[0];t&&(this._selectedPanelId=t.id,localStorage.setItem("span_panel_selected",this._selectedPanelId))}}async _updatePanelStatusWatch(){if(!this.hass||!this._selectedPanelId)return;if(this._selectedPanelId===yn)return;if(this._watchedPanelId===this._selectedPanelId)return;const e=this._selectedPanelId;this._watchedPanelId=e;try{const t=new We(this._errorStore),n=await Ye(this.hass,e,t);if(this._selectedPanelId!==e)return;const i=n.topology?.panel_entities?.panel_status;i&&(this._errorStore.watchPanelStatus(i),this._errorStore.updateHass(this.hass))}catch(t){console.warn("SPAN Panel: unable to fetch topology for panel status watching",t),this._watchedPanelId===e&&(this._watchedPanelId=null)}}async _discoverPanels(){if(!this._discovering&&this.hass){this._discovering=!0;try{let e;try{const t=new We(this._errorStore);e=(await t.callWS(this.hass,{type:"config/device_registry/list"},{errorId:"fetch:topology"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id)}catch(e){return console.error("SPAN Panel: device discovery failed",e),void this._errorStore.add({key:"discovery-failed",level:"error",message:n("error.discovery_failed"),persistent:!0,retryFn:()=>{this._errorStore.remove("discovery-failed"),this._discoverPanels()}})}this._favorites=await this._loadFavorites(),this._panels=this._buildPanelList(e,this._favorites),this._favoritesViewState=function(){try{const e=localStorage.getItem(vn);if(!e)return{expanded:{activity:[],area:[]}};const t=JSON.parse(e);if(!t||"object"!=typeof t)return{expanded:{activity:[],area:[]}};const n=t.expanded??{activity:[],area:[]};return{activeTab:t.activeTab,expanded:{activity:Array.isArray(n.activity)?n.activity:[],area:Array.isArray(n.area)?n.area:[]},searchQuery:"string"==typeof t.searchQuery?t.searchQuery:void 0}}catch{return{expanded:{activity:[],area:[]}}}}(),this._discovered=!0;const t=localStorage.getItem("span_panel_selected");if(t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:e.length>0&&(this._selectedPanelId=e[0].id),this._selectedPanelId===yn){const e=this._favoritesViewState.activeTab;"activity"===e||"area"===e||"monitoring"===e?this._activeTab=e:"dashboard"===this._activeTab&&(this._activeTab="activity")}this._chartMetric=localStorage.getItem("span_panel_metric")||"power"}finally{this._discovering=!1}}}_buildPanelList(e,t){if(!Qe(t))return e;return[{id:yn,name:n("panel.favorites"),model:"__favorites__"},...e]}async _loadFavorites(){return this.hass?this._favCache.fetch(this.hass):{}}async _refreshFavorites(){const e=++this._refreshSeq;this._favCache.invalidate();const t=await this._loadFavorites();if(e!==this._refreshSeq)return;const n=this._selectedPanelId===yn;this._favorites=t;const i=this._panels.filter(e=>e.id!==yn);if(this._panels=this._buildPanelList(i,t),n&&!Qe(t)){!function(){try{localStorage.removeItem(vn)}catch{}}(),this._favoritesViewState={expanded:{activity:[],area:[]}};const e=i[0];e?(this._selectedPanelId=e.id,localStorage.setItem("span_panel_selected",e.id)):this._selectedPanelId=null}else this._isFavoritesView?this._scheduleTabRender():this._applyPanelFavorites()}_buildTabList(){const e=[];return this._isFavoritesView||e.push({id:"dashboard",label:n("tab.by_panel"),icon:"mdi:view-dashboard"}),e.push({id:"activity",label:n("tab.by_activity"),icon:"mdi:sort-descending"},{id:"area",label:n("tab.by_area"),icon:"mdi:home-group"},{id:"monitoring",label:n("tab.monitoring"),icon:"mdi:monitor-eye"}),e}_buildFavoritesSummaryHTML(){return function(e){return`\n
\n \n
\n ${Oe(n("header.enable_switches"))}\n
\n \n
\n
\n
\n ${et()}\n
\n \n \n
\n
\n
\n `}("current"===(this._chartMetric||"power"))}_buildFavoritesPanelStatsGridHTML(e,t){if(0===e.length)return"";return`
${e.map(e=>`\n
\n
${Oe(e.panelName||e.topology.device_name||"")}
\n ${tt(e.topology,t,e.panelDeviceId)}\n
\n `).join("")}
`}_updateFavoritesPanelStats(e,t){if(this.hass&&0!==this._favoritesPanelStats.length)for(const n of this._favoritesPanelStats){const i=e.querySelector(`.panel-stats[data-stats-panel-id="${qt(n.panelDeviceId)}"]`);i&&Ut(i,this.hass,n.topology,t,0)}}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _recoverIfNeeded(e=0){if(!this._discovered||!this.hass)return;const t=()=>{e>=3||(this._recoverTimer&&clearTimeout(this._recoverTimer),this._recoverTimer=setTimeout(()=>{this._recoverTimer=null,this._recoverIfNeeded(e+1)},2e3*(e+1)))};try{await this._scheduleTabRender()}catch{return void t()}const n=this._root.getElementById("tab-content");n&&0===n.childNodes.length&&t()}async _scheduleTabRender(){await this.updateComplete,this._sidePanelOpen()?this._pendingTabRender=!0:await this._tabRenderScheduler()}_sidePanelOpen(){const e=this.shadowRoot?.getElementById("tab-content");return!!e?.querySelector("span-side-panel[open]")}async _renderTab(){const e=this._beginRender();this._dashboardTab.stop(),this._monitoringTab.stop(),this._listCtrl.stop(),this._listDashCtrl.stopIntervals();for(const e of this._favoritesMonitoringTabs.values())e.stop();this._favoritesMonitoringTabs.clear(),this._favoritesPanelStats=[];const t=this._root.getElementById("tab-content");if(t)if(this._isFavoritesView)await this._renderFavoritesTab(t,e);else switch(this._listDashCtrl.clearFavoriteRefs(),this._listCtrl.setViewName(null),this._applyPanelFavorites(),this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig(),n=this._panels.find(e=>e.id===this._selectedPanelId),i=n?.config_entries?.[0]??null;await this._dashboardTab.render(t,this.hass,this._selectedPanelId??"",e,i);break}case"activity":{t.innerHTML="";const n=this._panels.find(e=>e.id===this._selectedPanelId),i=n?.config_entries?.[0]??null;try{const n=new We(this._errorStore),s=await Ye(this.hass,this._selectedPanelId??void 0,n);if(e())return;const o=this._buildDashboardConfig();if(this._listDashCtrl.init(s.topology,o,this.hass,i),this._listDashCtrl.powerHistory.clear(),await this._listDashCtrl.monitoringCache.fetch(this.hass,i),e())return;if(await this._listDashCtrl.fetchAndBuildHorizonMaps(),e())return;const r=s.topology?nt(s.topology,o):"";if(this._listCtrl.setColumns(this._listColumns),this._listCtrl.renderActivityView(t,this.hass,s.topology,o,this._listDashCtrl.monitoringCache.status,r),await this._listDashCtrl.loadHistory(),e())return;this._listDashCtrl.updateDOM(t),this._listDashCtrl.startIntervals(t)}catch(n){if(e())return;const i=document.createElement("p");i.style.color="var(--error-color)",i.textContent=n.message,t.appendChild(i)}break}case"area":{t.innerHTML="";const i=this._panels.find(e=>e.id===this._selectedPanelId),s=i?.config_entries?.[0]??null;try{const i=new We(this._errorStore),o=await Ye(this.hass,this._selectedPanelId??void 0,i);if(e())return;const r=this._buildDashboardConfig();if(this._listDashCtrl.init(o.topology,r,this.hass,s),this._listDashCtrl.powerHistory.clear(),await this._listDashCtrl.monitoringCache.fetch(this.hass,s),e())return;if(await this._listDashCtrl.fetchAndBuildHorizonMaps(),e())return;const a=o.topology?nt(o.topology,r):"";if(this._listCtrl.setColumns(this._listColumns),this._listCtrl.renderAreaView(t,this.hass,o.topology,r,this._listDashCtrl.monitoringCache.status,a),await this._listDashCtrl.loadHistory(),e())return;this._listDashCtrl.updateDOM(t),this._listDashCtrl.startIntervals(t),this._areaUnsub||this._areaSubscribing||(this._areaSubscribing=!0,async function(e,t,i,s){if(!e.connection)return()=>{};const o=async()=>{try{const n=new Map;for(const[e,i]of Object.entries(t.circuits))n.set(e,i.area);await Ze(e,t);for(const[e,s]of Object.entries(t.circuits))if(s.area!==n.get(e))return void i()}catch(e){console.warn("[span-panel] area registry update failed:",e),s?.add({key:"fetch:areas",level:"warning",message:n("error.areas_failed"),persistent:!1})}},[r,a]=await Promise.all([e.connection.subscribeEvents(o,"entity_registry_updated"),e.connection.subscribeEvents(o,"area_registry_updated")]);return()=>{r(),a()}}(this.hass,o.topology,()=>{"area"===this._activeTab&&this._scheduleTabRender()},this._errorStore).then(e=>{this._areaSubscribing?this._areaUnsub=e:e()}).catch(e=>{this._areaSubscribing=!1,console.warn("SPAN Panel: area subscription failed",e),this._errorStore.add({key:"subscribe:area",level:"warning",message:n("error.areas_failed"),persistent:!1})}))}catch(e){const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=e instanceof Error?e.message:String(e),t.appendChild(n)}break}case"monitoring":{t.innerHTML="";const e=this._panels.find(e=>e.id===this._selectedPanelId),n=e?.config_entries?.[0]??null;await this._monitoringTab.render(t,this.hass,n??void 0);break}}}async _renderFavoritesTab(e,t){if(e.innerHTML="",!this.hass)return;const i=this._panels.filter(e=>e.id!==yn),s=await this._favCtrl.build(this.hass,this._favorites,i,this._errorStore);if(t())return;const o=s.perPanelStats.map(e=>{const t=e.topology.panel_entities?.panel_status;return"string"==typeof t?{entityId:t,panelName:e.panelName}:null}).filter(e=>null!==e);this._errorStore.watchPanelStatuses(o),this._errorStore.updateHass(this.hass);const r=new Map;for(const e of s.perPanelStats){const t=i.find(t=>t.id===e.panelDeviceId);r.set(e.panelDeviceId,{panelName:e.panelName,topology:e.topology,configEntryId:t?.config_entries?.[0]??null})}this._listDashCtrl.setFavoritesPerPanelInfo(r);const a=s.topology,l=s.entryIds[0]??null,c=Object.keys(a.circuits).length>0,d=Object.keys(a.sub_devices??{}).length>0;if(!c&&!d){const t=document.createElement("p");return t.style.color="var(--secondary-text-color)",t.style.padding="24px",t.textContent=n("list.no_results"),void e.appendChild(t)}if(this._listDashCtrl.setFavoriteRefs(a._favoriteRefs),this._listDashCtrl.setPanelFavorites(null),"monitoring"===this._activeTab)return this._listCtrl.setViewName(null),void await this._renderFavoritesMonitoring(e,s.entryIds,i);const h=this._activeTab,u=new Set(Object.keys(a.circuits)),p=this._favoritesViewState.expanded[h].filter(e=>u.has(e));this._listCtrl.setViewName(h),this._listCtrl.setInitialExpansion(p),this._listCtrl.setInitialSearchQuery(this._favoritesViewState.searchQuery??""),this._listCtrl.setColumns(this._listColumns);const g=this._buildDashboardConfig();if(this._listDashCtrl.init(a,g,this.hass,l),this._listDashCtrl.powerHistory.clear(),await this._listDashCtrl.fetchAndBuildHorizonMaps(),t())return;const _=await this._listDashCtrl.fetchMergedMonitoringStatus(s.entryIds);if(!t()){this._favoritesPanelStats=s.perPanelStats;try{if(await this._listDashCtrl.loadHistory(),t())return;const n=this._buildFavoritesSummaryHTML(),i=this._buildFavoritesPanelStatsGridHTML(s.perPanelStats,g),o=n+i+(d?`
\n
${Et(a,this.hass,g)}
\n
`:"");"activity"===h?this._listCtrl.renderActivityView(e,this.hass,a,g,_,o):this._listCtrl.renderAreaView(e,this.hass,a,g,_,o),this._updateFavoritesPanelStats(e,g),this._listDashCtrl.setupResizeObserver(e,e),this._listDashCtrl.startIntervals(e,()=>{this._updateFavoritesPanelStats(e,g)})}catch(n){if(t())return;const i=document.createElement("p");i.style.color="var(--error-color)",i.textContent=n.message,e.appendChild(i)}}}async _renderFavoritesMonitoring(e,t,n){if(!this.hass)return;const i=document.createElement("div");i.className="favorites-monitoring-stack",e.appendChild(i);const s=new Map;for(const e of n){const t=e.config_entries?.[0];t&&s.set(t,e)}const o=new Map;for(const e of t){const t=s.get(e),n=document.createElement("div");n.className="favorites-monitoring-block",n.style.marginBottom="24px";const r=document.createElement("h2");r.style.margin="8px 0 12px",r.style.fontSize="1em",r.textContent=t?.name_by_user??t?.name??e,n.appendChild(r);const a=document.createElement("div");n.appendChild(a),i.appendChild(n);const l=new sn;l.errorStore=this._errorStore,o.set(e,l);try{await l.render(a,this.hass,e)}catch(t){console.warn("SPAN Panel: favorites monitoring render failed",e,t);const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=t.message??String(t),a.appendChild(n)}}this._favoritesMonitoringTabs=o}_applyPanelFavorites(){if(!this._selectedPanelId||this._isFavoritesView)return this._listDashCtrl.setPanelFavorites(null),void this._dashboardTab.setPanelFavorites(null);const e=this._favorites[this._selectedPanelId],t={panelDeviceId:this._selectedPanelId,circuitUuids:new Set(e?.circuits??[]),subDeviceIds:new Set(e?.sub_devices??[])};this._listDashCtrl.setPanelFavorites(t),this._dashboardTab.setPanelFavorites(t)}};wn._shellStyles=C` + `}_onPanelChange(t){const e=t.target;this._selectedPanelId=e.value,localStorage.setItem("span_panel_selected",e.value),this._isFavoritesView&&"dashboard"===this._activeTab&&(this._activeTab="activity"),this._areaSubscribing=!1,this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null)}get _isFavoritesView(){return this._selectedPanelId===zT}_onTabClick(t){const e=t.target.closest(".shared-tab");if(!e)return;const n=e.dataset.tab;n&&n!==this._activeTab&&(this._activeTab=n,this._isFavoritesView&&"dashboard"!==n&&(this._favoritesViewState.activeTab=n,LT(this._favoritesViewState)))}_onTabContentClick(t){const e=t.target.closest(".unit-btn");if(e){const t=e.dataset.unit;if(!t||t===this._chartMetric)return;return this._chartMetric=t,void localStorage.setItem("span_panel_metric",t)}}_onSidePanelClosed(){if("dashboard"===this._activeTab){const t=this._dashboardTab._ctrl;t.monitoringCache.invalidate(),t.graphSettingsCache.invalidate()}this._listDashCtrl.monitoringMultiCache.invalidate(),this._pendingTabRender&&(this._pendingTabRender=!1,this._scheduleTabRender())}_onUnitChanged(t){const e=t.detail;e&&e!==this._chartMetric&&(this._chartMetric=e,localStorage.setItem("span_panel_metric",e))}_onListColumnsChanged(t){const e=t.detail;"number"!=typeof e||1!==e&&2!==e&&3!==e||e===this._listColumns||(this._listColumns=e,Ut(e))}_onGraphSettingsChanged(){if("dashboard"===this._activeTab){const t=this._root.getElementById("tab-content");if(t){this._dashboardTab._ctrl.onGraphSettingsChanged(t)}}}_onNavigateTab(t){const e=t.detail;e&&(this._activeTab=e)}_onFavoritesViewStateChangedEvent(t){if(!this._isFavoritesView)return;const e=t.detail;if(!e)return;const n=this._favoritesViewState;n.activeTab=e.view;const i=this._listDashCtrl.topology,r=i?.circuits;r&&Object.keys(r).length>0?n.expanded[e.view]=e.expanded.filter(t=>t in r):n.expanded[e.view]=e.expanded,n.searchQuery=e.searchQuery,this._persistFavoritesViewStateTimer&&clearTimeout(this._persistFavoritesViewStateTimer),this._persistFavoritesViewStateTimer=setTimeout(()=>{this._persistFavoritesViewStateTimer=null,LT(n)},250)}_subscribeDeviceRegistry(){!this._deviceRegistryUnsub&&this.hass?.connection&&(this._deviceRegistryUnsub=this.hass.connection.subscribeEvents(()=>this._refreshPanels(),"device_registry_updated"))}_unsubscribeDeviceRegistry(){this._deviceRegistryUnsub&&(this._deviceRegistryUnsub.then(t=>t()),this._deviceRegistryUnsub=null)}async _refreshPanels(){if(!this.hass||!this._discovered)return;const t=(await this.hass.callWS({type:"config/device_registry/list"})).filter(t=>t.identifiers?.some(t=>t[0]===s)&&!t.via_device_id),e=this._panels.filter(t=>t.id!==zT),n=new Map(e.map(t=>[t.id,t])),i=new Set(t.map(t=>t.id)),r=n.size!==i.size||[...n.keys()].some(t=>!i.has(t)),o=!r&&t.some(t=>{const e=n.get(t.id);return!!e&&(e.name!==t.name||e.name_by_user!==t.name_by_user)});if((r||o)&&(this._panels=this._buildPanelList(t,this._favorites),!this._panels.some(t=>t.id===this._selectedPanelId)&&this._panels.length>0)){const e=t[0];e&&(this._selectedPanelId=e.id,localStorage.setItem("span_panel_selected",this._selectedPanelId))}}async _updatePanelStatusWatch(){if(!this.hass||!this._selectedPanelId)return;if(this._selectedPanelId===zT)return;if(this._watchedPanelId===this._selectedPanelId)return;const t=this._selectedPanelId;this._watchedPanelId=t;try{const e=new qt(this._errorStore),n=await se(this.hass,t,e);if(this._selectedPanelId!==t)return;const i=n.topology?.panel_entities?.panel_status;i&&(this._errorStore.watchPanelStatus(i),this._errorStore.updateHass(this.hass))}catch(e){console.warn("SPAN Panel: unable to fetch topology for panel status watching",e),this._watchedPanelId===t&&(this._watchedPanelId=null)}}async _discoverPanels(){if(!this._discovering&&this.hass){this._discovering=!0;try{let t;try{const e=new qt(this._errorStore);t=(await e.callWS(this.hass,{type:"config/device_registry/list"},{errorId:"fetch:topology"})).filter(t=>t.identifiers?.some(t=>t[0]===s)&&!t.via_device_id)}catch(t){return console.error("SPAN Panel: device discovery failed",t),void this._errorStore.add({key:"discovery-failed",level:"error",message:n("error.discovery_failed"),persistent:!0,retryFn:()=>{this._errorStore.remove("discovery-failed"),this._discoverPanels()}})}this._favorites=await this._loadFavorites(),this._panels=this._buildPanelList(t,this._favorites),this._favoritesViewState=function(){try{const t=localStorage.getItem(PT);if(!t)return{expanded:{activity:[],area:[]}};const e=JSON.parse(t);if(!e||"object"!=typeof e)return{expanded:{activity:[],area:[]}};const n=e.expanded??{activity:[],area:[]};return{activeTab:e.activeTab,expanded:{activity:Array.isArray(n.activity)?n.activity:[],area:Array.isArray(n.area)?n.area:[]},searchQuery:"string"==typeof e.searchQuery?e.searchQuery:void 0}}catch{return{expanded:{activity:[],area:[]}}}}(),this._discovered=!0;const e=localStorage.getItem("span_panel_selected");if(e&&this._panels.some(t=>t.id===e)?this._selectedPanelId=e:t.length>0&&(this._selectedPanelId=t[0].id),this._selectedPanelId===zT){const t=this._favoritesViewState.activeTab;"activity"===t||"area"===t||"monitoring"===t?this._activeTab=t:"dashboard"===this._activeTab&&(this._activeTab="activity")}this._chartMetric=localStorage.getItem("span_panel_metric")||"power"}finally{this._discovering=!1}}}_buildPanelList(t,e){if(!Zt(e))return t;return[{id:zT,name:n("panel.favorites"),model:"__favorites__"},...t]}async _loadFavorites(){return this.hass?this._favCache.fetch(this.hass):{}}async _refreshFavorites(){const t=++this._refreshSeq;this._favCache.invalidate();const e=await this._loadFavorites();if(t!==this._refreshSeq)return;const n=this._selectedPanelId===zT;this._favorites=e;const i=this._panels.filter(t=>t.id!==zT);if(this._panels=this._buildPanelList(i,e),n&&!Zt(e)){!function(){try{localStorage.removeItem(PT)}catch{}}(),this._favoritesViewState={expanded:{activity:[],area:[]}};const t=i[0];t?(this._selectedPanelId=t.id,localStorage.setItem("span_panel_selected",t.id)):this._selectedPanelId=null}else this._isFavoritesView?this._scheduleTabRender():this._applyPanelFavorites()}_buildTabList(){const t=[];return this._isFavoritesView||t.push({id:"dashboard",label:n("tab.by_panel"),icon:"mdi:view-dashboard"}),t.push({id:"activity",label:n("tab.by_activity"),icon:"mdi:sort-descending"},{id:"area",label:n("tab.by_area"),icon:"mdi:home-group"},{id:"monitoring",label:n("tab.monitoring"),icon:"mdi:monitor-eye"}),t}_buildFavoritesSummaryHTML(){return function(t){return`\n
\n \n
\n ${Bt(n("header.enable_switches"))}\n
\n \n
\n
\n
\n ${le()}\n
\n \n \n
\n
\n
\n `}("current"===(this._chartMetric||"power"))}_buildFavoritesPanelStatsGridHTML(t,e){if(0===t.length)return"";return`
${t.map(t=>`\n
\n
${Bt(t.panelName||t.topology.device_name||"")}
\n ${ce(t.topology,e,t.panelDeviceId)}\n
\n `).join("")}
`}_updateFavoritesPanelStats(t,e){if(this.hass&&0!==this._favoritesPanelStats.length)for(const n of this._favoritesPanelStats){const i=t.querySelector(`.panel-stats[data-stats-panel-id="${nT(n.panelDeviceId)}"]`);i&&iT(i,this.hass,n.topology,e,0)}}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _scheduleTabRender(){await this.updateComplete,this._sidePanelOpen()?this._pendingTabRender=!0:await this._tabRenderScheduler()}_sidePanelOpen(){const t=this.shadowRoot?.getElementById("tab-content");return!!t?.querySelector("span-side-panel[open]")}async _renderTab(){const t=this._beginRender();this._dashboardTab.stop(),this._monitoringTab.stop(),this._listCtrl.stop(),this._listDashCtrl.stopIntervals();for(const t of this._favoritesMonitoringTabs.values())t.stop();this._favoritesMonitoringTabs.clear(),this._favoritesPanelStats=[];const e=this._root.getElementById("tab-content");if(e)if(this._isFavoritesView)await this._renderFavoritesTab(e,t);else switch(this._listDashCtrl.clearFavoriteRefs(),this._listCtrl.setViewName(null),this._applyPanelFavorites(),this._activeTab){case"dashboard":{e.innerHTML="";const t=this._buildDashboardConfig(),n=this._panels.find(t=>t.id===this._selectedPanelId),i=n?.config_entries?.[0]??null;await this._dashboardTab.render(e,this.hass,this._selectedPanelId??"",t,i);break}case"activity":{e.innerHTML="";const n=this._panels.find(t=>t.id===this._selectedPanelId),i=n?.config_entries?.[0]??null;try{const n=new qt(this._errorStore),r=await se(this.hass,this._selectedPanelId??void 0,n);if(t())return;const o=this._buildDashboardConfig();if(this._listDashCtrl.init(r.topology,o,this.hass,i),this._listDashCtrl.powerHistory.clear(),await this._listDashCtrl.monitoringCache.fetch(this.hass,i),t())return;if(await this._listDashCtrl.fetchAndBuildHorizonMaps(),t())return;const a=r.topology?ue(r.topology,o):"";if(this._listCtrl.setColumns(this._listColumns),this._listCtrl.renderActivityView(e,this.hass,r.topology,o,this._listDashCtrl.monitoringCache.status,a),await this._listDashCtrl.loadHistory(),t())return;this._listDashCtrl.updateDOM(e),this._listDashCtrl.startIntervals(e)}catch(n){if(t())return;const i=document.createElement("p");i.style.color="var(--error-color)",i.textContent=n.message,e.appendChild(i)}break}case"area":{e.innerHTML="";const i=this._panels.find(t=>t.id===this._selectedPanelId),r=i?.config_entries?.[0]??null;try{const i=new qt(this._errorStore),o=await se(this.hass,this._selectedPanelId??void 0,i);if(t())return;const a=this._buildDashboardConfig();if(this._listDashCtrl.init(o.topology,a,this.hass,r),this._listDashCtrl.powerHistory.clear(),await this._listDashCtrl.monitoringCache.fetch(this.hass,r),t())return;if(await this._listDashCtrl.fetchAndBuildHorizonMaps(),t())return;const s=o.topology?ue(o.topology,a):"";if(this._listCtrl.setColumns(this._listColumns),this._listCtrl.renderAreaView(e,this.hass,o.topology,a,this._listDashCtrl.monitoringCache.status,s),await this._listDashCtrl.loadHistory(),t())return;this._listDashCtrl.updateDOM(e),this._listDashCtrl.startIntervals(e),this._areaUnsub||this._areaSubscribing||(this._areaSubscribing=!0,async function(t,e,i,r){if(!t.connection)return()=>{};const o=async()=>{try{const n=new Map;for(const[t,i]of Object.entries(e.circuits))n.set(t,i.area);await ae(t,e);for(const[t,r]of Object.entries(e.circuits))if(r.area!==n.get(t))return void i()}catch(t){console.warn("[span-panel] area registry update failed:",t),r?.add({key:"fetch:areas",level:"warning",message:n("error.areas_failed"),persistent:!1})}},[a,s]=await Promise.all([t.connection.subscribeEvents(o,"entity_registry_updated"),t.connection.subscribeEvents(o,"area_registry_updated")]);return()=>{a(),s()}}(this.hass,o.topology,()=>{"area"===this._activeTab&&this._scheduleTabRender()},this._errorStore).then(t=>{this._areaSubscribing?this._areaUnsub=t:t()}).catch(t=>{this._areaSubscribing=!1,console.warn("SPAN Panel: area subscription failed",t),this._errorStore.add({key:"subscribe:area",level:"warning",message:n("error.areas_failed"),persistent:!1})}))}catch(t){const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=t instanceof Error?t.message:String(t),e.appendChild(n)}break}case"monitoring":{e.innerHTML="";const t=this._panels.find(t=>t.id===this._selectedPanelId),n=t?.config_entries?.[0]??null;await this._monitoringTab.render(e,this.hass,n??void 0);break}}}async _renderFavoritesTab(t,e){if(t.innerHTML="",!this.hass)return;const i=this._panels.filter(t=>t.id!==zT),r=await this._favCtrl.build(this.hass,this._favorites,i,this._errorStore);if(e())return;const o=r.perPanelStats.map(t=>{const e=t.topology.panel_entities?.panel_status;return"string"==typeof e?{entityId:e,panelName:t.panelName}:null}).filter(t=>null!==t);this._errorStore.watchPanelStatuses(o),this._errorStore.updateHass(this.hass);const a=new Map;for(const t of r.perPanelStats){const e=i.find(e=>e.id===t.panelDeviceId);a.set(t.panelDeviceId,{panelName:t.panelName,topology:t.topology,configEntryId:e?.config_entries?.[0]??null})}this._listDashCtrl.setFavoritesPerPanelInfo(a);const s=r.topology,l=r.entryIds[0]??null,c=Object.keys(s.circuits).length>0,u=Object.keys(s.sub_devices??{}).length>0;if(!c&&!u){const e=document.createElement("p");return e.style.color="var(--secondary-text-color)",e.style.padding="24px",e.textContent=n("list.no_results"),void t.appendChild(e)}if(this._listDashCtrl.setFavoriteRefs(s._favoriteRefs),this._listDashCtrl.setPanelFavorites(null),"monitoring"===this._activeTab)return this._listCtrl.setViewName(null),void await this._renderFavoritesMonitoring(t,r.entryIds,i);const h=this._activeTab,d=new Set(Object.keys(s.circuits)),p=this._favoritesViewState.expanded[h].filter(t=>d.has(t));this._listCtrl.setViewName(h),this._listCtrl.setInitialExpansion(p),this._listCtrl.setInitialSearchQuery(this._favoritesViewState.searchQuery??""),this._listCtrl.setColumns(this._listColumns);const f=this._buildDashboardConfig();if(this._listDashCtrl.init(s,f,this.hass,l),this._listDashCtrl.powerHistory.clear(),await this._listDashCtrl.fetchAndBuildHorizonMaps(),e())return;const g=await this._listDashCtrl.fetchMergedMonitoringStatus(r.entryIds);if(!e()){this._favoritesPanelStats=r.perPanelStats;try{if(await this._listDashCtrl.loadHistory(),e())return;const n=this._buildFavoritesSummaryHTML(),i=this._buildFavoritesPanelStatsGridHTML(r.perPanelStats,f),o=n+i+(u?`
\n
${Ne(s,this.hass,f)}
\n
`:"");"activity"===h?this._listCtrl.renderActivityView(t,this.hass,s,f,g,o):this._listCtrl.renderAreaView(t,this.hass,s,f,g,o),this._updateFavoritesPanelStats(t,f),this._listDashCtrl.setupResizeObserver(t,t),this._listDashCtrl.startIntervals(t,()=>{this._updateFavoritesPanelStats(t,f)})}catch(n){if(e())return;const i=document.createElement("p");i.style.color="var(--error-color)",i.textContent=n.message,t.appendChild(i)}}}async _renderFavoritesMonitoring(t,e,n){if(!this.hass)return;const i=document.createElement("div");i.className="favorites-monitoring-stack",t.appendChild(i);const r=new Map;for(const t of n){const e=t.config_entries?.[0];e&&r.set(e,t)}const o=new Map;for(const t of e){const e=r.get(t),n=document.createElement("div");n.className="favorites-monitoring-block",n.style.marginBottom="24px";const a=document.createElement("h2");a.style.margin="8px 0 12px",a.style.fontSize="1em",a.textContent=e?.name_by_user??e?.name??t,n.appendChild(a);const s=document.createElement("div");n.appendChild(s),i.appendChild(n);const l=new yT;l.errorStore=this._errorStore,o.set(t,l);try{await l.render(s,this.hass,t)}catch(e){console.warn("SPAN Panel: favorites monitoring render failed",t,e);const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=e.message??String(e),s.appendChild(n)}}this._favoritesMonitoringTabs=o}_applyPanelFavorites(){if(!this._selectedPanelId||this._isFavoritesView)return this._listDashCtrl.setPanelFavorites(null),void this._dashboardTab.setPanelFavorites(null);const t=this._favorites[this._selectedPanelId],e={panelDeviceId:this._selectedPanelId,circuitUuids:new Set(t?.circuits??[]),subDeviceIds:new Set(t?.sub_devices??[])};this._listDashCtrl.setPanelFavorites(e),this._dashboardTab.setPanelFavorites(e)}};NT._shellStyles=M` :host { color: var(--primary-text-color); } @@ -250,4 +381,4 @@ const Ee=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)} opacity: 1; border-bottom-color: var(--app-header-text-color, white); } - `,wn.styles=[bn._shellStyles,$(Kt)],m([Ne({attribute:!1})],wn.prototype,"hass",void 0),m([Ne({type:Boolean,reflect:!0})],wn.prototype,"narrow",void 0),m([Me()],wn.prototype,"_panels",void 0),m([Me()],wn.prototype,"_selectedPanelId",void 0),m([Me()],wn.prototype,"_activeTab",void 0),m([Me()],wn.prototype,"_discovered",void 0),m([Me()],wn.prototype,"_chartMetric",void 0),m([Me()],wn.prototype,"_listColumns",void 0),m([Me()],wn.prototype,"_favorites",void 0),wn=bn=m([Ee("span-panel")],wn),console.warn("%c SPAN-PANEL %c v0.9.4 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;");let xn=!1;const Sn=wn.prototype.connectedCallback;wn.prototype.connectedCallback=function(){xn=!0,Sn.call(this)};const $n=wn.prototype.disconnectedCallback;wn.prototype.disconnectedCallback=function(){xn=!1,$n.call(this)},document.addEventListener("visibilitychange",()=>{"visible"===document.visibilityState&&(xn||window.location.pathname.includes("span-panel")&&setTimeout(()=>{xn||location.reload()},200))}); + `,NT.styles=[ET._shellStyles,k(uT)],_([Et({attribute:!1})],NT.prototype,"hass",void 0),_([Et({type:Boolean,reflect:!0})],NT.prototype,"narrow",void 0),_([zt()],NT.prototype,"_panels",void 0),_([zt()],NT.prototype,"_selectedPanelId",void 0),_([zt()],NT.prototype,"_activeTab",void 0),_([zt()],NT.prototype,"_discovered",void 0),_([zt()],NT.prototype,"_chartMetric",void 0),_([zt()],NT.prototype,"_listColumns",void 0),_([zt()],NT.prototype,"_favorites",void 0),NT=ET=_([(t=>(e,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(t,e)}):customElements.define(t,e)})("span-panel")],NT),console.warn("%c SPAN-PANEL %c v0.9.4 ","background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;","background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;"); diff --git a/package-lock.json b/package-lock.json index f57b9c6..d12c990 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,15 +8,19 @@ "name": "span-panel-card", "version": "0.9.4", "dependencies": { + "@mdi/js": "^7.4.47", + "echarts": "^6.0.0", "lit": "^3.3.2" }, "devDependencies": { "@eslint/js": "^10.0.1", "@evilmartians/lefthook": "^2.1.3", "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-replace": "^6.0.3", "@rollup/plugin-terser": "^0.4.0", "@rollup/plugin-typescript": "^12.3.0", "eslint": "^10.0.3", + "happy-dom": "^20.9.0", "markdownlint-cli2": "^0.21.0", "prettier": "^3.8.1", "rollup": "^4.0.0", @@ -329,6 +333,12 @@ "@lit-labs/ssr-dom-shim": "^1.5.0" } }, + "node_modules/@mdi/js": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", + "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==", + "license": "Apache-2.0" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", @@ -701,6 +711,28 @@ } } }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-terser": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", @@ -1231,6 +1263,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -1251,6 +1293,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.58.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", @@ -1862,6 +1921,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -2271,6 +2346,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/happy-dom": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.9.0.tgz", + "integrity": "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/happy-dom/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4305,6 +4411,13 @@ "dev": true, "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, "node_modules/unicorn-magic": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", @@ -4514,6 +4627,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4557,6 +4680,28 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -4569,6 +4714,21 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" } } } diff --git a/package.json b/package.json index 8a4a07d..3c72b4b 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,19 @@ "test:watch": "vitest" }, "dependencies": { + "@mdi/js": "^7.4.47", + "echarts": "^6.0.0", "lit": "^3.3.2" }, "devDependencies": { "@eslint/js": "^10.0.1", "@evilmartians/lefthook": "^2.1.3", "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-replace": "^6.0.3", "@rollup/plugin-terser": "^0.4.0", "@rollup/plugin-typescript": "^12.3.0", "eslint": "^10.0.3", + "happy-dom": "^20.9.0", "markdownlint-cli2": "^0.21.0", "prettier": "^3.8.1", "rollup": "^4.0.0", diff --git a/rollup.config.mjs b/rollup.config.mjs index 3bc9d0a..fdb83b1 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,9 +1,26 @@ +import replace from "@rollup/plugin-replace"; import resolve from "@rollup/plugin-node-resolve"; import terser from "@rollup/plugin-terser"; import typescript from "@rollup/plugin-typescript"; const dev = process.env.ROLLUP_WATCH === "true"; -const plugins = [resolve({ browser: true }), typescript(), ...(dev ? [] : [terser()])]; +const plugins = [ + // ECharts (and some Lit internals) reference process.env.NODE_ENV for + // dev-mode warning code. ``process`` doesn't exist in the browser, so + // without this replacement the bundle throws ``ReferenceError: process + // is not defined`` on first import and the entire panel fails to + // mount. Replace at build time so the dead branches get DCE'd by + // terser. + replace({ + preventAssignment: true, + values: { + "process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"), + }, + }), + resolve({ browser: true }), + typescript(), + ...(dev ? [] : [terser()]), +]; export default [ { diff --git a/src/card/card-styles.ts b/src/card/card-styles.ts index 69fb079..16e9807 100644 --- a/src/card/card-styles.ts +++ b/src/card/card-styles.ts @@ -3,7 +3,12 @@ export const CARD_STYLES: string = ` --span-accent: var(--primary-color, #4dd9af); } - ha-card { + /* Card shell — replaces . Theme variables (--ha-card-*) are + stable HA contracts (not the deprecated component APIs flagged by the + 2026.4 frontend blog), so they stay in place to keep visual parity + with the rest of HA's dashboards. */ + .span-card { + display: block; padding: 24px; background: var(--card-background-color, #1c1c1c); color: var(--primary-text-color, #e0e0e0); @@ -124,7 +129,7 @@ export const CARD_STYLES: string = ` .shedding-legend { display: flex; gap: 12px; flex-wrap: wrap; justify-content: flex-end; } .shedding-legend-item { display: inline-flex; align-items: center; gap: 3px; } - .shedding-legend-item ha-icon { --mdc-icon-size: 16px; } + .shedding-legend-item span-icon { --mdc-icon-size: 16px; } .shedding-legend-secondary { --mdc-icon-size: 12px; opacity: 0.8; } .shedding-legend-text { font-size: 9px; font-weight: 600; } .shedding-legend-label { font-size: 0.7em; color: var(--secondary-text-color, #999); } @@ -187,7 +192,7 @@ export const CARD_STYLES: string = ` z-index: 1; transition: none; } - .slide-confirm-knob ha-icon { + .slide-confirm-knob span-icon { --mdc-icon-size: 14px; color: var(--card-background-color, #1c1c1c); } @@ -246,15 +251,16 @@ export const CARD_STYLES: string = ` .panel-grid { display: grid; - grid-template-columns: 28px 1fr 1fr 28px; - gap: 8px; + /* Five columns: left tab label, left cell, explicit 8px spacer, + right cell, right tab label. Spacer is in-band rather than a + column-gap so we can keep inter-cell space without paying an + equal gap between each cell and its tab label. The tab columns + are sized to fit a 2-digit breaker number (the font is 0.85em + of the panel body ≈ 14px glyph width). */ + grid-template-columns: 14px 1fr 8px 1fr 14px; + column-gap: 0; + row-gap: 8px; align-items: stretch; - /* Mark the grid as a size-query container so individual cells can - fold to a name-on-row-1 layout based on the grid's rendered - width (see .circuit-slot @container rule below). Viewport-based - media queries would mis-fire here because cells are half-width - of the grid, not half-width of the viewport. */ - container-type: inline-size; } .tab-label { @@ -313,7 +319,7 @@ export const CARD_STYLES: string = ` color: var(--span-accent); font-size: 0.7em; font-weight: 700; - padding: 2px 7px; + padding: 2px 3px; border-radius: 4px; white-space: nowrap; border: 1px solid color-mix(in srgb, var(--span-accent) 25%, transparent); @@ -331,63 +337,76 @@ export const CARD_STYLES: string = ` .circuit-controls { display: flex; align-items: center; gap: 10px; flex-shrink: 0; } - /* Narrow-cell fold for By Panel breaker cells. At full width the - cell packs breaker + utilization + name on the left of .circuit-header - and power + toggle on the right, with .circuit-status (shedding + - gear) below. Each cell is half the .panel-grid width, so truncation - kicks in once the grid is narrower than ~760px (cell ~340px) — - well before any viewport-based media query would fire. Query the - grid's own width via a container query and, once crossed, collapse - the nested flex containers into a single grid so the name gets the - whole first row and all readings/controls/gear drop to a second row. - 'display: contents' on the wrappers removes them from the layout - tree so the leaf elements can be placed by the outer grid. */ - @container (max-width: 760px) { - .circuit-slot { - display: grid; - grid-template-columns: auto auto auto 1fr auto auto auto; - grid-template-areas: - "name name name name name name name" - "badge util shed . status power gear" - "chart chart chart chart chart chart chart"; - row-gap: 6px; - column-gap: 8px; - } - .circuit-slot > .circuit-header, - .circuit-slot > .circuit-status, - .circuit-header > .circuit-info, - .circuit-header > .circuit-controls { - display: contents; - } - .circuit-slot .circuit-name { - grid-area: name; - justify-self: start; - } - .circuit-slot .breaker-badge { - grid-area: badge; - } - .circuit-slot .utilization { - grid-area: util; - } - .circuit-slot .shedding-icon, - .circuit-slot .shedding-composite { - grid-area: shed; - } - .circuit-slot .toggle-pill { - grid-area: status; - justify-self: end; - } - .circuit-slot .power-value { - grid-area: power; - justify-self: end; - } - .circuit-slot .gear-icon.circuit-gear { - grid-area: gear; - justify-self: end; - } - .circuit-slot > .chart-container { - grid-area: chart; - } + /* Truncation-driven fold for By Panel breaker cells. The .is-folded + class is added/removed by the JS observer in + src/core/truncation-fold.ts when the .circuit-name actually + ellipsizes. Pixel thresholds can't get this right because name + length varies wildly per circuit (e.g. "Spa" vs + "Commissioned PV System") — only measuring the live name vs its + container catches the exact moment of truncation. + + When folded the nested flex wrappers (.circuit-header, + .circuit-info, .circuit-controls, .circuit-status) collapse via + 'display: contents' so the leaf elements participate directly in + the outer grid: name gets the whole first row, readings/controls/ + gear drop to a second row, chart stays as the full-width third. */ + .circuit-slot.is-folded { + display: grid; + /* Columns: badges + relay-toggle pack tight on the left, slack + absorbed by the 1fr column between the relay and the power + reading, keeping power + gear pinned to the right edge. The + previous layout placed the slack between the shedding icon and + the relay, which read as wasted padding the user pointed out. */ + grid-template-columns: auto auto auto auto 1fr auto auto; + /* Rows: name and controls sized to content; chart absorbs any + extra cell height. Without the explicit 1fr on row 3, a tall + cell (e.g. .circuit-col-span's 280px min-height for 240V + double-pole breakers) distributes excess space equally across + all three rows via the default align-content:stretch, which + pushes the chart down and vertically inflates the badge and + relay toggle to fill the controls row. */ + grid-template-rows: auto auto 1fr; + grid-template-areas: + "name name name name name name name" + "badge util shed status . power gear" + "chart chart chart chart chart chart chart"; + row-gap: 6px; + column-gap: 8px; + } + .circuit-slot.is-folded > .circuit-header, + .circuit-slot.is-folded > .circuit-status, + .circuit-slot.is-folded > .circuit-header > .circuit-info, + .circuit-slot.is-folded > .circuit-header > .circuit-controls { + display: contents; + } + .circuit-slot.is-folded .circuit-name { + grid-area: name; + justify-self: start; + } + .circuit-slot.is-folded .breaker-badge { + grid-area: badge; + } + .circuit-slot.is-folded .utilization { + grid-area: util; + } + .circuit-slot.is-folded .shedding-icon, + .circuit-slot.is-folded .shedding-composite { + grid-area: shed; + } + .circuit-slot.is-folded .toggle-pill { + grid-area: status; + justify-self: end; + } + .circuit-slot.is-folded .power-value { + grid-area: power; + justify-self: end; + } + .circuit-slot.is-folded .gear-icon.circuit-gear { + grid-area: gear; + justify-self: end; + } + .circuit-slot.is-folded > .chart-container { + grid-area: chart; } .power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; } @@ -474,10 +493,6 @@ export const CARD_STYLES: string = ` border-color: #f44336 !important; box-shadow: 0 0 8px rgba(244, 67, 54, 0.3); } - .circuit-custom-monitoring { - border-left: 3px solid #ff9800; - } - .chart-container { width: 100%; aspect-ratio: 4 / 1; @@ -651,6 +666,13 @@ export const CARD_STYLES: string = ` align-items: center; padding: 12px 16px; gap: 10px; + /* min-width: 0 lets the row shrink below the sum of its + non-shrinking children when its parent .list-cell is in a + narrow CSS-grid track (multi-column list mode). Without this + the row would maintain its intrinsic min-content width and + overflow the cell, leaving the name unshrunk and the + truncation-fold observer with no signal to react to. */ + min-width: 0; background: var(--card-background-color, #1c1c1c); border: 1px solid var(--divider-color, #333); border-radius: 8px; @@ -738,52 +760,59 @@ export const CARD_STYLES: string = ` color: var(--primary-text-color); } - /* Narrow-viewport fold: the flat flex row truncates the circuit - name heavily once breaker + utilization + shedding + toggle + - power + gear + chevron are all competing for width. Switch to a - two-row grid so the name gets the full width (paired only with - the expand chevron), and the badges/controls/reading/gear drop - to a secondary row underneath. Named areas keep the CSS readable - despite the flat HTML child order. */ - @media (max-width: 520px) { - .list-row { - display: grid; - grid-template-columns: auto auto auto 1fr auto auto auto; - grid-template-areas: - "name name name name name name chevron" - "badge util shed . status power gear"; - row-gap: 6px; - column-gap: 8px; - } - .list-row > .list-circuit-name { - grid-area: name; - justify-self: start; - } - .list-row > .list-expand-toggle { - grid-area: chevron; - } - .list-row > .breaker-badge { - grid-area: badge; - } - .list-row > .utilization { - grid-area: util; - } - .list-row > .shedding-icon, - .list-row > .shedding-composite { - grid-area: shed; - } - .list-row > .toggle-pill, - .list-row > .list-status-badge { - grid-area: status; - } - .list-row > .list-power-value { - grid-area: power; - justify-self: end; - } - .list-row > .gear-icon.circuit-gear { - grid-area: gear; - justify-self: end; - } + /* Truncation-driven fold for list rows. The .is-folded class is + added/removed by the JS observer in src/core/truncation-fold.ts + when the .list-circuit-name actually ellipsizes — pixel breakpoints + can't track this because name length varies wildly per circuit + ("Spa" vs "Commissioned PV System") and any single threshold + misfires for the other end of the range. Switch to a two-row grid + so the name gets the full width (paired only with the expand + chevron) and the badges/controls/reading/gear drop to a secondary + row underneath. Named areas keep the CSS readable despite the flat + HTML child order. */ + .list-row.is-folded { + display: grid; + /* Row 1: name spans the row up to the chevron at the trailing + column. Row 2: badge + util + shed + relay-toggle pack left, + the 1fr column absorbs slack between the relay and the power + reading, power + gear stay pinned to the right edge. The + earlier layout placed the slack between the shedding icon and + the relay, which the user flagged as wasted padding. */ + grid-template-columns: auto auto auto auto 1fr auto auto; + grid-template-areas: + "name name name name name name chevron" + "badge util shed status . power gear"; + row-gap: 6px; + column-gap: 8px; + } + .list-row.is-folded > .list-circuit-name { + grid-area: name; + justify-self: start; + } + .list-row.is-folded > .list-expand-toggle { + grid-area: chevron; + } + .list-row.is-folded > .breaker-badge { + grid-area: badge; + } + .list-row.is-folded > .utilization { + grid-area: util; + } + .list-row.is-folded > .shedding-icon, + .list-row.is-folded > .shedding-composite { + grid-area: shed; + } + .list-row.is-folded > .toggle-pill, + .list-row.is-folded > .list-status-badge { + grid-area: status; + } + .list-row.is-folded > .list-power-value { + grid-area: power; + justify-self: end; + } + .list-row.is-folded > .gear-icon.circuit-gear { + grid-area: gear; + justify-self: end; } /* ── Expanded circuit content ──────────────────────────── */ diff --git a/src/card/span-panel-card.ts b/src/card/span-panel-card.ts index 244ee11..17084eb 100644 --- a/src/card/span-panel-card.ts +++ b/src/card/span-panel-card.ts @@ -18,6 +18,8 @@ import { RetryManager } from "../core/retry-manager.js"; import { CARD_STYLES } from "./card-styles.js"; import "../core/side-panel.js"; import "../core/error-banner.js"; +import "../core/span-icon.js"; +import "../core/span-switch.js"; import type { HomeAssistant, PanelTopology, PanelDevice, CardConfig } from "../types.js"; interface SpanSidePanelElement extends HTMLElement { @@ -74,7 +76,6 @@ export class SpanPanelCard extends LitElement { */ private _areaSubscribing = false; private _tabBarCleanup: (() => void) | null = null; - private _onVisibilityChange: (() => void) | null = null; static override styles = unsafeCSS(CARD_STYLES); @@ -97,13 +98,6 @@ export class SpanPanelCard extends LitElement { connectedCallback(): void { super.connectedCallback(); this._ctrl.startIntervals(this._root); - - this._onVisibilityChange = () => { - if (document.visibilityState !== "visible" || !this._discovered || !this.hass) return; - this._ctrl.recordSamples(); - this._ctrl.updateDOM(this._root); - }; - document.addEventListener("visibilitychange", this._onVisibilityChange); } disconnectedCallback(): void { @@ -118,10 +112,6 @@ export class SpanPanelCard extends LitElement { this._tabBarCleanup(); this._tabBarCleanup = null; } - if (this._onVisibilityChange) { - document.removeEventListener("visibilitychange", this._onVisibilityChange); - this._onVisibilityChange = null; - } this._errorStore.dispose(); super.disconnectedCallback(); } @@ -174,16 +164,17 @@ export class SpanPanelCard extends LitElement { if (!this._discovered) { const hasError = this._errorStore.hasPersistent("discovery-failed"); return html` - +
${hasError ? nothing : html`
${escapeHtml(t("card.connecting"))}
`} - +
`; } // State 3: Discovered — render card shell; content populated imperatively return html` -
-
+ `; } @@ -392,7 +383,7 @@ export class SpanPanelCard extends LitElement { const slideEl = container.querySelector(".slide-confirm"); if (slideEl) { - const haCard = this._root.querySelector("ha-card"); + const haCard = this._root.querySelector(".span-card"); this._ctrl.bindSlideConfirm(slideEl, haCard); if (haCard) haCard.classList.add("switches-disabled"); } @@ -405,7 +396,7 @@ export class SpanPanelCard extends LitElement { this._ctrl.recordSamples(); this._ctrl.updateDOM(this._root); - this._ctrl.setupResizeObserver(this._root, this._root.querySelector("ha-card")); + this._ctrl.setupResizeObserver(this._root, this._root.querySelector(".span-card")); } else if (this._activeTab === "activity") { container.innerHTML = ""; const listHeaderHTML = buildHeaderHTML(this._topology, this._config); @@ -532,7 +523,7 @@ export class SpanPanelCard extends LitElement { ); return html` - +
SPAN Panel @@ -541,7 +532,7 @@ export class SpanPanelCard extends LitElement {
${cards}
${t("card.no_device")}
- +
`; } } diff --git a/src/chart/chart-update.ts b/src/chart/chart-update.ts index 28bc5e7..3b7a51d 100644 --- a/src/chart/chart-update.ts +++ b/src/chart/chart-update.ts @@ -1,16 +1,21 @@ import { buildChartOptions } from "./chart-options.js"; +import "./span-chart.js"; +import type { SpanChart } from "./span-chart.js"; import type { HomeAssistant, HistoryPoint, ChartMetricDef } from "../types.js"; -interface HaChartBaseElement extends HTMLElement { - hass: HomeAssistant; - options: unknown; - data: unknown; - height: string; -} - +/** + * Render or update the live chart for a circuit slot. Reuses the + * existing element in the container if present so each + * tick only re-flows the chart's data, not the DOM around it. + * + * The hass argument is kept on the call site for parity with the prior + * shape but no longer flows into the chart — span-chart + * owns its rendering directly via ECharts. Theme/colour parity is + * handled by the chart-options builder reading CSS custom properties. + */ export function updateChart( container: HTMLElement, - hass: HomeAssistant, + _hass: HomeAssistant, history: HistoryPoint[] | undefined, durationMs: number, metric: ChartMetricDef | undefined, @@ -23,18 +28,16 @@ export function updateChart( const minH = heightPx ?? 120; container.style.minHeight = minH + "px"; - let chart = container.querySelector("ha-chart-base") as HaChartBaseElement | null; + let chart = container.querySelector("span-chart") as SpanChart | null; if (!chart) { - chart = document.createElement("ha-chart-base") as HaChartBaseElement; + chart = document.createElement("span-chart") as SpanChart; chart.style.display = "block"; chart.style.width = "100%"; - chart.hass = hass; container.innerHTML = ""; container.appendChild(chart); } const actualH = container.clientHeight; chart.height = (actualH > 0 ? actualH : minH) + "px"; - chart.hass = hass; chart.options = options; chart.data = series; } diff --git a/src/chart/span-chart.ts b/src/chart/span-chart.ts new file mode 100644 index 0000000..b80dd46 --- /dev/null +++ b/src/chart/span-chart.ts @@ -0,0 +1,158 @@ +import { LitElement, html, css } from "lit"; +import { property } from "lit/decorators.js"; +import * as echarts from "echarts/core"; +import { LineChart } from "echarts/charts"; +import { GridComponent, TooltipComponent } from "echarts/components"; +import { SVGRenderer } from "echarts/renderers"; +import type { EChartsType } from "echarts/core"; +import type { BuildChartResult } from "./chart-options.js"; + +// Register only the pieces we use. Tree-shakes the rest of ECharts so +// the bundle stays as small as possible. SVG renderer (vs Canvas) is +// the right pick for shadow-DOM cards: SVG paints natively into the +// shadow root with no global stylesheet dependency, which is exactly +// the constraint that makes ApexCharts brittle here. +echarts.use([LineChart, GridComponent, TooltipComponent, SVGRenderer]); + +type ChartOptions = BuildChartResult["options"]; +type ChartSeries = BuildChartResult["series"]; + +/** + * Replacement for HA's . Owns the lifecycle of an + * ECharts instance bound to a div in the element's shadow root. + * + * ECharts is the same charting library HA uses inside ha-chart-base, + * picked here because it renders cleanly inside Lit shadow roots + * (pure SVG, no reliance on global stylesheets) — ApexCharts + * specifically struggles with shadow-DOM size detection and CSS + * scoping, which made it unworkable for this card. + * + * Lifecycle: + * firstUpdated – init the chart on the host div with the first + * options + series. + * updated – setOption with the merged options on subsequent + * property changes; the second argument ``true`` + * tells ECharts to replace (not merge) so removing + * a series actually removes it. + * resize – ResizeObserver on the host calls chart.resize() + * so the SVG re-flows to the new dimensions. + * disconnected – dispose the ECharts instance and disconnect the + * observer so hidden tabs don't leak chart instances. + */ +export class SpanChart extends LitElement { + @property({ attribute: false }) options: ChartOptions | null = null; + @property({ attribute: false }) data: ChartSeries = []; + @property({ type: String }) height = "120px"; + + private _chart: EChartsType | null = null; + private _resizeObserver: ResizeObserver | null = null; + + static override styles = css` + :host { + display: block; + width: 100%; + } + .chart-host { + width: 100%; + height: 100%; + } + `; + + protected override render(): unknown { + return html`
`; + } + + connectedCallback(): void { + super.connectedCallback(); + // If the element is being reattached after a previous disconnect + // (e.g. moved between DOM positions, adoptNode), firstUpdated has + // already run and won't run again. Re-mount the chart so the live + // SVG comes back. + if (this.hasUpdated && !this._chart && this.options) { + this._mount(); + this._observeResize(); + } + } + + protected override firstUpdated(): void { + this._mount(); + this._observeResize(); + } + + protected override updated(changed: Map): void { + if (changed.has("options") || changed.has("data")) { + if (this._chart && this.options) { + // ECharts' setOption takes its loose ECBasicOption shape; our + // strongly-typed ChartOptionsDef is structurally compatible but + // not assignable without a cast at the boundary. + this._chart.setOption(this._mergedOptions() as unknown as Parameters[0], true); + } else if (!this._chart && this.options) { + // Late-arriving options: firstUpdated already ran but options + // wasn't set yet so _mount bailed. Mount now that we have a + // valid option set. Without this branch the chart would stay + // permanently blank for any caller that assigns options after + // the first Lit update cycle. + this._mount(); + } + } + if (changed.has("height") && this._chart) { + this._chart.resize(); + } + } + + disconnectedCallback(): void { + this._destroy(); + super.disconnectedCallback(); + } + + private _mount(): void { + if (!this.options) return; + const host = this.shadowRoot?.querySelector(".chart-host") as HTMLElement | null; + if (!host) return; + this._chart = echarts.init(host, undefined, { renderer: "svg" }); + // ECharts' setOption takes its loose ECBasicOption shape; our + // strongly-typed ChartOptionsDef is structurally compatible but + // not assignable without a cast at the boundary. + this._chart.setOption(this._mergedOptions() as unknown as Parameters[0], true); + } + + private _mergedOptions(): ChartOptions & { series: ChartSeries } { + if (!this.options) throw new Error("span-chart: options not set"); + return { ...this.options, series: this.data }; + } + + private _observeResize(): void { + if (typeof ResizeObserver === "undefined") return; + this._resizeObserver = new ResizeObserver(() => { + if (this._chart) this._chart.resize(); + }); + const host = this.shadowRoot?.querySelector(".chart-host") as HTMLElement | null; + if (host) this._resizeObserver.observe(host); + } + + private _destroy(): void { + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + this._resizeObserver = null; + } + if (this._chart) { + this._chart.dispose(); + this._chart = null; + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "span-chart": SpanChart; + } +} + +// Guarded registration: see span-icon.ts for the rationale. +try { + if (!customElements.get("span-chart")) { + customElements.define("span-chart", SpanChart); + } +} catch { + // Scoped custom element registry may throw on duplicate registration after upgrade +} diff --git a/src/core/circuit-state.ts b/src/core/circuit-state.ts index 20011ec..4d955b6 100644 --- a/src/core/circuit-state.ts +++ b/src/core/circuit-state.ts @@ -1,4 +1,4 @@ -import { hasCustomOverrides, isAlertActive } from "./monitoring-status.js"; +import { isAlertActive } from "./monitoring-status.js"; import type { Circuit, MonitoringPointInfo } from "../types.js"; /** @@ -12,6 +12,5 @@ export function getCircuitStateClasses(_circuit: Circuit, monitoringInfo: Monito if (!isOn) classes.push("circuit-off"); if (isProducer) classes.push("circuit-producer"); if (isAlertActive(monitoringInfo)) classes.push("circuit-alert"); - if (hasCustomOverrides(monitoringInfo)) classes.push("circuit-custom-monitoring"); return classes.join(" "); } diff --git a/src/core/dashboard-controller.ts b/src/core/dashboard-controller.ts index a2c38c2..4344909 100644 --- a/src/core/dashboard-controller.ts +++ b/src/core/dashboard-controller.ts @@ -652,7 +652,7 @@ export class DashboardController { if (pos >= SLIDE_THRESHOLD) { knob.style.left = maxX + "px"; slideEl.classList.add("confirmed"); - knob.querySelector("ha-icon")?.setAttribute("icon", "mdi:lock-open"); + knob.querySelector("span-icon")?.setAttribute("icon", "mdi:lock-open"); textEl.textContent = (slideEl as HTMLElement).dataset.textOn ?? ""; if (parent) parent.classList.remove("switches-disabled"); } else { @@ -685,7 +685,7 @@ export class DashboardController { slideEl.classList.remove("confirmed"); knob.classList.add("snapping"); knob.style.left = "2px"; - knob.querySelector("ha-icon")?.setAttribute("icon", "mdi:lock"); + knob.querySelector("span-icon")?.setAttribute("icon", "mdi:lock"); textEl.textContent = (slideEl as HTMLElement).dataset.textOff ?? ""; if (parent) parent.classList.add("switches-disabled"); }); @@ -728,7 +728,7 @@ export class DashboardController { if (this._resizeDebounce) clearTimeout(this._resizeDebounce); this._resizeDebounce = setTimeout(() => { for (const container of root.querySelectorAll(".chart-container")) { - const chart = container.querySelector("ha-chart-base"); + const chart = container.querySelector("span-chart"); if (chart) chart.remove(); } this.updateDOM(root); diff --git a/src/core/error-banner.ts b/src/core/error-banner.ts index 92374c4..f01b5d9 100644 --- a/src/core/error-banner.ts +++ b/src/core/error-banner.ts @@ -1,9 +1,8 @@ import { LitElement, html, css, nothing } from "lit"; -import { customElement, state } from "lit/decorators.js"; +import { state } from "lit/decorators.js"; import { t } from "../i18n.js"; import type { ErrorStore, ErrorEntry } from "./error-store.js"; -@customElement("span-error-banner") export class SpanErrorBanner extends LitElement { private _store: ErrorStore | null = null; private _unsub: (() => void) | null = null; @@ -98,7 +97,7 @@ export class SpanErrorBanner extends LitElement { return html`${this._errors.map( entry => html` @@ -117,3 +116,22 @@ export class SpanErrorBanner extends LitElement { } } } + +declare global { + interface HTMLElementTagNameMap { + "span-error-banner": SpanErrorBanner; + } +} + +// Guarded registration: card and panel bundles both import this module, and +// each bundle carries its own SpanErrorBanner class. Using @customElement +// throws when the second bundle loads because the tag is already taken, and +// that throw aborts the bundle's module execution before `@customElement("span-panel")` +// ever runs — leaving an un-upgraded HTMLElement (blank view). +try { + if (!customElements.get("span-error-banner")) { + customElements.define("span-error-banner", SpanErrorBanner); + } +} catch { + // Scoped custom element registry may throw on duplicate registration after upgrade +} diff --git a/src/core/favorites-store.ts b/src/core/favorites-store.ts index 3d5bcda..fb26746 100644 --- a/src/core/favorites-store.ts +++ b/src/core/favorites-store.ts @@ -46,8 +46,10 @@ export async function fetchFavorites(hass: HomeAssistant): Promise /** * Mark the entity (a circuit current/power sensor or any sub-device * sensor) as a favorite. The backend resolves the entity_id to its - * panel + (circuit_uuid | sub_device_id) tuple, so callers never need - * to know storage shapes or internal identifiers. + * panel + (circuit_uuid | sub_device_id) tuple based on the entity's + * device attachment in the registry — entities on a sub-device become + * sub-device favorites; entities on the main panel device become + * circuit favorites. */ export async function addFavorite(hass: HomeAssistant, entityId: string): Promise { const resp = await _callFavoritesService(hass, "add_favorite", { @@ -59,7 +61,7 @@ export async function addFavorite(hass: HomeAssistant, entityId: string): Promis /** * Remove the entity from the favorites map. See ``addFavorite`` for the - * reasoning behind the entity_id API. + * resolution rule. */ export async function removeFavorite(hass: HomeAssistant, entityId: string): Promise { const resp = await _callFavoritesService(hass, "remove_favorite", { diff --git a/src/core/grid-renderer.ts b/src/core/grid-renderer.ts index 7d80d06..d1dd535 100644 --- a/src/core/grid-renderer.ts +++ b/src/core/grid-renderer.ts @@ -3,8 +3,8 @@ import { formatPowerSigned, formatPowerUnit } from "../helpers/format.js"; import { t } from "../i18n.js"; import { tabToRow, tabToCol, classifyDualTab } from "../helpers/layout.js"; import { getChartMetric } from "../helpers/chart.js"; -import { DEVICE_TYPE_PV, RELAY_STATE_CLOSED, SHEDDING_PRIORITIES, MONITORING_COLORS } from "../constants.js"; -import { getCircuitMonitoringInfo, hasCustomOverrides } from "./monitoring-status.js"; +import { DEVICE_TYPE_PV, RELAY_STATE_CLOSED, SHEDDING_PRIORITIES } from "../constants.js"; +import { getCircuitMonitoringInfo } from "./monitoring-status.js"; import { getCircuitStateClasses } from "./circuit-state.js"; import type { PanelTopology, Circuit, HomeAssistant, CardConfig, MonitoringStatus, MonitoringPointInfo, SheddingPriorityDef } from "../types.js"; @@ -79,8 +79,8 @@ export function buildGridHTML( if (leftEntry && leftEntry.layout === "row-span") { const { monInfo, sheddingPriority } = lookupMonitoring(leftEntry); - gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2 / 4", "row-span", hass, config, monInfo, sheddingPriority); - gridHTML += `
${rightTab}
`; + gridHTML += renderCircuitSlot(leftEntry.uuid, leftEntry.circuit, row, "2 / 5", "row-span", hass, config, monInfo, sheddingPriority); + gridHTML += `
${rightTab}
`; continue; } @@ -96,13 +96,13 @@ export function buildGridHTML( if (!rowsToSkipRight.has(row)) { if (rightEntry && (rightEntry.layout === "col-span" || rightEntry.layout === "single")) { const { monInfo, sheddingPriority } = lookupMonitoring(rightEntry); - gridHTML += renderCircuitSlot(rightEntry.uuid, rightEntry.circuit, row, "3", rightEntry.layout, hass, config, monInfo, sheddingPriority); + gridHTML += renderCircuitSlot(rightEntry.uuid, rightEntry.circuit, row, "4", rightEntry.layout, hass, config, monInfo, sheddingPriority); } else if (!occupiedTabs.has(rightTab)) { - gridHTML += renderEmptySlot(row, "3"); + gridHTML += renderEmptySlot(row, "4"); } } - gridHTML += `
${rightTab}
`; + gridHTML += `
${rightTab}
`; } return gridHTML; } @@ -165,29 +165,27 @@ export function renderCircuitSlot( if (shedInfo.icon2) { const safeIcon2 = escapeHtml(shedInfo.icon2); sheddingHTML = ` - - + + `; } else if (shedInfo.textLabel) { const safeTextLabel = escapeHtml(shedInfo.textLabel); sheddingHTML = ` - + ${safeTextLabel} `; } else { - sheddingHTML = ``; + title="${safeLabel}">`; } } // Gear icon - const hasOverridesFlag = monitoringInfo && hasCustomOverrides(monitoringInfo); - const gearColor = hasOverridesFlag ? MONITORING_COLORS.custom : "#555"; const gearHTML = ``; // Utilization — prefer monitoring data, fall back to live current / breaker rating diff --git a/src/core/header-renderer.ts b/src/core/header-renderer.ts index 43c3286..2755252 100644 --- a/src/core/header-renderer.ts +++ b/src/core/header-renderer.ts @@ -32,12 +32,12 @@ export function buildSheddingLegendHTML(): string { let icons: string; if (cfg.icon2) { const icon2 = escapeHtml(cfg.icon2); - icons = ``; + icons = ``; } else if (cfg.textLabel) { const textLabel = escapeHtml(cfg.textLabel); - icons = `${textLabel}`; + icons = `${textLabel}`; } else { - icons = ``; + icons = ``; } return `
${icons}${label}
`; }) @@ -157,14 +157,14 @@ export function buildHeaderHTML(topology: PanelTopology, config: CardConfig, opt

${panelName}

${serial} ${ showSwitches ? `
${escapeHtml(t("header.enable_switches"))}
- +
` : "" diff --git a/src/core/list-renderer.ts b/src/core/list-renderer.ts index bc1e35b..7e72e7f 100644 --- a/src/core/list-renderer.ts +++ b/src/core/list-renderer.ts @@ -2,8 +2,7 @@ import { escapeHtml } from "../helpers/sanitize.js"; import { formatPowerSigned, formatPowerUnit } from "../helpers/format.js"; import { t } from "../i18n.js"; import { getChartMetric } from "../helpers/chart.js"; -import { RELAY_STATE_CLOSED, SHEDDING_PRIORITIES, MONITORING_COLORS, DEVICE_TYPE_PV } from "../constants.js"; -import { hasCustomOverrides } from "./monitoring-status.js"; +import { RELAY_STATE_CLOSED, SHEDDING_PRIORITIES, DEVICE_TYPE_PV } from "../constants.js"; import { getCircuitStateClasses } from "./circuit-state.js"; import type { Circuit, HomeAssistant, CardConfig, MonitoringPointInfo, SheddingPriorityDef } from "../types.js"; @@ -17,7 +16,7 @@ export function buildSearchBarHTML(currentQuery: string = ""): string {
`; @@ -86,18 +85,18 @@ export function buildListRowHTML( SHEDDING_PRIORITIES.unknown ?? { icon: "mdi:help", color: "#999", label: () => "Unknown" }; if (shedInfo.icon2) { sheddingHTML = ` - - + + `; } else if (shedInfo.textLabel) { sheddingHTML = ` - + ${shedInfo.textLabel} `; } else { - sheddingHTML = ``; + title="${shedInfo.label()}">`; } } @@ -116,12 +115,10 @@ export function buildListRowHTML( } // Gear — matches the breaker-grid's gear so onGearClick handles it unchanged. - const hasOverridesFlag = monitoringInfo ? hasCustomOverrides(monitoringInfo) : false; - const gearColor = hasOverridesFlag ? MONITORING_COLORS.custom : "#555"; const gearHTML = ``; // Controllable circuits get a real toggle-pill arm-protected by the @@ -147,7 +144,7 @@ export function buildListRowHTML( ${gearHTML}
`; diff --git a/src/core/list-view-controller.ts b/src/core/list-view-controller.ts index 2ad0565..4cd5e9e 100644 --- a/src/core/list-view-controller.ts +++ b/src/core/list-view-controller.ts @@ -6,6 +6,7 @@ import { getChartMetric } from "../helpers/chart.js"; import { t } from "../i18n.js"; import { getCircuitMonitoringInfo } from "./monitoring-status.js"; import { buildSearchBarHTML, buildListRowHTML, buildExpandedChartHTML, buildAreaHeaderHTML } from "./list-renderer.js"; +import { observeFold } from "./truncation-fold.js"; import type { DashboardController } from "./dashboard-controller.js"; import type { HomeAssistant, PanelTopology, CardConfig, Circuit, MonitoringStatus } from "../types.js"; import type { ErrorStore } from "./error-store.js"; @@ -164,6 +165,14 @@ export class ListViewController { */ private _columns = 1; + /** + * Tear-down for the truncation-fold observer. Re-bound on every + * render so the previous render's observer is disconnected before + * a new one is attached — otherwise we'd accumulate observers on + * every search/sort/expand cycle. + */ + private _foldUnobserve: (() => void) | null = null; + constructor(ctrl: DashboardController) { this._ctrl = ctrl; } @@ -239,6 +248,7 @@ export class ListViewController { this._bindEvents(container); if (this._searchQuery) this._applyFilter(container); this._ctrl.updateDOM(container); + this._attachFoldObserver(container); } renderAreaView( @@ -310,6 +320,7 @@ export class ListViewController { this._bindEvents(container); if (this._searchQuery) this._applyFilter(container); this._ctrl.updateDOM(container); + this._attachFoldObserver(container); } updateCollapsedRows(root: Element | ShadowRoot, hass: HomeAssistant, topology: PanelTopology, config: CardConfig): void { @@ -499,12 +510,34 @@ export class ListViewController { this._container.removeEventListener("graph-settings-changed", this._graphSettingsHandler); } } + if (this._foldUnobserve) { + this._foldUnobserve(); + this._foldUnobserve = null; + } this._container = null; this._clickHandler = null; this._inputHandler = null; this._graphSettingsHandler = null; } + /** + * Attach the truncation-fold observer to the rendered list view. + * Each row's name is measured against its allotted width; when the + * ellipsis is engaged, the row receives ``.is-folded`` and the CSS + * grid in card-styles.ts swaps the layout to two rows. + */ + private _attachFoldObserver(container: HTMLElement): void { + if (this._foldUnobserve) { + this._foldUnobserve(); + this._foldUnobserve = null; + } + this._foldUnobserve = observeFold(container, { + rowSelector: ".list-row", + nameSelector: ".list-circuit-name", + foldClass: "is-folded", + }); + } + private _applyFilter(container: HTMLElement): void { const clearBtn = container.querySelector(".list-search-clear"); if (clearBtn) clearBtn.style.display = this._searchQuery ? "" : "none"; diff --git a/src/core/side-panel.ts b/src/core/side-panel.ts index 20fc023..ede1860 100644 --- a/src/core/side-panel.ts +++ b/src/core/side-panel.ts @@ -5,6 +5,7 @@ import { INTEGRATION_DOMAIN, SHEDDING_PRIORITIES, GRAPH_HORIZONS, DEFAULT_GRAPH_ import { t } from "../i18n.js"; import { addFavorite, removeFavorite } from "./favorites-store.js"; import { sortedCircuitsForSection } from "./favorites-sections.js"; +import type { SpanSwitch } from "./span-switch.js"; import type { HomeAssistant, PanelTopology, GraphSettings, CircuitEntities, CircuitGraphOverride, MonitoringPointInfo } from "../types.js"; import type { ErrorStore } from "./error-store.js"; @@ -108,12 +109,6 @@ interface FavoritesModeConfig { type SidePanelConfig = PanelModeConfig | CircuitModeConfig | SubDeviceModeConfig | FavoritesModeConfig; -// ── Custom element interface for ha-switch ─────────────────────────────── - -interface HaSwitchElement extends HTMLElement { - checked: boolean; -} - // ── Styles ──────────────────────────────────────────────────────────────── const STYLES = ` @@ -343,7 +338,7 @@ const STYLES = ` .fav-heart:hover:not(.active) { background: var(--secondary-background-color, #f5f5f5); } - .fav-heart ha-icon { + .fav-heart span-icon { --mdc-icon-size: 16px; } @@ -957,7 +952,7 @@ class SpanSidePanel extends HTMLElement { btn.setAttribute("aria-checked", String(isFavorite)); btn.setAttribute("aria-label", t("sidepanel.save_to_favorites")); - const icon = document.createElement("ha-icon"); + const icon = document.createElement("span-icon"); icon.setAttribute("icon", isFavorite ? "mdi:heart" : "mdi:heart-outline"); btn.appendChild(icon); @@ -1010,7 +1005,7 @@ class SpanSidePanel extends HTMLElement { * Build a Favorite section with a heart icon (filled = favorited, * outlined = not). Used in both the per-circuit and per-sub-device * side panels. A heart deliberately avoids the visual confusion of - * placing an ha-switch directly under the breaker relay switch. + * placing a span-switch directly under the breaker relay switch. */ private _appendFavoriteHeartSection(body: HTMLDivElement, entityId: string, isFavorite: boolean): void { const section = document.createElement("div"); @@ -1176,7 +1171,7 @@ class SpanSidePanel extends HTMLElement { label.className = "field-label"; label.textContent = t("sidepanel.breaker"); - const toggle = document.createElement("ha-switch") as HaSwitchElement; + const toggle = document.createElement("span-switch"); toggle.dataset.role = "relay-toggle"; const entityId = cfg.entities.switch; const currentState = this._hass?.states?.[entityId]?.state; @@ -1359,7 +1354,7 @@ class SpanSidePanel extends HTMLElement { sectionLabel.textContent = t("sidepanel.monitoring"); sectionLabel.style.margin = "0"; - const enableToggle = document.createElement("ha-switch") as HaSwitchElement; + const enableToggle = document.createElement("span-switch"); enableToggle.dataset.role = "monitoring-toggle"; const info = cfg.monitoringInfo; @@ -1579,7 +1574,7 @@ class SpanSidePanel extends HTMLElement { // Update relay toggle if (cfg.entities?.switch) { - const toggle = this.shadowRoot?.querySelector('[data-role="relay-toggle"]'); + const toggle = this.shadowRoot?.querySelector('[data-role="relay-toggle"]'); if (toggle) { const currentState = this._hass?.states?.[cfg.entities.switch]?.state; if (currentState === "on") { diff --git a/src/core/span-icon.ts b/src/core/span-icon.ts new file mode 100644 index 0000000..7072b05 --- /dev/null +++ b/src/core/span-icon.ts @@ -0,0 +1,127 @@ +import { LitElement, html, css, svg, nothing } from "lit"; +import { property } from "lit/decorators.js"; +import { + mdiAlert, + mdiAlertCircle, + mdiBattery, + mdiBatteryAlertVariantOutline, + mdiChevronDown, + mdiClose, + mdiCog, + mdiHeart, + mdiHeartOutline, + mdiHelp, + mdiHelpCircleOutline, + mdiHomeGroup, + mdiInformation, + mdiLock, + mdiLockOpen, + mdiMenu, + mdiMonitorEye, + mdiRouterWireless, + mdiSortDescending, + mdiTransmissionTower, + mdiViewDashboard, +} from "@mdi/js"; + +/** + * Curated map of MDI tokens used by this card. Adding a new icon here + * is the only step required to use it as . + * + * Vendoring icons via @mdi/js (rather than reaching for HA's ) + * means the bundle only carries the path strings we actually reference. + * Keeping the map small and explicit also catches typos at review time — + * an unknown name renders nothing and warns once instead of silently + * shipping a generic placeholder. + */ +const MDI_PATHS: Readonly> = Object.freeze({ + "mdi:alert": mdiAlert, + "mdi:alert-circle": mdiAlertCircle, + "mdi:battery": mdiBattery, + "mdi:battery-alert-variant-outline": mdiBatteryAlertVariantOutline, + "mdi:chevron-down": mdiChevronDown, + "mdi:close": mdiClose, + "mdi:cog": mdiCog, + "mdi:heart": mdiHeart, + "mdi:heart-outline": mdiHeartOutline, + "mdi:help": mdiHelp, + "mdi:help-circle-outline": mdiHelpCircleOutline, + "mdi:home-group": mdiHomeGroup, + "mdi:information": mdiInformation, + "mdi:lock": mdiLock, + "mdi:lock-open": mdiLockOpen, + "mdi:menu": mdiMenu, + "mdi:monitor-eye": mdiMonitorEye, + "mdi:router-wireless": mdiRouterWireless, + "mdi:sort-descending": mdiSortDescending, + "mdi:transmission-tower": mdiTransmissionTower, + "mdi:view-dashboard": mdiViewDashboard, +}); + +const _warned = new Set(); +function warnOnce(name: string): void { + if (_warned.has(name)) return; + _warned.add(name); + console.warn(`SPAN: unknown icon "${name}". Add it to MDI_PATHS in span-icon.ts.`); +} + +/** + * Drop-in replacement for HA's . Renders an inline SVG path + * looked up from a small curated map of @mdi/js constants. + * + * Honors --mdc-icon-size so existing per-site sizing rules in + * card-styles.ts continue to work without modification. The element is + * display: inline-flex so it aligns with adjacent text the same way + * does inside flex rows. + */ +export class SpanIcon extends LitElement { + @property({ type: String }) icon = ""; + + static override styles = css` + :host { + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--mdc-icon-size, 24px); + height: var(--mdc-icon-size, 24px); + vertical-align: middle; + color: inherit; + flex-shrink: 0; + } + svg { + width: 100%; + height: 100%; + display: block; + fill: currentColor; + } + `; + + protected override render(): unknown { + if (!this.icon) return nothing; + const path = MDI_PATHS[this.icon]; + if (!path) { + warnOnce(this.icon); + return nothing; + } + return html``; + } +} + +declare global { + interface HTMLElementTagNameMap { + "span-icon": SpanIcon; + } +} + +// Guarded registration: both span-panel.js and span-panel-card.js are +// loaded onto the same document when a user has the Lovelace card on +// a dashboard alongside the integration's HA panel. The second bundle +// to import this module would otherwise throw NotSupportedError on the +// duplicate define, which would break whichever view loaded second. +try { + if (!customElements.get("span-icon")) { + customElements.define("span-icon", SpanIcon); + } +} catch { + // Scoped custom element registry may throw on duplicate registration after upgrade +} diff --git a/src/core/span-switch.ts b/src/core/span-switch.ts new file mode 100644 index 0000000..6925fac --- /dev/null +++ b/src/core/span-switch.ts @@ -0,0 +1,135 @@ +import { LitElement, html, css } from "lit"; +import { property } from "lit/decorators.js"; + +/** + * Drop-in replacement for HA's . Track + thumb toggle that + * mirrors HA's visual conventions (rounded pill, animated thumb, primary + * accent on the on-state, muted track on off) so existing call sites in + * side-panel.ts get the same look. + * + * Properties: + * checked – on/off state (reflects to attribute for [checked] CSS) + * disabled – greys the control and blocks pointer events + * + * Fires a native `change` event with `bubbles: true, composed: true` so + * shadow-root listeners and the existing addEventListener("change", ...) + * handlers in side-panel.ts continue to work unchanged. + */ +export class SpanSwitch extends LitElement { + @property({ type: Boolean, reflect: true }) checked = false; + @property({ type: Boolean, reflect: true }) disabled = false; + + static override styles = css` + :host { + --span-switch-on: var(--switch-checked-color, var(--primary-color, #4dd9af)); + --span-switch-off: var(--switch-unchecked-track-color, rgba(120, 120, 120, 0.45)); + --span-switch-thumb-on: var(--switch-checked-button-color, #fff); + --span-switch-thumb-off: var(--switch-unchecked-button-color, #fafafa); + --span-switch-track-w: 36px; + --span-switch-track-h: 20px; + --span-switch-thumb: 14px; + display: inline-flex; + align-items: center; + vertical-align: middle; + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: transparent; + } + :host([disabled]) { + cursor: not-allowed; + opacity: 0.5; + } + .track { + position: relative; + width: var(--span-switch-track-w); + height: var(--span-switch-track-h); + border-radius: calc(var(--span-switch-track-h) / 2); + background: var(--span-switch-off); + transition: background 0.2s ease; + } + .thumb { + position: absolute; + top: 50%; + left: 3px; + width: var(--span-switch-thumb); + height: var(--span-switch-thumb); + border-radius: 50%; + background: var(--span-switch-thumb-off); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); + transform: translateY(-50%); + transition: + left 0.2s ease, + background 0.2s ease; + } + :host([checked]) .track { + background: var(--span-switch-on); + } + :host([checked]) .thumb { + left: calc(var(--span-switch-track-w) - var(--span-switch-thumb) - 3px); + background: var(--span-switch-thumb-on); + } + :host(:focus-visible) .track { + outline: 2px solid var(--span-switch-on); + outline-offset: 2px; + } + `; + + connectedCallback(): void { + super.connectedCallback(); + if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0"); + if (!this.hasAttribute("role")) this.setAttribute("role", "switch"); + this.setAttribute("aria-checked", String(this.checked)); + this.addEventListener("click", this._onActivate); + this.addEventListener("keydown", this._onKeydown); + } + + disconnectedCallback(): void { + this.removeEventListener("click", this._onActivate); + this.removeEventListener("keydown", this._onKeydown); + super.disconnectedCallback(); + } + + protected override updated(changed: Map): void { + if (changed.has("checked")) this.setAttribute("aria-checked", String(this.checked)); + if (changed.has("disabled")) this.setAttribute("aria-disabled", String(this.disabled)); + } + + protected override render(): unknown { + return html` +
+
+
+ `; + } + + private _onActivate = (ev: Event): void => { + if (this.disabled) { + ev.preventDefault(); + ev.stopPropagation(); + return; + } + this.checked = !this.checked; + this.dispatchEvent(new Event("change", { bubbles: true, composed: true })); + }; + + private _onKeydown = (ev: KeyboardEvent): void => { + if (ev.key !== " " && ev.key !== "Enter") return; + ev.preventDefault(); + this._onActivate(ev); + }; +} + +declare global { + interface HTMLElementTagNameMap { + "span-switch": SpanSwitch; + } +} + +// Guarded registration: see span-icon.ts for the rationale. +try { + if (!customElements.get("span-switch")) { + customElements.define("span-switch", SpanSwitch); + } +} catch { + // Scoped custom element registry may throw on duplicate registration after upgrade +} diff --git a/src/core/sub-device-renderer.ts b/src/core/sub-device-renderer.ts index b79b907..8e440e0 100644 --- a/src/core/sub-device-renderer.ts +++ b/src/core/sub-device-renderer.ts @@ -67,7 +67,7 @@ export function buildSubDevicesHTML(topology: PanelTopology, hass: HomeAssistant ${escapeHtml(sub.name || "")} ${powerEid ? `${formatPowerSigned(powerW)} ${formatPowerUnit(powerW)}` : ""} ${chartsHTML} diff --git a/src/core/tab-bar-renderer.ts b/src/core/tab-bar-renderer.ts index 9143f2e..8a13143 100644 --- a/src/core/tab-bar-renderer.ts +++ b/src/core/tab-bar-renderer.ts @@ -15,7 +15,7 @@ export function buildTabBarHTML(tabs: TabDef[], activeTab: string, style: "text" const activeClass = tab.id === activeTab ? " active" : ""; const safeId = escapeHtml(tab.id); if (style === "icon") { - return ``; + return ``; } return ``; }) diff --git a/src/core/truncation-fold.ts b/src/core/truncation-fold.ts new file mode 100644 index 0000000..475f458 --- /dev/null +++ b/src/core/truncation-fold.ts @@ -0,0 +1,180 @@ +/** + * Container-uniform truncation-driven fold. + * + * Pixel-based container queries cannot fold "just as the name starts to + * truncate" because name length varies per circuit ("Spa" vs + * "Commissioned PV System"). Per-row decisions, however, leave the + * grid uneven: short-name rows stay one-line while long-name rows + * fold to two, scattering row heights across the view. + * + * Solution: measure each row's name, but apply the fold uniformly to + * every row in the container. The decision is a single boolean — "does + * the longest name fit?" — and every row gets the same layout. The + * grid stays visually even regardless of per-circuit name length. + * + * Hysteresis: once folded we record the row width that triggered it. + * The unfold side waits until rows grow past + * ``foldedAtWidth + hysteresisPx`` before lifting the class. Without + * this the layout would flip back as soon as it folded (folded layout + * gives the name a full row → no truncation → unfold check fires → + * back to one-line → truncation again → fold → loop). + */ + +export interface FoldConfig { + /** CSS selector for the row element that gets the fold class. */ + rowSelector: string; + /** CSS selector (relative to row) for the name element measured. */ + nameSelector: string; + /** Class to toggle on the row when folded. */ + foldClass: string; + /** Extra px the row must grow past the fold-trigger width before + * unfolding. Defaults to 48px — large enough to avoid oscillation + * for borderline-fitting names, where the unfolded layout consumes + * additional width on the chrome (badges, controls) that a folded + * row's measurement does not reflect. */ + hysteresisPx?: number; +} + +/** + * Watch every row inside ``container`` and toggle the fold class + * uniformly across all rows when any one of them would have its name + * truncated. Returns an ``unobserve`` function that tears down the + * ResizeObserver + MutationObserver — call it before the next render + * to avoid leaking observers across re-renders. + * + * One ResizeObserver instance is shared across rows; a MutationObserver + * on the container picks up rows added later (filter changes, + * expand/collapse, search). + */ +export function observeFold(container: HTMLElement, config: FoldConfig): () => void { + const hysteresis = config.hysteresisPx ?? 48; + const observed = new WeakSet(); + /** + * Width of a representative row when the fold was triggered. While + * folded we wait for that width to grow past the hysteresis margin + * before unfolding. ``null`` means currently unfolded. + */ + let foldedAtWidth: number | null = null; + + /** + * True when ``row``'s name is currently truncated under the + * unfolded layout. Two signals: + * - ``scrollWidth > clientWidth`` is the textbook ellipsis check + * and works whenever the name has any positive width. + * - ``clientWidth < 1`` covers the case where other flex children + * have squeezed the name's flex slot down to zero. By definition + * the name is then invisible and counts as truncated; ``scroll + * Width`` may also be 0 in that state which would make the first + * check miss. + */ + const isRowTruncated = (row: HTMLElement): boolean => { + const name = row.querySelector(config.nameSelector); + if (!name) return false; + if (row.clientWidth === 0) return false; + if (name.clientWidth < 1) return true; + return name.scrollWidth > name.clientWidth + 1; + }; + + /** + * Re-evaluate the container's fold state from the live row + * measurements. Called whenever any row resizes (ResizeObserver) or + * the row set changes (MutationObserver after a filter / sort / + * expand). + */ + const evaluate = (): void => { + const rows = container.querySelectorAll(config.rowSelector); + if (rows.length === 0) return; + // Use the first row as the size reference. Rows in the same + // container share width (single-column = full-width, grid = 1fr + // tracks of equal width), so any one is representative. + const refWidth = rows[0]!.clientWidth; + if (refWidth === 0) return; + + if (foldedAtWidth !== null) { + // Currently folded — only unfold once rows grow past the trigger + // width plus hysteresis. While folded the names have their own + // row each so per-row truncation checks are uninformative; the + // width comparison is the only reliable "wide enough now" signal. + if (refWidth > foldedAtWidth + hysteresis) { + for (const row of rows) row.classList.remove(config.foldClass); + foldedAtWidth = null; + } else { + // Stay folded — but make sure any row that arrived while we + // were already in the folded state (filter clears, area + // regroup, etc.) also wears the class. Without this, late + // additions render unfolded against an otherwise-folded grid + // and break the uniform-fold guarantee. + for (const row of rows) row.classList.add(config.foldClass); + } + return; + } + + // Currently unfolded — fold every row as soon as any one of them + // would truncate. Uniform fold keeps the grid visually even + // instead of scattering row heights based on per-circuit name + // length. + let anyTruncated = false; + for (const row of rows) { + if (isRowTruncated(row)) { + anyTruncated = true; + break; + } + } + if (anyTruncated) { + for (const row of rows) row.classList.add(config.foldClass); + foldedAtWidth = refWidth; + } + }; + + const ro = new ResizeObserver(() => evaluate()); + + const attachAll = (): void => { + const rows = container.querySelectorAll(config.rowSelector); + for (const row of rows) { + if (observed.has(row)) continue; + observed.add(row); + ro.observe(row); + } + // Always defer one rAF to land after layout, even if no new rows + // were observed: a row removal (e.g. search filter hides the + // longest-name row) can change whether the remaining rows still + // need the fold, and the MutationObserver path calls us for + // removals as well as additions. + requestAnimationFrame(() => evaluate()); + }; + + attachAll(); + + // Watch the subtree because rows can be added under a wrapper that + // persists across renders (e.g. an incremental row append into an + // existing .list-view rather than a full container.innerHTML reset). + // Today's render paths happen to mutate the container's direct child + // — but tying observer correctness to that invariant is fragile; any + // future incremental-row code would silently regress the fold. + // Filter mutations so row-internal churn (expand/collapse, chart + // remounts) doesn't trigger a needless attachAll pass. + const mutationTouchesRows = (mutations: MutationRecord[]): boolean => { + const touchesRows = (nodes: NodeList): boolean => { + for (const node of nodes) { + if (!(node instanceof Element)) continue; + if (node.matches(config.rowSelector)) return true; + if (node.querySelector(config.rowSelector)) return true; + } + return false; + }; + for (const m of mutations) { + if (touchesRows(m.addedNodes) || touchesRows(m.removedNodes)) return true; + } + return false; + }; + + const mo = new MutationObserver(mutations => { + if (mutationTouchesRows(mutations)) attachAll(); + }); + mo.observe(container, { childList: true, subtree: true }); + + return (): void => { + ro.disconnect(); + mo.disconnect(); + }; +} diff --git a/src/panel/favorites-summary.ts b/src/panel/favorites-summary.ts index 5cd0953..e5ba223 100644 --- a/src/panel/favorites-summary.ts +++ b/src/panel/favorites-summary.ts @@ -16,12 +16,12 @@ export function buildFavoritesSummaryHTML(isAmpsMode: boolean): string { return `
${escapeHtml(t("header.enable_switches"))}
- +
diff --git a/src/panel/index.ts b/src/panel/index.ts index e7ff528..379e5c0 100644 --- a/src/panel/index.ts +++ b/src/panel/index.ts @@ -1,38 +1,8 @@ import { CARD_VERSION } from "../constants.js"; -import { SpanPanelElement } from "./span-panel.js"; +import "./span-panel.js"; console.warn( `%c SPAN-PANEL %c v${CARD_VERSION} `, "background: var(--primary-color, #4dd9af); color: #000; font-weight: 700; padding: 2px 6px; border-radius: 4px 0 0 4px;", "background: var(--secondary-background-color, #333); color: var(--primary-text-color, #fff); padding: 2px 6px; border-radius: 0 4px 4px 0;" ); - -// HA removes after WS reconnect while the browser tab -// is backgrounded and does not re-create it — regardless of whether the -// element uses LitElement or vanilla HTMLElement. Track connection state -// and reload when the tab is restored with a missing panel. - -let _panelConnected = false; - -const origConnected = SpanPanelElement.prototype.connectedCallback; -SpanPanelElement.prototype.connectedCallback = function () { - _panelConnected = true; - origConnected.call(this); -}; - -const origDisconnected = SpanPanelElement.prototype.disconnectedCallback; -SpanPanelElement.prototype.disconnectedCallback = function () { - _panelConnected = false; - origDisconnected.call(this); -}; - -document.addEventListener("visibilitychange", () => { - if (document.visibilityState !== "visible") return; - if (_panelConnected) return; - if (!window.location.pathname.includes("span-panel")) return; - - setTimeout(() => { - if (_panelConnected) return; - location.reload(); - }, 200); -}); diff --git a/src/panel/span-menu-button.ts b/src/panel/span-menu-button.ts new file mode 100644 index 0000000..a47977a --- /dev/null +++ b/src/panel/span-menu-button.ts @@ -0,0 +1,88 @@ +import { LitElement, html, css, svg } from "lit"; +import { property } from "lit/decorators.js"; +import { mdiMenu } from "@mdi/js"; + +/** + * Replacement for HA's . Renders a hamburger icon that + * fires the documented `hass-toggle-menu` custom event on click — the + * same event HA's drawer listens for. The event API is part of HA's + * panel contract (panels live inside the drawer-aware shell), not a + * component API, so it stays stable across the frontend component + * migrations the dev blog flagged. + * + * Mirrors HA's narrow-only display: hidden on wider viewports where the + * drawer is permanent, visible on narrow viewports where the drawer is + * collapsed behind the hamburger. The `narrow` property reflects to an + * attribute so CSS can drive visibility without re-rendering. + */ +export class SpanMenuButton extends LitElement { + @property({ type: Boolean, reflect: true }) narrow = false; + + static override styles = css` + :host { + display: none; + align-items: center; + justify-content: center; + } + :host([narrow]) { + display: inline-flex; + } + button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + padding: 0; + background: transparent; + border: none; + border-radius: 50%; + color: inherit; + cursor: pointer; + transition: background 0.15s ease; + -webkit-tap-highlight-color: transparent; + } + button:hover, + button:focus-visible { + background: color-mix(in srgb, currentColor 12%, transparent); + outline: none; + } + svg { + width: 24px; + height: 24px; + fill: currentColor; + } + `; + + protected override render(): unknown { + return html` + + `; + } + + private _toggle = (): void => { + this.dispatchEvent( + new CustomEvent("hass-toggle-menu", { + bubbles: true, + composed: true, + }) + ); + }; +} + +declare global { + interface HTMLElementTagNameMap { + "span-menu-button": SpanMenuButton; + } +} + +// Guarded registration: see span-icon.ts for the rationale. +try { + if (!customElements.get("span-menu-button")) { + customElements.define("span-menu-button", SpanMenuButton); + } +} catch { + // Scoped custom element registry may throw on duplicate registration after upgrade +} diff --git a/src/panel/span-panel.ts b/src/panel/span-panel.ts index daa3443..3d2f871 100644 --- a/src/panel/span-panel.ts +++ b/src/panel/span-panel.ts @@ -6,6 +6,9 @@ import { setLanguage, t } from "../i18n.js"; import { ErrorStore } from "../core/error-store.js"; import "../core/side-panel.js"; import "../core/error-banner.js"; +import "../core/span-icon.js"; +import "../core/span-switch.js"; +import "./span-menu-button.js"; import { DashboardTab } from "./tab-dashboard.js"; import { MonitoringTab } from "./tab-monitoring.js"; import { ListViewController, type FavoritesViewStateDetail } from "../core/list-view-controller.js"; @@ -89,7 +92,6 @@ export class SpanPanelElement extends LitElement { private _areaUnsub: (() => void) | null = null; /** Cleared on disconnect to tell a pending subscribe to self-cancel. */ private _areaSubscribing = false; - private _onVisibilityChange: (() => void) | null = null; private _onFavoritesChanged: (() => void) | null = null; private _deviceRegistryUnsub: Promise<() => void> | null = null; /** @@ -100,12 +102,6 @@ export class SpanPanelElement extends LitElement { * column count, horizon edits) the user made inside the sidebar. */ private _pendingTabRender = false; - /** - * Pending retry timer for ``_recoverIfNeeded``. Cleared on disconnect - * so a delayed retry cannot fire against a detached element after HA - * has torn down the panel. - */ - private _recoverTimer: ReturnType | null = null; private static _shellStyles = css` :host { @@ -234,12 +230,6 @@ export class SpanPanelElement extends LitElement { this._favCache.errorStore = this._errorStore; this._monitoringTab.errorStore = this._errorStore; - this._onVisibilityChange = (): void => { - if (document.visibilityState !== "visible" || !this._discovered || !this.hass) return; - this._recoverIfNeeded(); - }; - document.addEventListener("visibilitychange", this._onVisibilityChange); - this._onFavoritesChanged = (): void => { this._refreshFavorites(); }; @@ -260,10 +250,6 @@ export class SpanPanelElement extends LitElement { this._areaUnsub(); this._areaUnsub = null; } - if (this._onVisibilityChange) { - document.removeEventListener("visibilitychange", this._onVisibilityChange); - this._onVisibilityChange = null; - } if (this._onFavoritesChanged) { document.removeEventListener(FAVORITES_CHANGED_EVENT, this._onFavoritesChanged); this._onFavoritesChanged = null; @@ -273,10 +259,6 @@ export class SpanPanelElement extends LitElement { clearTimeout(this._persistFavoritesViewStateTimer); this._persistFavoritesViewStateTimer = null; } - if (this._recoverTimer) { - clearTimeout(this._recoverTimer); - this._recoverTimer = null; - } this._errorStore.dispose(); super.disconnectedCallback(); } @@ -376,10 +358,11 @@ export class SpanPanelElement extends LitElement { protected render(): unknown { setLanguage(this.hass?.language); - // ``ha-menu-button`` reads ``this.hass.kioskMode`` in its willUpdate; - // creating it before HA has assigned ``hass`` throws. Render a bare - // shell until hass arrives — the ``hass`` setter will request a new - // render via Lit reactivity once HA injects the property. + // Render a bare shell until HA has injected ``hass``; downstream + // template fragments (panel selector, tab bar, error banner, side + // panels) all read from ``this.hass`` and would throw otherwise. + // The ``hass`` setter triggers a Lit reactivity pass once HA delivers + // the property, which re-renders into the discovered/loaded state. if (!this.hass) { return html`
@@ -398,7 +381,7 @@ export class SpanPanelElement extends LitElement { return html`
- +
Span Panel
@@ -412,7 +395,7 @@ export class SpanPanelElement extends LitElement { return html`
- +