mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-05 06:08:59 +00:00
fix(led): colour the active core from its resolved light
Keep off and unavailable presentation unchanged. Extend the rendered colour and surface matrix and retain the independent white-source geometry oracle. Issue: #790 User-Visible: yes
This commit is contained in:
+122
-14
@@ -3,8 +3,8 @@
|
||||
* the View and on the static card. No LED chunk without a displayed active
|
||||
* strip (a hidden shape or an unbound strip loads nothing); an active bound
|
||||
* strip replaces the icon, paints the two-stroke stripe and — with Glow — the
|
||||
* linear field; off/unavailable have no field, unavailable is dashed; without
|
||||
* Glow the core takes the source colour. The whole length is one target: a
|
||||
* linear field; off/unavailable have no field, unavailable is dashed. The on
|
||||
* core takes the source colour with or without Glow. The whole length is one target: a
|
||||
* click toggles once, a touch pan calls nothing. A hidden marker or an
|
||||
* HA-disabled device loads no chunk (r1 M4); the value badge stays at the
|
||||
* anchor, passive (r1 M1). The static card is passive in all four
|
||||
@@ -12,6 +12,8 @@
|
||||
*/
|
||||
import { launch, check, finish } from './serve.mjs';
|
||||
import { installHpTestOnPage } from './helpers/hp-test.mjs';
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const { page, browser } = await launch({ width: 1000, height: 820 }, 1);
|
||||
const ledRequests = [];
|
||||
@@ -48,6 +50,18 @@ const stripe = (marker) => evaluate((marker) => {
|
||||
coreWidth: Number(g.querySelector('.led-core')?.getAttribute('stroke-width')) } : null;
|
||||
}, marker);
|
||||
const field = () => evaluate(() => window.__card.shadowRoot.querySelectorAll('[data-led-field]').length);
|
||||
const painted = () => evaluate(() => {
|
||||
const root = window.__card.shadowRoot;
|
||||
const core = root.querySelector('[data-marker="d_light1"] .led-core');
|
||||
const pool = root.querySelector('[data-led-field="ceiling"] .led-pool');
|
||||
return { core: core?.getAttribute('stroke'), field: pool?.getAttribute('fill'),
|
||||
coreOpacity: core ? getComputedStyle(core).strokeOpacity : null,
|
||||
fieldOpacity: Number(pool?.getAttribute('fill-opacity')),
|
||||
geometry: ['.led-outline', '.led-core', '.led-hit'].map(selector => {
|
||||
const path = root.querySelector(`[data-marker="d_light1"] ${selector}`);
|
||||
return [path?.getAttribute('d'), path?.getAttribute('stroke-width')];
|
||||
}) };
|
||||
});
|
||||
|
||||
await settle();
|
||||
check('no strips: no LED chunk requested', ledRequests.length, 0);
|
||||
@@ -82,9 +96,68 @@ check('unbound strip is absent from the View', await evaluate(() => window.__car
|
||||
check('no round pool at the anchor: the strip is the source', await evaluate(() =>
|
||||
!window.__card.shadowRoot.querySelector('[data-glow-source="light.ceiling"]')));
|
||||
check('no auto-slot reserved for the strip’s marker', await evaluate(() => !('d_light1' in (window.__card._defPos || {}))));
|
||||
await setState('light.ceiling', 'on', { rgb_color: [128, 213, 255], brightness: 64 });
|
||||
await settle();
|
||||
const onStripe = await stripe('d_light1');
|
||||
check('on with Glow: white core and a field', JSON.stringify([onStripe?.state, onStripe?.core, onStripe?.dash, await field() > 0]),
|
||||
JSON.stringify(['on', '#FFFFFF', null, true]));
|
||||
check('on with Glow: source-coloured core and a field', [onStripe?.state, onStripe?.core, onStripe?.dash, await field() > 0],
|
||||
['on', '#80d5ff', null, true]);
|
||||
const firstPaint = await painted();
|
||||
check('#790: rendered core and field share the resolved RGB colour', [firstPaint.core, firstPaint.field], ['#80d5ff', '#80d5ff']);
|
||||
await setState('light.ceiling', 'on', { rgb_color: [225, 70, 35] });
|
||||
await settle();
|
||||
const rgbPaint = await painted();
|
||||
check('#790: HA RGB update changes the existing core and field', [rgbPaint.core, rgbPaint.field], ['#e14623', '#e14623']);
|
||||
check('#790: a colour update changes no outline, core or hit geometry', rgbPaint.geometry, firstPaint.geometry);
|
||||
await setState('light.ceiling', 'on', { rgb_color: null, color_temp_kelvin: 3000 });
|
||||
await settle();
|
||||
const warmPaint = await painted();
|
||||
check('#790: colour temperature uses the same resolved field colour', warmPaint.core === warmPaint.field
|
||||
&& warmPaint.core !== '#FFFFFF' && warmPaint.core !== rgbPaint.core);
|
||||
await setStrips(ceilingStrip, { glow_enabled: true }, { d_light1: { glow_color: { c: '#d837a6', bri: 0.2 } } });
|
||||
await settle();
|
||||
const manualPaint = await painted();
|
||||
check('#790: saved manual colour updates core and field', [manualPaint.core, manualPaint.field], ['#d837a6', '#d837a6']);
|
||||
check('#790: field brightness never becomes core opacity', manualPaint.coreOpacity === '1' && manualPaint.fieldOpacity < 1);
|
||||
check('#790: manual colour changes no physical geometry', manualPaint.geometry, firstPaint.geometry);
|
||||
|
||||
// AC3: the same public HA/config path in both themes, Flat/2.5D and on/off.
|
||||
// Optional local review captures are not golden baselines or release files.
|
||||
const captureDir = process.env.HP_LED_COLOR_SCREENSHOTS;
|
||||
if (captureDir) await mkdir(captureDir, { recursive: true });
|
||||
await setStrips(ceilingStrip, { glow_enabled: true });
|
||||
for (const dark of [false, true]) for (const iso of [false, true]) {
|
||||
await evaluate(async dark => {
|
||||
const card = window.__card;
|
||||
card.hass = { ...card.hass, themes: { ...(card.hass.themes || {}), darkMode: dark } };
|
||||
document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
|
||||
await window.__hpTest.settled();
|
||||
}, dark);
|
||||
await evaluate(iso => window.__hpTest.setVolumetricView(iso), iso);
|
||||
let onGeometry;
|
||||
for (const state of ['on', 'off']) {
|
||||
await setState('light.ceiling', state, { rgb_color: [128, 213, 255] });
|
||||
await page.waitForTimeout(600);
|
||||
const actual = await painted();
|
||||
const label = `#790: ${dark ? 'dark' : 'light'}, ${iso ? '2.5D' : 'Flat'}, ${state}`;
|
||||
check(`${label}: core colour`, actual.core, state === 'on' ? '#80d5ff' : '#FFFFFF');
|
||||
if (state === 'on') onGeometry = actual.geometry;
|
||||
else check(`${label}: geometry stays unchanged`, actual.geometry, onGeometry);
|
||||
if (captureDir) {
|
||||
const box = await page.locator('[data-led-strip="ceiling"] .led-outline').boundingBox();
|
||||
const x = Math.max(0, box.x - 30), y = Math.max(0, box.y - 30);
|
||||
await page.screenshot({ path: join(captureDir, `${dark ? 'dark' : 'light'}-${iso ? 'iso' : 'flat'}-${state}.png`),
|
||||
clip: { x, y, width: Math.min(1000 - x, box.width + 60), height: Math.min(820 - y, box.height + 60) } });
|
||||
}
|
||||
}
|
||||
}
|
||||
await evaluate(async () => {
|
||||
const card = window.__card;
|
||||
card.hass = { ...card.hass, themes: { ...(card.hass.themes || {}), darkMode: false } };
|
||||
document.documentElement.style.colorScheme = 'light';
|
||||
await window.__hpTest.setVolumetricView(false);
|
||||
});
|
||||
await setState('light.ceiling', 'on', { rgb_color: [128, 213, 255] });
|
||||
await settle();
|
||||
|
||||
// r1 M1 (ТЗ §5): the strip keeps the device's value badge, passive, at the
|
||||
// half-length anchor — the icon core, pulse and slot stay suppressed.
|
||||
@@ -186,8 +259,13 @@ await settle();
|
||||
await setState('light.ceiling', 'unavailable');
|
||||
await settle();
|
||||
const unavailable = await stripe('d_light1');
|
||||
check('unavailable: dashed, no field', JSON.stringify([unavailable?.state, !!unavailable?.dash, await field()]),
|
||||
JSON.stringify(['unavailable', true, 0]));
|
||||
check('unavailable: dashed grey, no field', [unavailable?.state, unavailable?.core, !!unavailable?.dash, await field()],
|
||||
['unavailable', '#9e9e9e', true, 0]);
|
||||
await setState('light.ceiling', 'unknown');
|
||||
await settle();
|
||||
const unknown = await stripe('d_light1');
|
||||
check('#790: unknown keeps the grey dashed unavailable presentation',
|
||||
[unknown?.state, unknown?.core, !!unknown?.dash, await field()], ['unavailable', '#9e9e9e', true, 0]);
|
||||
await setState('light.ceiling', 'on', { rgb_color: [128, 213, 255] });
|
||||
await setStrips(await evaluate(() => window.__card._serverCfg.spaces[0].led_strips), { glow_enabled: false });
|
||||
await settle();
|
||||
@@ -197,8 +275,23 @@ await settle();
|
||||
// finish before asserting the stable no-Glow contract.
|
||||
await page.waitForFunction(() => !window.__card.shadowRoot.querySelector('[data-led-field]'));
|
||||
const colored = await stripe('d_light1');
|
||||
check('on without Glow: coloured core, no field', JSON.stringify([colored?.state, colored?.core !== '#FFFFFF', await field()]),
|
||||
JSON.stringify(['on', true, 0]));
|
||||
check('on without Glow: source-coloured core, no field', [colored?.state, colored?.core, await field()],
|
||||
['on', '#80d5ff', 0]);
|
||||
await evaluate(async () => {
|
||||
await window.__hpTest.setServerConfig(cfg => {
|
||||
cfg.spaces[0].settings.glow_enabled = true;
|
||||
const room = cfg.spaces[0].rooms.find(room => room.id === 'r1');
|
||||
room.settings = { ...(room.settings || {}), glow: false };
|
||||
});
|
||||
});
|
||||
await settle();
|
||||
check('#790: room Glow override changes no live core colour',
|
||||
[(await stripe('d_light1'))?.core, await field()], ['#80d5ff', 0]);
|
||||
await evaluate(async () => {
|
||||
await window.__hpTest.setServerConfig(cfg => {
|
||||
cfg.spaces[0].rooms.find(room => room.id === 'r1').settings.glow = null;
|
||||
});
|
||||
});
|
||||
|
||||
// The static card (ТЗ §8, AC14, r1 M6): the full light_pools × live_states
|
||||
// matrix. Passive in all four: no hit path, no focus, a click calls nothing.
|
||||
@@ -235,7 +328,7 @@ const matrix = await evaluate(async () => {
|
||||
target?.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
|
||||
}
|
||||
out[key] = {
|
||||
state: g?.dataset.state, white: core?.getAttribute('stroke') === '#FFFFFF',
|
||||
state: g?.dataset.state, core: core?.getAttribute('stroke'),
|
||||
field: !!sr.querySelector('[data-led-field]'),
|
||||
passive: !sr.querySelector('.led-hit, [data-led-strip] [role], [data-led-strip] [tabindex]'),
|
||||
badge: !!sr.querySelector('[data-led-badge="d_light1"] .value-badge'),
|
||||
@@ -245,16 +338,31 @@ const matrix = await evaluate(async () => {
|
||||
return out;
|
||||
});
|
||||
check('static live_states:false + light_pools:false — neutral, no field, passive', JSON.stringify(matrix['pools=false,live=false']),
|
||||
JSON.stringify({ state: 'off', white: true, field: false, passive: true, badge: true }));
|
||||
JSON.stringify({ state: 'off', core: '#FFFFFF', field: false, passive: true, badge: true }));
|
||||
check('static live_states:true + light_pools:false — source colour in the core, no field, passive', JSON.stringify(matrix['pools=false,live=true']),
|
||||
JSON.stringify({ state: 'on', white: false, field: false, passive: true, badge: true }));
|
||||
JSON.stringify({ state: 'on', core: '#80d5ff', field: false, passive: true, badge: true }));
|
||||
check('static live_states:false + light_pools:true — neutral, no field, passive', JSON.stringify(matrix['pools=true,live=false']),
|
||||
JSON.stringify({ state: 'off', white: true, field: false, passive: true, badge: true }));
|
||||
check('static live_states:true + light_pools:true — white core and the field, passive', JSON.stringify(matrix['pools=true,live=true']),
|
||||
JSON.stringify({ state: 'on', white: true, field: true, passive: true, badge: true }));
|
||||
JSON.stringify({ state: 'off', core: '#FFFFFF', field: false, passive: true, badge: true }));
|
||||
check('static live_states:true + light_pools:true — source colour and the field, passive', JSON.stringify(matrix['pools=true,live=true']),
|
||||
JSON.stringify({ state: 'on', core: '#80d5ff', field: true, passive: true, badge: true }));
|
||||
check('a click on any static stripe toggles nothing', await evaluate(() => window.__card.hass.states['light.ceiling'].state), 'on');
|
||||
|
||||
for (const mode of ['plan', 'decor']) {
|
||||
await evaluate(mode => window.__hpTest.setMode(mode), mode);
|
||||
check(`#790: ${mode} keeps the coloured core passive and translucent`, await evaluate(() => {
|
||||
const root = window.__card.shadowRoot;
|
||||
return [root.querySelector('[data-marker="d_light1"] .led-core')?.getAttribute('stroke'),
|
||||
root.querySelector('.led-passive')?.getAttribute('opacity'), !!root.querySelector('.led-hit')];
|
||||
}), ['#80d5ff', '0.45', false]);
|
||||
}
|
||||
await evaluate(() => window.__hpTest.setMode('devices'));
|
||||
check('#790: Devices retains the live core colour', (await stripe('d_light1'))?.core, '#80d5ff');
|
||||
await setStrips([...ceilingStrip, { id: 'loose', points: [[0.12, 0.45], [0.30, 0.45]], marker: null }],
|
||||
{ glow_enabled: true });
|
||||
check('#790: unbound Devices strip keeps its grey dashed presentation', await evaluate(() => {
|
||||
const path = window.__card.shadowRoot.querySelector('[data-led-unbound="loose"]');
|
||||
return [path?.getAttribute('stroke'), !!path?.getAttribute('stroke-dasharray')];
|
||||
}), ['#8a8a8a', true]);
|
||||
await page.locator('.led-select-hit[data-led-select="ceiling"]').dispatchEvent('click');
|
||||
await page.waitForTimeout(100);
|
||||
await page.click('[data-led-action="settings"]');
|
||||
|
||||
@@ -89,8 +89,11 @@ for (const scenario of scenarios) {
|
||||
// Public HA state input, not a write to the card's private resolver.
|
||||
await page.evaluate(async state => {
|
||||
const card = window.__card, previous = card.hass.states['light.ceiling'];
|
||||
// This geometry-only oracle deliberately uses a white source in
|
||||
// both states; source-colour behaviour is held by the Glow smoke.
|
||||
card.hass = { ...card.hass, states: { ...card.hass.states,
|
||||
'light.ceiling': { ...previous, state } } };
|
||||
'light.ceiling': { ...previous, state,
|
||||
attributes: { ...previous.attributes, rgb_color: [255, 255, 255] } } } };
|
||||
await window.__hpTest.settled();
|
||||
}, state);
|
||||
await page.waitForFunction(state => window.__card.shadowRoot
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- An on LED strip now shows its light colour inside the outline even when
|
||||
Glow is enabled; off strips stay white
|
||||
([#790](https://github.com/Matysh/houseplan-card/issues/790)).
|
||||
|
||||
## v1.79.0 — 2026-10-03
|
||||
|
||||
- LED strips can now replace device icons: draw and edit their shape in the
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- Ядро включённой LED-ленты внутри обводки теперь показывает цвет её свечения
|
||||
и при включённом Glow; выключенная лента остаётся белой
|
||||
([#790](https://github.com/Matysh/houseplan-card/issues/790)).
|
||||
|
||||
## v1.79.0 — 2026-10-03
|
||||
|
||||
- LED-ленты теперь могут заменять значки устройств: нарисуйте и отредактируйте
|
||||
|
||||
+5
-3
@@ -459,9 +459,11 @@ only the geometry differs:
|
||||
the saved points. Unsafe acute angles retain the short connector. A stored
|
||||
four-corner loop stays four-cornered without steps or protruding hooks;
|
||||
a genuinely tilted side stays tilted (#787, #788).
|
||||
- **Core.** With effective Glow (space `glow_enabled` + room `glow`) the core
|
||||
stays white and the colour is the field; without Glow the core takes the
|
||||
source colour and there is no field. Off: white core, no field.
|
||||
- **Core.** An on strip uses the same resolved source colour as its field,
|
||||
whether effective Glow (space `glow_enabled` + room `glow`) is on or off.
|
||||
RGB, colour temperature and manual colour changes update both through the
|
||||
shared light resolver; field brightness does not change core opacity.
|
||||
Without Glow there is no field. Off: white core, no field.
|
||||
Unavailable/unknown: dashed grey stripe, no field — the link is kept.
|
||||
- **Surfaces.** The full card renders field and stripe in the View; the
|
||||
Devices editor shows active strips (unbound ones as grey dashes);
|
||||
|
||||
@@ -30,6 +30,7 @@ Everything computable from the tree and git; regenerate, never edit by hand
|
||||
| Current local cycle | **Stable v1.79.0 candidate** — promotion of published beta.7, with no new feature source. The line adds LED strips and the moon on any background, stabilizes 2.5D layout, and improves room-fill transitions and warm remount. Exact-SHA full gates precede publication; `main` is promoted only to the tested candidate. |
|
||||
| Branches | `main` carries stable releases only; pre-release tags point at `dev`. Work lands on `dev`, which is equal to or ahead of `main`, never behind. |
|
||||
| LED regression acceptance | #788 covers mixed face/free and fractional-coordinate joins, winding-safe field unions, retained endpoints/acute vertices and circle–wall transition events. The actual owner export is exercised locally; public fixtures are synthetic. Pixel oracles, not just SVG counts, protect the visible falloff. |
|
||||
| LED core colour | #790 makes the on core follow the resolved light colour with or without Glow. Off, unavailable and non-live static strips keep their neutral presentation; geometry and field brightness are unchanged. |
|
||||
| Warm remount | Delayed editor restoration preserves the original View camera, reserves pending chrome space, and yields synchronously to mode/space navigation (#762). Header/stage dimensions are published as one settled pair. Canonical: `docs/WARM-REMOUNT.md`. |
|
||||
| 2.5D View | Public since #649: the installation-wide General settings switch `settings.volumetric_view` (Display). Flat stays the default and byte-for-byte unchanged; editors and `houseplan-space-card` stay Flat. Canonical: `docs/ISOMETRIC.md`. |
|
||||
| Input support | Owner's rule since 2026-08-08: View and kiosk are fully supported and release-blocking on touch; the three editors are desktop-first, touch editing is best effort. Canonical: `docs/TOUCH-SUPPORT.md`. |
|
||||
|
||||
+4
-3
@@ -1058,9 +1058,10 @@ editor** — there is no need to switch to the Plan editor.
|
||||
| In the device dialog | **Show as LED strip** draws a shape for the same marker (or brings a saved one back at once); **Show as icon** returns the ordinary icon, the shape is kept hidden and comes back without redrawing. The usual unsaved-changes question comes first |
|
||||
|
||||
Binding creates no new Home Assistant device: the same marker, light
|
||||
settings, tap actions and badge — drawn at the middle of the strip's length. The strip glows when the device does: with
|
||||
Glow on, a white core and a soft band of colour 30 cm wide by default (or the
|
||||
device's own radius); without Glow the core takes the light's colour. Light
|
||||
settings, tap actions and badge — drawn at the middle of the strip's length.
|
||||
When the device is on, the core inside its outline uses the selected light colour, with or without
|
||||
Glow. Glow adds a soft band of colour with a 30 cm radius by default (or the
|
||||
device's own radius). An off strip stays white. Light
|
||||
does not pass through walls or closed doors. In View the whole strip is one
|
||||
tap target, like an icon. Every finished shape change and every switch of the
|
||||
representation is one **Undo/Redo** step.
|
||||
|
||||
@@ -1155,9 +1155,10 @@ binding tombstone не вырезает её из живого устройст
|
||||
|
||||
Привязка не создаёт нового устройства в Home Assistant: тот же маркер, те же
|
||||
настройки света, действия по нажатию и бейдж — он стоит в середине длины ленты. Лента светится, когда светится
|
||||
устройство: при включённом Glow — белое ядро и мягкая цветная полоса света
|
||||
радиусом 30 см (или персональный радиус устройства), без Glow — ядро цвета
|
||||
источника. Свет не проходит сквозь стены и закрытые двери. В просмотре лента
|
||||
устройство: ядро внутри обводки имеет выбранный цвет свечения независимо от
|
||||
Glow. При включённом Glow добавляется мягкая полоса света радиусом 30 см
|
||||
(или персональный радиус устройства). Выключенная лента остаётся белой.
|
||||
Свет не проходит сквозь стены и закрытые двери. В просмотре лента
|
||||
— одна цель нажатия по всей длине, как значок. Каждое законченное изменение
|
||||
формы и смена представления — один шаг **Отменить/Повторить**.
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ The invariant depends on computed CSS, SVG paint, clipping, stacking or pixels p
|
||||
- `iso-sun-flat-wedges-remain`
|
||||
- `iso-theme-dark-wall-rule-returns`
|
||||
- `led-badge-dropped`
|
||||
- `led-core-coloured-under-glow`
|
||||
- `led-core-white-under-glow`
|
||||
- `led-field-endpoint-dropped`
|
||||
- `led-field-disc-cancels-fan`
|
||||
- `led-field-compound-clip-children`
|
||||
|
||||
@@ -14276,13 +14276,13 @@ const MUTANT_DEFINITIONS = [
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'led-core-coloured-under-glow',
|
||||
id: 'led-core-white-under-glow',
|
||||
guard: 'node demo/smoke_led_strip_glow.mjs',
|
||||
because: "#780 ТЗ §3: with Glow the core stays white and the colour is the field; only without Glow the core takes the source colour",
|
||||
because: '#790: the actual SVG core must match the resolved field colour across live HA/config updates, with or without Glow; this needs the rendered DOM and its update lifecycle',
|
||||
patches: [{
|
||||
file: 'src/led-strip-runtime.ts',
|
||||
find: ": view.state === 'on' && !view.glow && view.appearance ? view.appearance.c : CORE_IDLE;",
|
||||
replace: ": view.state === 'on' && view.appearance ? view.appearance.c : CORE_IDLE;",
|
||||
find: ": view.state === 'on' && view.appearance ? view.appearance.c : CORE_IDLE;",
|
||||
replace: ": view.state === 'on' && !view.glow && view.appearance ? view.appearance.c : CORE_IDLE;",
|
||||
}],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -136,8 +136,8 @@ export function faceContext(scene: LightBarrierScene | null, epsilon: number): F
|
||||
/**
|
||||
* The state of one strip from the card's own light resolution (ТЗ §3, §5):
|
||||
* an unavailable source is a grey dashed stripe without a field — never an
|
||||
* unbinding; on with Glow is a white core and a field; on without Glow is a
|
||||
* core in the source colour; off is a white core.
|
||||
* unbinding; on uses the source colour for the core, with a field when Glow
|
||||
* is enabled; off is a white core.
|
||||
*/
|
||||
export function ledStripView(input: {
|
||||
strip: LedStripModel;
|
||||
@@ -293,7 +293,7 @@ export function renderLedStripes(input: LedStripeInput): TemplateResult {
|
||||
const { t, path, d } = ledStripePath(view.strip, input.faces, input.d);
|
||||
const unavailable = view.state === 'unavailable';
|
||||
const core = unavailable ? UNAVAILABLE
|
||||
: view.state === 'on' && !view.glow && view.appearance ? view.appearance.c : CORE_IDLE;
|
||||
: view.state === 'on' && view.appearance ? view.appearance.c : CORE_IDLE;
|
||||
const hitWidth = (2 * stripHitRadiusPx(t * input.perUnit)) / (input.perUnit || 1);
|
||||
const dev = view.device;
|
||||
const own = (e: MouseEvent) => nearestOwner(e, view, input).device;
|
||||
|
||||
Reference in New Issue
Block a user