feat v1.15.6: room hover also reveals the border — keep stroke colour, hide via --room-stroke-op, so hover's stroke-opacity:1 shows the outline even when borders are off

This commit is contained in:
Matysh
2026-07-08 18:13:23 +03:00
parent 5f9cfb9e10
commit fbd3887e14
8 changed files with 19 additions and 14 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ PLANS_DIR = "houseplan/plans" # relative to the HA configuration directory
FILES_URL = "/houseplan_files/files" FILES_URL = "/houseplan_files/files"
FILES_DIR = "houseplan/files" FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only" CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.15.5" VERSION = "1.15.6"
DEFAULT_CONFIG: dict = { DEFAULT_CONFIG: dict = {
"spaces": [], "spaces": [],
@@ -1011,7 +1011,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
<svg viewBox="${n.x} ${n.y} ${n.w} ${n.h}" preserveAspectRatio="xMidYMid meet"> <svg viewBox="${n.x} ${n.y} ${n.w} ${n.h}" preserveAspectRatio="xMidYMid meet">
${this._markup?this._renderMarkupDefs(i):W} ${this._markup?this._renderMarkupDefs(i):W}
${e.bg?j`<image href="${e.bg.href}" x="${e.bg.x}" y="${e.bg.y}" width="${e.bg.w}" height="${e.bg.h}" preserveAspectRatio="none" />`:W} ${e.bg?j`<image href="${e.bg.href}" x="${e.bg.x}" y="${e.bg.y}" width="${e.bg.w}" height="${e.bg.h}" preserveAspectRatio="none" />`:W}
${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":""),s="";if(!this._markup&&(o.showBorders||"none"!==o.fill)){i+=" styled";const e=[];o.showBorders?e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.opacity}`):e.push("--room-stroke:transparent","--room-stroke-op:0");const a=t.area?function(t,e,i,s,o=20,a=25){if("lqi"===t)return null==e?null:ft(e);if("light"===t)return"none"===i?null:"on"===i?"#ffd45c":"#9aa0a6";if("temp"===t){if(null==s)return null;const t=Math.min(o,a),e=Math.max(o,a);return s<t?"#4fc3f7":s>e?"#ffd45c":"#66d17a"}return null}(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?function(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}(this.hass,this._devices,t.area):"none","temp"===o.fill?Nt(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax):null;a?(i+=" filled",e.push(`--room-fill:${a}`,`--room-fill-op:${(.3*o.opacity).toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),this._config?.show_signal?this._roomLqi(t.area):null,t.area?Nt(this.hass,this._devices,t.area):null),r=!e.bg&&!o.showNames||this._markup,n=this._roomCenter(t),l=t.poly?j`<polygon class="${i}" style="${s}" points="${t.poly.map(t=>t.join(",")).join(" ")}" ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":""),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 a=t.area?function(t,e,i,s,o=20,a=25){if("lqi"===t)return null==e?null:ft(e);if("light"===t)return"none"===i?null:"on"===i?"#ffd45c":"#9aa0a6";if("temp"===t){if(null==s)return null;const t=Math.min(o,a),e=Math.max(o,a);return s<t?"#4fc3f7":s>e?"#ffd45c":"#66d17a"}return null}(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?function(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}(this.hass,this._devices,t.area):"none","temp"===o.fill?Nt(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax):null;a?(i+=" filled",e.push(`--room-fill:${a}`,`--room-fill-op:${(.3*o.opacity).toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),this._config?.show_signal?this._roomLqi(t.area):null,t.area?Nt(this.hass,this._devices,t.area):null),r=!e.bg&&!o.showNames||this._markup,n=this._roomCenter(t),l=t.poly?j`<polygon class="${i}" style="${s}" points="${t.poly.map(t=>t.join(",")).join(" ")}"
@click=${()=>this._clickRoom(t)} @mousemove=${a} @click=${()=>this._clickRoom(t)} @mousemove=${a}
@mouseleave=${()=>this._tip=null}></polygon>`:j`<rect class="${i}" style="${s}" @mouseleave=${()=>this._tip=null}></polygon>`:j`<rect class="${i}" style="${s}"
x="${t.x}" y="${t.y}" width="${t.w}" height="${t.h}" rx="${.03*Math.min(t.w,t.h)}" x="${t.x}" y="${t.y}" width="${t.w}" height="${t.h}" rx="${.03*Math.min(t.w,t.h)}"
@@ -1315,4 +1315,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
</button> </button>
</div> </div>
</div> </div>
</div>`}}Yt.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},_markup:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Yt.styles=Lt,customElements.get("houseplan-card")||customElements.define("houseplan-card",Yt),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.15.5 ","background:#3ea6ff;color:#04121f;font-weight:700",""); </div>`}}Yt.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},_markup:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Yt.styles=Lt,customElements.get("houseplan-card")||customElements.define("houseplan-card",Yt),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.15.6 ","background:#3ea6ff;color:#04121f;font-weight:700","");
+1 -1
View File
@@ -16,5 +16,5 @@
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues", "issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
"requirements": [], "requirements": [],
"single_config_entry": true, "single_config_entry": true,
"version": "1.15.5" "version": "1.15.6"
} }
+2 -2
View File
@@ -1011,7 +1011,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
<svg viewBox="${n.x} ${n.y} ${n.w} ${n.h}" preserveAspectRatio="xMidYMid meet"> <svg viewBox="${n.x} ${n.y} ${n.w} ${n.h}" preserveAspectRatio="xMidYMid meet">
${this._markup?this._renderMarkupDefs(i):W} ${this._markup?this._renderMarkupDefs(i):W}
${e.bg?j`<image href="${e.bg.href}" x="${e.bg.x}" y="${e.bg.y}" width="${e.bg.w}" height="${e.bg.h}" preserveAspectRatio="none" />`:W} ${e.bg?j`<image href="${e.bg.href}" x="${e.bg.x}" y="${e.bg.y}" width="${e.bg.w}" height="${e.bg.h}" preserveAspectRatio="none" />`:W}
${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":""),s="";if(!this._markup&&(o.showBorders||"none"!==o.fill)){i+=" styled";const e=[];o.showBorders?e.push(`--room-stroke:${o.color}`,`--room-stroke-op:${o.opacity}`):e.push("--room-stroke:transparent","--room-stroke-op:0");const a=t.area?function(t,e,i,s,o=20,a=25){if("lqi"===t)return null==e?null:ft(e);if("light"===t)return"none"===i?null:"on"===i?"#ffd45c":"#9aa0a6";if("temp"===t){if(null==s)return null;const t=Math.min(o,a),e=Math.max(o,a);return s<t?"#4fc3f7":s>e?"#ffd45c":"#66d17a"}return null}(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?function(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}(this.hass,this._devices,t.area):"none","temp"===o.fill?Nt(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax):null;a?(i+=" filled",e.push(`--room-fill:${a}`,`--room-fill-op:${(.3*o.opacity).toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),this._config?.show_signal?this._roomLqi(t.area):null,t.area?Nt(this.hass,this._devices,t.area):null),r=!e.bg&&!o.showNames||this._markup,n=this._roomCenter(t),l=t.poly?j`<polygon class="${i}" style="${s}" points="${t.poly.map(t=>t.join(",")).join(" ")}" ${e.rooms.filter(t=>t.area||this._markup||o.showBorders).map(t=>{let i="room "+(e.bg?"overlay":"yard")+(this._markup?" outlined":""),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 a=t.area?function(t,e,i,s,o=20,a=25){if("lqi"===t)return null==e?null:ft(e);if("light"===t)return"none"===i?null:"on"===i?"#ffd45c":"#9aa0a6";if("temp"===t){if(null==s)return null;const t=Math.min(o,a),e=Math.max(o,a);return s<t?"#4fc3f7":s>e?"#ffd45c":"#66d17a"}return null}(o.fill,"lqi"===o.fill?this._roomLqi(t.area):null,"light"===o.fill?function(t,e,i){let s=!1;for(const o of e)if(o.area===i)for(const e of o.entities)if(e.startsWith("light.")&&(s=!0,"on"===t.states[e]?.state))return"on";return s?"off":"none"}(this.hass,this._devices,t.area):"none","temp"===o.fill?Nt(this.hass,this._devices,t.area):null,o.tempMin,o.tempMax):null;a?(i+=" filled",e.push(`--room-fill:${a}`,`--room-fill-op:${(.3*o.opacity).toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),s=e.join(";")}const a=e=>this._showTip(e,t.name,this._t("tip.room"),this._config?.show_signal?this._roomLqi(t.area):null,t.area?Nt(this.hass,this._devices,t.area):null),r=!e.bg&&!o.showNames||this._markup,n=this._roomCenter(t),l=t.poly?j`<polygon class="${i}" style="${s}" points="${t.poly.map(t=>t.join(",")).join(" ")}"
@click=${()=>this._clickRoom(t)} @mousemove=${a} @click=${()=>this._clickRoom(t)} @mousemove=${a}
@mouseleave=${()=>this._tip=null}></polygon>`:j`<rect class="${i}" style="${s}" @mouseleave=${()=>this._tip=null}></polygon>`:j`<rect class="${i}" style="${s}"
x="${t.x}" y="${t.y}" width="${t.w}" height="${t.h}" rx="${.03*Math.min(t.w,t.h)}" x="${t.x}" y="${t.y}" width="${t.w}" height="${t.h}" rx="${.03*Math.min(t.w,t.h)}"
@@ -1315,4 +1315,4 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
</button> </button>
</div> </div>
</div> </div>
</div>`}}Yt.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},_markup:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Yt.styles=Lt,customElements.get("houseplan-card")||customElements.define("houseplan-card",Yt),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.15.5 ","background:#3ea6ff;color:#04121f;font-weight:700",""); </div>`}}Yt.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},_markup:{state:!0},_tool:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},Yt.styles=Lt,customElements.get("houseplan-card")||customElements.define("houseplan-card",Yt),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.15.6 ","background:#3ea6ff;color:#04121f;font-weight:700","");
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## v1.15.6 — 2026-07-08 (room hover also reveals the border)
- Hovering a room now **shows its outline** even when borders are turned off. The stroke
colour (`--room-stroke`) is now always set to the room colour and only hidden via
`--room-stroke-op` — so the existing `stroke-opacity: 1` on hover reveals a crisp border
(previously the stroke was `transparent` when borders were off, so hover showed nothing).
## v1.15.5 — 2026-07-08 (fix: room hover was always grey even when filled) ## v1.15.5 — 2026-07-08 (fix: room hover was always grey even when filled)
- Room hover now **darkens the current fill** for filled rooms and only greys **unfilled** - Room hover now **darkens the current fill** for filled rooms and only greys **unfilled**
ones (as intended since v1.15.1). The legacy `.room.overlay:hover` / `.room.yard:hover` ones (as intended since v1.15.1). The legacy `.room.overlay:hover` / `.room.yard:hover`
+3 -1
View File
@@ -13,7 +13,7 @@
| Item | State | | Item | State |
|---|---| |---|---|
| Version | **v1.15.5** everywhere (manifest, const.py, package.json, CARD_VERSION) | | Version | **v1.15.6** everywhere (manifest, const.py, package.json, CARD_VERSION) |
| GitHub | https://github.com/Matysh/houseplan-card — branch `main`, releases v1.9.3…v1.11.2 | | GitHub | https://github.com/Matysh/houseplan-card — branch `main`, releases v1.9.3…v1.11.2 |
| CI | `.github/workflows/validate.yml` (hacs + hassfest + frontend + backend) — **fully green** since v1.11.1; `release.yml` auto-attaches the card bundle (needs `permissions: contents: write`, fixed) | | CI | `.github/workflows/validate.yml` (hacs + hassfest + frontend + backend) — **fully green** since v1.11.1; `release.yml` auto-attaches the card bundle (needs `permissions: contents: write`, fixed) |
| HACS | Works as custom repository (id 1290210112 on the home instance). **Inclusion PR: https://github.com/hacs/default/pull/9004** (queue ≈2 months as of 2026-07). Lesson: #8995 was auto-closed by hacs-bot — the PR body MUST be their exact template with every checkbox ticked and all 3 links (release, HACS action run, hassfest run); a custom body gets closed without discussion | | HACS | Works as custom repository (id 1290210112 on the home instance). **Inclusion PR: https://github.com/hacs/default/pull/9004** (queue ≈2 months as of 2026-07). Lesson: #8995 was auto-closed by hacs-bot — the PR body MUST be their exact template with every checkbox ticked and all 3 links (release, HACS action run, hassfest run); a custom body gets closed without discussion |
@@ -60,6 +60,8 @@
badge). Verified live. Demo stub made faithful so the smoke guards it. badge). Verified live. Demo stub made faithful so the smoke guards it.
- **v1.15.5** — fix: room hover was always grey; legacy overlay/yard hover rules scoped - **v1.15.5** — fix: room hover was always grey; legacy overlay/yard hover rules scoped
with `:not(.styled)` so filled rooms darken their fill, unfilled ones grey. with `:not(.styled)` so filled rooms darken their fill, unfilled ones grey.
- **v1.15.6** — room hover also reveals the border (stroke colour kept, hidden via
opacity) even when borders are off.
## Where things live ## Where things live
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "houseplan-card", "name": "houseplan-card",
"version": "1.15.5", "version": "1.15.6",
"description": "Interactive house plan Lovelace card for Home Assistant", "description": "Interactive house plan Lovelace card for Home Assistant",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
+3 -6
View File
@@ -25,7 +25,7 @@ import './editor';
import { cardStyles } from './styles'; import { cardStyles } from './styles';
import { langOf, t, type I18nKey } from './i18n'; import { langOf, t, type I18nKey } from './i18n';
const CARD_VERSION = '1.15.5'; const CARD_VERSION = '1.15.6';
const LS_KEY = 'houseplan_card_layout_v1'; 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_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
const LS_ZOOM = 'houseplan_card_zoom_v1'; const LS_ZOOM = 'houseplan_card_zoom_v1';
@@ -1919,11 +1919,8 @@ class HouseplanCard extends LitElement {
if (!this._markup && (disp.showBorders || disp.fill !== 'none')) { if (!this._markup && (disp.showBorders || disp.fill !== 'none')) {
cls += ' styled'; cls += ' styled';
const st: string[] = []; const st: string[] = [];
if (disp.showBorders) { // keep the stroke colour even when borders are hidden, so hover can reveal it
st.push(`--room-stroke:${disp.color}`, `--room-stroke-op:${disp.opacity}`); st.push(`--room-stroke:${disp.color}`, `--room-stroke-op:${disp.showBorders ? disp.opacity : 0}`);
} else {
st.push('--room-stroke:transparent', '--room-stroke-op:0');
}
const fillC = r.area const fillC = r.area
? roomFillColor( ? roomFillColor(
disp.fill, disp.fill,