diff --git a/custom_components/houseplan/const.py b/custom_components/houseplan/const.py
index 30aecaf..cee8190 100755
--- a/custom_components/houseplan/const.py
+++ b/custom_components/houseplan/const.py
@@ -45,7 +45,7 @@ PLAN_ORPHAN_TTL_S = 3600
SCHEDULED_GRACE_S = 30 * 24 * 3600
FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only"
-VERSION = "1.51.0"
+VERSION = "1.51.1"
DEFAULT_CONFIG: dict = {
"spaces": [],
diff --git a/custom_components/houseplan/frontend/houseplan-card.js b/custom_components/houseplan/frontend/houseplan-card.js
index e62207b..38b26f0 100755
--- a/custom_components/houseplan/frontend/houseplan-card.js
+++ b/custom_components/houseplan/frontend/houseplan-card.js
@@ -1583,15 +1583,15 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
z-index: 120;
max-width: 90vw;
}
- `,Gi=1e3;function Vi(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}function Ki(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Zi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Gi,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*Gi,i[1]*e,i[2]*Gi,i[3]*e],bg:t.plan_url?{href:bi(t.plan_url),...Vi(t.plan_aspect,Gi)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ki(t)};return{id:i.id,name:i.name,area:i.area??null,open_to:i.open_to||void 0,settings:i.settings||void 0,x:null!=i.x?i.x*Gi:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*Gi:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*Gi,t[1]*e]):void 0}})}}):[]}function Ji(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Xi(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Yi(t){const e=Zi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=ti(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=pt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:dt),h=Oi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Bi(t.lang,e),iconRules:c}),d=h.filter(e=>e.space===t.spaceId&&!e.hidden),p=function(t,e,i){const s={},o=i/100*Gi*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Ji(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));je(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=p[e])}return s}(d,i,r),u=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";const r=vi(o.fill,e);if(o.showBorders||"none"!==r){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],a=e.area?ni(r,"lqi"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=Ti(t,o.entities);null!=e&&s.push(e)}return He(s)}(t.hass,d,e.area):null,"light"===r?Ii(t.hass,d,e.area):"none","temp"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=zi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}(t.hass,d,e.area):null,o.tempMin,o.tempMax,si(t.cfg?.settings)):null;a?(s+=" filled",i.push(`--room-fill:${a.c}`,`--room-fill-op:${a.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const a=!i.bg&&!o.showNames,l=Xi(e),c=e.poly?j` `:j` `;return j`${c}${a?j`${e.name} `:G}`}),_=d.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space)return{x:n.x*Gi,y:n.y*Gi};if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,p,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
+ `,Gi=1e3;function Vi(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}function Ki(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Zi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Gi,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*Gi,i[1]*e,i[2]*Gi,i[3]*e],bg:t.plan_url?{href:bi(t.plan_url),...Vi(t.plan_aspect,Gi)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ki(t)};return{id:i.id,name:i.name,area:i.area??null,open_to:i.open_to||void 0,settings:i.settings||void 0,x:null!=i.x?i.x*Gi:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*Gi:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*Gi,t[1]*e]):void 0}})}}):[]}function Ji(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Xi(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Yi(t){const e=Zi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=ti(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=pt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:dt),h=Oi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Bi(t.lang,e),iconRules:c}),d=h.filter(e=>e.space===t.spaceId),p=d.filter(t=>!t.hidden),u=function(t,e,i){const s={},o=i/100*Gi*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Ji(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));je(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=p[e])}return s}(p,i,r),_=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";const r=vi(o.fill,e);if(o.showBorders||"none"!==r){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],a=e.area?ni(r,"lqi"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=Ti(t,o.entities);null!=e&&s.push(e)}return He(s)}(t.hass,d,e.area):null,"light"===r?Ii(t.hass,d,e.area):"none","temp"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=zi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}(t.hass,d,e.area):null,o.tempMin,o.tempMax,si(t.cfg?.settings)):null;a?(s+=" filled",i.push(`--room-fill:${a.c}`,`--room-fill-op:${a.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const a=!i.bg&&!o.showNames,l=Xi(e),c=e.poly?j`
`:j`
`;return j`${c}${a?j`
${e.name} `:G}`}),m=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space)return{x:n.x*Gi,y:n.y*Gi};if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,u,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
-
`}),m=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i){const s=i["rl_"+(t.id||"")];if(s&&s.s===e)return{x:s.x*Gi,y:s.y*Gi};const o=Xi(t);return{x:o[0],y:o[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`
${e.name}
`}):[],g=i.bg?t.displayUrl?t.displayUrl(i.bg.href):i.bg.href:"";return B`
+
`}),g=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i){const s=i["rl_"+(t.id||"")];if(s&&s.s===e)return{x:s.x*Gi,y:s.y*Gi};const o=Xi(t);return{x:o[0],y:o[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`${e.name}
`}):[],f=i.bg?t.displayUrl?t.displayUrl(i.bg.href):i.bg.href:"";return B`
- ${g?j` `:G}
- ${u}
+ ${f?j` `:G}
+ ${_}
-
${_}${m}
+
${m}${g}
`}let Qi=null,ts=null,es=!1;const is=new Set;function ss(){if(Qi)return Qi;try{const t=JSON.parse(localStorage.getItem("houseplan_card_cfg_v1")||"null");if(t&&t.config&&Array.isArray(t.config.spaces))return{config:t.config,rev:t.rev||0,layout:t.layout||{}}}catch{}return null}function os(t){return Qi?Promise.resolve(Qi):ts||(ts=async function(t){const[e,i]=await Promise.all([t.callWS({type:"houseplan/config/get"}),t.callWS({type:"houseplan/layout/get"})]);if(Qi={config:e?.config??null,rev:e?.rev??0,layout:i?.layout??{}},!es&&t.connection?.subscribeEvents){es=!0;const e=()=>{Qi=null,is.forEach(t=>t())};try{await t.connection.subscribeEvents(e,"houseplan_config_updated"),await t.connection.subscribeEvents(e,"houseplan_layout_updated")}catch{es=!1}}return Qi}(t).finally(()=>{ts=null}),ts)}class ns extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Hi(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[];return[t.length?{name:"space",selector:{select:{mode:"dropdown",options:t}}}:{name:"space",selector:{text:{}}},{name:"title",selector:{text:{}}},{name:"show_button",selector:{boolean:{}}},{name:"button_label",selector:{text:{}}},{name:"button_target",selector:{text:{}}},{name:"aspect_ratio",selector:{text:{}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const t=this._lang,e={space:Bi(t,"editor.space"),title:Bi(t,"editor.title"),show_button:Bi(t,"editor.show_button"),button_label:Bi(t,"editor.button_label"),button_target:Bi(t,"editor.button_target"),aspect_ratio:Bi(t,"editor.aspect_ratio"),icon_size:Bi(t,"editor.icon_size")};return B`${this._toast}`:G}
- `}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=t.hidden?"":this._stateClass(t),a=this._liveTemp(t),l=this._liveHum(t),c=i&&!t.virtual?Ti(this.hass,t.entities):null,h=t.marker,d=h?.display||"badge",p="ripple"===d||"icon_ripple"===d,u=t.primary?this.hass.states[t.primary]:void 0,_="value"===d?null!=a?a+"°":null!=l?l+"%":u&&!isNaN(parseFloat(u.state))?parseFloat(u.state)+(u.attributes?.unit_of_measurement?" "+u.attributes.unit_of_measurement:""):null:null,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states?ri(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(di),b=this._config?.live_states&&!t.hidden?f.length?f.map(t=>ai(this.hass.states[t])).find(t=>t)||null:"light"===m?ai(u):null:null,v=this._config?.live_states&&!t.hidden&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&xi.has(e))}(m,u?.attributes?.device_class,u?.state),y=p&&!t.hidden&&!!t.primary&&$e(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,k=Number(h?.ripple_size)>0?Number(h.ripple_size):3,$=[`left:${o}%`,`top:${n}%`];return 1!==w&&$.push(`--dev-scale:${w}`),p&&($.push(`--ripple-scale:${k}`),h?.ripple_color?$.push(`--ripple-color:${h.ripple_color}`):b&&$.push(`--ripple-color:${b}`)),b&&$.push(`--light-color:${b}`),B`ai(this.hass.states[t])).find(t=>t)||null:"light"===m?ai(u):null:null,v=this._config?.live_states&&!t.hidden&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&xi.has(e))}(m,u?.attributes?.device_class,u?.state),y=p&&!t.hidden&&!!t.primary&&$e(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,k=Number(h?.ripple_size)>0?Number(h.ripple_size):3,$=[`left:${o}%`,`top:${n}%`];return 1!==w&&$.push(`--dev-scale:${w}`),p&&($.push(`--ripple-scale:${k}`),h?.ripple_color?$.push(`--ripple-color:${h.ripple_color}`):b&&$.push(`--ripple-color:${b}`)),b&&$.push(`--light-color:${b}`),B`
this._clickDevice(e,t)}
@@ -2668,4 +2668,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
`}
- `}}fs.properties={_hdrH:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},fs.ZOOM_MAX=8,fs.ZOOM_MIN=.4,fs._touchSeen=!1,fs._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,fs.styles=Wi,customElements.get("houseplan-card")||customElements.define("houseplan-card",fs),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.51.0 ","background:#3ea6ff;color:#04121f;font-weight:700","");
+ `}}fs.properties={_hdrH:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},fs.ZOOM_MAX=8,fs.ZOOM_MIN=.4,fs._touchSeen=!1,fs._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,fs.styles=Wi,customElements.get("houseplan-card")||customElements.define("houseplan-card",fs),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.51.1 ","background:#3ea6ff;color:#04121f;font-weight:700","");
diff --git a/custom_components/houseplan/manifest.json b/custom_components/houseplan/manifest.json
index a58abde..f17d1ce 100755
--- a/custom_components/houseplan/manifest.json
+++ b/custom_components/houseplan/manifest.json
@@ -16,5 +16,5 @@
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
"requirements": [],
"single_config_entry": true,
- "version": "1.51.0"
+ "version": "1.51.1"
}
diff --git a/demo/smoke_hidden_flag.mjs b/demo/smoke_hidden_flag.mjs
index 1483cc0..a2aae24 100644
--- a/demo/smoke_hidden_flag.mjs
+++ b/demo/smoke_hidden_flag.mjs
@@ -73,4 +73,77 @@ Object.assign(out, await page.evaluate(async () => {
return o;
}));
+// --- HP-1510-02: призрак не показывает live-значения ----------------------
+Object.assign(out, await page.evaluate(async () => {
+ const o = {};
+ const c = window.__card;
+ const sr = () => c.shadowRoot || c.renderRoot;
+ // датчик с числом + display:value, скрыт
+ c.hass = { ...c.hass, states: { ...c.hass.states,
+ 'sensor.power_meter': { state: '42', attributes: { unit_of_measurement: 'kW' } } } };
+ c._serverCfg.markers = c._serverCfg.markers || [];
+ c._serverCfg.markers.push({ id: 'pm', binding: 'entity:sensor.power_meter',
+ display: 'value', hidden: true, space: 'f1' });
+ c._cfgEpoch++; c._regSignature = '';
+ c._maybeRebuildDevices();
+ c._setMode('devices'); c._showHidden = true;
+ c.requestUpdate(); await c.updateComplete;
+ const ghosts = [...sr().querySelectorAll('.dev.ghost')];
+ const pm = ghosts.find((g) => g.textContent.includes('42')) || null;
+ o.ghostHidesValue = pm === null; // «42 kW» не отрисован
+ o.ghostNoLiveBadges = ghosts.every((g) =>
+ !g.querySelector('.valtext') && !g.querySelector('.tval') && !g.querySelector('.hval') && !g.querySelector('.lqi'));
+ o.ghostKeepsIcon = ghosts.every((g) => !!g.querySelector('ha-icon') || g.classList.contains('noicon'));
+ c._setMode('view'); c._showHidden = false;
+ return o;
+}));
+
+// --- HP-1510-01: LQI комнаты одинаков на полной и статичной карточке ------
+Object.assign(out, await page.evaluate(async () => {
+ const o = {};
+ const c = window.__card;
+ await customElements.whenDefined('houseplan-space-card');
+ const cfg = JSON.parse(JSON.stringify(c._serverCfg));
+ const f1 = cfg.spaces.find((s) => s.id === 'f1');
+ f1.settings = { ...(f1.settings || {}), fill_mode: 'lqi', show_borders: true };
+ // прячем ВСЕ устройства зоны living_room: комнату красит только скрытое
+ for (const d of c._devices.filter((x) => x.area === 'living_room' && !x.virtual)) {
+ if (!cfg.markers.some((m) => m.id === d.id)) {
+ cfg.markers.push({ id: d.id, binding: d.bindingKind + ':' + d.bindingRef, hidden: true });
+ } else {
+ cfg.markers = cfg.markers.map((m) => (m.id === d.id ? { ...m, hidden: true } : m));
+ }
+ }
+ c._serverCfg = cfg; c._cfgEpoch++; c._regSignature = '';
+ c._maybeRebuildDevices(); c.requestUpdate(); await c.updateComplete;
+ const fullRoom = [...(c.shadowRoot || c.renderRoot).querySelectorAll('.room')]
+ .find((r) => (r.getAttribute('style') || '').includes('--room-fill'));
+ const fullFill = fullRoom ? (fullRoom.getAttribute('style').match(/--room-fill:([^;]+)/) || [])[1] : null;
+
+ const hass = { ...c.hass, callWS: async (m) => {
+ if (m.type === 'houseplan/config/get') return { config: cfg, rev: 1 };
+ if (m.type === 'houseplan/layout/get') return { layout: {}, rev: 1 };
+ return { ok: true };
+ } };
+ const host = document.createElement('div');
+ document.body.appendChild(host);
+ const card = document.createElement('houseplan-space-card');
+ card.setConfig({ type: 'custom:houseplan-space-card', space: 'f1' });
+ card.hass = hass;
+ host.appendChild(card);
+ const t0 = Date.now();
+ while (!card.renderRoot?.querySelector('.room') && Date.now() - t0 < 6000) {
+ await new Promise((r) => setTimeout(r, 60));
+ }
+ await card.updateComplete;
+ const stRoom = [...card.renderRoot.querySelectorAll('.room')]
+ .find((r) => (r.getAttribute('style') || '').includes('--room-fill'));
+ const stFill = stRoom ? (stRoom.getAttribute('style').match(/--room-fill:([^;]+)/) || [])[1] : null;
+ o.fullPaintsHiddenLqi = !!fullFill;
+ o.staticPaintsHiddenLqi = !!stFill;
+ o.lqiParity = !!fullFill && fullFill === stFill;
+ host.remove();
+ return o;
+}));
+
await finish(browser, checkAll(out));
diff --git a/demo/srv/assets/houseplan-card.js b/demo/srv/assets/houseplan-card.js
index e62207b..38b26f0 100755
--- a/demo/srv/assets/houseplan-card.js
+++ b/demo/srv/assets/houseplan-card.js
@@ -1583,15 +1583,15 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
z-index: 120;
max-width: 90vw;
}
- `,Gi=1e3;function Vi(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}function Ki(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Zi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Gi,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*Gi,i[1]*e,i[2]*Gi,i[3]*e],bg:t.plan_url?{href:bi(t.plan_url),...Vi(t.plan_aspect,Gi)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ki(t)};return{id:i.id,name:i.name,area:i.area??null,open_to:i.open_to||void 0,settings:i.settings||void 0,x:null!=i.x?i.x*Gi:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*Gi:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*Gi,t[1]*e]):void 0}})}}):[]}function Ji(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Xi(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Yi(t){const e=Zi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=ti(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=pt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:dt),h=Oi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Bi(t.lang,e),iconRules:c}),d=h.filter(e=>e.space===t.spaceId&&!e.hidden),p=function(t,e,i){const s={},o=i/100*Gi*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Ji(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));je(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=p[e])}return s}(d,i,r),u=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";const r=vi(o.fill,e);if(o.showBorders||"none"!==r){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],a=e.area?ni(r,"lqi"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=Ti(t,o.entities);null!=e&&s.push(e)}return He(s)}(t.hass,d,e.area):null,"light"===r?Ii(t.hass,d,e.area):"none","temp"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=zi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}(t.hass,d,e.area):null,o.tempMin,o.tempMax,si(t.cfg?.settings)):null;a?(s+=" filled",i.push(`--room-fill:${a.c}`,`--room-fill-op:${a.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const a=!i.bg&&!o.showNames,l=Xi(e),c=e.poly?j` `:j` `;return j`${c}${a?j`${e.name} `:G}`}),_=d.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space)return{x:n.x*Gi,y:n.y*Gi};if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,p,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
+ `,Gi=1e3;function Vi(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}function Ki(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Zi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Gi,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*Gi,i[1]*e,i[2]*Gi,i[3]*e],bg:t.plan_url?{href:bi(t.plan_url),...Vi(t.plan_aspect,Gi)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ki(t)};return{id:i.id,name:i.name,area:i.area??null,open_to:i.open_to||void 0,settings:i.settings||void 0,x:null!=i.x?i.x*Gi:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*Gi:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*Gi,t[1]*e]):void 0}})}}):[]}function Ji(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Xi(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Yi(t){const e=Zi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=ti(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=pt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:dt),h=Oi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Bi(t.lang,e),iconRules:c}),d=h.filter(e=>e.space===t.spaceId),p=d.filter(t=>!t.hidden),u=function(t,e,i){const s={},o=i/100*Gi*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Ji(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));je(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=p[e])}return s}(p,i,r),_=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";const r=vi(o.fill,e);if(o.showBorders||"none"!==r){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],a=e.area?ni(r,"lqi"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=Ti(t,o.entities);null!=e&&s.push(e)}return He(s)}(t.hass,d,e.area):null,"light"===r?Ii(t.hass,d,e.area):"none","temp"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=zi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}(t.hass,d,e.area):null,o.tempMin,o.tempMax,si(t.cfg?.settings)):null;a?(s+=" filled",i.push(`--room-fill:${a.c}`,`--room-fill-op:${a.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const a=!i.bg&&!o.showNames,l=Xi(e),c=e.poly?j`
`:j`
`;return j`${c}${a?j`
${e.name} `:G}`}),m=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space)return{x:n.x*Gi,y:n.y*Gi};if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,u,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
-
`}),m=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i){const s=i["rl_"+(t.id||"")];if(s&&s.s===e)return{x:s.x*Gi,y:s.y*Gi};const o=Xi(t);return{x:o[0],y:o[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`
${e.name}
`}):[],g=i.bg?t.displayUrl?t.displayUrl(i.bg.href):i.bg.href:"";return B`
+
`}),g=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i){const s=i["rl_"+(t.id||"")];if(s&&s.s===e)return{x:s.x*Gi,y:s.y*Gi};const o=Xi(t);return{x:o[0],y:o[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`${e.name}
`}):[],f=i.bg?t.displayUrl?t.displayUrl(i.bg.href):i.bg.href:"";return B`
- ${g?j` `:G}
- ${u}
+ ${f?j` `:G}
+ ${_}
-
${_}${m}
+
${m}${g}
`}let Qi=null,ts=null,es=!1;const is=new Set;function ss(){if(Qi)return Qi;try{const t=JSON.parse(localStorage.getItem("houseplan_card_cfg_v1")||"null");if(t&&t.config&&Array.isArray(t.config.spaces))return{config:t.config,rev:t.rev||0,layout:t.layout||{}}}catch{}return null}function os(t){return Qi?Promise.resolve(Qi):ts||(ts=async function(t){const[e,i]=await Promise.all([t.callWS({type:"houseplan/config/get"}),t.callWS({type:"houseplan/layout/get"})]);if(Qi={config:e?.config??null,rev:e?.rev??0,layout:i?.layout??{}},!es&&t.connection?.subscribeEvents){es=!0;const e=()=>{Qi=null,is.forEach(t=>t())};try{await t.connection.subscribeEvents(e,"houseplan_config_updated"),await t.connection.subscribeEvents(e,"houseplan_layout_updated")}catch{es=!1}}return Qi}(t).finally(()=>{ts=null}),ts)}class ns extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Hi(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[];return[t.length?{name:"space",selector:{select:{mode:"dropdown",options:t}}}:{name:"space",selector:{text:{}}},{name:"title",selector:{text:{}}},{name:"show_button",selector:{boolean:{}}},{name:"button_label",selector:{text:{}}},{name:"button_target",selector:{text:{}}},{name:"aspect_ratio",selector:{text:{}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const t=this._lang,e={space:Bi(t,"editor.space"),title:Bi(t,"editor.title"),show_button:Bi(t,"editor.show_button"),button_label:Bi(t,"editor.button_label"),button_target:Bi(t,"editor.button_target"),aspect_ratio:Bi(t,"editor.aspect_ratio"),icon_size:Bi(t,"editor.icon_size")};return B`${this._toast}`:G}
- `}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=t.hidden?"":this._stateClass(t),a=this._liveTemp(t),l=this._liveHum(t),c=i&&!t.virtual?Ti(this.hass,t.entities):null,h=t.marker,d=h?.display||"badge",p="ripple"===d||"icon_ripple"===d,u=t.primary?this.hass.states[t.primary]:void 0,_="value"===d?null!=a?a+"°":null!=l?l+"%":u&&!isNaN(parseFloat(u.state))?parseFloat(u.state)+(u.attributes?.unit_of_measurement?" "+u.attributes.unit_of_measurement:""):null:null,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states?ri(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(di),b=this._config?.live_states&&!t.hidden?f.length?f.map(t=>ai(this.hass.states[t])).find(t=>t)||null:"light"===m?ai(u):null:null,v=this._config?.live_states&&!t.hidden&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&xi.has(e))}(m,u?.attributes?.device_class,u?.state),y=p&&!t.hidden&&!!t.primary&&$e(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,k=Number(h?.ripple_size)>0?Number(h.ripple_size):3,$=[`left:${o}%`,`top:${n}%`];return 1!==w&&$.push(`--dev-scale:${w}`),p&&($.push(`--ripple-scale:${k}`),h?.ripple_color?$.push(`--ripple-color:${h.ripple_color}`):b&&$.push(`--ripple-color:${b}`)),b&&$.push(`--light-color:${b}`),B`ai(this.hass.states[t])).find(t=>t)||null:"light"===m?ai(u):null:null,v=this._config?.live_states&&!t.hidden&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&xi.has(e))}(m,u?.attributes?.device_class,u?.state),y=p&&!t.hidden&&!!t.primary&&$e(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,k=Number(h?.ripple_size)>0?Number(h.ripple_size):3,$=[`left:${o}%`,`top:${n}%`];return 1!==w&&$.push(`--dev-scale:${w}`),p&&($.push(`--ripple-scale:${k}`),h?.ripple_color?$.push(`--ripple-color:${h.ripple_color}`):b&&$.push(`--ripple-color:${b}`)),b&&$.push(`--light-color:${b}`),B`
this._clickDevice(e,t)}
@@ -2668,4 +2668,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
`}
- `}}fs.properties={_hdrH:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},fs.ZOOM_MAX=8,fs.ZOOM_MIN=.4,fs._touchSeen=!1,fs._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,fs.styles=Wi,customElements.get("houseplan-card")||customElements.define("houseplan-card",fs),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.51.0 ","background:#3ea6ff;color:#04121f;font-weight:700","");
+ `}}fs.properties={_hdrH:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},fs.ZOOM_MAX=8,fs.ZOOM_MIN=.4,fs._touchSeen=!1,fs._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,fs.styles=Wi,customElements.get("houseplan-card")||customElements.define("houseplan-card",fs),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.51.1 ","background:#3ea6ff;color:#04121f;font-weight:700","");
diff --git a/dist/houseplan-card.js b/dist/houseplan-card.js
index e62207b..38b26f0 100755
--- a/dist/houseplan-card.js
+++ b/dist/houseplan-card.js
@@ -1583,15 +1583,15 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
z-index: 120;
max-width: 90vw;
}
- `,Gi=1e3;function Vi(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}function Ki(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Zi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Gi,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*Gi,i[1]*e,i[2]*Gi,i[3]*e],bg:t.plan_url?{href:bi(t.plan_url),...Vi(t.plan_aspect,Gi)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ki(t)};return{id:i.id,name:i.name,area:i.area??null,open_to:i.open_to||void 0,settings:i.settings||void 0,x:null!=i.x?i.x*Gi:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*Gi:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*Gi,t[1]*e]):void 0}})}}):[]}function Ji(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Xi(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Yi(t){const e=Zi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=ti(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=pt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:dt),h=Oi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Bi(t.lang,e),iconRules:c}),d=h.filter(e=>e.space===t.spaceId&&!e.hidden),p=function(t,e,i){const s={},o=i/100*Gi*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Ji(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));je(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=p[e])}return s}(d,i,r),u=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";const r=vi(o.fill,e);if(o.showBorders||"none"!==r){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],a=e.area?ni(r,"lqi"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=Ti(t,o.entities);null!=e&&s.push(e)}return He(s)}(t.hass,d,e.area):null,"light"===r?Ii(t.hass,d,e.area):"none","temp"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=zi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}(t.hass,d,e.area):null,o.tempMin,o.tempMax,si(t.cfg?.settings)):null;a?(s+=" filled",i.push(`--room-fill:${a.c}`,`--room-fill-op:${a.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const a=!i.bg&&!o.showNames,l=Xi(e),c=e.poly?j` `:j` `;return j`${c}${a?j`${e.name} `:G}`}),_=d.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space)return{x:n.x*Gi,y:n.y*Gi};if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,p,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
+ `,Gi=1e3;function Vi(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}function Ki(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Zi(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=Gi,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*Gi,i[1]*e,i[2]*Gi,i[3]*e],bg:t.plan_url?{href:bi(t.plan_url),...Vi(t.plan_aspect,Gi)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ki(t)};return{id:i.id,name:i.name,area:i.area??null,open_to:i.open_to||void 0,settings:i.settings||void 0,x:null!=i.x?i.x*Gi:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*Gi:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*Gi,t[1]*e]):void 0}})}}):[]}function Ji(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Xi(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}function Yi(t){const e=Zi(t.cfg),i=e.find(e=>e.id===t.spaceId);if(!i)return null;const s=i.vb,o=ti(t.cfg.spaces.find(e=>e.id===t.spaceId)),n=t.iconSize??2.5,r=n>8?2.5:n,a={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(a[t.area]=e.id);const l=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):ht,c=pt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:dt),h=Oi({hass:t.hass,areaToSpace:a,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:l,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>Bi(t.lang,e),iconRules:c}),d=h.filter(e=>e.space===t.spaceId),p=d.filter(t=>!t.hidden),u=function(t,e,i){const s={},o=i/100*Gi*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Ji(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));je(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=p[e])}return s}(p,i,r),_=i.rooms.filter(t=>t.area||o.showBorders).map(e=>{let s="room "+(i.bg?"overlay":"yard"),n="";const r=vi(o.fill,e);if(o.showBorders||"none"!==r){s+=" styled";const i=[`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`],a=e.area?ni(r,"lqi"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i||o.virtual)continue;const e=Ti(t,o.entities);null!=e&&s.push(e)}return He(s)}(t.hass,d,e.area):null,"light"===r?Ii(t.hass,d,e.area):"none","temp"===r?function(t,e,i){const s=[];for(const o of e){if(o.area!==i)continue;if("mdi:thermometer"!==o.icon&&"mdi:air-filter"!==o.icon)continue;const e=zi(t,o.entities);null!=e&&s.push(e)}return s.length?Math.round(s.reduce((t,e)=>t+e,0)/s.length*10)/10:null}(t.hass,d,e.area):null,o.tempMin,o.tempMax,si(t.cfg?.settings)):null;a?(s+=" filled",i.push(`--room-fill:${a.c}`,`--room-fill-op:${a.a.toFixed(3)}`)):i.push("--room-fill:transparent","--room-fill-op:0"),n=i.join(";")}const a=!i.bg&&!o.showNames,l=Xi(e),c=e.poly?j`
`:j`
`;return j`${c}${a?j`
${e.name} `:G}`}),m=p.map(e=>{const o=function(t,e,i,s,o){const n=e[t.id];if(n&&n.s===t.space)return{x:n.x*Gi,y:n.y*Gi};if(s[t.id])return s[t.id];const r=o.vb;return{x:r[0]+r[2]/2,y:r[1]+r[3]/2}}(e,t.layout,t.cfg,u,i),n=(o.x-s[0])/s[2]*100,r=(o.y-s[1])/s[3]*100;return B`
-
`}),m=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i){const s=i["rl_"+(t.id||"")];if(s&&s.s===e)return{x:s.x*Gi,y:s.y*Gi};const o=Xi(t);return{x:o[0],y:o[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`
${e.name}
`}):[],g=i.bg?t.displayUrl?t.displayUrl(i.bg.href):i.bg.href:"";return B`
+
`}),g=o.showNames?i.rooms.filter(t=>t.name).map(e=>{const n=function(t,e,i){const s=i["rl_"+(t.id||"")];if(s&&s.s===e)return{x:s.x*Gi,y:s.y*Gi};const o=Xi(t);return{x:o[0],y:o[1]}}(e,i.id,t.layout,t.cfg),r=(n.x-s[0])/s[2]*100,a=(n.y-s[1])/s[3]*100,l=Math.min(1,o.opacity+.25);return B`${e.name}
`}):[],f=i.bg?t.displayUrl?t.displayUrl(i.bg.href):i.bg.href:"";return B`
- ${g?j` `:G}
- ${u}
+ ${f?j` `:G}
+ ${_}
-
${_}${m}
+
${m}${g}
`}let Qi=null,ts=null,es=!1;const is=new Set;function ss(){if(Qi)return Qi;try{const t=JSON.parse(localStorage.getItem("houseplan_card_cfg_v1")||"null");if(t&&t.config&&Array.isArray(t.config.spaces))return{config:t.config,rev:t.rev||0,layout:t.layout||{}}}catch{}return null}function os(t){return Qi?Promise.resolve(Qi):ts||(ts=async function(t){const[e,i]=await Promise.all([t.callWS({type:"houseplan/config/get"}),t.callWS({type:"houseplan/layout/get"})]);if(Qi={config:e?.config??null,rev:e?.rev??0,layout:i?.layout??{}},!es&&t.connection?.subscribeEvents){es=!0;const e=()=>{Qi=null,is.forEach(t=>t())};try{await t.connection.subscribeEvents(e,"houseplan_config_updated"),await t.connection.subscribeEvents(e,"houseplan_layout_updated")}catch{es=!1}}return Qi}(t).finally(()=>{ts=null}),ts)}class ns extends lt{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return Hi(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[];return[t.length?{name:"space",selector:{select:{mode:"dropdown",options:t}}}:{name:"space",selector:{text:{}}},{name:"title",selector:{text:{}}},{name:"show_button",selector:{boolean:{}}},{name:"button_label",selector:{text:{}}},{name:"button_target",selector:{text:{}}},{name:"aspect_ratio",selector:{text:{}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const t=this._lang,e={space:Bi(t,"editor.space"),title:Bi(t,"editor.title"),show_button:Bi(t,"editor.show_button"),button_label:Bi(t,"editor.button_label"),button_target:Bi(t,"editor.button_target"),aspect_ratio:Bi(t,"editor.aspect_ratio"),icon_size:Bi(t,"editor.icon_size")};return B`${this._toast}`:G}
- `}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=t.hidden?"":this._stateClass(t),a=this._liveTemp(t),l=this._liveHum(t),c=i&&!t.virtual?Ti(this.hass,t.entities):null,h=t.marker,d=h?.display||"badge",p="ripple"===d||"icon_ripple"===d,u=t.primary?this.hass.states[t.primary]:void 0,_="value"===d?null!=a?a+"°":null!=l?l+"%":u&&!isNaN(parseFloat(u.state))?parseFloat(u.state)+(u.attributes?.unit_of_measurement?" "+u.attributes.unit_of_measurement:""):null:null,m=t.primary?t.primary.split(".")[0]:null,g=this._config?.live_states?ri(t.icon,m,u?.attributes?.device_class,u?.state,!!h?.icon):t.icon,f=(h?.controls||[]).filter(di),b=this._config?.live_states&&!t.hidden?f.length?f.map(t=>ai(this.hass.states[t])).find(t=>t)||null:"light"===m?ai(u):null:null,v=this._config?.live_states&&!t.hidden&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&xi.has(e))}(m,u?.attributes?.device_class,u?.state),y=p&&!t.hidden&&!!t.primary&&$e(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,k=Number(h?.ripple_size)>0?Number(h.ripple_size):3,$=[`left:${o}%`,`top:${n}%`];return 1!==w&&$.push(`--dev-scale:${w}`),p&&($.push(`--ripple-scale:${k}`),h?.ripple_color?$.push(`--ripple-color:${h.ripple_color}`):b&&$.push(`--ripple-color:${b}`)),b&&$.push(`--light-color:${b}`),B`ai(this.hass.states[t])).find(t=>t)||null:"light"===m?ai(u):null:null,v=this._config?.live_states&&!t.hidden&&function(t,e,i){return"on"===i&&("siren"===t||"binary_sensor"===t&&!!e&&xi.has(e))}(m,u?.attributes?.device_class,u?.state),y=p&&!t.hidden&&!!t.primary&&$e(this.hass.states[t.primary]?.state),w=Number(h?.size)>0?Number(h.size):1,x=Number(h?.angle)||0,k=Number(h?.ripple_size)>0?Number(h.ripple_size):3,$=[`left:${o}%`,`top:${n}%`];return 1!==w&&$.push(`--dev-scale:${w}`),p&&($.push(`--ripple-scale:${k}`),h?.ripple_color?$.push(`--ripple-color:${h.ripple_color}`):b&&$.push(`--ripple-color:${b}`)),b&&$.push(`--light-color:${b}`),B`
this._clickDevice(e,t)}
@@ -2668,4 +2668,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
`}
- `}}fs.properties={_hdrH:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},fs.ZOOM_MAX=8,fs.ZOOM_MIN=.4,fs._touchSeen=!1,fs._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,fs.styles=Wi,customElements.get("houseplan-card")||customElements.define("houseplan-card",fs),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.51.0 ","background:#3ea6ff;color:#04121f;font-weight:700","");
+ `}}fs.properties={_hdrH:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorTextDialog:{state:!0},_kioskDialog:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},fs.ZOOM_MAX=8,fs.ZOOM_MIN=.4,fs._touchSeen=!1,fs._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,fs.styles=Wi,customElements.get("houseplan-card")||customElements.define("houseplan-card",fs),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info("%c HOUSEPLAN-CARD %c v1.51.1 ","background:#3ea6ff;color:#04121f;font-weight:700","");
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 255eb1f..e3f504e 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,5 +1,20 @@
# Changelog
+## v1.51.1 — 2026-07-29
+
+**From the v1.51.0 review**
+
+- **The static card counts hidden devices in room LQI again (HP-1510-01).**
+ Its visibility filter had quietly become the aggregation filter: the same
+ room showed different Zigbee health on the two cards. Aggregation and
+ rendering use separate lists now — hidden devices count toward signal on
+ both cards, are drawn on neither, and still cast no light.
+- **A ghost shows no live numbers (HP-1510-02).** A hidden device in "Show
+ hidden" suppressed the state colors but still painted its value text,
+ temperature, humidity, LQI badge and state-morphed icon. All of that is
+ gone: the ghost keeps only the base icon and name — enough to recognise it
+ and open the dialog.
+
## v1.51.0 — 2026-07-29
**Hiding is an explicit flag now** (docs/FILTERING.md)
diff --git a/docs/CHANGELOG.ru.md b/docs/CHANGELOG.ru.md
index c4d36f7..1c79236 100755
--- a/docs/CHANGELOG.ru.md
+++ b/docs/CHANGELOG.ru.md
@@ -6,6 +6,22 @@
> **Правило проекта:** оба файла пополняются в одном коммите с самим
> изменением — как и остальная документация (см. docs/STATUS.md).
+## v1.51.1 — 2026-07-29
+
+**По ревью v1.51.0**
+
+- **Статичная карточка снова учитывает скрытые устройства в LQI комнаты
+ (HP-1510-01).** Её фильтр видимости незаметно стал фильтром агрегации: одна
+ и та же комната показывала разное Zigbee-здоровье на двух карточках. Теперь
+ агрегация и отрисовка используют разные списки — скрытые устройства
+ считаются в сигнале на обеих карточках, не рисуются ни на одной и
+ по-прежнему не дают света.
+- **Призрак не показывает живых чисел (HP-1510-02).** Скрытое устройство в
+ «Показать скрытые» подавляло цвета состояний, но по-прежнему рисовало
+ значение, температуру, влажность, бейдж LQI и морф иконки. Всё это убрано:
+ у призрака только базовая иконка и имя — достаточно, чтобы узнать
+ устройство и открыть диалог.
+
## v1.51.0 — 2026-07-29
**Скрытие — теперь явная галка** (docs/FILTERING.md)
diff --git a/docs/STATUS.md b/docs/STATUS.md
index 552213e..2e3200e 100644
--- a/docs/STATUS.md
+++ b/docs/STATUS.md
@@ -15,12 +15,12 @@
| Item | State |
|---|---|
-| Version | **v1.51.0** everywhere (manifest, const.py, package.json, CARD_VERSION); deployed to the home instance |
+| Version | **v1.51.1** everywhere (manifest, const.py, package.json, CARD_VERSION); deployed to the home instance |
| Workflow | Since 2026-07-22: minor changes go to branch **`dev`** (build + smokes → deploy home → commit → push, NO release); releases are batched on the owner's command (merge dev→main, one tag, one release with a summary changelog, CI checked on dev beforehand) |
| GitHub | https://github.com/Matysh/houseplan-card — **`main` carries every published release, the latest tag is the current version above**; `dev` is where work lands and is merged into `main` at release time (so `dev` is normally equal to or ahead of `main`, never behind). Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
| CI | validate.yml (hacs + hassfest + frontend + backend) green; release.yml attaches the bundle on release publish |
| HACS | Custom repository works. **Inclusion PR: hacs/default#9004** — open, valid, labeled; ~864 older open PRs but merge rate ≈180/mo; realistic ETA 1–3 months (checked 2026-07-24) |
-| Home instance | ha.jbstudio.pro (SSH port 323, key `ha_jb`), deployed **v1.51.0** via direct copy (HACS custom repo also installed) |
+| Home instance | ha.jbstudio.pro (SSH port 323, key `ha_jb`), deployed **v1.51.1** via direct copy (HACS custom repo also installed) |
| Localization | UI en/ru (src/i18n/*.json), everything user-visible localized incl. kiosk popover |
| Tests | Four layers: frontend unit (`npm test`, node:test over `test-build/`), pure backend (`pytest tests_backend`, runs anywhere), HA-harness backend (same folder, CI only — needs py3.13 + pytest-homeassistant-custom-component), and browser smokes (`demo/smoke_*.mjs`, headless chromium). **Counts are not written down here** — they went stale within two releases while the version line beside them was kept current, which reads as less coverage than exists (review R5-2). Run `npm run inventory` for the current numbers, or read them off the last CI run |
| Community | **Telegram chat: https://t.me/ha_houseplan** (created 2026-07-27) — the primary user-facing support channel; GitHub issues stay for bugs/features. Link it from any new release notes and posts |
diff --git a/docs/TESTING.md b/docs/TESTING.md
index c714657..9c22fd8 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -244,6 +244,12 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
at 70% of a device icon and zooming WITH the plan; the small metric rows
under the room name now show in the plan editor too
[auto: smoke_room_cards gearDetached/plainInPlan]
+- [ ] Hidden LQI parity (v1.51.1, HP-1510-01): a room whose only Zigbee
+ devices are hidden paints the same lqi fill on the full and the static
+ card [auto: smoke_hidden_flag lqiParity]
+- [ ] Ghost shows no numbers (v1.51.1, HP-1510-02): a hidden value-display
+ device renders as a plain ghost — no value/temp/hum/LQI, no icon morph
+ [auto: smoke_hidden_flag ghostHidesValue]
- [ ] Hide-from-plan flag (dev, docs/FILTERING.md): every device dialog has
the checkbox, incl. virtual; hidden devices vanish from every mode and
the count, still count toward room LQI, cast no glow/light fill; the
diff --git a/package-lock.json b/package-lock.json
index b1e8c1e..94f16f8 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "houseplan-card",
- "version": "1.51.0",
+ "version": "1.51.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "houseplan-card",
- "version": "1.51.0",
+ "version": "1.51.1",
"license": "MIT",
"dependencies": {
"lit": "^3.1.3",
diff --git a/package.json b/package.json
index 221383e..5fa76be 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "houseplan-card",
- "version": "1.51.0",
+ "version": "1.51.1",
"description": "Interactive house plan Lovelace card for Home Assistant",
"license": "MIT",
"type": "module",
diff --git a/src/houseplan-card.ts b/src/houseplan-card.ts
index e581bc4..13de12e 100755
--- a/src/houseplan-card.ts
+++ b/src/houseplan-card.ts
@@ -36,7 +36,7 @@ import { cardStyles } from './styles';
import { fitInSquare, contentBounds, spaceModels } from './space-geometry';
import { langOf, t, type I18nKey } from './i18n';
-const CARD_VERSION = '1.51.0';
+const CARD_VERSION = '1.51.1';
const LS_KEY = 'houseplan_card_layout_v1';
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
const LS_ZOOM = 'houseplan_card_zoom_v1';
@@ -4206,17 +4206,20 @@ class HouseplanCard extends LitElement {
const p = this._pos(d);
const left = ((p.x - view.x) / view.w) * 100;
const top = ((p.y - view.y) / view.h) * 100;
- // a ghost is configuration, not status: no yellow, no open, no unavail
+ // a ghost is configuration, not status: no yellow, no open, no unavail —
+ // and no live numbers either (HP-1510-02): no value text, no temperature,
+ // no humidity, no LQI badge, no state-morphed icon. The base icon and the
+ // name stay — enough to recognise the device and open its dialog.
const cls = d.hidden ? '' : this._stateClass(d);
- const temp = this._liveTemp(d);
- const hum = this._liveHum(d);
- const lqi = showLqi && !d.virtual ? lqiFor(this.hass, d.entities) : null;
+ const temp = d.hidden ? null : this._liveTemp(d);
+ const hum = d.hidden ? null : this._liveHum(d);
+ const lqi = showLqi && !d.virtual && !d.hidden ? lqiFor(this.hass, d.entities) : null;
const m = d.marker;
const disp = m?.display || 'badge';
const ripple = disp === 'ripple' || disp === 'icon_ripple';
// value-only display: the measurement IS the marker
const primarySt = d.primary ? this.hass.states[d.primary] : undefined;
- const valText = disp === 'value'
+ const valText = disp === 'value' && !d.hidden
? (temp != null ? temp + '°'
: hum != null ? hum + '%'
: primarySt && !isNaN(parseFloat(primarySt.state))
@@ -4225,7 +4228,7 @@ class HouseplanCard extends LitElement {
: null;
// live state variants of the auto icon (doors, locks, bulbs), like core HA
const domain = d.primary ? d.primary.split('.')[0] : null;
- const icon = this._config?.live_states
+ const icon = this._config?.live_states && !d.hidden
? stateIcon(d.icon, domain, primarySt?.attributes?.device_class, primarySt?.state, !!m?.icon)
: d.icon;
// RGB lights color the icon (and the ripple, unless a custom ripple color is set);
diff --git a/src/space-render.ts b/src/space-render.ts
index 21b185c..d5fbd9e 100644
--- a/src/space-render.ts
+++ b/src/space-render.ts
@@ -65,8 +65,13 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
loc,
iconRules,
});
- // the static card never shows hidden devices — there is no editor here
- const devs = all.filter((d) => d.space === o.spaceId && !d.hidden);
+ // Two lists, two jobs (HP-1510-01): AGGREGATION sees every device of the
+ // space — hidden ones still count toward room LQI, same as the full card —
+ // while RENDERING sees only the visible ones (there is no editor here, so
+ // hidden devices are never drawn). Filtering one list for both jobs made
+ // the same room show different Zigbee health on the two cards.
+ const spaceDevs = all.filter((d) => d.space === o.spaceId);
+ const devs = spaceDevs.filter((d) => !d.hidden);
const defPos = defaultPositions(devs, space, iconPct);
const roomShapes = space.rooms
@@ -83,9 +88,9 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
const fillC = r.area
? roomFillStyle(
fill,
- fill === 'lqi' ? areaLqi(o.hass, devs, r.area) : null,
- fill === 'light' ? areaLights(o.hass, devs, r.area) : 'none',
- fill === 'temp' ? areaTemp(o.hass, devs, r.area) : null,
+ fill === 'lqi' ? areaLqi(o.hass, spaceDevs, r.area) : null,
+ fill === 'light' ? areaLights(o.hass, spaceDevs, r.area) : 'none',
+ fill === 'temp' ? areaTemp(o.hass, spaceDevs, r.area) : null,
disp.tempMin,
disp.tempMax,
fillColorsOf(o.cfg?.settings),