mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
Merge inert-openings: View-mode cursors and inert openings v1.28.1
This commit is contained in:
@@ -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.28.0"
|
||||
VERSION = "1.28.1"
|
||||
|
||||
DEFAULT_CONFIG: dict = {
|
||||
"spaces": [],
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,5 +16,5 @@
|
||||
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
|
||||
"requirements": [],
|
||||
"single_config_entry": true,
|
||||
"version": "1.28.0"
|
||||
"version": "1.28.1"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { launch } from './serve.mjs';
|
||||
const { page, browser } = await launch();
|
||||
const res = await page.evaluate(async () => {
|
||||
const out = {};
|
||||
const c = window.__card;
|
||||
const sr = () => c.shadowRoot || c.renderRoot;
|
||||
// добавить открывание на f1
|
||||
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== 'f1' ? s : ({
|
||||
...s, openings: [{ id: 'op1', type: 'door', rx: 550, ry: 200, angle: 90, len_cm: 90 }] })) };
|
||||
c.requestUpdate(); await c.updateComplete;
|
||||
const hit = sr().querySelector('.op-hit');
|
||||
out.hasOpening = !!hit;
|
||||
const cs = hit ? getComputedStyle(hit) : null;
|
||||
out.viewInert = cs ? cs.pointerEvents === 'none' && cs.cursor === 'default' : null;
|
||||
out.lockBadgeInert = (() => { const l = sr().querySelector('.oplock'); return l ? getComputedStyle(l).pointerEvents === 'none' : 'no-badge'; })();
|
||||
// View: клик по открыванию ничего не открывает
|
||||
c._opClick({ stopPropagation(){} }, { id: 'op1', rx: 550, ry: 200, rlen: 90 });
|
||||
out.viewClickNoop = !c._openingInfo && !c._openingDialog;
|
||||
// курсор устройств в Просмотре — pointer, в Устройствах — grab
|
||||
const dev = sr().querySelector('.dev');
|
||||
out.viewDevCursor = getComputedStyle(dev).cursor;
|
||||
// бейдж замка: замок привязан → бейдж кликабелен и открывает инфо
|
||||
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s2) => s2.id !== 'f1' ? s2 : ({
|
||||
...s2, openings: [{ id: 'op1', type: 'door', rx: 550, ry: 200, angle: 90, len_cm: 90, lock: 'lock.front_door' }] })) };
|
||||
c.requestUpdate(); await c.updateComplete;
|
||||
const badge = sr().querySelector('.oplock');
|
||||
out.badgeShown = !!badge;
|
||||
const bcs = badge ? getComputedStyle(badge) : null;
|
||||
out.badgeClickableInView = bcs ? bcs.pointerEvents === 'auto' && bcs.cursor === 'pointer' : null;
|
||||
badge?.click(); await c.updateComplete;
|
||||
out.badgeOpensInfo = !!c._openingInfo;
|
||||
c._openingInfo = null;
|
||||
// Plan: op-hit интерактивен, клик редактирует
|
||||
c._setMode('devices'); await c.updateComplete;
|
||||
out.devModeCursor = getComputedStyle(sr().querySelector('.dev')).cursor;
|
||||
c._setMode('plan'); await c.updateComplete;
|
||||
out.badgeInertInPlan = (() => { const b = sr().querySelector('.oplock'); return b ? getComputedStyle(b).pointerEvents === 'none' : 'no-badge'; })();
|
||||
const cs2 = getComputedStyle(sr().querySelector('.op-hit'));
|
||||
out.planInteractive = cs2.pointerEvents === 'auto' && cs2.cursor === 'grab';
|
||||
const op = c._spaceModel().openings?.[0] || { id: 'op1', rx: 550, ry: 200, rlen: 90 };
|
||||
c._opClick({ stopPropagation(){} }, op);
|
||||
out.planClickEdits = !!c._openingDialog;
|
||||
c._openingDialog = null; c._setMode('view');
|
||||
return out;
|
||||
});
|
||||
console.log(JSON.stringify(res, null, 1));
|
||||
await browser.close();
|
||||
File diff suppressed because one or more lines are too long
Vendored
+20
-15
File diff suppressed because one or more lines are too long
@@ -1,5 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
## v1.28.1 — 2026-07-22 (View-mode polish: cursors and inert openings)
|
||||
- **Device icons in View no longer show the grab cursor** (drag lives in the
|
||||
Devices mode); they show a pointer — clicking still works exactly as before.
|
||||
- **Doors and windows in View are pure drawings**: no grab cursor, no hover
|
||||
outline, no hit target, no click — regardless of what is bound to them.
|
||||
- **The lock badge is the one exception**: when a lock is bound, the badge is
|
||||
shown and clickable in View (pointer cursor, click opens the door/lock info
|
||||
card). It stays inert in Plan so it does not fight editing.
|
||||
- In Plan an opening remains fully interactive: grab cursor + hover outline,
|
||||
dragging along walls, and a click with ANY tool opens its properties dialog.
|
||||
|
||||
## v1.28.0 — 2026-07-21 (sub-area rooms: manual placement without an HA area; issue #3)
|
||||
- **Devices can now be placed into rooms that have no Home Assistant area.**
|
||||
The marker dialog's room list includes area-less rooms (marked "no area,
|
||||
|
||||
+10
-2
@@ -40,8 +40,16 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
|
||||
- [ ] Devices: icon drag works, click opens the marker editor directly; +/👁/↺/⬡
|
||||
buttons; accent stage frame [auto]
|
||||
- [ ] Mode tabs hidden for non-admin users; segmented control highlights the active mode
|
||||
- [ ] Opening: single click in View shows the info card; in Plan a click edits it;
|
||||
double-click no longer does anything special [auto]
|
||||
- [ ] Openings in View (v1.28.1): the door/window itself is a pure drawing — no
|
||||
cursor change, no hover outline, no hit target, no click, regardless of
|
||||
bindings [auto]
|
||||
- [ ] The LOCK BADGE is the one exception: when a lock is bound it is shown and
|
||||
clickable in View (pointer cursor, click → door/lock info card); inert in
|
||||
Plan so it does not fight editing [auto]
|
||||
- [ ] Device icons in View show a pointer cursor (no grab); grab only in the
|
||||
Devices mode [auto]
|
||||
- [ ] In Plan an opening is interactive: grab cursor, hover outline, drag along
|
||||
walls, click (any tool) opens its properties [auto]
|
||||
|
||||
## Onboarding ★
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "houseplan-card",
|
||||
"version": "1.28.0",
|
||||
"version": "1.28.1",
|
||||
"description": "Interactive house plan Lovelace card for Home Assistant",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
+4
-10
@@ -32,7 +32,7 @@ import './space-card';
|
||||
import { cardStyles } from './styles';
|
||||
import { langOf, t, type I18nKey } from './i18n';
|
||||
|
||||
const CARD_VERSION = '1.28.0';
|
||||
const CARD_VERSION = '1.28.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';
|
||||
@@ -1261,15 +1261,9 @@ class HouseplanCard extends LitElement {
|
||||
private _opClick(ev: MouseEvent, o: OpeningCfg & { rx: number; ry: number; rlen: number }): void {
|
||||
ev.stopPropagation();
|
||||
if (this._opDrag?.moved) return; // that click was the tail of a drag
|
||||
if (this._mode === 'view') {
|
||||
// view: an opening is a status object — show the door/lock info card
|
||||
this._openingInfo = o;
|
||||
return;
|
||||
}
|
||||
if (this._mode === 'plan' && this._tool !== 'opening') {
|
||||
// plan: any click on an opening edits it (the Opening tool also does)
|
||||
this._editOpening(o);
|
||||
}
|
||||
// openings are inert outside Plan mode (owner's decision: View must not
|
||||
// interact with them at all); in Plan any click on an opening edits it
|
||||
if (this._mode === 'plan') this._editOpening(o);
|
||||
}
|
||||
|
||||
private _saveOpening(): void {
|
||||
|
||||
+18
-13
@@ -242,22 +242,26 @@ export const cardStyles = css`
|
||||
transition: opacity 0.15s;
|
||||
pointer-events: none;
|
||||
}
|
||||
g.opening:hover .op-outline {
|
||||
.stage.markup g.opening:hover .op-outline {
|
||||
opacity: 0.9;
|
||||
}
|
||||
/* openings are pure status graphics outside Plan mode: no cursor, no hover,
|
||||
no hit target — View must not interact with them at all */
|
||||
.op-hit {
|
||||
fill: transparent;
|
||||
cursor: grab;
|
||||
pointer-events: auto;
|
||||
touch-action: none; /* drags, not scrolls, on touch */
|
||||
}
|
||||
.op-hit:active {
|
||||
cursor: grabbing;
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
.stage.markup .op-hit {
|
||||
pointer-events: none; /* markup clicks go to the stage tools */
|
||||
pointer-events: auto;
|
||||
cursor: grab;
|
||||
touch-action: none; /* drags, not scrolls, on touch */
|
||||
}
|
||||
.stage.markup .op-hit:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.oplock {
|
||||
pointer-events: none; /* inert while editing; clickable in View (rule below) */
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
width: calc(var(--icon-size, 2.5cqw) * 0.62);
|
||||
@@ -268,9 +272,11 @@ export const cardStyles = css`
|
||||
justify-content: center;
|
||||
background: var(--hp-bg);
|
||||
border: 1px solid var(--hp-line);
|
||||
z-index: 1;
|
||||
}
|
||||
.stage.mode-view .oplock {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
.oplock ha-icon {
|
||||
--mdc-icon-size: calc(var(--icon-size, 2.5cqw) * 0.4);
|
||||
@@ -540,7 +546,7 @@ export const cardStyles = css`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--hp-txt);
|
||||
cursor: grab;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
transition: background 0.15s, border-color 0.15s, opacity 0.2s;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
|
||||
@@ -553,9 +559,8 @@ export const cardStyles = css`
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
}
|
||||
.dev:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.stage.mode-devices .dev { cursor: grab; }
|
||||
.stage.mode-devices .dev:active { cursor: grabbing; }
|
||||
.dev:hover {
|
||||
background: var(--hp-accent);
|
||||
color: var(--text-primary-color, #fff);
|
||||
|
||||
Reference in New Issue
Block a user