From ae88740f92b124aa20bacd71ec5728863655e21c Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:20:30 -0700 Subject: [PATCH 1/6] fix(panel): retry tab render on visibility restore when render bails silently Restores _recoverIfNeeded helper removed during the LitElement refactor (c4154d2). The visibilitychange handler now wraps _scheduleTabRender in try/catch and verifies #tab-content received content; on thrown error or zero child nodes, it retries with 2s/4s/6s backoff up to 3 attempts. The Favorites render path clears its container before awaiting several async build steps (FavoritesController.build, fetchAndBuildHorizonMaps, fetchMergedMonitoringStatus). When the browser tab is backgrounded and HA's WebSocket drops, one of those resolves empty or null without throwing, leaving the container blank with no console error and no mechanism to retry. By Panel survives because its simpler dashboard render produces an error message on failure instead of a silent bail. --- dist/span-panel.js | 4 +-- src/panel/span-panel.ts | 64 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/dist/span-panel.js b/dist/span-panel.js index c5e5b8b..2b562cb 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -102,7 +102,7 @@ const Ee=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)} .retry-btn:hover { opacity: 0.8; } - `,m([Me()],Xe.prototype,"_errors",void 0),Xe=m([Ee("span-error-banner")],Xe);const it=g.power;function st(e){return it.unit(e)}function ot(e){return(e<0?"-":"")+it.format(e)}function rt(e){return(Math.abs(e)/1e3).toFixed(1)}function at(e){return Math.ceil(e/2)}function lt(e){return e%2==0?1:0}function ct(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return at(t)===at(n)?"row-span":lt(t)===lt(n)?"col-span":"row-span"}function dt(e){const t=e.chart_metric??s;return g[t]??g[s]}function ht(e,t){const n=function(e){return dt(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class pt{constructor(){this._status=null,this._lastFetch=0,this._inflight=null,this._generation=0,this._errorStore=null,this._retry=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}async fetch(e,t){const i=Date.now();if(this._inflight&&this._inflight.gen===this._generation)return this._inflight.promise;if(this._status&&i-this._lastFetch<3e4)return this._status;const s=this._generation,o=(async()=>{try{const i={};t&&(i.config_entry_id=t);const o={type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0},r=this._retry?await this._retry.callWS(e,o,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await e.callWS(o),l=r?.response??null;return s===this._generation&&(this._status=l,this._lastFetch=Date.now()),l}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:n("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 ut{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 pt,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 gt(e,t){return e?.circuits?e.circuits[t]??null:null}function _t(e){return!!e&&void 0!==e.continuous_threshold_pct}function ft(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"),_t(t)&&s.push("circuit-custom-monitoring"),s.join(" ")}function vt(e,t,i,s,o,r,a,d,h,p=!1){const u=t.entities?.power,g=u?r.states[u]:null,_=g&&parseFloat(g.state)||0,m=t.device_type===c||_<0,b=t.entities?.switch,y=b?r.states[b]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===l,x=t.breaker_rating_a,S=x?`${Math.round(x)}A`:"",$=Oe(t.name||n("grid.unknown")),C=dt(a);let P;if("current"===C.entityRole){const e=t.entities?.current,n=e?r.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${C.format(i)}A`}else P=`${ot(_)}${st(_)}`;const k=h||"unknown";let E="";if("unknown"!==k){const e=f[k]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"},t=Oe(e.label()),n=Oe(e.icon),i=Oe(e.color);if(e.icon2){E=`\n \n \n `}else if(e.textLabel){E=`\n \n ${Oe(e.textLabel)}\n `}else E=``}const z=d&&_t(d)?v:"#555",A=``;let N="",M=d?.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 ${$}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${E}\n ${A}\n
\n
\n
\n `}function mt(e,t){return`\n
\n \n
\n `}const bt={names:["power","battery power"],suffixes:["_power"]},yt={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},wt={names:["state of energy"],suffixes:["_soe_kwh"]},xt={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function St(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 $t(e){return St(e,bt)}function Ct(e){return St(e,yt)}function Pt(e){return St(e,wt)}function kt(e){return St(e,xt)}function Et(e,t,i){const s=!1!==i.show_battery,o=!1!==i.show_evse;if(!e.sub_devices)return"";const r=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===d&&!s)&&!(e.type===h&&!o));if(0===r.length)return"";const a=r.filter(([,e])=>e.type===h).length;let l=0,c="";for(const[e,s]of r){const o=s.type===h?n("subdevice.ev_charger"):s.type===d?n("subdevice.battery"):n("subdevice.fallback"),r=$t(s),p=r?t.states[r]:void 0,u=p&&parseFloat(p.state)||0,g=s.type===d,_=s.type===h,f=g?Ct(s):null,v=g?Pt(s):null,m=g?kt(s):null,b=zt(s,t,i,new Set([r,f,v,m].filter(e=>null!==e))),y=At(e,s,g,r,f,v);let w="";g?w="sub-device-bess":_&&(l++,l===a&&a%2==1&&(w="sub-device-full")),c+=`\n
\n
\n ${Oe(o)}\n ${Oe(s.name||"")}\n ${r?`${ot(u)} ${st(u)}`:""}\n \n
\n ${y}\n ${b}\n
\n `}return c}function zt(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 ${Oe(l)}:\n ${Oe(d)}\n
\n `}return o}function At(e,t,i,s,o,r){if(i){const t=[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!o},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!r},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!s}].filter(e=>e.available);return`\n
\n ${t.map(e=>`\n
\n
${Oe(e.title)}
\n
\n
\n `).join("")}\n
\n `}return s?`
`:""}function Nt(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 Mt(e){const t=r[e];return t?t.ms:r[o].ms}function It(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function Tt(e){return Math.max(500,Math.floor(e/5e3))}function Dt(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 Ft(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 Lt(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 Ht(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=It(i),l=Tt(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,Ft(t,a,l))}}}function Ot(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:$t(i)};i.type===d&&(e.soc=Ct(i),e.soe=Pt(i));for(const[i,s]of Object.entries(e))s&&t.push({entityId:s,key:`${p}${n}_${i}`,devId:n})}return t}async function Rt(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=ht(i,n);if(!t)continue;let o;o=s&&s.has(e)?Mt(s.get(e)):Nt(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 Ot(t)){let t;t=o&&o.has(s)?Mt(o.get(s)):Nt(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(Lt(e,n.entityIds,n.uuidByEntity,t,i)):a.push(Ht(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(a)}function qt(e,t,n,i,o,r,a,l,c){const{options:d,series:h}=function(e,t,n,i,o,r=!1){n||(n=g[s]);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}}],_=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};h?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),o&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),u.push({type:"line",data:[[d,.8*o],[c,.8*o]],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,o],[c,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const v={xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,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:v,series:u}}(n,i,o,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 _=e.clientHeight;u.height=(_>0?_:p)+"px",u.hass=t,u.options=d,u.data=h}function jt(e){return"function"==typeof globalThis.CSS?.escape?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function Ut(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=rt(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=rt(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=rt(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=rt(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 _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}function Wt(e,t,i,s,o,r){if(!e||!i||!t)return;const a=Nt(s);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],s=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(s))}!function(e,t,n,i,s){const o=e.querySelector(".panel-stats");o&&Ut(o,t,n,i,s)}(e,t,i,s,d);const h=dt(s),p="current"===h.entityRole;for(const[s,d]of Object.entries(i.circuits)){const i=e.querySelector(`.circuit-slot[data-uuid="${jt(s)}"]`);if(!i)continue;const u=d.entities?.power,g=u?t.states[u]:null,_=g&&parseFloat(g.state)||0,v=d.device_type===c||_<0,m=d.entities?.switch,b=m?t.states[m]:null,y=b?"on"===b.state:(g?.attributes?.relay_state||d.relay_state)===l,w=i.querySelector(".power-value");if(w)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${h.format(i)}A`}else w.innerHTML=`${ot(_)}${st(_)}`;const x=i.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=x.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",v),d.always_on)S="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const $=f[S]??f.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",$.icon),C.style.color=$.color,C.title=$.label());const P=i.querySelector(".shedding-icon-secondary");P&&($.icon2?(P.setAttribute("icon",$.icon2),P.style.color=$.color,P.style.display=""):P.style.display="none");const k=i.querySelector(".shedding-label");k&&($.textLabel?(k.textContent=$.textLabel,k.style.color=$.color,k.style.display=""):k.style.display="none");const E=i.querySelector(".chart-container");if(E){const e=o.get(s)||[],n=i.classList.contains("circuit-col-span")?200:100,l=r?.has(s)?Mt(r.get(s)):a,p=d.device_type===c;qt(E,t,e,l,h,v,n,d.breaker_rating_a??void 0,p)}}}class Gt{get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}constructor(){this._errorStore=null,this._retry=null,this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const s={type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0},o=this._retry?await this._retry.callWS(e,s,{errorId:"fetch:graph_settings",errorMessage:n("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:n("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 Vt(e,t){if(!e)return o;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??o}function Bt(e,t){if(!e)return o;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??o}class Qt{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new pt,this.monitoringMultiCache=new ut,this.graphSettingsCache=new Gt,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,Vt(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,Bt(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:n("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,Vt(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,Bt(i,s))}}async loadHistory(){await Rt(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)??o;if(!r[i]?.useRealtime)continue;const s=ht(n,this._config);if(!s)continue;const a=this._hass.states[s];if(!a)continue;const l=parseFloat(a.state);if(isNaN(l))continue;const c=Mt(i),d=It(c),h=Tt(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 Ot(this._topology))i.has(t)&&s.add(e);const o=new Map;try{await Rt(this._hass,this._topology,this._config,o,t,i);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:n("error.history_failed"),persistent:!1})}}updateDOM(e){this._hass&&this._topology&&this._config&&(Wt(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=Nt(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${jt(i)}"]`);if(!n)continue;const l=$t(a);if(l){const e=t.states[l],i=e&&parseFloat(e.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${ot(i)} ${st(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");qt(e,t,a,o?.has(i)?Mt(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="${jt(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 i=e.target,s=i?.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:n("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 r=i.dataset.uuid;if(r&&this._topology){const e=this._topology.circuits[r];if(e){const t=this._favRefs?.[r]??null,n=t&&"circuit"===t.kind?t.targetId:r,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??o,p=a?.circuits?.[n],u=p?{...p,globalHorizon:h}:{horizon:h,has_override:!1,globalHorizon:h},g=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,_=null!==t||(this._panelFavorites?.circuitUuids.has(n)??!1),f=this._inFavoritesView||null!==this._panelFavorites;return void s.open({...e,uuid:n,monitoringInfo:d,showMonitoring:this._showMonitoring,graphHorizonInfo:u,showFavorites:f,favoritePanelDeviceId:g,isFavorite:_,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 r;t?r=await this._fetchGraphSettingsFresh(i):(await this.graphSettingsCache.fetch(this._hass,i),r=this.graphSettingsCache.settings);const l=r?.global_horizon??o,c=r?.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 i={type:"call_service",domain:a,service:"get_graph_settings",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:graph_settings",errorMessage:n("error.graph_settings_failed")}):await this._hass.callWS(i);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 i={type:"call_service",domain:a,service:"get_monitoring_status",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await this._hass.callWS(i),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()}}const Kt='\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 }\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 .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 min-width: 70px;\n text-align: right;\n flex-shrink: 0;\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 /* ── 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';class Jt{constructor(){this._ctrl=new Qt,this._container=null,this._onGearClick=null,this._onToggleClick=null,this._onSidePanelClosed=null,this._onGraphSettingsChanged=null}get hass(){return this._ctrl.hass}set hass(e){this._ctrl.hass=e}set errorStore(e){this._ctrl.errorStore=e}setPanelFavorites(e){this._ctrl.setPanelFavorites(e)}async render(e,t,i,s,o){let r,a;this.stop(),this._ctrl.reset(),this._ctrl.showMonitoring=!0,this._container=e,this._ctrl.hass=t;try{const e=await Ye(t,i);r=e.topology,a=e.panelSize}catch(t){return void(e.innerHTML=`

${Oe(t.message)}

`)}this._ctrl.init(r,s,t,o??null),await this._ctrl.monitoringCache.fetch(t,o??null),await this._ctrl.fetchAndBuildHorizonMaps();const l=Math.ceil(a/2),c=this._ctrl.monitoringCache.status,d=nt(r,s),h=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),i=Object.values(e.mains??{}),s=[...t,...i],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 ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${o>0?`${o} ${n(o>1?"status.warnings":"status.warning")}`:""}\n ${r>0?`${r} ${n(r>1?"status.alerts":"status.alert")}`:""}\n ${a>0?`${a} ${n(a>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(c),p=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":ct(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=at(Math.max(...n));0===lt(e)?a.add(i):l.add(i)}function c(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=s?gt(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+=vt(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+=mt(e,"2"));else{const{monInfo:t,sheddingPriority:s}=c(h);d+=vt(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+=mt(e,"3"));else{const{monInfo:t,sheddingPriority:s}=c(p);d+=vt(p.uuid,p.circuit,e,"3",p.layout,n,i,t,s)}d+=`
${s}
`}return d}(r,l,t,s,c),u=Et(r,t,s);e.innerHTML=`\n \n ${d}\n ${h}\n ${u?`
${u}
`:""}\n ${!1!==s.show_panel?`\n
\n ${p}\n
\n `:""}\n \n `,this._onGearClick=t=>{this._ctrl.onGearClick(t,e)},this._onToggleClick=t=>{this._ctrl.onToggleClick(t,e)},e.addEventListener("click",this._onGearClick),e.addEventListener("click",this._onToggleClick),this._onSidePanelClosed=()=>{this._ctrl.monitoringCache.invalidate(),this._ctrl.graphSettingsCache.invalidate()},e.addEventListener("side-panel-closed",this._onSidePanelClosed),this._onGraphSettingsChanged=()=>this._ctrl.onGraphSettingsChanged(e),e.addEventListener("graph-settings-changed",this._onGraphSettingsChanged);try{await this._ctrl.loadHistory()}catch{}this._ctrl.updateDOM(e);const g=e.querySelector(".slide-confirm");g&&(this._ctrl.bindSlideConfirm(g,e),e.classList.add("switches-disabled")),this._ctrl.setupResizeObserver(e,e),this._ctrl.startIntervals(e)}stop(){this._ctrl.stopIntervals(),this._container&&(this._onGearClick&&(this._container.removeEventListener("click",this._onGearClick),this._onGearClick=null),this._onToggleClick&&(this._container.removeEventListener("click",this._onToggleClick),this._onToggleClick=null),this._onSidePanelClosed&&(this._container.removeEventListener("side-panel-closed",this._onSidePanelClosed),this._onSidePanelClosed=null),this._onGraphSettingsChanged&&(this._container.removeEventListener("graph-settings-changed",this._onGraphSettingsChanged),this._onGraphSettingsChanged=null))}}const Xt="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Zt="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Yt="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",en="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",tn="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function nn(e,t,n,i,s){return`\n ${i}\n `}class sn{constructor(){this.errorStore=null,this._debounceTimer=null,this._configEntryId=null,this._notifyCloseHandler=null,this._headerHTML=""}stop(){this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null),this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null)}async render(e,t,i,s=""){let o;void 0!==i&&(this._configEntryId=i),this._headerHTML=s,this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=function(e){if(!e||"object"!=typeof e)return null;const t=e,n={};return"boolean"==typeof t.enabled&&(n.enabled=t.enabled),t.global_settings&&"object"==typeof t.global_settings&&(n.global_settings=t.global_settings),t.circuits&&"object"==typeof t.circuits&&(n.circuits=t.circuits),t.mains&&"object"==typeof t.mains&&(n.mains=t.mains),n}(n?.response)}catch(e){console.warn("SPAN Panel: monitoring status fetch failed",e),o=null}const r=o?.global_settings??{},l=!0===o?.enabled,c=o?.circuits??{},d=o?.mains??{},h=new Set;for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&h.add(e);const p=new Set(["notify","send_message"]);for(const e of Object.keys(t.services?.notify||{}))p.has(e)||h.add(`notify.${e}`);h.add("event_bus");const u=[...h].sort(),g=r.notify_targets??"",_=("string"==typeof g?g.split(","):g).map(e=>e.trim()).filter(Boolean),f=u.length>0&&u.every(e=>_.includes(e)),v=r.notification_title_template??"SPAN: {name} {alert_type}",m=r.notification_message_template??"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",b=r.notification_priority??"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name??"").localeCompare(t.name??"")),w=Object.entries(d),x=[...y,...w],S=x.length>0&&x.every(([,e])=>!1!==e.monitoring_enabled),$=x.some(([,e])=>!1!==e.monitoring_enabled),C=y.map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),P=Object.entries(d).map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","mains")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");e.innerHTML=`\n ${this._headerHTML}\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n \n
\n \n
\n ${0===u.length?`
${n("notification.no_targets")}
`:u.map(e=>{const i=_.includes(e),s="event_bus"===e,o=s?null:t.states[e],r=o?.attributes?.friendly_name,a=s?n("notification.event_bus_target"):r?`${Oe(r)} (${Oe(e)})`:Oe(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m} {local_time}\n
\n
\n ${n("notification.event_bus_help")} span_panel_current_alert\n ${n("notification.event_bus_payload")} alert_source alert_id\n alert_name alert_type current_a\n breaker_rating_a threshold_pct utilization_pct\n panel_serial window_duration_s local_time\n
\n\n
\n ${n("notification.test_label")}\n \n \n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${P}\n ${C}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const k=e.querySelector("#toggle-all-circuits");k&&!S&&$&&(k.indeterminate=!0);const E=e.querySelector("#notify-all-targets");if(E&&u.length>0){const e=_.length>0;!f&&e&&(E.indeterminate=!0)}this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,d),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),s=e.querySelector("#global-fields"),o=e.querySelector("#global-status"),r=()=>{const t=[["continuous_threshold_pct","#g-continuous"],["spike_threshold_pct","#g-spike"],["window_duration_m","#g-window"],["cooldown_duration_m","#g-cooldown"]],n={};for(const[i,s]of t){const t=e.querySelector(s);if(!t)return null;const o=parseInt(t.value,10);if(Number.isNaN(o))return null;n[i]=o}return n},a=(e,t,i)=>{if(!e)return;const s=t instanceof Error?t.message:i;e.textContent=`${n("error.prefix")} ${s}`,e.style.color="var(--error-color, #f44336)"},l=()=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i=r();if(i)try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a(o,e,n("error.failed_save"))}else a(o,null,n("error.failed_save"))},u)};i&&i.addEventListener("change",async()=>{const o=i.checked;s&&(s.style.opacity=o?"":"0.4",s.style.pointerEvents=o?"":"none");const l=e.querySelector("#global-status");try{if(o){const e=r();if(!e)return void a(l,null,n("error.failed"));await this._callSetGlobal(t,e)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void a(l,e,n("error.failed"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",l)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),s=e.querySelector("#notify-target-dropdown"),o=e.querySelector("#notify-target-label");if(!i||!s)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==s.style.display;s.style.display=t?"none":"block"});const r=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(s.style.display="none")};document.addEventListener("click",r),this._notifyCloseHandler=r;const a=()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);if(o){const e=i.map(e=>"event_bus"===e?n("notification.event_bus_target"):e);o.textContent=e.length?e.join(", "):n("notification.none_selected")}const s=e.querySelector("#notify-all-targets");if(s){const t=[...e.querySelectorAll(".notify-target-cb")];s.checked=t.length>0&&t.every(e=>e.checked),s.indeterminate=!s.checked&&t.some(e=>e.checked)}this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch(e){console.warn("SPAN Panel: notification targets save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)},l=e.querySelector("#notify-all-targets");l&&l.addEventListener("change",()=>{for(const t of e.querySelectorAll(".notify-target-cb"))t.checked=l.checked;const t=e.querySelector("#notify-target-btn");t&&(t.style.opacity=l.checked?"0.4":"",t.style.pointerEvents=l.checked?"none":""),l.checked&&(s.style.display="none"),a()});for(const t of e.querySelectorAll(".notify-target-cb"))t.addEventListener("change",()=>{a()})}_bindNotificationSettings(e,t){const i=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),o=e.querySelector("#g-message-template"),r=(e,i)=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:i})}catch(e){console.warn("SPAN Panel: notification settings save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)};i&&i.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:i.value}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: notification priority change failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),o&&o.addEventListener("input",()=>{r("notification_message_template",o.value)});const l=e.querySelector("#test-notification-btn"),c=e.querySelector("#test-notification-status");l&&l.addEventListener("click",async()=>{l.disabled=!0,c&&(c.textContent=n("notification.test_sending"),c.style.color="var(--secondary-text-color)");try{this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null);const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value).join(", ");await this._callSetGlobal(t,{notify_targets:i});const s={};this._configEntryId&&(s.config_entry_id=this._configEntryId),await t.callWS({type:"call_service",domain:a,service:"test_notification",service_data:s}),c&&(c.textContent=n("notification.test_sent"),c.style.color="var(--success-color, #4caf50)")}catch(e){if(c){const t=e instanceof Error?e.message:n("error.failed");c.textContent=`${n("error.prefix")} ${t}`,c.style.color="var(--error-color, #f44336)"}}finally{l.disabled=!1}})}_bindToggleAll(e,t,i,s){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const r=o.checked,l=[...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: circuit monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})),...Object.keys(s).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: mains monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}))];await Promise.all(l),await this.render(e,t)})}_bindMainsToggles(e,t){for(const i of e.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: mains threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const i of e.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: circuit threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const i=new Map;for(const s of e.querySelectorAll(".threshold-input"))s.addEventListener("input",()=>{const o=`${s.dataset.entity}-${s.dataset.field}`,r=i.get(o);r&&clearTimeout(r),i.set(o,setTimeout(async()=>{const i=parseInt(s.value,10);if(!i||i<1)return;const o=s.dataset.entity,r=s.dataset.field,l=s.dataset.type,c="mains"===l?"set_mains_threshold":"set_circuit_threshold",d="mains"===l?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[d]:o,[r]:i})}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: threshold input save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),s.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity;if(!i)return;const s=n.dataset.type,o="mains"===s?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===s?{leg:i}:{circuit_id:i});await t.callService(a,o,r),await this.render(e,t)})}}function on(e=""){const t=e?` value="${Oe(e)}"`:"",i=e?"":"display:none;";return`\n
\n \n \n
\n `}function rn(e,t,i,s,o,r,a){const c=t.entities?.power,d=c?i.states[c]:null,h=d&&parseFloat(d.state)||0,p=t.entities?.switch,u=p?i.states[p]:null,g=u?"on"===u.state:(d?.attributes?.relay_state||t.relay_state)===l,_=t.breaker_rating_a,m=_?`${Math.round(_)}A`:"",b=Oe(t.name||n("grid.unknown")),y=dt(s),w="current"===y.entityRole;let x;if(g)if(w){const e=t.entities?.current,n=e?i.states[e]:null,s=n&&parseFloat(n.state)||0;x=`${y.format(s)}A`}else x=`${ot(h)}${st(h)}`;else x="";const S=r||"unknown";let $="";if("unknown"!==S){const e=f[S]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};$=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}let C="",P=o?.utilization_pct??null;if(null==P&&t.breaker_rating_a){const e=t.entities?.current,n=e?i.states[e]:null,s=n?Math.abs(parseFloat(n.state)||0):0;P=Math.round(s/t.breaker_rating_a*1e3)/10}if(null!=P){C=`=80?"utilization-warning":"utilization-normal"}">${Math.round(P)}%`}const k=!!o&&_t(o)?v:"#555",E=``,z=!1!==t.is_user_controllable&&!!t.entities?.switch?`
\n ${n(g?"grid.on":"grid.off")}\n \n
`:`${g?"ON":"OFF"}`;return`\n
\n ${m?`${m}`:""}\n ${C}\n ${b}\n ${$}\n ${z}\n \n ${x}\n \n ${E}\n \n
\n `}function an(e,t,n,i,s){const o=t.entities?.power,r=o?n.states[o]:null,a=r&&parseFloat(r.state)||0,d=t.device_type===c||a<0,h=t.entities?.switch,p=h?n.states[h]:null,u=ft(0,s,p?"on"===p.state:(r?.attributes?.relay_state||t.relay_state)===l,d),g=Oe(e);return`\n
\n
\n
\n
\n
\n `}function ln(e){return`
${Oe(e)}
`}function cn(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)===l;let c;if("current"===(n.chart_metric||"power")){const n=e.entities?.current,i=n?t.states[n]:null;c=i?Math.abs(parseFloat(i.state)||0):0}else c=r?Math.abs(parseFloat(r.state)||0):0;return{isOn:a,value:c}}function dn(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 hn(e,t,n,i){const s=cn(e,n,i),o=cn(t,n,i);return s.isOn&&!o.isOn?-1:!s.isOn&&o.isOn?1:o.value-s.value}function pn(e,t,n){return e.sort((e,i)=>hn(e[1],i[1],t,n))}function un(e){return e.entities?.current??e.entities?.power??""}class gn{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=pn(Object.entries(n.circuits),t,i);let a=o+on(this._searchQuery);a+=`
`;for(const[e,n]of r){const o=gt(s,un(n)),r=dn(n,t),l=this._expandedUuids.has(e);a+=`
`,a+=rn(e,n,t,i,o,r,l),l&&(a+=an(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,i,s,o,r){this._unbindEvents(),this._hass=t,this._topology=i,this._config=s,this._monitoringStatus=o;const a=n("list.unassigned_area"),l=new Map;for(const[e,t]of Object.entries(i.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+on(this._searchQuery);d+=`
`;for(const e of c){const n=l.get(e);if(!n)continue;const i=pn(n,t,s);d+=ln(e);for(const[e,n]of i){const i=gt(o,un(n)),r=dn(n,t),a=this._expandedUuids.has(e);d+=`
`,d+=rn(e,n,t,s,i,r,a),a&&(d+=an(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,i,s){const o=dt(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=i.circuits[a];if(!l)continue;const{isOn:c,value:d}=cn(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=`${ot(i)}${st(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=n(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)=>hn(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,i,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=jt(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=gt(this._monitoringStatus,un(t)),o=an(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()}}}function _n(e,t){return`${e}|${t}`}class fn{async build(e,t,n,i){const s=new Map;for(const e of n)s.set(e.id,e);const o=i?new We(i):null,r=[];for(const[n,i]of Object.entries(t)){if(!((i?.circuits?.length??0)>0||(i?.sub_devices?.length??0)>0))continue;const t=s.get(n);t&&r.push((async()=>{try{const i=await Ye(e,n,o);return{panelDeviceId:n,panel:t,topology:i.topology}}catch(e){return console.warn("SPAN Panel: favorites topology fetch failed",n,e),{panelDeviceId:n,panel:t,topology:null}}})())}const a=(await Promise.all(r)).filter(e=>null!==e.topology),l=a.length>1,c={},d={},h={},p=new Set,u=[];for(const{panelDeviceId:e,panel:n,topology:i}of a){if(!i)continue;const s=n.config_entries?.[0]??null;s&&p.add(s);const o=n.name_by_user??n.name??i.device_name??"";u.push({panelDeviceId:e,panelName:o,topology:i});const r=t[e],a=r?.circuits??[],g=r?.sub_devices??[];for(const t of a){const n=i.circuits?.[t];if(!n)continue;const r=_n(e,t),a=l&&o?`${o} · ${n.name}`:n.name;c[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"circuit",targetId:t,configEntryId:s}}for(const t of g){const n=i.sub_devices?.[t];if(!n)continue;const r=_n(e,t),a=l&&o&&n.name?`${o} · ${n.name}`:n.name??t;d[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"sub_device",targetId:t,configEntryId:s}}}return{topology:{circuits:c,sub_devices:d,panel_entities:{},device_name:"",_favoriteRefs:h},entryIds:Array.from(p),perPanelStats:u}}}const vn="span_panel_favorites_view_state";function mn(e){try{localStorage.setItem(vn,JSON.stringify(e))}catch{}}var bn;const yn="favorites";let wn=bn=class extends Pe{constructor(){super(...arguments),this.narrow=!1,this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._listColumns=qe(),this._favorites={},this._favoritesViewState={expanded:{activity:[],area:[]}},this._favoritesPanelStats=[],this._dashboardTab=new Jt,this._monitoringTab=new sn,this._listDashCtrl=new Qt,this._listCtrl=new gn(this._listDashCtrl),this._favCache=new Be,this._favCtrl=new fn,this._favoritesMonitoringTabs=new Map,this._errorStore=new Le,this._watchedPanelId=null,this._discovering=!1,this._refreshSeq=0,this._areaUnsub=null,this._areaSubscribing=!1,this._onVisibilityChange=null,this._onFavoritesChanged=null,this._deviceRegistryUnsub=null,this._pendingTabRender=!1,this._persistFavoritesViewStateTimer=null,this._tabRenderScheduler=function(e){let t=null,n=!1;return async function i(){if(t)return n=!0,void await t.catch(()=>{});const s=(async()=>{try{await e()}finally{t=null,n&&(n=!1,await i())}})();t=s,await s}}(async()=>this._renderTab()),this._beginRender=function(){let e=0;return()=>{e+=1;const t=e;return()=>e!==t}}()}get _root(){const e=this.shadowRoot;if(!e)throw new Error("span-panel: shadow root is not available");return e}connectedCallback(){super.connectedCallback(),this._dashboardTab.errorStore=this._errorStore,this._listDashCtrl.errorStore=this._errorStore,this._favCache.errorStore=this._errorStore,this._monitoringTab.errorStore=this._errorStore,this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this.hass&&this._scheduleTabRender()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._onFavoritesChanged=()=>{this._refreshFavorites()},document.addEventListener(Ge,this._onFavoritesChanged),this._subscribeDeviceRegistry()}disconnectedCallback(){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._areaSubscribing=!1,this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._onFavoritesChanged&&(document.removeEventListener(Ge,this._onFavoritesChanged),this._onFavoritesChanged=null),this._unsubscribeDeviceRegistry(),this._persistFavoritesViewStateTimer&&(clearTimeout(this._persistFavoritesViewStateTimer),this._persistFavoritesViewStateTimer=null),this._errorStore.dispose(),super.disconnectedCallback()}firstUpdated(){this.hass&&!this._discovered&&this._discoverPanels()}updated(e){if(e.has("hass")){const t=e.get("hass");this._dashboardTab.hass=this.hass,this._listDashCtrl.hass=this.hass,this._errorStore.updateHass(this.hass),this._discovered?this._root.getElementById("tab-content")||this._scheduleTabRender():this._discoverPanels(),!t&&this.hass&&this._subscribeDeviceRegistry()}if(this._discovered&&(e.has("_discovered")||e.has("_activeTab")||e.has("_selectedPanelId")||e.has("_chartMetric")||e.has("_listColumns"))){if(this._isFavoritesView&&"dashboard"===this._activeTab)return void(this._activeTab="activity");this._scheduleTabRender()}if(e.has("_selectedPanelId")&&(this._selectedPanelId!==yn&&this._selectedPanelId?(this._updatePanelStatusWatch(),this._listDashCtrl.setFavoritesPerPanelInfo(null)):(this._errorStore.clearPanelStatusWatch(),this._watchedPanelId=null)),this._discovered&&(e.has("_panels")||e.has("_selectedPanelId"))){const e=this.shadowRoot?.getElementById("panel-select");e&&null!==this._selectedPanelId&&e.value!==this._selectedPanelId&&(e.value=this._selectedPanelId)}if(e.has("hass")&&this._discovered&&("activity"===this._activeTab||"area"===this._activeTab)){const e=this._root.getElementById("tab-content"),t=this._listDashCtrl.topology;if(e&&t){this._listCtrl.updateCollapsedRows(e,this.hass,t,this._buildDashboardConfig());const n=e.querySelector("span-side-panel");n&&(n.hass=this.hass,n.errorStore=this._errorStore)}}}setConfig(e){}render(){var i,s,o;if(i=this.hass?.language,e=i&&t[i]?i:"en",!this.hass)return le` + `,m([Me()],Xe.prototype,"_errors",void 0),Xe=m([Ee("span-error-banner")],Xe);const it=g.power;function st(e){return it.unit(e)}function ot(e){return(e<0?"-":"")+it.format(e)}function rt(e){return(Math.abs(e)/1e3).toFixed(1)}function at(e){return Math.ceil(e/2)}function lt(e){return e%2==0?1:0}function ct(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return at(t)===at(n)?"row-span":lt(t)===lt(n)?"col-span":"row-span"}function dt(e){const t=e.chart_metric??s;return g[t]??g[s]}function ht(e,t){const n=function(e){return dt(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class pt{constructor(){this._status=null,this._lastFetch=0,this._inflight=null,this._generation=0,this._errorStore=null,this._retry=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}async fetch(e,t){const i=Date.now();if(this._inflight&&this._inflight.gen===this._generation)return this._inflight.promise;if(this._status&&i-this._lastFetch<3e4)return this._status;const s=this._generation,o=(async()=>{try{const i={};t&&(i.config_entry_id=t);const o={type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0},r=this._retry?await this._retry.callWS(e,o,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await e.callWS(o),l=r?.response??null;return s===this._generation&&(this._status=l,this._lastFetch=Date.now()),l}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:n("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 ut{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 pt,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 gt(e,t){return e?.circuits?e.circuits[t]??null:null}function _t(e){return!!e&&void 0!==e.continuous_threshold_pct}function ft(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"),_t(t)&&s.push("circuit-custom-monitoring"),s.join(" ")}function vt(e,t,i,s,o,r,a,d,h,p=!1){const u=t.entities?.power,g=u?r.states[u]:null,_=g&&parseFloat(g.state)||0,m=t.device_type===c||_<0,b=t.entities?.switch,y=b?r.states[b]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===l,x=t.breaker_rating_a,S=x?`${Math.round(x)}A`:"",$=Oe(t.name||n("grid.unknown")),C=dt(a);let P;if("current"===C.entityRole){const e=t.entities?.current,n=e?r.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${C.format(i)}A`}else P=`${ot(_)}${st(_)}`;const k=h||"unknown";let E="";if("unknown"!==k){const e=f[k]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"},t=Oe(e.label()),n=Oe(e.icon),i=Oe(e.color);if(e.icon2){E=`\n \n \n `}else if(e.textLabel){E=`\n \n ${Oe(e.textLabel)}\n `}else E=``}const z=d&&_t(d)?v:"#555",A=``;let N="",M=d?.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 ${$}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${E}\n ${A}\n
\n
\n
\n `}function mt(e,t){return`\n
\n \n
\n `}const bt={names:["power","battery power"],suffixes:["_power"]},yt={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},wt={names:["state of energy"],suffixes:["_soe_kwh"]},xt={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function St(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 $t(e){return St(e,bt)}function Ct(e){return St(e,yt)}function Pt(e){return St(e,wt)}function kt(e){return St(e,xt)}function Et(e,t,i){const s=!1!==i.show_battery,o=!1!==i.show_evse;if(!e.sub_devices)return"";const r=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===d&&!s)&&!(e.type===h&&!o));if(0===r.length)return"";const a=r.filter(([,e])=>e.type===h).length;let l=0,c="";for(const[e,s]of r){const o=s.type===h?n("subdevice.ev_charger"):s.type===d?n("subdevice.battery"):n("subdevice.fallback"),r=$t(s),p=r?t.states[r]:void 0,u=p&&parseFloat(p.state)||0,g=s.type===d,_=s.type===h,f=g?Ct(s):null,v=g?Pt(s):null,m=g?kt(s):null,b=zt(s,t,i,new Set([r,f,v,m].filter(e=>null!==e))),y=At(e,s,g,r,f,v);let w="";g?w="sub-device-bess":_&&(l++,l===a&&a%2==1&&(w="sub-device-full")),c+=`\n
\n
\n ${Oe(o)}\n ${Oe(s.name||"")}\n ${r?`${ot(u)} ${st(u)}`:""}\n \n
\n ${y}\n ${b}\n
\n `}return c}function zt(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 ${Oe(l)}:\n ${Oe(d)}\n
\n `}return o}function At(e,t,i,s,o,r){if(i){const t=[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!o},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!r},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!s}].filter(e=>e.available);return`\n
\n ${t.map(e=>`\n
\n
${Oe(e.title)}
\n
\n
\n `).join("")}\n
\n `}return s?`
`:""}function Nt(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 Mt(e){const t=r[e];return t?t.ms:r[o].ms}function It(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function Tt(e){return Math.max(500,Math.floor(e/5e3))}function Dt(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 Ft(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 Lt(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 Ht(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=It(i),l=Tt(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,Ft(t,a,l))}}}function Ot(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:$t(i)};i.type===d&&(e.soc=Ct(i),e.soe=Pt(i));for(const[i,s]of Object.entries(e))s&&t.push({entityId:s,key:`${p}${n}_${i}`,devId:n})}return t}async function Rt(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=ht(i,n);if(!t)continue;let o;o=s&&s.has(e)?Mt(s.get(e)):Nt(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 Ot(t)){let t;t=o&&o.has(s)?Mt(o.get(s)):Nt(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(Lt(e,n.entityIds,n.uuidByEntity,t,i)):a.push(Ht(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(a)}function qt(e,t,n,i,o,r,a,l,c){const{options:d,series:h}=function(e,t,n,i,o,r=!1){n||(n=g[s]);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}}],_=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};h?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),o&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),u.push({type:"line",data:[[d,.8*o],[c,.8*o]],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,o],[c,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const v={xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,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:v,series:u}}(n,i,o,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 _=e.clientHeight;u.height=(_>0?_:p)+"px",u.hass=t,u.options=d,u.data=h}function jt(e){return"function"==typeof globalThis.CSS?.escape?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function Ut(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=rt(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=rt(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=rt(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=rt(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 _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}function Wt(e,t,i,s,o,r){if(!e||!i||!t)return;const a=Nt(s);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],s=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(s))}!function(e,t,n,i,s){const o=e.querySelector(".panel-stats");o&&Ut(o,t,n,i,s)}(e,t,i,s,d);const h=dt(s),p="current"===h.entityRole;for(const[s,d]of Object.entries(i.circuits)){const i=e.querySelector(`.circuit-slot[data-uuid="${jt(s)}"]`);if(!i)continue;const u=d.entities?.power,g=u?t.states[u]:null,_=g&&parseFloat(g.state)||0,v=d.device_type===c||_<0,m=d.entities?.switch,b=m?t.states[m]:null,y=b?"on"===b.state:(g?.attributes?.relay_state||d.relay_state)===l,w=i.querySelector(".power-value");if(w)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${h.format(i)}A`}else w.innerHTML=`${ot(_)}${st(_)}`;const x=i.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=x.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",v),d.always_on)S="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const $=f[S]??f.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",$.icon),C.style.color=$.color,C.title=$.label());const P=i.querySelector(".shedding-icon-secondary");P&&($.icon2?(P.setAttribute("icon",$.icon2),P.style.color=$.color,P.style.display=""):P.style.display="none");const k=i.querySelector(".shedding-label");k&&($.textLabel?(k.textContent=$.textLabel,k.style.color=$.color,k.style.display=""):k.style.display="none");const E=i.querySelector(".chart-container");if(E){const e=o.get(s)||[],n=i.classList.contains("circuit-col-span")?200:100,l=r?.has(s)?Mt(r.get(s)):a,p=d.device_type===c;qt(E,t,e,l,h,v,n,d.breaker_rating_a??void 0,p)}}}class Gt{get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}constructor(){this._errorStore=null,this._retry=null,this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const s={type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0},o=this._retry?await this._retry.callWS(e,s,{errorId:"fetch:graph_settings",errorMessage:n("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:n("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 Vt(e,t){if(!e)return o;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??o}function Bt(e,t){if(!e)return o;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??o}class Qt{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new pt,this.monitoringMultiCache=new ut,this.graphSettingsCache=new Gt,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,Vt(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,Bt(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:n("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,Vt(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,Bt(i,s))}}async loadHistory(){await Rt(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)??o;if(!r[i]?.useRealtime)continue;const s=ht(n,this._config);if(!s)continue;const a=this._hass.states[s];if(!a)continue;const l=parseFloat(a.state);if(isNaN(l))continue;const c=Mt(i),d=It(c),h=Tt(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 Ot(this._topology))i.has(t)&&s.add(e);const o=new Map;try{await Rt(this._hass,this._topology,this._config,o,t,i);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:n("error.history_failed"),persistent:!1})}}updateDOM(e){this._hass&&this._topology&&this._config&&(Wt(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=Nt(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${jt(i)}"]`);if(!n)continue;const l=$t(a);if(l){const e=t.states[l],i=e&&parseFloat(e.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${ot(i)} ${st(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");qt(e,t,a,o?.has(i)?Mt(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="${jt(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 i=e.target,s=i?.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:n("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 r=i.dataset.uuid;if(r&&this._topology){const e=this._topology.circuits[r];if(e){const t=this._favRefs?.[r]??null,n=t&&"circuit"===t.kind?t.targetId:r,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??o,p=a?.circuits?.[n],u=p?{...p,globalHorizon:h}:{horizon:h,has_override:!1,globalHorizon:h},g=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,_=null!==t||(this._panelFavorites?.circuitUuids.has(n)??!1),f=this._inFavoritesView||null!==this._panelFavorites;return void s.open({...e,uuid:n,monitoringInfo:d,showMonitoring:this._showMonitoring,graphHorizonInfo:u,showFavorites:f,favoritePanelDeviceId:g,isFavorite:_,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 r;t?r=await this._fetchGraphSettingsFresh(i):(await this.graphSettingsCache.fetch(this._hass,i),r=this.graphSettingsCache.settings);const l=r?.global_horizon??o,c=r?.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 i={type:"call_service",domain:a,service:"get_graph_settings",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:graph_settings",errorMessage:n("error.graph_settings_failed")}):await this._hass.callWS(i);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 i={type:"call_service",domain:a,service:"get_monitoring_status",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await this._hass.callWS(i),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()}}const Kt='\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 }\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 .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 min-width: 70px;\n text-align: right;\n flex-shrink: 0;\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 /* ── 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';class Jt{constructor(){this._ctrl=new Qt,this._container=null,this._onGearClick=null,this._onToggleClick=null,this._onSidePanelClosed=null,this._onGraphSettingsChanged=null}get hass(){return this._ctrl.hass}set hass(e){this._ctrl.hass=e}set errorStore(e){this._ctrl.errorStore=e}setPanelFavorites(e){this._ctrl.setPanelFavorites(e)}async render(e,t,i,s,o){let r,a;this.stop(),this._ctrl.reset(),this._ctrl.showMonitoring=!0,this._container=e,this._ctrl.hass=t;try{const e=await Ye(t,i);r=e.topology,a=e.panelSize}catch(t){return void(e.innerHTML=`

${Oe(t.message)}

`)}this._ctrl.init(r,s,t,o??null),await this._ctrl.monitoringCache.fetch(t,o??null),await this._ctrl.fetchAndBuildHorizonMaps();const l=Math.ceil(a/2),c=this._ctrl.monitoringCache.status,d=nt(r,s),h=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),i=Object.values(e.mains??{}),s=[...t,...i],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 ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${o>0?`${o} ${n(o>1?"status.warnings":"status.warning")}`:""}\n ${r>0?`${r} ${n(r>1?"status.alerts":"status.alert")}`:""}\n ${a>0?`${a} ${n(a>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(c),p=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":ct(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=at(Math.max(...n));0===lt(e)?a.add(i):l.add(i)}function c(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=s?gt(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+=vt(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+=mt(e,"2"));else{const{monInfo:t,sheddingPriority:s}=c(h);d+=vt(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+=mt(e,"3"));else{const{monInfo:t,sheddingPriority:s}=c(p);d+=vt(p.uuid,p.circuit,e,"3",p.layout,n,i,t,s)}d+=`
${s}
`}return d}(r,l,t,s,c),u=Et(r,t,s);e.innerHTML=`\n \n ${d}\n ${h}\n ${u?`
${u}
`:""}\n ${!1!==s.show_panel?`\n
\n ${p}\n
\n `:""}\n \n `,this._onGearClick=t=>{this._ctrl.onGearClick(t,e)},this._onToggleClick=t=>{this._ctrl.onToggleClick(t,e)},e.addEventListener("click",this._onGearClick),e.addEventListener("click",this._onToggleClick),this._onSidePanelClosed=()=>{this._ctrl.monitoringCache.invalidate(),this._ctrl.graphSettingsCache.invalidate()},e.addEventListener("side-panel-closed",this._onSidePanelClosed),this._onGraphSettingsChanged=()=>this._ctrl.onGraphSettingsChanged(e),e.addEventListener("graph-settings-changed",this._onGraphSettingsChanged);try{await this._ctrl.loadHistory()}catch{}this._ctrl.updateDOM(e);const g=e.querySelector(".slide-confirm");g&&(this._ctrl.bindSlideConfirm(g,e),e.classList.add("switches-disabled")),this._ctrl.setupResizeObserver(e,e),this._ctrl.startIntervals(e)}stop(){this._ctrl.stopIntervals(),this._container&&(this._onGearClick&&(this._container.removeEventListener("click",this._onGearClick),this._onGearClick=null),this._onToggleClick&&(this._container.removeEventListener("click",this._onToggleClick),this._onToggleClick=null),this._onSidePanelClosed&&(this._container.removeEventListener("side-panel-closed",this._onSidePanelClosed),this._onSidePanelClosed=null),this._onGraphSettingsChanged&&(this._container.removeEventListener("graph-settings-changed",this._onGraphSettingsChanged),this._onGraphSettingsChanged=null))}}const Xt="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Zt="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Yt="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",en="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",tn="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function nn(e,t,n,i,s){return`\n ${i}\n `}class sn{constructor(){this.errorStore=null,this._debounceTimer=null,this._configEntryId=null,this._notifyCloseHandler=null,this._headerHTML=""}stop(){this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null),this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null)}async render(e,t,i,s=""){let o;void 0!==i&&(this._configEntryId=i),this._headerHTML=s,this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=function(e){if(!e||"object"!=typeof e)return null;const t=e,n={};return"boolean"==typeof t.enabled&&(n.enabled=t.enabled),t.global_settings&&"object"==typeof t.global_settings&&(n.global_settings=t.global_settings),t.circuits&&"object"==typeof t.circuits&&(n.circuits=t.circuits),t.mains&&"object"==typeof t.mains&&(n.mains=t.mains),n}(n?.response)}catch(e){console.warn("SPAN Panel: monitoring status fetch failed",e),o=null}const r=o?.global_settings??{},l=!0===o?.enabled,c=o?.circuits??{},d=o?.mains??{},h=new Set;for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&h.add(e);const p=new Set(["notify","send_message"]);for(const e of Object.keys(t.services?.notify||{}))p.has(e)||h.add(`notify.${e}`);h.add("event_bus");const u=[...h].sort(),g=r.notify_targets??"",_=("string"==typeof g?g.split(","):g).map(e=>e.trim()).filter(Boolean),f=u.length>0&&u.every(e=>_.includes(e)),v=r.notification_title_template??"SPAN: {name} {alert_type}",m=r.notification_message_template??"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",b=r.notification_priority??"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name??"").localeCompare(t.name??"")),w=Object.entries(d),x=[...y,...w],S=x.length>0&&x.every(([,e])=>!1!==e.monitoring_enabled),$=x.some(([,e])=>!1!==e.monitoring_enabled),C=y.map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),P=Object.entries(d).map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","mains")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");e.innerHTML=`\n ${this._headerHTML}\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n \n
\n \n
\n ${0===u.length?`
${n("notification.no_targets")}
`:u.map(e=>{const i=_.includes(e),s="event_bus"===e,o=s?null:t.states[e],r=o?.attributes?.friendly_name,a=s?n("notification.event_bus_target"):r?`${Oe(r)} (${Oe(e)})`:Oe(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m} {local_time}\n
\n
\n ${n("notification.event_bus_help")} span_panel_current_alert\n ${n("notification.event_bus_payload")} alert_source alert_id\n alert_name alert_type current_a\n breaker_rating_a threshold_pct utilization_pct\n panel_serial window_duration_s local_time\n
\n\n
\n ${n("notification.test_label")}\n \n \n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${P}\n ${C}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const k=e.querySelector("#toggle-all-circuits");k&&!S&&$&&(k.indeterminate=!0);const E=e.querySelector("#notify-all-targets");if(E&&u.length>0){const e=_.length>0;!f&&e&&(E.indeterminate=!0)}this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,d),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),s=e.querySelector("#global-fields"),o=e.querySelector("#global-status"),r=()=>{const t=[["continuous_threshold_pct","#g-continuous"],["spike_threshold_pct","#g-spike"],["window_duration_m","#g-window"],["cooldown_duration_m","#g-cooldown"]],n={};for(const[i,s]of t){const t=e.querySelector(s);if(!t)return null;const o=parseInt(t.value,10);if(Number.isNaN(o))return null;n[i]=o}return n},a=(e,t,i)=>{if(!e)return;const s=t instanceof Error?t.message:i;e.textContent=`${n("error.prefix")} ${s}`,e.style.color="var(--error-color, #f44336)"},l=()=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i=r();if(i)try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a(o,e,n("error.failed_save"))}else a(o,null,n("error.failed_save"))},u)};i&&i.addEventListener("change",async()=>{const o=i.checked;s&&(s.style.opacity=o?"":"0.4",s.style.pointerEvents=o?"":"none");const l=e.querySelector("#global-status");try{if(o){const e=r();if(!e)return void a(l,null,n("error.failed"));await this._callSetGlobal(t,e)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void a(l,e,n("error.failed"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",l)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),s=e.querySelector("#notify-target-dropdown"),o=e.querySelector("#notify-target-label");if(!i||!s)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==s.style.display;s.style.display=t?"none":"block"});const r=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(s.style.display="none")};document.addEventListener("click",r),this._notifyCloseHandler=r;const a=()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);if(o){const e=i.map(e=>"event_bus"===e?n("notification.event_bus_target"):e);o.textContent=e.length?e.join(", "):n("notification.none_selected")}const s=e.querySelector("#notify-all-targets");if(s){const t=[...e.querySelectorAll(".notify-target-cb")];s.checked=t.length>0&&t.every(e=>e.checked),s.indeterminate=!s.checked&&t.some(e=>e.checked)}this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch(e){console.warn("SPAN Panel: notification targets save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)},l=e.querySelector("#notify-all-targets");l&&l.addEventListener("change",()=>{for(const t of e.querySelectorAll(".notify-target-cb"))t.checked=l.checked;const t=e.querySelector("#notify-target-btn");t&&(t.style.opacity=l.checked?"0.4":"",t.style.pointerEvents=l.checked?"none":""),l.checked&&(s.style.display="none"),a()});for(const t of e.querySelectorAll(".notify-target-cb"))t.addEventListener("change",()=>{a()})}_bindNotificationSettings(e,t){const i=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),o=e.querySelector("#g-message-template"),r=(e,i)=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:i})}catch(e){console.warn("SPAN Panel: notification settings save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)};i&&i.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:i.value}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: notification priority change failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),o&&o.addEventListener("input",()=>{r("notification_message_template",o.value)});const l=e.querySelector("#test-notification-btn"),c=e.querySelector("#test-notification-status");l&&l.addEventListener("click",async()=>{l.disabled=!0,c&&(c.textContent=n("notification.test_sending"),c.style.color="var(--secondary-text-color)");try{this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null);const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value).join(", ");await this._callSetGlobal(t,{notify_targets:i});const s={};this._configEntryId&&(s.config_entry_id=this._configEntryId),await t.callWS({type:"call_service",domain:a,service:"test_notification",service_data:s}),c&&(c.textContent=n("notification.test_sent"),c.style.color="var(--success-color, #4caf50)")}catch(e){if(c){const t=e instanceof Error?e.message:n("error.failed");c.textContent=`${n("error.prefix")} ${t}`,c.style.color="var(--error-color, #f44336)"}}finally{l.disabled=!1}})}_bindToggleAll(e,t,i,s){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const r=o.checked,l=[...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: circuit monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})),...Object.keys(s).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: mains monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}))];await Promise.all(l),await this.render(e,t)})}_bindMainsToggles(e,t){for(const i of e.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: mains threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const i of e.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: circuit threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const i=new Map;for(const s of e.querySelectorAll(".threshold-input"))s.addEventListener("input",()=>{const o=`${s.dataset.entity}-${s.dataset.field}`,r=i.get(o);r&&clearTimeout(r),i.set(o,setTimeout(async()=>{const i=parseInt(s.value,10);if(!i||i<1)return;const o=s.dataset.entity,r=s.dataset.field,l=s.dataset.type,c="mains"===l?"set_mains_threshold":"set_circuit_threshold",d="mains"===l?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[d]:o,[r]:i})}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: threshold input save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),s.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity;if(!i)return;const s=n.dataset.type,o="mains"===s?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===s?{leg:i}:{circuit_id:i});await t.callService(a,o,r),await this.render(e,t)})}}function on(e=""){const t=e?` value="${Oe(e)}"`:"",i=e?"":"display:none;";return`\n
\n \n \n
\n `}function rn(e,t,i,s,o,r,a){const c=t.entities?.power,d=c?i.states[c]:null,h=d&&parseFloat(d.state)||0,p=t.entities?.switch,u=p?i.states[p]:null,g=u?"on"===u.state:(d?.attributes?.relay_state||t.relay_state)===l,_=t.breaker_rating_a,m=_?`${Math.round(_)}A`:"",b=Oe(t.name||n("grid.unknown")),y=dt(s),w="current"===y.entityRole;let x;if(g)if(w){const e=t.entities?.current,n=e?i.states[e]:null,s=n&&parseFloat(n.state)||0;x=`${y.format(s)}A`}else x=`${ot(h)}${st(h)}`;else x="";const S=r||"unknown";let $="";if("unknown"!==S){const e=f[S]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};$=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}let C="",P=o?.utilization_pct??null;if(null==P&&t.breaker_rating_a){const e=t.entities?.current,n=e?i.states[e]:null,s=n?Math.abs(parseFloat(n.state)||0):0;P=Math.round(s/t.breaker_rating_a*1e3)/10}if(null!=P){C=`=80?"utilization-warning":"utilization-normal"}">${Math.round(P)}%`}const k=!!o&&_t(o)?v:"#555",E=``,z=!1!==t.is_user_controllable&&!!t.entities?.switch?`
\n ${n(g?"grid.on":"grid.off")}\n \n
`:`${g?"ON":"OFF"}`;return`\n
\n ${m?`${m}`:""}\n ${C}\n ${b}\n ${$}\n ${z}\n \n ${x}\n \n ${E}\n \n
\n `}function an(e,t,n,i,s){const o=t.entities?.power,r=o?n.states[o]:null,a=r&&parseFloat(r.state)||0,d=t.device_type===c||a<0,h=t.entities?.switch,p=h?n.states[h]:null,u=ft(0,s,p?"on"===p.state:(r?.attributes?.relay_state||t.relay_state)===l,d),g=Oe(e);return`\n
\n
\n
\n
\n
\n `}function ln(e){return`
${Oe(e)}
`}function cn(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)===l;let c;if("current"===(n.chart_metric||"power")){const n=e.entities?.current,i=n?t.states[n]:null;c=i?Math.abs(parseFloat(i.state)||0):0}else c=r?Math.abs(parseFloat(r.state)||0):0;return{isOn:a,value:c}}function dn(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 hn(e,t,n,i){const s=cn(e,n,i),o=cn(t,n,i);return s.isOn&&!o.isOn?-1:!s.isOn&&o.isOn?1:o.value-s.value}function pn(e,t,n){return e.sort((e,i)=>hn(e[1],i[1],t,n))}function un(e){return e.entities?.current??e.entities?.power??""}class gn{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=pn(Object.entries(n.circuits),t,i);let a=o+on(this._searchQuery);a+=`
`;for(const[e,n]of r){const o=gt(s,un(n)),r=dn(n,t),l=this._expandedUuids.has(e);a+=`
`,a+=rn(e,n,t,i,o,r,l),l&&(a+=an(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,i,s,o,r){this._unbindEvents(),this._hass=t,this._topology=i,this._config=s,this._monitoringStatus=o;const a=n("list.unassigned_area"),l=new Map;for(const[e,t]of Object.entries(i.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+on(this._searchQuery);d+=`
`;for(const e of c){const n=l.get(e);if(!n)continue;const i=pn(n,t,s);d+=ln(e);for(const[e,n]of i){const i=gt(o,un(n)),r=dn(n,t),a=this._expandedUuids.has(e);d+=`
`,d+=rn(e,n,t,s,i,r,a),a&&(d+=an(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,i,s){const o=dt(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=i.circuits[a];if(!l)continue;const{isOn:c,value:d}=cn(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=`${ot(i)}${st(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=n(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)=>hn(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,i,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=jt(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=gt(this._monitoringStatus,un(t)),o=an(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()}}}function _n(e,t){return`${e}|${t}`}class fn{async build(e,t,n,i){const s=new Map;for(const e of n)s.set(e.id,e);const o=i?new We(i):null,r=[];for(const[n,i]of Object.entries(t)){if(!((i?.circuits?.length??0)>0||(i?.sub_devices?.length??0)>0))continue;const t=s.get(n);t&&r.push((async()=>{try{const i=await Ye(e,n,o);return{panelDeviceId:n,panel:t,topology:i.topology}}catch(e){return console.warn("SPAN Panel: favorites topology fetch failed",n,e),{panelDeviceId:n,panel:t,topology:null}}})())}const a=(await Promise.all(r)).filter(e=>null!==e.topology),l=a.length>1,c={},d={},h={},p=new Set,u=[];for(const{panelDeviceId:e,panel:n,topology:i}of a){if(!i)continue;const s=n.config_entries?.[0]??null;s&&p.add(s);const o=n.name_by_user??n.name??i.device_name??"";u.push({panelDeviceId:e,panelName:o,topology:i});const r=t[e],a=r?.circuits??[],g=r?.sub_devices??[];for(const t of a){const n=i.circuits?.[t];if(!n)continue;const r=_n(e,t),a=l&&o?`${o} · ${n.name}`:n.name;c[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"circuit",targetId:t,configEntryId:s}}for(const t of g){const n=i.sub_devices?.[t];if(!n)continue;const r=_n(e,t),a=l&&o&&n.name?`${o} · ${n.name}`:n.name??t;d[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"sub_device",targetId:t,configEntryId:s}}}return{topology:{circuits:c,sub_devices:d,panel_entities:{},device_name:"",_favoriteRefs:h},entryIds:Array.from(p),perPanelStats:u}}}const vn="span_panel_favorites_view_state";function mn(e){try{localStorage.setItem(vn,JSON.stringify(e))}catch{}}var bn;const yn="favorites";let wn=bn=class extends Pe{constructor(){super(...arguments),this.narrow=!1,this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._listColumns=qe(),this._favorites={},this._favoritesViewState={expanded:{activity:[],area:[]}},this._favoritesPanelStats=[],this._dashboardTab=new Jt,this._monitoringTab=new sn,this._listDashCtrl=new Qt,this._listCtrl=new gn(this._listDashCtrl),this._favCache=new Be,this._favCtrl=new fn,this._favoritesMonitoringTabs=new Map,this._errorStore=new Le,this._watchedPanelId=null,this._discovering=!1,this._refreshSeq=0,this._areaUnsub=null,this._areaSubscribing=!1,this._onVisibilityChange=null,this._onFavoritesChanged=null,this._deviceRegistryUnsub=null,this._pendingTabRender=!1,this._recoverTimer=null,this._persistFavoritesViewStateTimer=null,this._tabRenderScheduler=function(e){let t=null,n=!1;return async function i(){if(t)return n=!0,void await t.catch(()=>{});const s=(async()=>{try{await e()}finally{t=null,n&&(n=!1,await i())}})();t=s,await s}}(async()=>this._renderTab()),this._beginRender=function(){let e=0;return()=>{e+=1;const t=e;return()=>e!==t}}()}get _root(){const e=this.shadowRoot;if(!e)throw new Error("span-panel: shadow root is not available");return e}connectedCallback(){super.connectedCallback(),this._dashboardTab.errorStore=this._errorStore,this._listDashCtrl.errorStore=this._errorStore,this._favCache.errorStore=this._errorStore,this._monitoringTab.errorStore=this._errorStore,this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this.hass&&this._recoverIfNeeded()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._onFavoritesChanged=()=>{this._refreshFavorites()},document.addEventListener(Ge,this._onFavoritesChanged),this._subscribeDeviceRegistry()}disconnectedCallback(){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._areaSubscribing=!1,this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._onFavoritesChanged&&(document.removeEventListener(Ge,this._onFavoritesChanged),this._onFavoritesChanged=null),this._unsubscribeDeviceRegistry(),this._persistFavoritesViewStateTimer&&(clearTimeout(this._persistFavoritesViewStateTimer),this._persistFavoritesViewStateTimer=null),this._recoverTimer&&(clearTimeout(this._recoverTimer),this._recoverTimer=null),this._errorStore.dispose(),super.disconnectedCallback()}firstUpdated(){this.hass&&!this._discovered&&this._discoverPanels()}updated(e){if(e.has("hass")){const t=e.get("hass");this._dashboardTab.hass=this.hass,this._listDashCtrl.hass=this.hass,this._errorStore.updateHass(this.hass),this._discovered?this._root.getElementById("tab-content")||this._scheduleTabRender():this._discoverPanels(),!t&&this.hass&&this._subscribeDeviceRegistry()}if(this._discovered&&(e.has("_discovered")||e.has("_activeTab")||e.has("_selectedPanelId")||e.has("_chartMetric")||e.has("_listColumns"))){if(this._isFavoritesView&&"dashboard"===this._activeTab)return void(this._activeTab="activity");this._scheduleTabRender()}if(e.has("_selectedPanelId")&&(this._selectedPanelId!==yn&&this._selectedPanelId?(this._updatePanelStatusWatch(),this._listDashCtrl.setFavoritesPerPanelInfo(null)):(this._errorStore.clearPanelStatusWatch(),this._watchedPanelId=null)),this._discovered&&(e.has("_panels")||e.has("_selectedPanelId"))){const e=this.shadowRoot?.getElementById("panel-select");e&&null!==this._selectedPanelId&&e.value!==this._selectedPanelId&&(e.value=this._selectedPanelId)}if(e.has("hass")&&this._discovered&&("activity"===this._activeTab||"area"===this._activeTab)){const e=this._root.getElementById("tab-content"),t=this._listDashCtrl.topology;if(e&&t){this._listCtrl.updateCollapsedRows(e,this.hass,t,this._buildDashboardConfig());const n=e.querySelector("span-side-panel");n&&(n.hass=this.hass,n.errorStore=this._errorStore)}}}setConfig(e){}render(){var i,s,o;if(i=this.hass?.language,e=i&&t[i]?i:"en",!this.hass)return le`
Span Panel
@@ -153,7 +153,7 @@ const Ee=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)} >
- `}_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,je(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="${jt(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 _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,p=new Set(Object.keys(a.circuits)),u=this._favoritesViewState.expanded[h].filter(e=>p.has(e));this._listCtrl.setViewName(h),this._listCtrl.setInitialExpansion(u),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(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,je(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="${jt(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,p=new Set(Object.keys(a.circuits)),u=this._favoritesViewState.expanded[h].filter(e=>p.has(e));this._listCtrl.setViewName(h),this._listCtrl.setInitialExpansion(u),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` :host { color: var(--primary-text-color); } diff --git a/src/panel/span-panel.ts b/src/panel/span-panel.ts index 6d08916..c4b10ff 100644 --- a/src/panel/span-panel.ts +++ b/src/panel/span-panel.ts @@ -100,6 +100,12 @@ 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 { @@ -230,7 +236,7 @@ export class SpanPanelElement extends LitElement { this._onVisibilityChange = (): void => { if (document.visibilityState !== "visible" || !this._discovered || !this.hass) return; - this._scheduleTabRender(); + this._recoverIfNeeded(); }; document.addEventListener("visibilitychange", this._onVisibilityChange); @@ -267,6 +273,10 @@ 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(); } @@ -871,6 +881,58 @@ export class SpanPanelElement extends LitElement { */ private readonly _beginRender = makeRenderToken(); + /** + * Visibility-restore recovery. When the browser tab is backgrounded + * and HA's WebSocket drops/reconnects, a tab re-render kicked off on + * ``visibilitychange`` can silently bail out mid-flight — a WS call + * resolving empty, a supersession race, or a cache returning null + * without throwing — and the Favorites view in particular is left + * with a blank ``#tab-content`` because it clears the container + * before awaiting its async build steps. + * + * Wrap ``_scheduleTabRender`` in a try/catch **and** verify the + * container produced content afterwards; if either fails, retry + * with backoff so the render can catch a freshly-reconnected WS. + * Mirrors the pre-LitElement ``_recoverIfNeeded`` helper removed + * during the c4154d2 refactor. + */ + private async _recoverIfNeeded(attempt = 0): Promise { + if (!this._discovered || !this.hass) return; + const MAX_ATTEMPTS = 3; + const BACKOFF_BASE_MS = 2000; + + const scheduleRetry = (): void => { + if (attempt >= MAX_ATTEMPTS) return; + if (this._recoverTimer) clearTimeout(this._recoverTimer); + this._recoverTimer = setTimeout( + () => { + this._recoverTimer = null; + this._recoverIfNeeded(attempt + 1); + }, + BACKOFF_BASE_MS * (attempt + 1) + ); + }; + + try { + await this._scheduleTabRender(); + } catch { + scheduleRetry(); + return; + } + + // A render that completed without throwing but left the tab + // container empty is the symptom we are recovering from. Every + // successful render path produces at least one child node — the + // empty-favorites state appends a ``

`` with ``list.no_results``, + // the error paths append a ``

`` with the error message, and the + // normal dashboard/activity/area/monitoring renders produce their + // respective headers/grids. Zero children means a silent bailout. + const container = this._root.getElementById("tab-content"); + if (container && container.childNodes.length === 0) { + scheduleRetry(); + } + } + /** * Coalesce tab-render requests. If a render is in-flight, remember * that another was requested and run exactly one follow-up once the From 74ddb32fd445b608d0856d10e71903b089642c7f Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:25:12 -0700 Subject: [PATCH 2/6] fix(list): close gap between relay toggle and power value The .list-row flex gap of 10px was applied between every pair of row children, including the ON/OFF badge (or toggle-pill) and the power reading. On narrow displays that gap came out of the flex:1 circuit name, truncating the label earlier than necessary. Negative margin-left on .list-power-value cancels the preceding gap without touching the gap before the gear or after the power value, so the row layout stays intact and the freed space is absorbed by .list-circuit-name. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 2 +- src/card/card-styles.ts | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 39b2709..6586f14 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -133,4 +133,4 @@ const ze=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(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 }\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 .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 min-width: 70px;\n text-align: right;\n flex-shrink: 0;\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 /* ── 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")),_=c(p,"0","59",i("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),a.appendChild(u.wrap),a.appendChild(g.wrap),a.appendChild(_.wrap),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.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(_)){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;"); + `}};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 }\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 .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 min-width: 70px;\n text-align: right;\n flex-shrink: 0;\n /* Cancel the .list-row flex gap against the preceding relay\n control (toggle-pill or list-status-badge). The 10px separator\n between the ON/OFF badge and the reading robs horizontal space\n from .list-circuit-name on narrow rows — close it up so the\n ellipsis threshold pushes out. */\n margin-left: -10px;\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 /* ── 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")),_=c(p,"0","59",i("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),a.appendChild(u.wrap),a.appendChild(g.wrap),a.appendChild(_.wrap),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.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(_)){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;"); diff --git a/dist/span-panel.js b/dist/span-panel.js index 2b562cb..76807e2 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -102,7 +102,7 @@ const Ee=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)} .retry-btn:hover { opacity: 0.8; } - `,m([Me()],Xe.prototype,"_errors",void 0),Xe=m([Ee("span-error-banner")],Xe);const it=g.power;function st(e){return it.unit(e)}function ot(e){return(e<0?"-":"")+it.format(e)}function rt(e){return(Math.abs(e)/1e3).toFixed(1)}function at(e){return Math.ceil(e/2)}function lt(e){return e%2==0?1:0}function ct(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return at(t)===at(n)?"row-span":lt(t)===lt(n)?"col-span":"row-span"}function dt(e){const t=e.chart_metric??s;return g[t]??g[s]}function ht(e,t){const n=function(e){return dt(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class pt{constructor(){this._status=null,this._lastFetch=0,this._inflight=null,this._generation=0,this._errorStore=null,this._retry=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}async fetch(e,t){const i=Date.now();if(this._inflight&&this._inflight.gen===this._generation)return this._inflight.promise;if(this._status&&i-this._lastFetch<3e4)return this._status;const s=this._generation,o=(async()=>{try{const i={};t&&(i.config_entry_id=t);const o={type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0},r=this._retry?await this._retry.callWS(e,o,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await e.callWS(o),l=r?.response??null;return s===this._generation&&(this._status=l,this._lastFetch=Date.now()),l}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:n("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 ut{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 pt,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 gt(e,t){return e?.circuits?e.circuits[t]??null:null}function _t(e){return!!e&&void 0!==e.continuous_threshold_pct}function ft(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"),_t(t)&&s.push("circuit-custom-monitoring"),s.join(" ")}function vt(e,t,i,s,o,r,a,d,h,p=!1){const u=t.entities?.power,g=u?r.states[u]:null,_=g&&parseFloat(g.state)||0,m=t.device_type===c||_<0,b=t.entities?.switch,y=b?r.states[b]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===l,x=t.breaker_rating_a,S=x?`${Math.round(x)}A`:"",$=Oe(t.name||n("grid.unknown")),C=dt(a);let P;if("current"===C.entityRole){const e=t.entities?.current,n=e?r.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${C.format(i)}A`}else P=`${ot(_)}${st(_)}`;const k=h||"unknown";let E="";if("unknown"!==k){const e=f[k]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"},t=Oe(e.label()),n=Oe(e.icon),i=Oe(e.color);if(e.icon2){E=`\n \n \n `}else if(e.textLabel){E=`\n \n ${Oe(e.textLabel)}\n `}else E=``}const z=d&&_t(d)?v:"#555",A=``;let N="",M=d?.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 ${$}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${E}\n ${A}\n
\n
\n
\n `}function mt(e,t){return`\n
\n \n
\n `}const bt={names:["power","battery power"],suffixes:["_power"]},yt={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},wt={names:["state of energy"],suffixes:["_soe_kwh"]},xt={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function St(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 $t(e){return St(e,bt)}function Ct(e){return St(e,yt)}function Pt(e){return St(e,wt)}function kt(e){return St(e,xt)}function Et(e,t,i){const s=!1!==i.show_battery,o=!1!==i.show_evse;if(!e.sub_devices)return"";const r=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===d&&!s)&&!(e.type===h&&!o));if(0===r.length)return"";const a=r.filter(([,e])=>e.type===h).length;let l=0,c="";for(const[e,s]of r){const o=s.type===h?n("subdevice.ev_charger"):s.type===d?n("subdevice.battery"):n("subdevice.fallback"),r=$t(s),p=r?t.states[r]:void 0,u=p&&parseFloat(p.state)||0,g=s.type===d,_=s.type===h,f=g?Ct(s):null,v=g?Pt(s):null,m=g?kt(s):null,b=zt(s,t,i,new Set([r,f,v,m].filter(e=>null!==e))),y=At(e,s,g,r,f,v);let w="";g?w="sub-device-bess":_&&(l++,l===a&&a%2==1&&(w="sub-device-full")),c+=`\n
\n
\n ${Oe(o)}\n ${Oe(s.name||"")}\n ${r?`${ot(u)} ${st(u)}`:""}\n \n
\n ${y}\n ${b}\n
\n `}return c}function zt(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 ${Oe(l)}:\n ${Oe(d)}\n
\n `}return o}function At(e,t,i,s,o,r){if(i){const t=[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!o},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!r},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!s}].filter(e=>e.available);return`\n
\n ${t.map(e=>`\n
\n
${Oe(e.title)}
\n
\n
\n `).join("")}\n
\n `}return s?`
`:""}function Nt(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 Mt(e){const t=r[e];return t?t.ms:r[o].ms}function It(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function Tt(e){return Math.max(500,Math.floor(e/5e3))}function Dt(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 Ft(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 Lt(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 Ht(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=It(i),l=Tt(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,Ft(t,a,l))}}}function Ot(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:$t(i)};i.type===d&&(e.soc=Ct(i),e.soe=Pt(i));for(const[i,s]of Object.entries(e))s&&t.push({entityId:s,key:`${p}${n}_${i}`,devId:n})}return t}async function Rt(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=ht(i,n);if(!t)continue;let o;o=s&&s.has(e)?Mt(s.get(e)):Nt(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 Ot(t)){let t;t=o&&o.has(s)?Mt(o.get(s)):Nt(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(Lt(e,n.entityIds,n.uuidByEntity,t,i)):a.push(Ht(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(a)}function qt(e,t,n,i,o,r,a,l,c){const{options:d,series:h}=function(e,t,n,i,o,r=!1){n||(n=g[s]);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}}],_=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};h?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),o&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),u.push({type:"line",data:[[d,.8*o],[c,.8*o]],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,o],[c,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const v={xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,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:v,series:u}}(n,i,o,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 _=e.clientHeight;u.height=(_>0?_:p)+"px",u.hass=t,u.options=d,u.data=h}function jt(e){return"function"==typeof globalThis.CSS?.escape?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function Ut(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=rt(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=rt(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=rt(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=rt(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 _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}function Wt(e,t,i,s,o,r){if(!e||!i||!t)return;const a=Nt(s);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],s=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(s))}!function(e,t,n,i,s){const o=e.querySelector(".panel-stats");o&&Ut(o,t,n,i,s)}(e,t,i,s,d);const h=dt(s),p="current"===h.entityRole;for(const[s,d]of Object.entries(i.circuits)){const i=e.querySelector(`.circuit-slot[data-uuid="${jt(s)}"]`);if(!i)continue;const u=d.entities?.power,g=u?t.states[u]:null,_=g&&parseFloat(g.state)||0,v=d.device_type===c||_<0,m=d.entities?.switch,b=m?t.states[m]:null,y=b?"on"===b.state:(g?.attributes?.relay_state||d.relay_state)===l,w=i.querySelector(".power-value");if(w)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${h.format(i)}A`}else w.innerHTML=`${ot(_)}${st(_)}`;const x=i.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=x.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",v),d.always_on)S="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const $=f[S]??f.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",$.icon),C.style.color=$.color,C.title=$.label());const P=i.querySelector(".shedding-icon-secondary");P&&($.icon2?(P.setAttribute("icon",$.icon2),P.style.color=$.color,P.style.display=""):P.style.display="none");const k=i.querySelector(".shedding-label");k&&($.textLabel?(k.textContent=$.textLabel,k.style.color=$.color,k.style.display=""):k.style.display="none");const E=i.querySelector(".chart-container");if(E){const e=o.get(s)||[],n=i.classList.contains("circuit-col-span")?200:100,l=r?.has(s)?Mt(r.get(s)):a,p=d.device_type===c;qt(E,t,e,l,h,v,n,d.breaker_rating_a??void 0,p)}}}class Gt{get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}constructor(){this._errorStore=null,this._retry=null,this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const s={type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0},o=this._retry?await this._retry.callWS(e,s,{errorId:"fetch:graph_settings",errorMessage:n("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:n("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 Vt(e,t){if(!e)return o;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??o}function Bt(e,t){if(!e)return o;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??o}class Qt{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new pt,this.monitoringMultiCache=new ut,this.graphSettingsCache=new Gt,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,Vt(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,Bt(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:n("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,Vt(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,Bt(i,s))}}async loadHistory(){await Rt(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)??o;if(!r[i]?.useRealtime)continue;const s=ht(n,this._config);if(!s)continue;const a=this._hass.states[s];if(!a)continue;const l=parseFloat(a.state);if(isNaN(l))continue;const c=Mt(i),d=It(c),h=Tt(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 Ot(this._topology))i.has(t)&&s.add(e);const o=new Map;try{await Rt(this._hass,this._topology,this._config,o,t,i);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:n("error.history_failed"),persistent:!1})}}updateDOM(e){this._hass&&this._topology&&this._config&&(Wt(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=Nt(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${jt(i)}"]`);if(!n)continue;const l=$t(a);if(l){const e=t.states[l],i=e&&parseFloat(e.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${ot(i)} ${st(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");qt(e,t,a,o?.has(i)?Mt(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="${jt(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 i=e.target,s=i?.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:n("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 r=i.dataset.uuid;if(r&&this._topology){const e=this._topology.circuits[r];if(e){const t=this._favRefs?.[r]??null,n=t&&"circuit"===t.kind?t.targetId:r,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??o,p=a?.circuits?.[n],u=p?{...p,globalHorizon:h}:{horizon:h,has_override:!1,globalHorizon:h},g=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,_=null!==t||(this._panelFavorites?.circuitUuids.has(n)??!1),f=this._inFavoritesView||null!==this._panelFavorites;return void s.open({...e,uuid:n,monitoringInfo:d,showMonitoring:this._showMonitoring,graphHorizonInfo:u,showFavorites:f,favoritePanelDeviceId:g,isFavorite:_,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 r;t?r=await this._fetchGraphSettingsFresh(i):(await this.graphSettingsCache.fetch(this._hass,i),r=this.graphSettingsCache.settings);const l=r?.global_horizon??o,c=r?.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 i={type:"call_service",domain:a,service:"get_graph_settings",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:graph_settings",errorMessage:n("error.graph_settings_failed")}):await this._hass.callWS(i);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 i={type:"call_service",domain:a,service:"get_monitoring_status",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await this._hass.callWS(i),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()}}const Kt='\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 }\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 .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 min-width: 70px;\n text-align: right;\n flex-shrink: 0;\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 /* ── 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';class Jt{constructor(){this._ctrl=new Qt,this._container=null,this._onGearClick=null,this._onToggleClick=null,this._onSidePanelClosed=null,this._onGraphSettingsChanged=null}get hass(){return this._ctrl.hass}set hass(e){this._ctrl.hass=e}set errorStore(e){this._ctrl.errorStore=e}setPanelFavorites(e){this._ctrl.setPanelFavorites(e)}async render(e,t,i,s,o){let r,a;this.stop(),this._ctrl.reset(),this._ctrl.showMonitoring=!0,this._container=e,this._ctrl.hass=t;try{const e=await Ye(t,i);r=e.topology,a=e.panelSize}catch(t){return void(e.innerHTML=`

${Oe(t.message)}

`)}this._ctrl.init(r,s,t,o??null),await this._ctrl.monitoringCache.fetch(t,o??null),await this._ctrl.fetchAndBuildHorizonMaps();const l=Math.ceil(a/2),c=this._ctrl.monitoringCache.status,d=nt(r,s),h=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),i=Object.values(e.mains??{}),s=[...t,...i],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 ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${o>0?`${o} ${n(o>1?"status.warnings":"status.warning")}`:""}\n ${r>0?`${r} ${n(r>1?"status.alerts":"status.alert")}`:""}\n ${a>0?`${a} ${n(a>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(c),p=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":ct(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=at(Math.max(...n));0===lt(e)?a.add(i):l.add(i)}function c(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=s?gt(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+=vt(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+=mt(e,"2"));else{const{monInfo:t,sheddingPriority:s}=c(h);d+=vt(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+=mt(e,"3"));else{const{monInfo:t,sheddingPriority:s}=c(p);d+=vt(p.uuid,p.circuit,e,"3",p.layout,n,i,t,s)}d+=`
${s}
`}return d}(r,l,t,s,c),u=Et(r,t,s);e.innerHTML=`\n \n ${d}\n ${h}\n ${u?`
${u}
`:""}\n ${!1!==s.show_panel?`\n
\n ${p}\n
\n `:""}\n \n `,this._onGearClick=t=>{this._ctrl.onGearClick(t,e)},this._onToggleClick=t=>{this._ctrl.onToggleClick(t,e)},e.addEventListener("click",this._onGearClick),e.addEventListener("click",this._onToggleClick),this._onSidePanelClosed=()=>{this._ctrl.monitoringCache.invalidate(),this._ctrl.graphSettingsCache.invalidate()},e.addEventListener("side-panel-closed",this._onSidePanelClosed),this._onGraphSettingsChanged=()=>this._ctrl.onGraphSettingsChanged(e),e.addEventListener("graph-settings-changed",this._onGraphSettingsChanged);try{await this._ctrl.loadHistory()}catch{}this._ctrl.updateDOM(e);const g=e.querySelector(".slide-confirm");g&&(this._ctrl.bindSlideConfirm(g,e),e.classList.add("switches-disabled")),this._ctrl.setupResizeObserver(e,e),this._ctrl.startIntervals(e)}stop(){this._ctrl.stopIntervals(),this._container&&(this._onGearClick&&(this._container.removeEventListener("click",this._onGearClick),this._onGearClick=null),this._onToggleClick&&(this._container.removeEventListener("click",this._onToggleClick),this._onToggleClick=null),this._onSidePanelClosed&&(this._container.removeEventListener("side-panel-closed",this._onSidePanelClosed),this._onSidePanelClosed=null),this._onGraphSettingsChanged&&(this._container.removeEventListener("graph-settings-changed",this._onGraphSettingsChanged),this._onGraphSettingsChanged=null))}}const Xt="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Zt="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Yt="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",en="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",tn="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function nn(e,t,n,i,s){return`\n ${i}\n `}class sn{constructor(){this.errorStore=null,this._debounceTimer=null,this._configEntryId=null,this._notifyCloseHandler=null,this._headerHTML=""}stop(){this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null),this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null)}async render(e,t,i,s=""){let o;void 0!==i&&(this._configEntryId=i),this._headerHTML=s,this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=function(e){if(!e||"object"!=typeof e)return null;const t=e,n={};return"boolean"==typeof t.enabled&&(n.enabled=t.enabled),t.global_settings&&"object"==typeof t.global_settings&&(n.global_settings=t.global_settings),t.circuits&&"object"==typeof t.circuits&&(n.circuits=t.circuits),t.mains&&"object"==typeof t.mains&&(n.mains=t.mains),n}(n?.response)}catch(e){console.warn("SPAN Panel: monitoring status fetch failed",e),o=null}const r=o?.global_settings??{},l=!0===o?.enabled,c=o?.circuits??{},d=o?.mains??{},h=new Set;for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&h.add(e);const p=new Set(["notify","send_message"]);for(const e of Object.keys(t.services?.notify||{}))p.has(e)||h.add(`notify.${e}`);h.add("event_bus");const u=[...h].sort(),g=r.notify_targets??"",_=("string"==typeof g?g.split(","):g).map(e=>e.trim()).filter(Boolean),f=u.length>0&&u.every(e=>_.includes(e)),v=r.notification_title_template??"SPAN: {name} {alert_type}",m=r.notification_message_template??"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",b=r.notification_priority??"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name??"").localeCompare(t.name??"")),w=Object.entries(d),x=[...y,...w],S=x.length>0&&x.every(([,e])=>!1!==e.monitoring_enabled),$=x.some(([,e])=>!1!==e.monitoring_enabled),C=y.map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),P=Object.entries(d).map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","mains")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");e.innerHTML=`\n ${this._headerHTML}\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n \n
\n \n
\n ${0===u.length?`
${n("notification.no_targets")}
`:u.map(e=>{const i=_.includes(e),s="event_bus"===e,o=s?null:t.states[e],r=o?.attributes?.friendly_name,a=s?n("notification.event_bus_target"):r?`${Oe(r)} (${Oe(e)})`:Oe(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m} {local_time}\n
\n
\n ${n("notification.event_bus_help")} span_panel_current_alert\n ${n("notification.event_bus_payload")} alert_source alert_id\n alert_name alert_type current_a\n breaker_rating_a threshold_pct utilization_pct\n panel_serial window_duration_s local_time\n
\n\n
\n ${n("notification.test_label")}\n \n \n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${P}\n ${C}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const k=e.querySelector("#toggle-all-circuits");k&&!S&&$&&(k.indeterminate=!0);const E=e.querySelector("#notify-all-targets");if(E&&u.length>0){const e=_.length>0;!f&&e&&(E.indeterminate=!0)}this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,d),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),s=e.querySelector("#global-fields"),o=e.querySelector("#global-status"),r=()=>{const t=[["continuous_threshold_pct","#g-continuous"],["spike_threshold_pct","#g-spike"],["window_duration_m","#g-window"],["cooldown_duration_m","#g-cooldown"]],n={};for(const[i,s]of t){const t=e.querySelector(s);if(!t)return null;const o=parseInt(t.value,10);if(Number.isNaN(o))return null;n[i]=o}return n},a=(e,t,i)=>{if(!e)return;const s=t instanceof Error?t.message:i;e.textContent=`${n("error.prefix")} ${s}`,e.style.color="var(--error-color, #f44336)"},l=()=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i=r();if(i)try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a(o,e,n("error.failed_save"))}else a(o,null,n("error.failed_save"))},u)};i&&i.addEventListener("change",async()=>{const o=i.checked;s&&(s.style.opacity=o?"":"0.4",s.style.pointerEvents=o?"":"none");const l=e.querySelector("#global-status");try{if(o){const e=r();if(!e)return void a(l,null,n("error.failed"));await this._callSetGlobal(t,e)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void a(l,e,n("error.failed"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",l)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),s=e.querySelector("#notify-target-dropdown"),o=e.querySelector("#notify-target-label");if(!i||!s)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==s.style.display;s.style.display=t?"none":"block"});const r=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(s.style.display="none")};document.addEventListener("click",r),this._notifyCloseHandler=r;const a=()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);if(o){const e=i.map(e=>"event_bus"===e?n("notification.event_bus_target"):e);o.textContent=e.length?e.join(", "):n("notification.none_selected")}const s=e.querySelector("#notify-all-targets");if(s){const t=[...e.querySelectorAll(".notify-target-cb")];s.checked=t.length>0&&t.every(e=>e.checked),s.indeterminate=!s.checked&&t.some(e=>e.checked)}this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch(e){console.warn("SPAN Panel: notification targets save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)},l=e.querySelector("#notify-all-targets");l&&l.addEventListener("change",()=>{for(const t of e.querySelectorAll(".notify-target-cb"))t.checked=l.checked;const t=e.querySelector("#notify-target-btn");t&&(t.style.opacity=l.checked?"0.4":"",t.style.pointerEvents=l.checked?"none":""),l.checked&&(s.style.display="none"),a()});for(const t of e.querySelectorAll(".notify-target-cb"))t.addEventListener("change",()=>{a()})}_bindNotificationSettings(e,t){const i=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),o=e.querySelector("#g-message-template"),r=(e,i)=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:i})}catch(e){console.warn("SPAN Panel: notification settings save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)};i&&i.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:i.value}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: notification priority change failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),o&&o.addEventListener("input",()=>{r("notification_message_template",o.value)});const l=e.querySelector("#test-notification-btn"),c=e.querySelector("#test-notification-status");l&&l.addEventListener("click",async()=>{l.disabled=!0,c&&(c.textContent=n("notification.test_sending"),c.style.color="var(--secondary-text-color)");try{this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null);const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value).join(", ");await this._callSetGlobal(t,{notify_targets:i});const s={};this._configEntryId&&(s.config_entry_id=this._configEntryId),await t.callWS({type:"call_service",domain:a,service:"test_notification",service_data:s}),c&&(c.textContent=n("notification.test_sent"),c.style.color="var(--success-color, #4caf50)")}catch(e){if(c){const t=e instanceof Error?e.message:n("error.failed");c.textContent=`${n("error.prefix")} ${t}`,c.style.color="var(--error-color, #f44336)"}}finally{l.disabled=!1}})}_bindToggleAll(e,t,i,s){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const r=o.checked,l=[...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: circuit monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})),...Object.keys(s).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: mains monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}))];await Promise.all(l),await this.render(e,t)})}_bindMainsToggles(e,t){for(const i of e.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: mains threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const i of e.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: circuit threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const i=new Map;for(const s of e.querySelectorAll(".threshold-input"))s.addEventListener("input",()=>{const o=`${s.dataset.entity}-${s.dataset.field}`,r=i.get(o);r&&clearTimeout(r),i.set(o,setTimeout(async()=>{const i=parseInt(s.value,10);if(!i||i<1)return;const o=s.dataset.entity,r=s.dataset.field,l=s.dataset.type,c="mains"===l?"set_mains_threshold":"set_circuit_threshold",d="mains"===l?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[d]:o,[r]:i})}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: threshold input save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),s.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity;if(!i)return;const s=n.dataset.type,o="mains"===s?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===s?{leg:i}:{circuit_id:i});await t.callService(a,o,r),await this.render(e,t)})}}function on(e=""){const t=e?` value="${Oe(e)}"`:"",i=e?"":"display:none;";return`\n
\n \n \n
\n `}function rn(e,t,i,s,o,r,a){const c=t.entities?.power,d=c?i.states[c]:null,h=d&&parseFloat(d.state)||0,p=t.entities?.switch,u=p?i.states[p]:null,g=u?"on"===u.state:(d?.attributes?.relay_state||t.relay_state)===l,_=t.breaker_rating_a,m=_?`${Math.round(_)}A`:"",b=Oe(t.name||n("grid.unknown")),y=dt(s),w="current"===y.entityRole;let x;if(g)if(w){const e=t.entities?.current,n=e?i.states[e]:null,s=n&&parseFloat(n.state)||0;x=`${y.format(s)}A`}else x=`${ot(h)}${st(h)}`;else x="";const S=r||"unknown";let $="";if("unknown"!==S){const e=f[S]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};$=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}let C="",P=o?.utilization_pct??null;if(null==P&&t.breaker_rating_a){const e=t.entities?.current,n=e?i.states[e]:null,s=n?Math.abs(parseFloat(n.state)||0):0;P=Math.round(s/t.breaker_rating_a*1e3)/10}if(null!=P){C=`=80?"utilization-warning":"utilization-normal"}">${Math.round(P)}%`}const k=!!o&&_t(o)?v:"#555",E=``,z=!1!==t.is_user_controllable&&!!t.entities?.switch?`
\n ${n(g?"grid.on":"grid.off")}\n \n
`:`${g?"ON":"OFF"}`;return`\n
\n ${m?`${m}`:""}\n ${C}\n ${b}\n ${$}\n ${z}\n \n ${x}\n \n ${E}\n \n
\n `}function an(e,t,n,i,s){const o=t.entities?.power,r=o?n.states[o]:null,a=r&&parseFloat(r.state)||0,d=t.device_type===c||a<0,h=t.entities?.switch,p=h?n.states[h]:null,u=ft(0,s,p?"on"===p.state:(r?.attributes?.relay_state||t.relay_state)===l,d),g=Oe(e);return`\n
\n
\n
\n
\n
\n `}function ln(e){return`
${Oe(e)}
`}function cn(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)===l;let c;if("current"===(n.chart_metric||"power")){const n=e.entities?.current,i=n?t.states[n]:null;c=i?Math.abs(parseFloat(i.state)||0):0}else c=r?Math.abs(parseFloat(r.state)||0):0;return{isOn:a,value:c}}function dn(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 hn(e,t,n,i){const s=cn(e,n,i),o=cn(t,n,i);return s.isOn&&!o.isOn?-1:!s.isOn&&o.isOn?1:o.value-s.value}function pn(e,t,n){return e.sort((e,i)=>hn(e[1],i[1],t,n))}function un(e){return e.entities?.current??e.entities?.power??""}class gn{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=pn(Object.entries(n.circuits),t,i);let a=o+on(this._searchQuery);a+=`
`;for(const[e,n]of r){const o=gt(s,un(n)),r=dn(n,t),l=this._expandedUuids.has(e);a+=`
`,a+=rn(e,n,t,i,o,r,l),l&&(a+=an(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,i,s,o,r){this._unbindEvents(),this._hass=t,this._topology=i,this._config=s,this._monitoringStatus=o;const a=n("list.unassigned_area"),l=new Map;for(const[e,t]of Object.entries(i.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+on(this._searchQuery);d+=`
`;for(const e of c){const n=l.get(e);if(!n)continue;const i=pn(n,t,s);d+=ln(e);for(const[e,n]of i){const i=gt(o,un(n)),r=dn(n,t),a=this._expandedUuids.has(e);d+=`
`,d+=rn(e,n,t,s,i,r,a),a&&(d+=an(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,i,s){const o=dt(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=i.circuits[a];if(!l)continue;const{isOn:c,value:d}=cn(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=`${ot(i)}${st(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=n(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)=>hn(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,i,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=jt(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=gt(this._monitoringStatus,un(t)),o=an(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()}}}function _n(e,t){return`${e}|${t}`}class fn{async build(e,t,n,i){const s=new Map;for(const e of n)s.set(e.id,e);const o=i?new We(i):null,r=[];for(const[n,i]of Object.entries(t)){if(!((i?.circuits?.length??0)>0||(i?.sub_devices?.length??0)>0))continue;const t=s.get(n);t&&r.push((async()=>{try{const i=await Ye(e,n,o);return{panelDeviceId:n,panel:t,topology:i.topology}}catch(e){return console.warn("SPAN Panel: favorites topology fetch failed",n,e),{panelDeviceId:n,panel:t,topology:null}}})())}const a=(await Promise.all(r)).filter(e=>null!==e.topology),l=a.length>1,c={},d={},h={},p=new Set,u=[];for(const{panelDeviceId:e,panel:n,topology:i}of a){if(!i)continue;const s=n.config_entries?.[0]??null;s&&p.add(s);const o=n.name_by_user??n.name??i.device_name??"";u.push({panelDeviceId:e,panelName:o,topology:i});const r=t[e],a=r?.circuits??[],g=r?.sub_devices??[];for(const t of a){const n=i.circuits?.[t];if(!n)continue;const r=_n(e,t),a=l&&o?`${o} · ${n.name}`:n.name;c[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"circuit",targetId:t,configEntryId:s}}for(const t of g){const n=i.sub_devices?.[t];if(!n)continue;const r=_n(e,t),a=l&&o&&n.name?`${o} · ${n.name}`:n.name??t;d[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"sub_device",targetId:t,configEntryId:s}}}return{topology:{circuits:c,sub_devices:d,panel_entities:{},device_name:"",_favoriteRefs:h},entryIds:Array.from(p),perPanelStats:u}}}const vn="span_panel_favorites_view_state";function mn(e){try{localStorage.setItem(vn,JSON.stringify(e))}catch{}}var bn;const yn="favorites";let wn=bn=class extends Pe{constructor(){super(...arguments),this.narrow=!1,this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._listColumns=qe(),this._favorites={},this._favoritesViewState={expanded:{activity:[],area:[]}},this._favoritesPanelStats=[],this._dashboardTab=new Jt,this._monitoringTab=new sn,this._listDashCtrl=new Qt,this._listCtrl=new gn(this._listDashCtrl),this._favCache=new Be,this._favCtrl=new fn,this._favoritesMonitoringTabs=new Map,this._errorStore=new Le,this._watchedPanelId=null,this._discovering=!1,this._refreshSeq=0,this._areaUnsub=null,this._areaSubscribing=!1,this._onVisibilityChange=null,this._onFavoritesChanged=null,this._deviceRegistryUnsub=null,this._pendingTabRender=!1,this._recoverTimer=null,this._persistFavoritesViewStateTimer=null,this._tabRenderScheduler=function(e){let t=null,n=!1;return async function i(){if(t)return n=!0,void await t.catch(()=>{});const s=(async()=>{try{await e()}finally{t=null,n&&(n=!1,await i())}})();t=s,await s}}(async()=>this._renderTab()),this._beginRender=function(){let e=0;return()=>{e+=1;const t=e;return()=>e!==t}}()}get _root(){const e=this.shadowRoot;if(!e)throw new Error("span-panel: shadow root is not available");return e}connectedCallback(){super.connectedCallback(),this._dashboardTab.errorStore=this._errorStore,this._listDashCtrl.errorStore=this._errorStore,this._favCache.errorStore=this._errorStore,this._monitoringTab.errorStore=this._errorStore,this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this.hass&&this._recoverIfNeeded()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._onFavoritesChanged=()=>{this._refreshFavorites()},document.addEventListener(Ge,this._onFavoritesChanged),this._subscribeDeviceRegistry()}disconnectedCallback(){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._areaSubscribing=!1,this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._onFavoritesChanged&&(document.removeEventListener(Ge,this._onFavoritesChanged),this._onFavoritesChanged=null),this._unsubscribeDeviceRegistry(),this._persistFavoritesViewStateTimer&&(clearTimeout(this._persistFavoritesViewStateTimer),this._persistFavoritesViewStateTimer=null),this._recoverTimer&&(clearTimeout(this._recoverTimer),this._recoverTimer=null),this._errorStore.dispose(),super.disconnectedCallback()}firstUpdated(){this.hass&&!this._discovered&&this._discoverPanels()}updated(e){if(e.has("hass")){const t=e.get("hass");this._dashboardTab.hass=this.hass,this._listDashCtrl.hass=this.hass,this._errorStore.updateHass(this.hass),this._discovered?this._root.getElementById("tab-content")||this._scheduleTabRender():this._discoverPanels(),!t&&this.hass&&this._subscribeDeviceRegistry()}if(this._discovered&&(e.has("_discovered")||e.has("_activeTab")||e.has("_selectedPanelId")||e.has("_chartMetric")||e.has("_listColumns"))){if(this._isFavoritesView&&"dashboard"===this._activeTab)return void(this._activeTab="activity");this._scheduleTabRender()}if(e.has("_selectedPanelId")&&(this._selectedPanelId!==yn&&this._selectedPanelId?(this._updatePanelStatusWatch(),this._listDashCtrl.setFavoritesPerPanelInfo(null)):(this._errorStore.clearPanelStatusWatch(),this._watchedPanelId=null)),this._discovered&&(e.has("_panels")||e.has("_selectedPanelId"))){const e=this.shadowRoot?.getElementById("panel-select");e&&null!==this._selectedPanelId&&e.value!==this._selectedPanelId&&(e.value=this._selectedPanelId)}if(e.has("hass")&&this._discovered&&("activity"===this._activeTab||"area"===this._activeTab)){const e=this._root.getElementById("tab-content"),t=this._listDashCtrl.topology;if(e&&t){this._listCtrl.updateCollapsedRows(e,this.hass,t,this._buildDashboardConfig());const n=e.querySelector("span-side-panel");n&&(n.hass=this.hass,n.errorStore=this._errorStore)}}}setConfig(e){}render(){var i,s,o;if(i=this.hass?.language,e=i&&t[i]?i:"en",!this.hass)return le` + `,m([Me()],Xe.prototype,"_errors",void 0),Xe=m([Ee("span-error-banner")],Xe);const it=g.power;function st(e){return it.unit(e)}function ot(e){return(e<0?"-":"")+it.format(e)}function rt(e){return(Math.abs(e)/1e3).toFixed(1)}function at(e){return Math.ceil(e/2)}function lt(e){return e%2==0?1:0}function ct(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return at(t)===at(n)?"row-span":lt(t)===lt(n)?"col-span":"row-span"}function dt(e){const t=e.chart_metric??s;return g[t]??g[s]}function ht(e,t){const n=function(e){return dt(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class pt{constructor(){this._status=null,this._lastFetch=0,this._inflight=null,this._generation=0,this._errorStore=null,this._retry=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}async fetch(e,t){const i=Date.now();if(this._inflight&&this._inflight.gen===this._generation)return this._inflight.promise;if(this._status&&i-this._lastFetch<3e4)return this._status;const s=this._generation,o=(async()=>{try{const i={};t&&(i.config_entry_id=t);const o={type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0},r=this._retry?await this._retry.callWS(e,o,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await e.callWS(o),l=r?.response??null;return s===this._generation&&(this._status=l,this._lastFetch=Date.now()),l}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:n("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 ut{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 pt,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 gt(e,t){return e?.circuits?e.circuits[t]??null:null}function _t(e){return!!e&&void 0!==e.continuous_threshold_pct}function ft(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"),_t(t)&&s.push("circuit-custom-monitoring"),s.join(" ")}function vt(e,t,i,s,o,r,a,d,h,p=!1){const u=t.entities?.power,g=u?r.states[u]:null,_=g&&parseFloat(g.state)||0,m=t.device_type===c||_<0,b=t.entities?.switch,y=b?r.states[b]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===l,x=t.breaker_rating_a,S=x?`${Math.round(x)}A`:"",$=Oe(t.name||n("grid.unknown")),C=dt(a);let P;if("current"===C.entityRole){const e=t.entities?.current,n=e?r.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${C.format(i)}A`}else P=`${ot(_)}${st(_)}`;const k=h||"unknown";let E="";if("unknown"!==k){const e=f[k]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"},t=Oe(e.label()),n=Oe(e.icon),i=Oe(e.color);if(e.icon2){E=`\n \n \n `}else if(e.textLabel){E=`\n \n ${Oe(e.textLabel)}\n `}else E=``}const z=d&&_t(d)?v:"#555",A=``;let N="",M=d?.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 ${$}\n
\n
\n \n ${P}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${n(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${E}\n ${A}\n
\n
\n
\n `}function mt(e,t){return`\n
\n \n
\n `}const bt={names:["power","battery power"],suffixes:["_power"]},yt={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},wt={names:["state of energy"],suffixes:["_soe_kwh"]},xt={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function St(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 $t(e){return St(e,bt)}function Ct(e){return St(e,yt)}function Pt(e){return St(e,wt)}function kt(e){return St(e,xt)}function Et(e,t,i){const s=!1!==i.show_battery,o=!1!==i.show_evse;if(!e.sub_devices)return"";const r=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===d&&!s)&&!(e.type===h&&!o));if(0===r.length)return"";const a=r.filter(([,e])=>e.type===h).length;let l=0,c="";for(const[e,s]of r){const o=s.type===h?n("subdevice.ev_charger"):s.type===d?n("subdevice.battery"):n("subdevice.fallback"),r=$t(s),p=r?t.states[r]:void 0,u=p&&parseFloat(p.state)||0,g=s.type===d,_=s.type===h,f=g?Ct(s):null,v=g?Pt(s):null,m=g?kt(s):null,b=zt(s,t,i,new Set([r,f,v,m].filter(e=>null!==e))),y=At(e,s,g,r,f,v);let w="";g?w="sub-device-bess":_&&(l++,l===a&&a%2==1&&(w="sub-device-full")),c+=`\n
\n
\n ${Oe(o)}\n ${Oe(s.name||"")}\n ${r?`${ot(u)} ${st(u)}`:""}\n \n
\n ${y}\n ${b}\n
\n `}return c}function zt(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 ${Oe(l)}:\n ${Oe(d)}\n
\n `}return o}function At(e,t,i,s,o,r){if(i){const t=[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!o},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!r},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!s}].filter(e=>e.available);return`\n
\n ${t.map(e=>`\n
\n
${Oe(e.title)}
\n
\n
\n `).join("")}\n
\n `}return s?`
`:""}function Nt(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 Mt(e){const t=r[e];return t?t.ms:r[o].ms}function It(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function Tt(e){return Math.max(500,Math.floor(e/5e3))}function Dt(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 Ft(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 Lt(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 Ht(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=It(i),l=Tt(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,Ft(t,a,l))}}}function Ot(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:$t(i)};i.type===d&&(e.soc=Ct(i),e.soe=Pt(i));for(const[i,s]of Object.entries(e))s&&t.push({entityId:s,key:`${p}${n}_${i}`,devId:n})}return t}async function Rt(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=ht(i,n);if(!t)continue;let o;o=s&&s.has(e)?Mt(s.get(e)):Nt(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 Ot(t)){let t;t=o&&o.has(s)?Mt(o.get(s)):Nt(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(Lt(e,n.entityIds,n.uuidByEntity,t,i)):a.push(Ht(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(a)}function qt(e,t,n,i,o,r,a,l,c){const{options:d,series:h}=function(e,t,n,i,o,r=!1){n||(n=g[s]);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}}],_=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:_<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};h?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),o&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),u.push({type:"line",data:[[d,.8*o],[c,.8*o]],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,o],[c,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const v={xAxis:{type:"time",min:d,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:f,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:v,series:u}}(n,i,o,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 _=e.clientHeight;u.height=(_>0?_:p)+"px",u.hass=t,u.options=d,u.data=h}function jt(e){return"function"==typeof globalThis.CSS?.escape?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function Ut(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=rt(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=rt(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=rt(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=rt(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 _=e.querySelector(".stat-grid-state .stat-value");if(_){const e=n.panel_entities?.dsm_state,i=e?t.states[e]:null;_.textContent=i?t.formatEntityState?.(i)||i.state:"--"}}function Wt(e,t,i,s,o,r){if(!e||!i||!t)return;const a=Nt(s);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],s=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(s))}!function(e,t,n,i,s){const o=e.querySelector(".panel-stats");o&&Ut(o,t,n,i,s)}(e,t,i,s,d);const h=dt(s),p="current"===h.entityRole;for(const[s,d]of Object.entries(i.circuits)){const i=e.querySelector(`.circuit-slot[data-uuid="${jt(s)}"]`);if(!i)continue;const u=d.entities?.power,g=u?t.states[u]:null,_=g&&parseFloat(g.state)||0,v=d.device_type===c||_<0,m=d.entities?.switch,b=m?t.states[m]:null,y=b?"on"===b.state:(g?.attributes?.relay_state||d.relay_state)===l,w=i.querySelector(".power-value");if(w)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${h.format(i)}A`}else w.innerHTML=`${ot(_)}${st(_)}`;const x=i.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=x.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",v),d.always_on)S="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const $=f[S]??f.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",$.icon),C.style.color=$.color,C.title=$.label());const P=i.querySelector(".shedding-icon-secondary");P&&($.icon2?(P.setAttribute("icon",$.icon2),P.style.color=$.color,P.style.display=""):P.style.display="none");const k=i.querySelector(".shedding-label");k&&($.textLabel?(k.textContent=$.textLabel,k.style.color=$.color,k.style.display=""):k.style.display="none");const E=i.querySelector(".chart-container");if(E){const e=o.get(s)||[],n=i.classList.contains("circuit-col-span")?200:100,l=r?.has(s)?Mt(r.get(s)):a,p=d.device_type===c;qt(E,t,e,l,h,v,n,d.breaker_rating_a??void 0,p)}}}class Gt{get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}constructor(){this._errorStore=null,this._retry=null,this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(e,t){const i=Date.now();if(this._fetching)return this._settings;if(this._settings&&i-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const i={};t&&(i.config_entry_id=t);const s={type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0},o=this._retry?await this._retry.callWS(e,s,{errorId:"fetch:graph_settings",errorMessage:n("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:n("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 Vt(e,t){if(!e)return o;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??o}function Bt(e,t){if(!e)return o;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??o}class Qt{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new pt,this.monitoringMultiCache=new ut,this.graphSettingsCache=new Gt,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,Vt(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,Bt(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:n("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,Vt(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,Bt(i,s))}}async loadHistory(){await Rt(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)??o;if(!r[i]?.useRealtime)continue;const s=ht(n,this._config);if(!s)continue;const a=this._hass.states[s];if(!a)continue;const l=parseFloat(a.state);if(isNaN(l))continue;const c=Mt(i),d=It(c),h=Tt(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 Ot(this._topology))i.has(t)&&s.add(e);const o=new Map;try{await Rt(this._hass,this._topology,this._config,o,t,i);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:n("error.history_failed"),persistent:!1})}}updateDOM(e){this._hass&&this._topology&&this._config&&(Wt(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=Nt(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${jt(i)}"]`);if(!n)continue;const l=$t(a);if(l){const e=t.states[l],i=e&&parseFloat(e.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${ot(i)} ${st(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");qt(e,t,a,o?.has(i)?Mt(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="${jt(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 i=e.target,s=i?.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:n("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 r=i.dataset.uuid;if(r&&this._topology){const e=this._topology.circuits[r];if(e){const t=this._favRefs?.[r]??null,n=t&&"circuit"===t.kind?t.targetId:r,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??o,p=a?.circuits?.[n],u=p?{...p,globalHorizon:h}:{horizon:h,has_override:!1,globalHorizon:h},g=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,_=null!==t||(this._panelFavorites?.circuitUuids.has(n)??!1),f=this._inFavoritesView||null!==this._panelFavorites;return void s.open({...e,uuid:n,monitoringInfo:d,showMonitoring:this._showMonitoring,graphHorizonInfo:u,showFavorites:f,favoritePanelDeviceId:g,isFavorite:_,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 r;t?r=await this._fetchGraphSettingsFresh(i):(await this.graphSettingsCache.fetch(this._hass,i),r=this.graphSettingsCache.settings);const l=r?.global_horizon??o,c=r?.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 i={type:"call_service",domain:a,service:"get_graph_settings",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:graph_settings",errorMessage:n("error.graph_settings_failed")}):await this._hass.callWS(i);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 i={type:"call_service",domain:a,service:"get_monitoring_status",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await this._hass.callWS(i),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()}}const Kt='\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 }\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 .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 min-width: 70px;\n text-align: right;\n flex-shrink: 0;\n /* Cancel the .list-row flex gap against the preceding relay\n control (toggle-pill or list-status-badge). The 10px separator\n between the ON/OFF badge and the reading robs horizontal space\n from .list-circuit-name on narrow rows — close it up so the\n ellipsis threshold pushes out. */\n margin-left: -10px;\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 /* ── 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';class Jt{constructor(){this._ctrl=new Qt,this._container=null,this._onGearClick=null,this._onToggleClick=null,this._onSidePanelClosed=null,this._onGraphSettingsChanged=null}get hass(){return this._ctrl.hass}set hass(e){this._ctrl.hass=e}set errorStore(e){this._ctrl.errorStore=e}setPanelFavorites(e){this._ctrl.setPanelFavorites(e)}async render(e,t,i,s,o){let r,a;this.stop(),this._ctrl.reset(),this._ctrl.showMonitoring=!0,this._container=e,this._ctrl.hass=t;try{const e=await Ye(t,i);r=e.topology,a=e.panelSize}catch(t){return void(e.innerHTML=`

${Oe(t.message)}

`)}this._ctrl.init(r,s,t,o??null),await this._ctrl.monitoringCache.fetch(t,o??null),await this._ctrl.fetchAndBuildHorizonMaps();const l=Math.ceil(a/2),c=this._ctrl.monitoringCache.status,d=nt(r,s),h=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),i=Object.values(e.mains??{}),s=[...t,...i],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 ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${o>0?`${o} ${n(o>1?"status.warnings":"status.warning")}`:""}\n ${r>0?`${r} ${n(r>1?"status.alerts":"status.alert")}`:""}\n ${a>0?`${a} ${n(a>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(c),p=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":ct(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=at(Math.max(...n));0===lt(e)?a.add(i):l.add(i)}function c(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=s?gt(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+=vt(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+=mt(e,"2"));else{const{monInfo:t,sheddingPriority:s}=c(h);d+=vt(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+=mt(e,"3"));else{const{monInfo:t,sheddingPriority:s}=c(p);d+=vt(p.uuid,p.circuit,e,"3",p.layout,n,i,t,s)}d+=`
${s}
`}return d}(r,l,t,s,c),u=Et(r,t,s);e.innerHTML=`\n \n ${d}\n ${h}\n ${u?`
${u}
`:""}\n ${!1!==s.show_panel?`\n
\n ${p}\n
\n `:""}\n \n `,this._onGearClick=t=>{this._ctrl.onGearClick(t,e)},this._onToggleClick=t=>{this._ctrl.onToggleClick(t,e)},e.addEventListener("click",this._onGearClick),e.addEventListener("click",this._onToggleClick),this._onSidePanelClosed=()=>{this._ctrl.monitoringCache.invalidate(),this._ctrl.graphSettingsCache.invalidate()},e.addEventListener("side-panel-closed",this._onSidePanelClosed),this._onGraphSettingsChanged=()=>this._ctrl.onGraphSettingsChanged(e),e.addEventListener("graph-settings-changed",this._onGraphSettingsChanged);try{await this._ctrl.loadHistory()}catch{}this._ctrl.updateDOM(e);const g=e.querySelector(".slide-confirm");g&&(this._ctrl.bindSlideConfirm(g,e),e.classList.add("switches-disabled")),this._ctrl.setupResizeObserver(e,e),this._ctrl.startIntervals(e)}stop(){this._ctrl.stopIntervals(),this._container&&(this._onGearClick&&(this._container.removeEventListener("click",this._onGearClick),this._onGearClick=null),this._onToggleClick&&(this._container.removeEventListener("click",this._onToggleClick),this._onToggleClick=null),this._onSidePanelClosed&&(this._container.removeEventListener("side-panel-closed",this._onSidePanelClosed),this._onSidePanelClosed=null),this._onGraphSettingsChanged&&(this._container.removeEventListener("graph-settings-changed",this._onGraphSettingsChanged),this._onGraphSettingsChanged=null))}}const Xt="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Zt="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;width:80px;font-size:0.85em;\n",Yt="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",en="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",tn="\n background:var(--secondary-background-color,#333);\n border:1px solid var(--divider-color);\n color:var(--primary-text-color);\n border-radius:4px;padding:6px 10px;flex:1;font-size:0.85em;\n font-family:monospace;\n";function nn(e,t,n,i,s){return`\n ${i}\n `}class sn{constructor(){this.errorStore=null,this._debounceTimer=null,this._configEntryId=null,this._notifyCloseHandler=null,this._headerHTML=""}stop(){this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null),this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null)}async render(e,t,i,s=""){let o;void 0!==i&&(this._configEntryId=i),this._headerHTML=s,this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null);try{const e={};this._configEntryId&&(e.config_entry_id=this._configEntryId);const n=await t.callWS({type:"call_service",domain:a,service:"get_monitoring_status",service_data:e,return_response:!0});o=function(e){if(!e||"object"!=typeof e)return null;const t=e,n={};return"boolean"==typeof t.enabled&&(n.enabled=t.enabled),t.global_settings&&"object"==typeof t.global_settings&&(n.global_settings=t.global_settings),t.circuits&&"object"==typeof t.circuits&&(n.circuits=t.circuits),t.mains&&"object"==typeof t.mains&&(n.mains=t.mains),n}(n?.response)}catch(e){console.warn("SPAN Panel: monitoring status fetch failed",e),o=null}const r=o?.global_settings??{},l=!0===o?.enabled,c=o?.circuits??{},d=o?.mains??{},h=new Set;for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&h.add(e);const p=new Set(["notify","send_message"]);for(const e of Object.keys(t.services?.notify||{}))p.has(e)||h.add(`notify.${e}`);h.add("event_bus");const u=[...h].sort(),g=r.notify_targets??"",_=("string"==typeof g?g.split(","):g).map(e=>e.trim()).filter(Boolean),f=u.length>0&&u.every(e=>_.includes(e)),v=r.notification_title_template??"SPAN: {name} {alert_type}",m=r.notification_message_template??"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",b=r.notification_priority??"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name??"").localeCompare(t.name??"")),w=Object.entries(d),x=[...y,...w],S=x.length>0&&x.every(([,e])=>!1!==e.monitoring_enabled),$=x.some(([,e])=>!1!==e.monitoring_enabled),C=y.map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),P=Object.entries(d).map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","mains")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");e.innerHTML=`\n ${this._headerHTML}\n
\n

${n("monitoring.heading")}

\n\n
\n
\n

${n("monitoring.global_settings")}

\n \n
\n\n
\n
\n ${n("monitoring.continuous")}\n \n
\n
\n ${n("monitoring.spike")}\n \n
\n
\n ${n("monitoring.window")}\n \n
\n
\n ${n("monitoring.cooldown")}\n \n
\n\n
\n

${n("notification.heading")}

\n\n
\n ${n("notification.targets")}\n \n
\n \n
\n ${0===u.length?`
${n("notification.no_targets")}
`:u.map(e=>{const i=_.includes(e),s="event_bus"===e,o=s?null:t.states[e],r=o?.attributes?.friendly_name,a=s?n("notification.event_bus_target"):r?`${Oe(r)} (${Oe(e)})`:Oe(e);return``}).join("")}\n
\n
\n
\n\n
\n ${n("notification.priority")}\n \n \n ${"critical"===b?n("notification.hint.critical"):"time-sensitive"===b?n("notification.hint.time_sensitive"):"passive"===b?n("notification.hint.passive"):"active"===b?n("notification.hint.active"):""}\n \n
\n\n
\n ${n("notification.title_template")}\n \n
\n\n
\n ${n("notification.message_template")}\n \n
\n\n
\n ${n("notification.placeholders")} {name} {entity_id} {alert_type}\n {current_a} {breaker_rating_a} {threshold_pct}\n {utilization_pct} {window_m} {local_time}\n
\n
\n ${n("notification.event_bus_help")} span_panel_current_alert\n ${n("notification.event_bus_payload")} alert_source alert_id\n alert_name alert_type current_a\n breaker_rating_a threshold_pct utilization_pct\n panel_serial window_duration_s local_time\n
\n\n
\n ${n("notification.test_label")}\n \n \n
\n
\n\n
\n
\n\n

${n("monitoring.monitored_points")}

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ${P}\n ${C}\n \n
${n("monitoring.col.name")}${n("monitoring.col.continuous")}${n("monitoring.col.spike")}${n("monitoring.col.window")}${n("monitoring.col.cooldown")}
\n \n
\n
\n `;const k=e.querySelector("#toggle-all-circuits");k&&!S&&$&&(k.indeterminate=!0);const E=e.querySelector("#notify-all-targets");if(E&&u.length>0){const e=_.length>0;!f&&e&&(E.indeterminate=!0)}this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,d),this._bindCircuitToggles(e,t),this._bindMainsToggles(e,t),this._bindThresholdInputs(e,t),this._bindResetButtons(e,t)}_serviceData(e){return this._configEntryId&&(e.config_entry_id=this._configEntryId),e}_callSetGlobal(e,t){return e.callWS({type:"call_service",domain:a,service:"set_global_monitoring",service_data:this._serviceData({...t})})}_bindGlobalControls(e,t){const i=e.querySelector("#monitoring-enabled"),s=e.querySelector("#global-fields"),o=e.querySelector("#global-status"),r=()=>{const t=[["continuous_threshold_pct","#g-continuous"],["spike_threshold_pct","#g-spike"],["window_duration_m","#g-window"],["cooldown_duration_m","#g-cooldown"]],n={};for(const[i,s]of t){const t=e.querySelector(s);if(!t)return null;const o=parseInt(t.value,10);if(Number.isNaN(o))return null;n[i]=o}return n},a=(e,t,i)=>{if(!e)return;const s=t instanceof Error?t.message:i;e.textContent=`${n("error.prefix")} ${s}`,e.style.color="var(--error-color, #f44336)"},l=()=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i=r();if(i)try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a(o,e,n("error.failed_save"))}else a(o,null,n("error.failed_save"))},u)};i&&i.addEventListener("change",async()=>{const o=i.checked;s&&(s.style.opacity=o?"":"0.4",s.style.pointerEvents=o?"":"none");const l=e.querySelector("#global-status");try{if(o){const e=r();if(!e)return void a(l,null,n("error.failed"));await this._callSetGlobal(t,e)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void a(l,e,n("error.failed"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",l)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),s=e.querySelector("#notify-target-dropdown"),o=e.querySelector("#notify-target-label");if(!i||!s)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==s.style.display;s.style.display=t?"none":"block"});const r=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(s.style.display="none")};document.addEventListener("click",r),this._notifyCloseHandler=r;const a=()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);if(o){const e=i.map(e=>"event_bus"===e?n("notification.event_bus_target"):e);o.textContent=e.length?e.join(", "):n("notification.none_selected")}const s=e.querySelector("#notify-all-targets");if(s){const t=[...e.querySelectorAll(".notify-target-cb")];s.checked=t.length>0&&t.every(e=>e.checked),s.indeterminate=!s.checked&&t.some(e=>e.checked)}this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch(e){console.warn("SPAN Panel: notification targets save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)},l=e.querySelector("#notify-all-targets");l&&l.addEventListener("change",()=>{for(const t of e.querySelectorAll(".notify-target-cb"))t.checked=l.checked;const t=e.querySelector("#notify-target-btn");t&&(t.style.opacity=l.checked?"0.4":"",t.style.pointerEvents=l.checked?"none":""),l.checked&&(s.style.display="none"),a()});for(const t of e.querySelectorAll(".notify-target-cb"))t.addEventListener("change",()=>{a()})}_bindNotificationSettings(e,t){const i=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),o=e.querySelector("#g-message-template"),r=(e,i)=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:i})}catch(e){console.warn("SPAN Panel: notification settings save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)};i&&i.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:i.value}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: notification priority change failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),o&&o.addEventListener("input",()=>{r("notification_message_template",o.value)});const l=e.querySelector("#test-notification-btn"),c=e.querySelector("#test-notification-status");l&&l.addEventListener("click",async()=>{l.disabled=!0,c&&(c.textContent=n("notification.test_sending"),c.style.color="var(--secondary-text-color)");try{this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null);const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value).join(", ");await this._callSetGlobal(t,{notify_targets:i});const s={};this._configEntryId&&(s.config_entry_id=this._configEntryId),await t.callWS({type:"call_service",domain:a,service:"test_notification",service_data:s}),c&&(c.textContent=n("notification.test_sent"),c.style.color="var(--success-color, #4caf50)")}catch(e){if(c){const t=e instanceof Error?e.message:n("error.failed");c.textContent=`${n("error.prefix")} ${t}`,c.style.color="var(--error-color, #f44336)"}}finally{l.disabled=!1}})}_bindToggleAll(e,t,i,s){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const r=o.checked,l=[...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: circuit monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})),...Object.keys(s).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: mains monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}))];await Promise.all(l),await this.render(e,t)})}_bindMainsToggles(e,t){for(const i of e.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: mains threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const i of e.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: circuit threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const i=new Map;for(const s of e.querySelectorAll(".threshold-input"))s.addEventListener("input",()=>{const o=`${s.dataset.entity}-${s.dataset.field}`,r=i.get(o);r&&clearTimeout(r),i.set(o,setTimeout(async()=>{const i=parseInt(s.value,10);if(!i||i<1)return;const o=s.dataset.entity,r=s.dataset.field,l=s.dataset.type,c="mains"===l?"set_mains_threshold":"set_circuit_threshold",d="mains"===l?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[d]:o,[r]:i})}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: threshold input save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),s.style.borderColor="var(--error-color, #f44336)"}},800))})}_bindResetButtons(e,t){for(const n of e.querySelectorAll(".reset-btn"))n.addEventListener("click",async()=>{const i=n.dataset.entity;if(!i)return;const s=n.dataset.type,o="mains"===s?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===s?{leg:i}:{circuit_id:i});await t.callService(a,o,r),await this.render(e,t)})}}function on(e=""){const t=e?` value="${Oe(e)}"`:"",i=e?"":"display:none;";return`\n
\n \n \n
\n `}function rn(e,t,i,s,o,r,a){const c=t.entities?.power,d=c?i.states[c]:null,h=d&&parseFloat(d.state)||0,p=t.entities?.switch,u=p?i.states[p]:null,g=u?"on"===u.state:(d?.attributes?.relay_state||t.relay_state)===l,_=t.breaker_rating_a,m=_?`${Math.round(_)}A`:"",b=Oe(t.name||n("grid.unknown")),y=dt(s),w="current"===y.entityRole;let x;if(g)if(w){const e=t.entities?.current,n=e?i.states[e]:null,s=n&&parseFloat(n.state)||0;x=`${y.format(s)}A`}else x=`${ot(h)}${st(h)}`;else x="";const S=r||"unknown";let $="";if("unknown"!==S){const e=f[S]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};$=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}let C="",P=o?.utilization_pct??null;if(null==P&&t.breaker_rating_a){const e=t.entities?.current,n=e?i.states[e]:null,s=n?Math.abs(parseFloat(n.state)||0):0;P=Math.round(s/t.breaker_rating_a*1e3)/10}if(null!=P){C=`=80?"utilization-warning":"utilization-normal"}">${Math.round(P)}%`}const k=!!o&&_t(o)?v:"#555",E=``,z=!1!==t.is_user_controllable&&!!t.entities?.switch?`
\n ${n(g?"grid.on":"grid.off")}\n \n
`:`${g?"ON":"OFF"}`;return`\n
\n ${m?`${m}`:""}\n ${C}\n ${b}\n ${$}\n ${z}\n \n ${x}\n \n ${E}\n \n
\n `}function an(e,t,n,i,s){const o=t.entities?.power,r=o?n.states[o]:null,a=r&&parseFloat(r.state)||0,d=t.device_type===c||a<0,h=t.entities?.switch,p=h?n.states[h]:null,u=ft(0,s,p?"on"===p.state:(r?.attributes?.relay_state||t.relay_state)===l,d),g=Oe(e);return`\n
\n
\n
\n
\n
\n `}function ln(e){return`
${Oe(e)}
`}function cn(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)===l;let c;if("current"===(n.chart_metric||"power")){const n=e.entities?.current,i=n?t.states[n]:null;c=i?Math.abs(parseFloat(i.state)||0):0}else c=r?Math.abs(parseFloat(r.state)||0):0;return{isOn:a,value:c}}function dn(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 hn(e,t,n,i){const s=cn(e,n,i),o=cn(t,n,i);return s.isOn&&!o.isOn?-1:!s.isOn&&o.isOn?1:o.value-s.value}function pn(e,t,n){return e.sort((e,i)=>hn(e[1],i[1],t,n))}function un(e){return e.entities?.current??e.entities?.power??""}class gn{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=pn(Object.entries(n.circuits),t,i);let a=o+on(this._searchQuery);a+=`
`;for(const[e,n]of r){const o=gt(s,un(n)),r=dn(n,t),l=this._expandedUuids.has(e);a+=`
`,a+=rn(e,n,t,i,o,r,l),l&&(a+=an(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,i,s,o,r){this._unbindEvents(),this._hass=t,this._topology=i,this._config=s,this._monitoringStatus=o;const a=n("list.unassigned_area"),l=new Map;for(const[e,t]of Object.entries(i.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+on(this._searchQuery);d+=`
`;for(const e of c){const n=l.get(e);if(!n)continue;const i=pn(n,t,s);d+=ln(e);for(const[e,n]of i){const i=gt(o,un(n)),r=dn(n,t),a=this._expandedUuids.has(e);d+=`
`,d+=rn(e,n,t,s,i,r,a),a&&(d+=an(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,i,s){const o=dt(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=i.circuits[a];if(!l)continue;const{isOn:c,value:d}=cn(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=`${ot(i)}${st(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=n(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)=>hn(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,i,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=jt(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=gt(this._monitoringStatus,un(t)),o=an(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()}}}function _n(e,t){return`${e}|${t}`}class fn{async build(e,t,n,i){const s=new Map;for(const e of n)s.set(e.id,e);const o=i?new We(i):null,r=[];for(const[n,i]of Object.entries(t)){if(!((i?.circuits?.length??0)>0||(i?.sub_devices?.length??0)>0))continue;const t=s.get(n);t&&r.push((async()=>{try{const i=await Ye(e,n,o);return{panelDeviceId:n,panel:t,topology:i.topology}}catch(e){return console.warn("SPAN Panel: favorites topology fetch failed",n,e),{panelDeviceId:n,panel:t,topology:null}}})())}const a=(await Promise.all(r)).filter(e=>null!==e.topology),l=a.length>1,c={},d={},h={},p=new Set,u=[];for(const{panelDeviceId:e,panel:n,topology:i}of a){if(!i)continue;const s=n.config_entries?.[0]??null;s&&p.add(s);const o=n.name_by_user??n.name??i.device_name??"";u.push({panelDeviceId:e,panelName:o,topology:i});const r=t[e],a=r?.circuits??[],g=r?.sub_devices??[];for(const t of a){const n=i.circuits?.[t];if(!n)continue;const r=_n(e,t),a=l&&o?`${o} · ${n.name}`:n.name;c[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"circuit",targetId:t,configEntryId:s}}for(const t of g){const n=i.sub_devices?.[t];if(!n)continue;const r=_n(e,t),a=l&&o&&n.name?`${o} · ${n.name}`:n.name??t;d[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"sub_device",targetId:t,configEntryId:s}}}return{topology:{circuits:c,sub_devices:d,panel_entities:{},device_name:"",_favoriteRefs:h},entryIds:Array.from(p),perPanelStats:u}}}const vn="span_panel_favorites_view_state";function mn(e){try{localStorage.setItem(vn,JSON.stringify(e))}catch{}}var bn;const yn="favorites";let wn=bn=class extends Pe{constructor(){super(...arguments),this.narrow=!1,this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._listColumns=qe(),this._favorites={},this._favoritesViewState={expanded:{activity:[],area:[]}},this._favoritesPanelStats=[],this._dashboardTab=new Jt,this._monitoringTab=new sn,this._listDashCtrl=new Qt,this._listCtrl=new gn(this._listDashCtrl),this._favCache=new Be,this._favCtrl=new fn,this._favoritesMonitoringTabs=new Map,this._errorStore=new Le,this._watchedPanelId=null,this._discovering=!1,this._refreshSeq=0,this._areaUnsub=null,this._areaSubscribing=!1,this._onVisibilityChange=null,this._onFavoritesChanged=null,this._deviceRegistryUnsub=null,this._pendingTabRender=!1,this._recoverTimer=null,this._persistFavoritesViewStateTimer=null,this._tabRenderScheduler=function(e){let t=null,n=!1;return async function i(){if(t)return n=!0,void await t.catch(()=>{});const s=(async()=>{try{await e()}finally{t=null,n&&(n=!1,await i())}})();t=s,await s}}(async()=>this._renderTab()),this._beginRender=function(){let e=0;return()=>{e+=1;const t=e;return()=>e!==t}}()}get _root(){const e=this.shadowRoot;if(!e)throw new Error("span-panel: shadow root is not available");return e}connectedCallback(){super.connectedCallback(),this._dashboardTab.errorStore=this._errorStore,this._listDashCtrl.errorStore=this._errorStore,this._favCache.errorStore=this._errorStore,this._monitoringTab.errorStore=this._errorStore,this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this.hass&&this._recoverIfNeeded()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._onFavoritesChanged=()=>{this._refreshFavorites()},document.addEventListener(Ge,this._onFavoritesChanged),this._subscribeDeviceRegistry()}disconnectedCallback(){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._areaSubscribing=!1,this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._onFavoritesChanged&&(document.removeEventListener(Ge,this._onFavoritesChanged),this._onFavoritesChanged=null),this._unsubscribeDeviceRegistry(),this._persistFavoritesViewStateTimer&&(clearTimeout(this._persistFavoritesViewStateTimer),this._persistFavoritesViewStateTimer=null),this._recoverTimer&&(clearTimeout(this._recoverTimer),this._recoverTimer=null),this._errorStore.dispose(),super.disconnectedCallback()}firstUpdated(){this.hass&&!this._discovered&&this._discoverPanels()}updated(e){if(e.has("hass")){const t=e.get("hass");this._dashboardTab.hass=this.hass,this._listDashCtrl.hass=this.hass,this._errorStore.updateHass(this.hass),this._discovered?this._root.getElementById("tab-content")||this._scheduleTabRender():this._discoverPanels(),!t&&this.hass&&this._subscribeDeviceRegistry()}if(this._discovered&&(e.has("_discovered")||e.has("_activeTab")||e.has("_selectedPanelId")||e.has("_chartMetric")||e.has("_listColumns"))){if(this._isFavoritesView&&"dashboard"===this._activeTab)return void(this._activeTab="activity");this._scheduleTabRender()}if(e.has("_selectedPanelId")&&(this._selectedPanelId!==yn&&this._selectedPanelId?(this._updatePanelStatusWatch(),this._listDashCtrl.setFavoritesPerPanelInfo(null)):(this._errorStore.clearPanelStatusWatch(),this._watchedPanelId=null)),this._discovered&&(e.has("_panels")||e.has("_selectedPanelId"))){const e=this.shadowRoot?.getElementById("panel-select");e&&null!==this._selectedPanelId&&e.value!==this._selectedPanelId&&(e.value=this._selectedPanelId)}if(e.has("hass")&&this._discovered&&("activity"===this._activeTab||"area"===this._activeTab)){const e=this._root.getElementById("tab-content"),t=this._listDashCtrl.topology;if(e&&t){this._listCtrl.updateCollapsedRows(e,this.hass,t,this._buildDashboardConfig());const n=e.querySelector("span-side-panel");n&&(n.hass=this.hass,n.errorStore=this._errorStore)}}}setConfig(e){}render(){var i,s,o;if(i=this.hass?.language,e=i&&t[i]?i:"en",!this.hass)return le`
Span Panel
diff --git a/src/card/card-styles.ts b/src/card/card-styles.ts index fe97ac4..f650910 100644 --- a/src/card/card-styles.ts +++ b/src/card/card-styles.ts @@ -638,6 +638,12 @@ export const CARD_STYLES: string = ` min-width: 70px; text-align: right; flex-shrink: 0; + /* Cancel the .list-row flex gap against the preceding relay + control (toggle-pill or list-status-badge). The 10px separator + between the ON/OFF badge and the reading robs horizontal space + from .list-circuit-name on narrow rows — close it up so the + ellipsis threshold pushes out. */ + margin-left: -10px; } .list-expand-toggle { From d540ec9113e198b9e0ff01c874e778209b9c396d Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:32:52 -0700 Subject: [PATCH 3/6] fix(list): drop 70px min-width on power-value so short readings hug the relay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous margin-left:-10px only closed the 10px flex gap, but the visible space came from .list-power-value's 70px min-width and text-align:right — short values like '1.3A' sat right-aligned in a 70px cell, leaving a 40+ px blank column that robbed horizontal space from .list-circuit-name on narrow rows. Let the value size to its content so the freed width flows back into the flex:1 name column. --- dist/span-panel-card.js | 2 +- dist/span-panel.js | 10 +++++----- src/card/card-styles.ts | 14 ++++++-------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/dist/span-panel-card.js b/dist/span-panel-card.js index 6586f14..ead0eb4 100644 --- a/dist/span-panel-card.js +++ b/dist/span-panel-card.js @@ -133,4 +133,4 @@ const ze=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(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 }\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 .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 min-width: 70px;\n text-align: right;\n flex-shrink: 0;\n /* Cancel the .list-row flex gap against the preceding relay\n control (toggle-pill or list-status-badge). The 10px separator\n between the ON/OFF badge and the reading robs horizontal space\n from .list-circuit-name on narrow rows — close it up so the\n ellipsis threshold pushes out. */\n margin-left: -10px;\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 /* ── 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")),_=c(p,"0","59",i("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),a.appendChild(u.wrap),a.appendChild(g.wrap),a.appendChild(_.wrap),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.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(_)){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;"); + `}};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 }\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 .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 /* ── 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")),_=c(p,"0","59",i("editor.minutes")),f=()=>{this._config={...this._config,history_days:parseInt(u.input.value)||0,history_hours:parseInt(g.input.value)||0,history_minutes:parseInt(_.input.value)||0},this._fireConfigChanged()};u.input.addEventListener("change",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),a.appendChild(u.wrap),a.appendChild(g.wrap),a.appendChild(_.wrap),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.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(_)){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;"); diff --git a/dist/span-panel.js b/dist/span-panel.js index 76807e2..49bcc4e 100644 --- a/dist/span-panel.js +++ b/dist/span-panel.js @@ -4,12 +4,12 @@ let e="en";const t={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_ac * 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)},P=y?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: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)=>!k(e,t),R={attribute:!0,type:String,converter:H,reflect:!1,useDefault:!1,hasChanged:O}; +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}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */Symbol.metadata??=Symbol("metadata"),I.litPropertyMetadata??=new WeakMap;let q=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(P(e))}else void 0!==e&&t.push(P(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){}};q.elementStyles=[],q.shadowRootOptions={mode:"open"},q[L("elementProperties")]=new Map,q[L("finalized")]=new Map,F?.({ReactiveElement:q}),(I.reactiveElementVersions??=[]).push("2.1.2"); + */Symbol.metadata??=Symbol("metadata"),I.litPropertyMetadata??=new WeakMap;let q=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){}};q.elementStyles=[],q.shadowRootOptions={mode:"open"},q[L("elementProperties")]=new Map,q[L("finalized")]=new Map,F?.({ReactiveElement:q}),(I.reactiveElementVersions??=[]).push("2.1.2"); /** * @license * Copyright 2017 Google LLC @@ -20,7 +20,7 @@ const j=globalThis,U=e=>e,W=j.trustedTypes,G=W?W.createPolicy("lit-html",{create * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */let Pe=class extends q{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){const t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=((e,t,n)=>{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}};Pe._$litElement$=!0,Pe.finalized=!0,Ce.litElementHydrateSupport?.({LitElement:Pe});const ke=Ce.litElementPolyfillSupport;ke?.({LitElement:Pe}),(Ce.litElementVersions??=[]).push("4.2.2"); + */let ke=class extends q{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){const t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=((e,t,n)=>{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"); /** * @license * Copyright 2017 Google LLC @@ -46,7 +46,7 @@ const Ee=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)} * @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 qe(){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 je(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 p=document.createElement("div");p.className="section-label",p.textContent=n("sidepanel.graph_horizon"),h.appendChild(p);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}`,u,()=>{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 p=i||{horizon:s,has_override:!1},g=p.has_override?p.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}`,u,()=>{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(_),p.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=qe(),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",()=>{je(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 p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(r))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:i}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=i,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&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 p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(r))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:i}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=i,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&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 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,_=a?.window_duration_m??15,f=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),p.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),p.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",_,1,180,"m",t)),p.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(p),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(p.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}`,u,()=>{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"),p=document.createElement("input");p.type="number",p.min=String(s),p.max=String(o),p.value=String(i),p.dataset.role=`threshold-${t}`,l&&(p.disabled=!0);const g=document.createElement("span");return g.textContent=r,h.appendChild(p),h.appendChild(g),l||p.addEventListener("input",()=>{this._debounce(`threshold-${t}`,u,()=>{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 Pe{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` + */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 qe(){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 je(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 p=document.createElement("div");p.className="section-label",p.textContent=n("sidepanel.graph_horizon"),h.appendChild(p);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}`,u,()=>{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 p=i||{horizon:s,has_override:!1},g=p.has_override?p.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}`,u,()=>{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(_),p.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=qe(),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",()=>{je(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 p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(r))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:i}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=i,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&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 p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(r))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:i}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=i,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&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 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,_=a?.window_duration_m??15,f=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(n("sidepanel.continuous_pct"),"continuous",u,t)),p.appendChild(this._createThresholdRow(n("sidepanel.spike_pct"),"spike",g,t)),p.appendChild(this._createDurationRow(n("sidepanel.window_duration"),"window-m",_,1,180,"m",t)),p.appendChild(this._createDurationRow(n("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(p),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(p.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}`,u,()=>{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"),p=document.createElement("input");p.type="number",p.min=String(s),p.max=String(o),p.value=String(i),p.dataset.role=`threshold-${t}`,l&&(p.disabled=!0);const g=document.createElement("span");return g.textContent=r,h.appendChild(p),h.appendChild(g),l||p.addEventListener("input",()=>{this._debounce(`threshold-${t}`,u,()=>{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`