From 551303a8c4ebde5690fe31d20cfeaafa8cbe53c9 Mon Sep 17 00:00:00 2001
From: cayossarian <23534755+cayossarian@users.noreply.github.com>
Date: Wed, 7 Oct 2026 15:56:55 -0700
Subject: [PATCH] chore(release): 0.9.5
---
dist/span-panel-card.js | 2 +-
dist/span-panel.js | 2 +-
package-lock.json | 4 ++--
package.json | 2 +-
src/constants.ts | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js
index 1660571..8f4714c 100644
--- a/dist/span-panel-card.js
+++ b/dist/span-panel-card.js
@@ -227,4 +227,4 @@ var vs={},ms={};var ys,_s=function(){function t(t,e,n){var i=this;this._sleepAft
${i("card.no_device")}
- `}};function WT(t,e){return ze(Object.entries(t)).filter(([,t])=>t.type===e).map(([t,e])=>{const n=De(e),i=e.name??"",r=Object.entries(e.entities??{}).filter(([t])=>!n.has(t)).map(([t,e])=>{let n=e.original_name??t;return i&&n.startsWith(i+" ")&&(n=n.slice(i.length+1)),{entityId:t,label:n}});return{devId:t,name:i||t,entities:r}})}VT.styles=T('\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 /* A switch Home Assistant reports unavailable: the relay state is still\n shown, but the pill cannot be operated. */\n .toggle-pill.toggle-unavailable {\n opacity: 0.4;\n pointer-events: none;\n cursor: default;\n }\n /* Locked switches dim every pill to 0.3; an inert one stays dimmer still. */\n .switches-disabled .toggle-pill.toggle-unavailable {\n opacity: 0.2;\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 .sub-identity { font-size: 0.85em; color: var(--secondary-text-color, #999); margin: 2px 0 6px; }\n .sub-note { font-size: 0.85em; color: var(--secondary-text-color, #999); font-style: italic; margin-top: 4px; }\n .sub-power-caption { font-size: 0.75em; color: var(--secondary-text-color, #999); margin-right: 6px; text-transform: uppercase; }\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})],VT.prototype,"hass",void 0),x([Nt()],VT.prototype,"_config",void 0),x([Nt()],VT.prototype,"_discovered",void 0),x([Nt()],VT.prototype,"_discovering",void 0),x([Nt()],VT.prototype,"_topology",void 0),x([Nt()],VT.prototype,"_activeTab",void 0),VT=x([(t=>(e,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(t,e)}):customElements.define(t,e)})("span-panel-card")],VT);class UT 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,d=parseInt(String(this._config.history_hours))||0,h=parseInt(String(this._config.history_minutes))||0,p=c(u,"0","30",i("editor.days")),f=c(d,"0","23",i("editor.hours")),g=c(h,"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"},{key:"show_solar",label:i("editor.solar_pv"),subDeviceType:"pv"}];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)}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities??{},n="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;";for(const[r,o]of Object.entries(this._entityContainers)){o.innerHTML="";for(const a of WT(t,r)){const t=document.createElement("div");if(t.textContent=a.name,t.style.cssText="font-size: 0.85em; font-weight: 500; color: var(--secondary-text-color); margin: 8px 0 4px;",o.appendChild(t),0===a.entities.length){const t=document.createElement("div");t.textContent=i("editor.no_entities"),t.style.cssText=n,o.appendChild(t);continue}for(const{entityId:t,label:i}of a.entities){const r=document.createElement("div");r.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[t],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=i,s.style.cssText=n,r.appendChild(a),r.appendChild(s),o.appendChild(r),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities??{}};a.checked?e[t]=!0:delete e[t],this._config={...this._config,visible_sub_entities:e},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",UT)}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;");
+ `}};function WT(t,e){return ze(Object.entries(t)).filter(([,t])=>t.type===e).map(([t,e])=>{const n=De(e),i=e.name??"",r=Object.entries(e.entities??{}).filter(([t])=>!n.has(t)).map(([t,e])=>{let n=e.original_name??t;return i&&n.startsWith(i+" ")&&(n=n.slice(i.length+1)),{entityId:t,label:n}});return{devId:t,name:i||t,entities:r}})}VT.styles=T('\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 /* A switch Home Assistant reports unavailable: the relay state is still\n shown, but the pill cannot be operated. */\n .toggle-pill.toggle-unavailable {\n opacity: 0.4;\n pointer-events: none;\n cursor: default;\n }\n /* Locked switches dim every pill to 0.3; an inert one stays dimmer still. */\n .switches-disabled .toggle-pill.toggle-unavailable {\n opacity: 0.2;\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 .sub-identity { font-size: 0.85em; color: var(--secondary-text-color, #999); margin: 2px 0 6px; }\n .sub-note { font-size: 0.85em; color: var(--secondary-text-color, #999); font-style: italic; margin-top: 4px; }\n .sub-power-caption { font-size: 0.75em; color: var(--secondary-text-color, #999); margin-right: 6px; text-transform: uppercase; }\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})],VT.prototype,"hass",void 0),x([Nt()],VT.prototype,"_config",void 0),x([Nt()],VT.prototype,"_discovered",void 0),x([Nt()],VT.prototype,"_discovering",void 0),x([Nt()],VT.prototype,"_topology",void 0),x([Nt()],VT.prototype,"_activeTab",void 0),VT=x([(t=>(e,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(t,e)}):customElements.define(t,e)})("span-panel-card")],VT);class UT 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,d=parseInt(String(this._config.history_hours))||0,h=parseInt(String(this._config.history_minutes))||0,p=c(u,"0","30",i("editor.days")),f=c(d,"0","23",i("editor.hours")),g=c(h,"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"},{key:"show_solar",label:i("editor.solar_pv"),subDeviceType:"pv"}];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)}_populateEntityCheckboxes(t){const e=this._config.visible_sub_entities??{},n="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;";for(const[r,o]of Object.entries(this._entityContainers)){o.innerHTML="";for(const a of WT(t,r)){const t=document.createElement("div");if(t.textContent=a.name,t.style.cssText="font-size: 0.85em; font-weight: 500; color: var(--secondary-text-color); margin: 8px 0 4px;",o.appendChild(t),0===a.entities.length){const t=document.createElement("div");t.textContent=i("editor.no_entities"),t.style.cssText=n,o.appendChild(t);continue}for(const{entityId:t,label:i}of a.entities){const r=document.createElement("div");r.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[t],a.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const s=document.createElement("span");s.textContent=i,s.style.cssText=n,r.appendChild(a),r.appendChild(s),o.appendChild(r),a.addEventListener("change",()=>{const e={...this._config.visible_sub_entities??{}};a.checked?e[t]=!0:delete e[t],this._config={...this._config,visible_sub_entities:e},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",UT)}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.5 ","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 132476c..f02f763 100644
--- a/dist/span-panel.js
+++ b/dist/span-panel.js
@@ -381,4 +381,4 @@ var $s={},Rs={};var Hs,Fs=function(){function t(t,e,n){var i=this;this._sleepAft
opacity: 1;
border-bottom-color: var(--app-header-text-color, white);
}
- `,OI.styles=[EI._shellStyles,T(zM)],b([zt({attribute:!1})],OI.prototype,"hass",void 0),b([zt({type:Boolean,reflect:!0})],OI.prototype,"narrow",void 0),b([Nt()],OI.prototype,"_panels",void 0),b([Nt()],OI.prototype,"_selectedPanelId",void 0),b([Nt()],OI.prototype,"_activeTab",void 0),b([Nt()],OI.prototype,"_discovered",void 0),b([Nt()],OI.prototype,"_chartMetric",void 0),b([Nt()],OI.prototype,"_listColumns",void 0),b([Nt()],OI.prototype,"_favorites",void 0),OI=EI=b([(t=>(e,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(t,e)}):customElements.define(t,e)})("span-panel")],OI),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;");
+ `,OI.styles=[EI._shellStyles,T(zM)],b([zt({attribute:!1})],OI.prototype,"hass",void 0),b([zt({type:Boolean,reflect:!0})],OI.prototype,"narrow",void 0),b([Nt()],OI.prototype,"_panels",void 0),b([Nt()],OI.prototype,"_selectedPanelId",void 0),b([Nt()],OI.prototype,"_activeTab",void 0),b([Nt()],OI.prototype,"_discovered",void 0),b([Nt()],OI.prototype,"_chartMetric",void 0),b([Nt()],OI.prototype,"_listColumns",void 0),b([Nt()],OI.prototype,"_favorites",void 0),OI=EI=b([(t=>(e,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(t,e)}):customElements.define(t,e)})("span-panel")],OI),console.warn("%c SPAN-PANEL %c v0.9.5 ","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 d12c990..00546aa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "span-panel-card",
- "version": "0.9.4",
+ "version": "0.9.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "span-panel-card",
- "version": "0.9.4",
+ "version": "0.9.5",
"dependencies": {
"@mdi/js": "^7.4.47",
"echarts": "^6.0.0",
diff --git a/package.json b/package.json
index 3c72b4b..2410ad1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "span-panel-card",
- "version": "0.9.4",
+ "version": "0.9.5",
"private": true,
"type": "module",
"scripts": {
diff --git a/src/constants.ts b/src/constants.ts
index 035f0a3..9ec512e 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,7 +1,7 @@
import { t } from "./i18n.js";
import type { ChartMetricDef, GraphHorizonPreset, SheddingPriorityDef } from "./types.js";
-export const CARD_VERSION = "0.9.4";
+export const CARD_VERSION = "0.9.5";
// -- Defaults --