mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
The Plan editor hid every device marker (display: none), so walls, openings and stairs were placed against devices from memory. It now shows them exactly as the Background editor does (#362): the same markers as View, at the Background editor's effective opacity, fully pointer-inert. The fade is filter: opacity(0.35) on each marker, not on .devlayer, because room labels and room settings buttons share that layer and stay opaque and interactive; filter multiplies the marker's own opacity (.unavail -> 0.35 x 0.35), as the Background layer does. Marker handlers already fail closed outside View/Devices. demo/smoke_plan_device_landmarks.mjs proves AC1/AC2: same markers as View, per-marker alpha equal to Background, core and capsule fall through, a real click on a marker reaches the Walls tool and a room settings button below, cursor and target unchanged, handlers inert, no tabindex. Three mutants guard hiding, opacity override and the pointer boundary. UX-MODES, DECOR-EDITOR, USER-GUIDE.ru mode table, changelogs. Issue: #687 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
68 lines
3.8 KiB
JavaScript
68 lines
3.8 KiB
JavaScript
import { launch, check, checkAll, finish } 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;
|
|
// 1) кнопка настроек комнаты в редакторе плана: заметная, фиксированного размера
|
|
c._setMode('plan'); await c.updateComplete;
|
|
const btn = sr().querySelector('.rlgearbtn');
|
|
out.gearButtonShown = !!btn;
|
|
const cs = btn ? getComputedStyle(btn) : null;
|
|
// 2026-07-29: владелец вдвое уменьшил кнопку — контракт теперь не «не
|
|
// меньше N px», а «размер от иконки устройства и кликабельность»
|
|
out.gearReadable = cs ? parseFloat(cs.fontSize) > 0 && cs.pointerEvents === 'auto' : null;
|
|
out.gearHasLabel = btn ? btn.textContent.trim().length > 0 : null;
|
|
const box = btn?.getBoundingClientRect();
|
|
// (маркеры устройств в редакторе плана — неинтерактивные ориентиры #687,
|
|
// кнопку с ними не сравниваем)
|
|
out.gearTapTarget = box ? box.height > 6 && box.height < 40 : null;
|
|
btn.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
|
|
await c.updateComplete;
|
|
out.gearOpensDialog = c._roomDialog === true && !!c._roomEditId;
|
|
c._roomDialogCancel(); await c.updateComplete;
|
|
// 2) комната без имени тоже получает кнопку (её там и называют)
|
|
const room = c._curSpaceCfg.rooms[0];
|
|
const savedName = room.name;
|
|
room.name = '';
|
|
c._saveConfig(); c.requestUpdate(); await c.updateComplete;
|
|
out.unnamedStillHasGear = sr().querySelectorAll('.rlgearbtn').length >= 1;
|
|
room.name = savedName; c._saveConfig(); c.requestUpdate(); await c.updateComplete;
|
|
// 3) метрики стали крупнее: 0.75em вместо 0.62em
|
|
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== c._space ? s : ({
|
|
...s, settings: { ...(s.settings || {}), show_names: true, label_temp: true } })) };
|
|
c._setMode('view'); c._saveConfig(); c.requestUpdate(); await c.updateComplete;
|
|
await new Promise((r) => setTimeout(r, 150));
|
|
const lbl = [...sr().querySelectorAll('.roomlabel')].find((l) => l.querySelector('.rlmetrics'));
|
|
if (lbl) {
|
|
const nameSz = parseFloat(getComputedStyle(lbl.querySelector('.rlname')).fontSize);
|
|
const metaSz = parseFloat(getComputedStyle(lbl.querySelector('.rlmetrics')).fontSize);
|
|
out.metricsRatio = Math.round((metaSz / nameSz) * 100) / 100;
|
|
} else out.metricsRatio = 'no-metrics';
|
|
// 4) modality lives on this card: touch clears transient hover, a synthetic
|
|
// compatibility mouse cannot restore it, and a real mouse can.
|
|
c._tip = { x: 1, y: 1, title: 't', meta: 'm' };
|
|
c._hoverRoom = { space: c._space, room: c._curSpaceCfg.rooms[0] };
|
|
c._notePointer(new PointerEvent('pointerdown', { pointerType: 'touch' }));
|
|
out.touchClearsTransientHover = c._tip === null && c._hoverRoom === null
|
|
&& !c.hasAttribute('data-pointer-hover');
|
|
const compatibilityMouse = new PointerEvent('pointermove', {
|
|
pointerType: 'mouse', clientX: 5, clientY: 5,
|
|
});
|
|
Object.defineProperty(compatibilityMouse, 'sourceCapabilities', {
|
|
value: { firesTouchEvents: true },
|
|
});
|
|
c._showTip(compatibilityMouse, 'compatibility', 'ignored');
|
|
out.compatibilityMouseIgnored = !c._tip && !c.hasAttribute('data-pointer-hover');
|
|
c._showTip(new PointerEvent('pointermove', {
|
|
pointerType: 'mouse', clientX: 7, clientY: 7,
|
|
}), 'real mouse', 'restored');
|
|
out.realMouseRestoresHover = c._tip?.title === 'real mouse'
|
|
&& c.hasAttribute('data-pointer-hover');
|
|
return out;
|
|
});
|
|
check('metricsRatio 0.75', res.metricsRatio, 0.75);
|
|
delete res.metricsRatio;
|
|
checkAll(res);
|
|
await finish(browser, res);
|