From 9c92bcdf2fbe44c6a5aa7331bd8948887f62546a Mon Sep 17 00:00:00 2001 From: Matysh Date: Thu, 23 Jul 2026 17:17:01 +0300 Subject: [PATCH] ux v1.38.3: true dashed open boundaries in the Plan editor too - room outline trimming applies in markup (blue .room-outline.outlined); merge/split-picked rooms keep the full amber stroke - smoke_openwall extended (14 checks); TESTING/CHANGELOG same-commit --- custom_components/houseplan/const.py | 2 +- .../houseplan/frontend/houseplan-card.js | 16 +++++++++++----- custom_components/houseplan/manifest.json | 2 +- demo/smoke_openwall.mjs | 7 ++++++- demo/srv/assets/houseplan-card.js | 16 +++++++++++----- dist/houseplan-card.js | 16 +++++++++++----- docs/CHANGELOG.md | 5 +++++ docs/TESTING.md | 3 +++ package.json | 2 +- src/houseplan-card.ts | 14 +++++++++----- src/styles.ts | 5 +++++ 11 files changed, 64 insertions(+), 24 deletions(-) diff --git a/custom_components/houseplan/const.py b/custom_components/houseplan/const.py index 39a2dcc..658c2ec 100755 --- a/custom_components/houseplan/const.py +++ b/custom_components/houseplan/const.py @@ -11,7 +11,7 @@ PLANS_DIR = "houseplan/plans" # relative to the HA configuration directory FILES_URL = "/houseplan_files/files" FILES_DIR = "houseplan/files" CONF_ADMIN_ONLY = "admin_only" -VERSION = "1.38.2" +VERSION = "1.38.3" DEFAULT_CONFIG: dict = { "spaces": [], diff --git a/custom_components/houseplan/frontend/houseplan-card.js b/custom_components/houseplan/frontend/houseplan-card.js index 2e44841..2666d99 100755 --- a/custom_components/houseplan/frontend/houseplan-card.js +++ b/custom_components/houseplan/frontend/houseplan-card.js @@ -598,6 +598,11 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow stroke-width: 2.5; pointer-events: none; } + /* Plan editor: trimmed outlines use the markup blue */ + .room-outline.outlined { + stroke: rgba(62, 166, 255, 0.55); + stroke-opacity: 1; + } .openwalls.hot .openwall { stroke: #ffc14d; opacity: 1; @@ -1693,16 +1698,17 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._editing&&!this._markup?j``:G} ${e.bg?j``:G} ${this._renderDecorLayer()} - ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";if(!this._markup&&(o.showBorders||"none"!==o.fill)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===o.fill?this._fillColors.glow_base:t.area?Qe(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?vi(this.hass,this._devices,t.area):"none","temp"===o.fill?yi(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const r=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,t.area?yi(this.hass,this._devices,t.area):null),a=!e.bg&&!o.showNames&&!this._markup,l=this._roomCenter(t),c=!this._markup&&t.id?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];c.length&&(i+=" noedge");const h=we(t),p=h?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(h,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],d=p.length&&h?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" + ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";if(!this._markup&&(o.showBorders||"none"!==o.fill)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===o.fill?this._fillColors.glow_base:t.area?Qe(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?vi(this.hass,this._devices,t.area):"none","temp"===o.fill?yi(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const r=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,t.area?yi(this.hass,this._devices,t.area):null),a=!e.bg&&!o.showNames&&!this._markup,l=this._roomCenter(t),c=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),h=t.id&&!c?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];h.length&&(i+=" noedge");const p=we(t),d=p?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(p,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],u=d.length&&p?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" @click=${()=>this._clickRoom(t)} @mousemove=${r} @mouseleave=${()=>this._tip=null}>`:t.poly?j`this._clickRoom(t)} @mousemove=${r} @mouseleave=${()=>this._tip=null}>`:j`this._clickRoom(t)} @mousemove=${r} - @mouseleave=${()=>this._tip=null}>`,u=c.length&&h?function(t,e,i=1e-6){const s=[];for(let o=0;oo||s>o)continue;const r=(t[0]-n[0])*h+(t[1]-n[1])*p,a=(t[2]-n[0])*h+(t[3]-n[1])*p,l=Math.max(0,Math.min(r,a)),u=Math.min(c,Math.max(r,a));u-l>i&&d.push([l,u])}if(!d.length){s.push([n[0],n[1],r[0],r[1]]);continue}d.sort((t,e)=>t[0]-e[0]);let u=0;for(const[t,e]of d)t-u>i&&s.push([n[0]+h*u,n[1]+p*u,n[0]+h*t,n[1]+p*t]),u=Math.max(u,e);c-u>i&&s.push([n[0]+h*u,n[1]+p*u,r[0],r[1]])}return s}(h,c,.02*this._gridPitch):null,_=u?j``:G;return j`${d}${_}${a?j`${t.name}`:G}`})} + @mouseleave=${()=>this._tip=null}>`,_=h.length&&p?function(t,e,i=1e-6){const s=[];for(let o=0;oo||s>o)continue;const r=(t[0]-n[0])*h+(t[1]-n[1])*p,a=(t[2]-n[0])*h+(t[3]-n[1])*p,l=Math.max(0,Math.min(r,a)),u=Math.min(c,Math.max(r,a));u-l>i&&d.push([l,u])}if(!d.length){s.push([n[0],n[1],r[0],r[1]]);continue}d.sort((t,e)=>t[0]-e[0]);let u=0;for(const[t,e]of d)t-u>i&&s.push([n[0]+h*u,n[1]+p*u,n[0]+h*t,n[1]+p*t]),u=Math.max(u,e);c-u>i&&s.push([n[0]+h*u,n[1]+p*u,r[0],r[1]])}return s}(p,h,.02*this._gridPitch):null,g=_?j``:G;return j`${u}${g}${a?j`${t.name}`:G}`})} ${"glow"!==o.fill||this._markup?G:this._renderGlowLayer(e)} ${this._renderOpenWalls(o)} ${this._markup?this._renderMarkupLayer(i):G} @@ -2300,4 +2306,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow - `}}Ki.properties={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},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{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}},Ki.styles=Mi,customElements.get("houseplan-card")||customElements.define("houseplan-card",Ki),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.38.2 ","background:#3ea6ff;color:#04121f;font-weight:700",""); + `}}Ki.properties={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},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{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}},Ki.styles=Mi,customElements.get("houseplan-card")||customElements.define("houseplan-card",Ki),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.38.3 ","background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/custom_components/houseplan/manifest.json b/custom_components/houseplan/manifest.json index 8006ed4..03f5d6f 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.38.2" + "version": "1.38.3" } diff --git a/demo/smoke_openwall.mjs b/demo/smoke_openwall.mjs index 1715af9..c568ab3 100644 --- a/demo/smoke_openwall.mjs +++ b/demo/smoke_openwall.mjs @@ -24,7 +24,12 @@ const res = await page.evaluate(async () => { const gi = order.indexOf('glowlayer'); const oi = order.indexOf('openwalls'); out.dashAboveGlow = gi === -1 || oi > gi; - c._setMode('plan'); c._tool = 'openwall'; await c.updateComplete; + // в редакторе плана: пунктир виден, штрих комнат вырезан, контур синий + c._setMode('plan'); c._tool = 'draw'; await c.updateComplete; + out.planDashes = sr().querySelectorAll('.openwall').length > 0; + out.planNoedge = sr().querySelectorAll('.room.noedge').length >= 2; + out.planBlueOutline = sr().querySelectorAll('.room-outline.outlined').length >= 2; + c._tool = 'openwall'; await c.updateComplete; // повторный клик закрывает c._openWallClick([550, 0.25 * H]); await c.updateComplete; out.toggledOff = !(c._curSpaceCfg.rooms.find((r) => r.id === 'r1').open_to || []).includes('r2'); diff --git a/demo/srv/assets/houseplan-card.js b/demo/srv/assets/houseplan-card.js index 2e44841..2666d99 100755 --- a/demo/srv/assets/houseplan-card.js +++ b/demo/srv/assets/houseplan-card.js @@ -598,6 +598,11 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow stroke-width: 2.5; pointer-events: none; } + /* Plan editor: trimmed outlines use the markup blue */ + .room-outline.outlined { + stroke: rgba(62, 166, 255, 0.55); + stroke-opacity: 1; + } .openwalls.hot .openwall { stroke: #ffc14d; opacity: 1; @@ -1693,16 +1698,17 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._editing&&!this._markup?j``:G} ${e.bg?j``:G} ${this._renderDecorLayer()} - ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";if(!this._markup&&(o.showBorders||"none"!==o.fill)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===o.fill?this._fillColors.glow_base:t.area?Qe(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?vi(this.hass,this._devices,t.area):"none","temp"===o.fill?yi(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const r=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,t.area?yi(this.hass,this._devices,t.area):null),a=!e.bg&&!o.showNames&&!this._markup,l=this._roomCenter(t),c=!this._markup&&t.id?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];c.length&&(i+=" noedge");const h=we(t),p=h?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(h,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],d=p.length&&h?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" + ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";if(!this._markup&&(o.showBorders||"none"!==o.fill)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===o.fill?this._fillColors.glow_base:t.area?Qe(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?vi(this.hass,this._devices,t.area):"none","temp"===o.fill?yi(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const r=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,t.area?yi(this.hass,this._devices,t.area):null),a=!e.bg&&!o.showNames&&!this._markup,l=this._roomCenter(t),c=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),h=t.id&&!c?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];h.length&&(i+=" noedge");const p=we(t),d=p?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(p,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],u=d.length&&p?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" @click=${()=>this._clickRoom(t)} @mousemove=${r} @mouseleave=${()=>this._tip=null}>`:t.poly?j`this._clickRoom(t)} @mousemove=${r} @mouseleave=${()=>this._tip=null}>`:j`this._clickRoom(t)} @mousemove=${r} - @mouseleave=${()=>this._tip=null}>`,u=c.length&&h?function(t,e,i=1e-6){const s=[];for(let o=0;oo||s>o)continue;const r=(t[0]-n[0])*h+(t[1]-n[1])*p,a=(t[2]-n[0])*h+(t[3]-n[1])*p,l=Math.max(0,Math.min(r,a)),u=Math.min(c,Math.max(r,a));u-l>i&&d.push([l,u])}if(!d.length){s.push([n[0],n[1],r[0],r[1]]);continue}d.sort((t,e)=>t[0]-e[0]);let u=0;for(const[t,e]of d)t-u>i&&s.push([n[0]+h*u,n[1]+p*u,n[0]+h*t,n[1]+p*t]),u=Math.max(u,e);c-u>i&&s.push([n[0]+h*u,n[1]+p*u,r[0],r[1]])}return s}(h,c,.02*this._gridPitch):null,_=u?j``:G;return j`${d}${_}${a?j`${t.name}`:G}`})} + @mouseleave=${()=>this._tip=null}>`,_=h.length&&p?function(t,e,i=1e-6){const s=[];for(let o=0;oo||s>o)continue;const r=(t[0]-n[0])*h+(t[1]-n[1])*p,a=(t[2]-n[0])*h+(t[3]-n[1])*p,l=Math.max(0,Math.min(r,a)),u=Math.min(c,Math.max(r,a));u-l>i&&d.push([l,u])}if(!d.length){s.push([n[0],n[1],r[0],r[1]]);continue}d.sort((t,e)=>t[0]-e[0]);let u=0;for(const[t,e]of d)t-u>i&&s.push([n[0]+h*u,n[1]+p*u,n[0]+h*t,n[1]+p*t]),u=Math.max(u,e);c-u>i&&s.push([n[0]+h*u,n[1]+p*u,r[0],r[1]])}return s}(p,h,.02*this._gridPitch):null,g=_?j``:G;return j`${u}${g}${a?j`${t.name}`:G}`})} ${"glow"!==o.fill||this._markup?G:this._renderGlowLayer(e)} ${this._renderOpenWalls(o)} ${this._markup?this._renderMarkupLayer(i):G} @@ -2300,4 +2306,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow - `}}Ki.properties={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},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{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}},Ki.styles=Mi,customElements.get("houseplan-card")||customElements.define("houseplan-card",Ki),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.38.2 ","background:#3ea6ff;color:#04121f;font-weight:700",""); + `}}Ki.properties={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},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{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}},Ki.styles=Mi,customElements.get("houseplan-card")||customElements.define("houseplan-card",Ki),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.38.3 ","background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/dist/houseplan-card.js b/dist/houseplan-card.js index 2e44841..2666d99 100755 --- a/dist/houseplan-card.js +++ b/dist/houseplan-card.js @@ -598,6 +598,11 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow stroke-width: 2.5; pointer-events: none; } + /* Plan editor: trimmed outlines use the markup blue */ + .room-outline.outlined { + stroke: rgba(62, 166, 255, 0.55); + stroke-opacity: 1; + } .openwalls.hot .openwall { stroke: #ffc14d; opacity: 1; @@ -1693,16 +1698,17 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow ${this._editing&&!this._markup?j``:G} ${e.bg?j``:G} ${this._renderDecorLayer()} - ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";if(!this._markup&&(o.showBorders||"none"!==o.fill)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===o.fill?this._fillColors.glow_base:t.area?Qe(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?vi(this.hass,this._devices,t.area):"none","temp"===o.fill?yi(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const r=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,t.area?yi(this.hass,this._devices,t.area):null),a=!e.bg&&!o.showNames&&!this._markup,l=this._roomCenter(t),c=!this._markup&&t.id?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];c.length&&(i+=" noedge");const h=we(t),p=h?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(h,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],d=p.length&&h?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" + ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":"");!this._markup||t.id!==this._mergeSel&&t.id!==this._splitSel?.roomId||(i+=" picked");let s="";if(!this._markup&&(o.showBorders||"none"!==o.fill)){i+=" styled";const e=[];e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.showBorders?o.opacity:0}`);const n="glow"===o.fill?this._fillColors.glow_base:t.area?Qe(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?vi(this.hass,this._devices,t.area):"none","temp"===o.fill?yi(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax,this._fillColors):null;n?(i+=" filled",e.push(`--room-fill:${n.c}`,`--room-fill-op:${n.a.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const r=e=>this._showTip(e,t.name,this._t("tip.room"),n?this._roomLqi(t.area):null,t.area?yi(this.hass,this._devices,t.area):null),a=!e.bg&&!o.showNames&&!this._markup,l=this._roomCenter(t),c=this._markup&&(t.id===this._mergeSel||t.id===this._splitSel?.roomId),h=t.id&&!c?this._openPairs().filter(e=>e.a.id===t.id||e.b.id===t.id).flatMap(t=>t.segs):[];h.length&&(i+=" noedge");const p=we(t),d=p?function(t,e,i=1e-6){const s=e.filter(e=>Re(t,e,i));return s.filter(t=>!s.some(e=>e!==t&&Re(e,t,i)))}(p,e.rooms.filter(e=>e!==t).map(t=>we(t)).filter(Boolean)):[],u=d.length&&p?j`t[0]+" "+t[1]).join(" L ")+" Z").join(" ")}" @click=${()=>this._clickRoom(t)} @mousemove=${r} @mouseleave=${()=>this._tip=null}>`:t.poly?j`this._clickRoom(t)} @mousemove=${r} @mouseleave=${()=>this._tip=null}>`:j`this._clickRoom(t)} @mousemove=${r} - @mouseleave=${()=>this._tip=null}>`,u=c.length&&h?function(t,e,i=1e-6){const s=[];for(let o=0;oo||s>o)continue;const r=(t[0]-n[0])*h+(t[1]-n[1])*p,a=(t[2]-n[0])*h+(t[3]-n[1])*p,l=Math.max(0,Math.min(r,a)),u=Math.min(c,Math.max(r,a));u-l>i&&d.push([l,u])}if(!d.length){s.push([n[0],n[1],r[0],r[1]]);continue}d.sort((t,e)=>t[0]-e[0]);let u=0;for(const[t,e]of d)t-u>i&&s.push([n[0]+h*u,n[1]+p*u,n[0]+h*t,n[1]+p*t]),u=Math.max(u,e);c-u>i&&s.push([n[0]+h*u,n[1]+p*u,r[0],r[1]])}return s}(h,c,.02*this._gridPitch):null,_=u?j``:G;return j`${d}${_}${a?j`${t.name}`:G}`})} + @mouseleave=${()=>this._tip=null}>`,_=h.length&&p?function(t,e,i=1e-6){const s=[];for(let o=0;oo||s>o)continue;const r=(t[0]-n[0])*h+(t[1]-n[1])*p,a=(t[2]-n[0])*h+(t[3]-n[1])*p,l=Math.max(0,Math.min(r,a)),u=Math.min(c,Math.max(r,a));u-l>i&&d.push([l,u])}if(!d.length){s.push([n[0],n[1],r[0],r[1]]);continue}d.sort((t,e)=>t[0]-e[0]);let u=0;for(const[t,e]of d)t-u>i&&s.push([n[0]+h*u,n[1]+p*u,n[0]+h*t,n[1]+p*t]),u=Math.max(u,e);c-u>i&&s.push([n[0]+h*u,n[1]+p*u,r[0],r[1]])}return s}(p,h,.02*this._gridPitch):null,g=_?j``:G;return j`${u}${g}${a?j`${t.name}`:G}`})} ${"glow"!==o.fill||this._markup?G:this._renderGlowLayer(e)} ${this._renderOpenWalls(o)} ${this._markup?this._renderMarkupLayer(i):G} @@ -2300,4 +2306,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow - `}}Ki.properties={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},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{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}},Ki.styles=Mi,customElements.get("houseplan-card")||customElements.define("houseplan-card",Ki),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.38.2 ","background:#3ea6ff;color:#04121f;font-weight:700",""); + `}}Ki.properties={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},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{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}},Ki.styles=Mi,customElements.get("houseplan-card")||customElements.define("houseplan-card",Ki),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.38.3 ","background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2b4c989..1d6dee4 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v1.38.3 — 2026-07-23 +- The Plan editor now shows open boundaries as a true dash as well: the blue + markup outlines are trimmed under the open stretches (rooms picked for + merge/split keep their full amber highlight). + ## v1.38.2 — 2026-07-23 - The card now **remembers where you were**: the selected space and the active editor survive navigation and closing the tab (localStorage; edit modes are diff --git a/docs/TESTING.md b/docs/TESTING.md index cba5287..c1f616f 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -140,6 +140,9 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi (explicit ripple color still wins); off/white lights unchanged [auto] - [ ] Alarm pulse (v1.27.0): leak/smoke/gas/CO/siren in 'on' pulse a red ring over any display mode; clears on 'off'; unavailable never alarms [auto]; reduced-motion static +- [ ] Dashed boundaries in the Plan editor (v1.38.3): open stretches render as + a true dash in markup too (blue trimmed outlines); merge/split-picked + rooms keep their full amber highlight [auto] - [ ] Nav persistence (v1.38.2): closing/reopening the tab restores the last space AND editor mode (admins; localStorage); a #space= deep link beats the saved space; a stale cache without the saved space retries after the diff --git a/package.json b/package.json index 437b708..98597a8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "houseplan-card", - "version": "1.38.2", + "version": "1.38.3", "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 27871ec..5b68cd2 100755 --- a/src/houseplan-card.ts +++ b/src/houseplan-card.ts @@ -32,7 +32,7 @@ import './space-card'; import { cardStyles } from './styles'; import { langOf, t, type I18nKey } from './i18n'; -const CARD_VERSION = '1.38.2'; +const CARD_VERSION = '1.38.3'; 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'; @@ -3201,8 +3201,11 @@ class HouseplanCard extends LitElement { const label = !space.bg && !disp.showNames && !this._markup; const c = this._roomCenter(r); // open boundaries: this room's solid stroke must not run beneath - // the dashed stretches — suppress it and draw a trimmed outline - const openCuts = !this._markup && r.id + // the dashed stretches — suppress it and draw a trimmed outline. + // Applies in the Plan editor too (picked rooms keep their full + // amber highlight — the merge/split selection must stay visible). + const isPicked = this._markup && (r.id === this._mergeSel || r.id === this._splitSel?.roomId); + const openCuts = r.id && !isPicked ? this._openPairs() .filter((pp) => pp.a.id === r.id || pp.b.id === r.id) .flatMap((pp) => pp.segs) @@ -3232,8 +3235,9 @@ class HouseplanCard extends LitElement { ? outlineWithout(myPoly, openCuts, this._gridPitch * 0.02) : null; const outline = trimmed - ? svg`` + ? svg`` : nothing; return svg`${shape}${outline}${label ? svg`${r.name}` : nothing}`; })} diff --git a/src/styles.ts b/src/styles.ts index 5db7031..e19b8d8 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -595,6 +595,11 @@ export const cardStyles = css` stroke-width: 2.5; pointer-events: none; } + /* Plan editor: trimmed outlines use the markup blue */ + .room-outline.outlined { + stroke: rgba(62, 166, 255, 0.55); + stroke-opacity: 1; + } .openwalls.hot .openwall { stroke: #ffc14d; opacity: 1;