mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
feat v1.39.0: pure light sources toggle on click by default
- resolveTapAction: no explicit action + domain light -> toggle (kettle et al. keep info via their non-light primary); explicit choice wins - device dialog shows the effective default (defaultTap) - unit tests updated (+1, 106); smoke_light_default_tap.mjs; docs same-commit
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.38.4"
|
||||
VERSION = "1.39.0"
|
||||
|
||||
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.38.4"
|
||||
"version": "1.39.0"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { launch } from './serve.mjs';
|
||||
const { page, browser } = await launch();
|
||||
const res = await page.evaluate(async () => {
|
||||
const out = {};
|
||||
const c = window.__card;
|
||||
const calls = [];
|
||||
c.hass = { ...c.hass, callService: (d, s, data) => { calls.push([d, s, data.entity_id]); return Promise.resolve(); } };
|
||||
await c.updateComplete;
|
||||
c._setMode('view'); await c.updateComplete;
|
||||
// лампа (primary light) без явного действия → клик = toggle
|
||||
const lamp = c._devices.find((d) => d.primary?.startsWith('light.') && !d.tapAction);
|
||||
out.hasLamp = !!lamp;
|
||||
c._infoCard = null;
|
||||
c._clickDevice(new MouseEvent('click'), lamp);
|
||||
out.lampToggles = JSON.stringify(calls.at(-1)) === JSON.stringify(['homeassistant', 'toggle', lamp.primary]);
|
||||
// устройство с не-light primary (сенсор) → клик = инфо
|
||||
const sensorDev = c._devices.find((d) => d.primary?.startsWith('sensor.') && !d.tapAction);
|
||||
const n = calls.length;
|
||||
c._infoCard = null;
|
||||
c._clickDevice(new MouseEvent('click'), sensorDev);
|
||||
out.sensorInfo = calls.length === n && !!c._infoCard;
|
||||
c._infoCard = null;
|
||||
// явный info у лампы отключает дефолт
|
||||
const cfgm = { id: lamp.id, binding: lamp.bindingKind + ':' + lamp.bindingRef, tap_action: 'info' };
|
||||
c._serverCfg = { ...c._serverCfg, markers: [...(c._serverCfg.markers || []).filter((m) => m.id !== lamp.id), cfgm] };
|
||||
c._regSignature = ''; c._maybeRebuildDevices(); await c.updateComplete;
|
||||
const lamp2 = c._devices.find((d) => d.id === lamp.id);
|
||||
const n2 = calls.length;
|
||||
c._clickDevice(new MouseEvent('click'), lamp2);
|
||||
out.explicitInfoWins = calls.length === n2 && !!c._infoCard;
|
||||
return out;
|
||||
});
|
||||
console.log(JSON.stringify(res, null, 1));
|
||||
await browser.close();
|
||||
File diff suppressed because one or more lines are too long
Vendored
+4
-4
File diff suppressed because one or more lines are too long
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## v1.39.0 — 2026-07-23 (lights toggle by default)
|
||||
- Pure light sources — devices whose primary entity is a `light` (bulbs,
|
||||
chandeliers, night lights, light groups) — now **toggle on click by
|
||||
default**, right from auto-placement, no per-device setting needed.
|
||||
Devices where light is a side function (a kettle's backlight: its primary
|
||||
is a sensor) keep the Device-card default. An explicit per-device choice
|
||||
always wins. The device dialog shows the effective default.
|
||||
|
||||
## v1.38.4 — 2026-07-23
|
||||
- Plan editor: the DERIVED wall segments (the markup layer's solid lines)
|
||||
are now trimmed under open boundaries as well — v1.38.3 only trimmed the
|
||||
|
||||
@@ -140,6 +140,12 @@ 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
|
||||
- [ ] Lights toggle by default (v1.39.0): a device whose PRIMARY entity is a
|
||||
light (bulbs, chandeliers, night lights, light groups) toggles on click
|
||||
out of the box — no per-device setting needed; the device dialog shows
|
||||
"Toggle" as its effective default; devices where light is a side
|
||||
function (kettle: primary = sensor) keep the Device-card default;
|
||||
explicit per-device "Device card" wins over the default [auto]
|
||||
- [ ] Derived walls cut too (v1.38.4): in the Plan editor the derived wall
|
||||
segments (.seg) no longer run solid through an open stretch — only the
|
||||
dash remains there [auto]
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "houseplan-card",
|
||||
"version": "1.38.4",
|
||||
"version": "1.39.0",
|
||||
"description": "Interactive house plan Lovelace card for Home Assistant",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -32,7 +32,7 @@ import './space-card';
|
||||
import { cardStyles } from './styles';
|
||||
import { langOf, t, type I18nKey } from './i18n';
|
||||
|
||||
const CARD_VERSION = '1.38.4';
|
||||
const CARD_VERSION = '1.39.0';
|
||||
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';
|
||||
@@ -169,7 +169,8 @@ class HouseplanCard extends LitElement {
|
||||
rippleSize: number; // in icon diameters
|
||||
size: number; // icon size multiplier
|
||||
angle: number; // icon rotation, degrees
|
||||
tapAction: string; // '' = card default
|
||||
tapAction: string; // '' = the effective default (defaultTap)
|
||||
defaultTap: 'info' | 'toggle';
|
||||
controls: string[]; // entities this icon toggles as a group
|
||||
controlsFilter: string;
|
||||
glowRadius: string; // per-device glow radius in display units; '' = global default
|
||||
@@ -2151,6 +2152,7 @@ class HouseplanCard extends LitElement {
|
||||
size: Number(d.marker?.size) > 0 ? Number(d.marker!.size) : 1,
|
||||
angle: Number(d.marker?.angle) || 0,
|
||||
tapAction: d.marker?.tap_action || '',
|
||||
defaultTap: d.primary?.split('.')[0] === 'light' ? 'toggle' : 'info',
|
||||
controls: [...(d.marker?.controls || [])],
|
||||
controlsFilter: '',
|
||||
glowRadius: Number(d.marker?.glow_radius_cm) > 0
|
||||
@@ -2172,7 +2174,7 @@ class HouseplanCard extends LitElement {
|
||||
name: '', binding: 'virtual', bindingMode: 'virtual', bindingOpen: false,
|
||||
showEntities: false, bindingFilter: '', icon: '', autoIcon: '',
|
||||
display: 'badge', rippleColor: '', rippleSize: 3, size: 1, angle: 0,
|
||||
tapAction: '', controls: [], controlsFilter: '', glowRadius: '', model: '',
|
||||
tapAction: '', defaultTap: 'info', controls: [], controlsFilter: '', glowRadius: '', model: '',
|
||||
link: '', description: '', pdfs: [], room: '', busy: false,
|
||||
};
|
||||
}
|
||||
@@ -4028,7 +4030,7 @@ class HouseplanCard extends LitElement {
|
||||
<select class="areasel"
|
||||
@change=${(e: Event) => (this._markerDialog = { ...d, tapAction: (e.target as HTMLSelectElement).value })}>
|
||||
${[['info', 'tap.info'], ['more-info', 'tap.more_info'], ['toggle', 'tap.toggle']].map(
|
||||
([v, k]) => html`<option value=${v} ?selected=${(d.tapAction || 'info') === v}>${this._t(k as any)}</option>`,
|
||||
([v, k]) => html`<option value=${v} ?selected=${(d.tapAction || d.defaultTap) === v}>${this._t(k as any)}</option>`,
|
||||
)}
|
||||
</select>
|
||||
|
||||
|
||||
+5
-1
@@ -492,7 +492,11 @@ export function resolveTapAction(
|
||||
cardDefault: string | null | undefined,
|
||||
domain: string | null | undefined,
|
||||
): TapAction {
|
||||
const want = explicit || cardDefault || 'info';
|
||||
// Pure light sources (the device's PRIMARY function is a lamp: bulbs,
|
||||
// chandeliers, night lights, light groups) toggle by default — no explicit
|
||||
// setting needed. Devices where light is a side function (a kettle's
|
||||
// backlight) have a non-light primary and keep the info default.
|
||||
const want = explicit || cardDefault || (domain === 'light' ? 'toggle' : 'info');
|
||||
if (want === 'more-info') return 'more-info';
|
||||
if (want !== 'toggle') return 'info';
|
||||
if (!domain || TOGGLE_FORBIDDEN_DOMAINS.has(domain)) return 'info';
|
||||
|
||||
+15
-2
@@ -175,8 +175,9 @@ test('icon rules: device_class fallback', () => {
|
||||
assert.equal(iconFromDeviceClasses([]), null);
|
||||
});
|
||||
|
||||
test('tap action: defaults to info', () => {
|
||||
assert.equal(resolveTapAction(undefined, undefined, 'light'), 'info');
|
||||
test('tap action: defaults — info everywhere except pure lights (v1.39.0)', () => {
|
||||
assert.equal(resolveTapAction(undefined, undefined, 'light'), 'toggle'); // лампы кликабельны из коробки
|
||||
assert.equal(resolveTapAction(undefined, undefined, 'switch'), 'info');
|
||||
assert.equal(resolveTapAction(null, 'info', 'switch'), 'info');
|
||||
assert.equal(resolveTapAction(null, 'more-info', 'sensor'), 'more-info');
|
||||
});
|
||||
@@ -721,3 +722,15 @@ test('outlineWithout: removes the cut stretch, keeps the rest', () => {
|
||||
// без вырезов — весь периметр
|
||||
assert.ok(Math.abs(outlineWithout(sq, []).reduce((a, s) => a + len(s), 0) - 40) < 1e-6);
|
||||
});
|
||||
|
||||
test('resolveTapAction: pure lights toggle by default (v1.39.0)', () => {
|
||||
assert.equal(resolveTapAction(null, null, 'light'), 'toggle');
|
||||
assert.equal(resolveTapAction('', undefined, 'light'), 'toggle');
|
||||
// явный выбор пользователя сильнее дефолта
|
||||
assert.equal(resolveTapAction('info', null, 'light'), 'info');
|
||||
// не-световые домены не трогаем
|
||||
assert.equal(resolveTapAction(null, null, 'switch'), 'info');
|
||||
assert.equal(resolveTapAction(null, null, 'sensor'), 'info');
|
||||
// замок никогда
|
||||
assert.equal(resolveTapAction('toggle', null, 'lock'), 'info');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user