mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
fix: host-gated groups belong to their owner surface; split mixed media wrappers (#266)
CI-шард смока поймал сдвиг каскада, невидимый golden-набору: smoke_device_icon_design — alert-shell стал серым, dark-unavailable core светлым. Причина: классификатор считал ведущий :host(...) владельцем селектора и уносил гейтнутые группы устройств в base — ВПЕРЁД их поверхности, меняя победителя при равной специфичности. Теперь :host-префикс — гейт, а не владелец: владелец — первый значимый токен после него; смешанные @media режутся на последовательные по-зонные копии обёртки (reduced-motion обёрток стало 12 поверх тех же правил 10 исходных — юнит заякорен на факт, сверка scope-ключом доказывает, что ни одно правило обёртку не потеряло). Два мутантных якоря вернулись в devices.styles.ts; исключение юнита непересечения опустело. Гейты: refactor-proof diff пуст · golden 129/129 без переприёмки · смоки device_icon_design, plan_snap_overlay, preloader OK · npm test 1318/0. Issue: #266 User-Visible: no
This commit is contained in:
File diff suppressed because one or more lines are too long
Vendored
+88
-84
File diff suppressed because one or more lines are too long
@@ -93,13 +93,20 @@ while (i < cssBody.length) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const zoneOfSelector = (header) => {
|
const zoneOfSelector = (header) => {
|
||||||
const first = (sel) => {
|
// A leading :host(...) compound GATES a rule, it does not own it: the owner
|
||||||
const t = sel.trim().match(/^[.:#\[]?[\w-]+/);
|
// is the first meaningful token after the gate. Classifying such groups
|
||||||
|
// into base moved them AHEAD of their surface in the cascade and flipped
|
||||||
|
// equal-specificity winners (smoke_device_icon_design caught alert shells
|
||||||
|
// and dark-unavailable cores) — the owner surface keeps their position.
|
||||||
|
const owner = (sel) => {
|
||||||
|
let rest = sel.trim().replace(/^:host(\([^)]*\))?\s*/, '');
|
||||||
|
if (!rest) return ':host';
|
||||||
|
const t = rest.match(/^[.:#\[]?[\w-]+/);
|
||||||
return t ? t[0] : null;
|
return t ? t[0] : null;
|
||||||
};
|
};
|
||||||
const zones = new Set();
|
const zones = new Set();
|
||||||
for (const sel of header.split(',')) {
|
for (const sel of header.split(',')) {
|
||||||
const token = first(sel);
|
const token = owner(sel);
|
||||||
if (!token) continue;
|
if (!token) continue;
|
||||||
const zone = T[token];
|
const zone = T[token];
|
||||||
if (!zone) throw new Error(`unclassified token «${token}» in «${header.slice(0, 80)}»`);
|
if (!zone) throw new Error(`unclassified token «${token}» in «${header.slice(0, 80)}»`);
|
||||||
@@ -118,10 +125,54 @@ for (const block of blocks) {
|
|||||||
zone = /^(hp-dev|dev|pulse|vac)/.test(name) ? 'devices'
|
zone = /^(hp-dev|dev|pulse|vac)/.test(name) ? 'devices'
|
||||||
: /^(hp-spin|spin|fade|toast)/.test(name) ? 'base' : 'base';
|
: /^(hp-spin|spin|fade|toast)/.test(name) ? 'base' : 'base';
|
||||||
} else if (/^@(media|supports)/.test(block.header)) {
|
} else if (/^@(media|supports)/.test(block.header)) {
|
||||||
// classify by the first inner selector
|
// Classify by ALL inner rules. A single-zone wrapper moves whole; a mixed
|
||||||
const inner = block.text.slice(block.text.indexOf('{') + 1);
|
// one is split into consecutive per-zone copies of the wrapper so every
|
||||||
const sel = inner.slice(0, inner.indexOf('{')).replace(/\/\*[\s\S]*?\*\//g, '').trim();
|
// rule stays with its owner surface without crossing zone order.
|
||||||
zone = zoneOfSelector(sel);
|
const openAt = block.text.indexOf('{');
|
||||||
|
const inner = block.text.slice(openAt + 1, block.text.lastIndexOf('}'));
|
||||||
|
const innerBlocks = [];
|
||||||
|
{
|
||||||
|
let k = 0, pend = '';
|
||||||
|
while (k < inner.length) {
|
||||||
|
const rest2 = inner.slice(k);
|
||||||
|
const cm2 = rest2.match(/^\s*\/\*[\s\S]*?\*\//);
|
||||||
|
if (cm2 && !rest2.slice(0, rest2.indexOf('/*')).includes('{')) {
|
||||||
|
pend += cm2[0]; k += cm2[0].length; continue;
|
||||||
|
}
|
||||||
|
const o2 = inner.indexOf('{', k);
|
||||||
|
if (o2 === -1) break;
|
||||||
|
const h2 = inner.slice(k, o2);
|
||||||
|
let d2 = 1, j2 = o2 + 1;
|
||||||
|
while (j2 < inner.length && d2 > 0) {
|
||||||
|
if (inner[j2] === '{') d2++;
|
||||||
|
else if (inner[j2] === '}') d2--;
|
||||||
|
j2++;
|
||||||
|
}
|
||||||
|
innerBlocks.push({ text: pend + h2 + inner.slice(o2, j2), header: h2.trim() });
|
||||||
|
pend = '';
|
||||||
|
k = j2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const innerZones = innerBlocks.map((b) => zoneOfSelector(b.header));
|
||||||
|
const uniq = [...new Set(innerZones)];
|
||||||
|
if (uniq.length <= 1) {
|
||||||
|
zone = uniq[0] ?? 'base';
|
||||||
|
} else {
|
||||||
|
const wrapperHeader = block.text.slice(0, openAt).trimEnd();
|
||||||
|
let g = 0;
|
||||||
|
while (g < innerBlocks.length) {
|
||||||
|
const zg = innerZones[g];
|
||||||
|
let h = g;
|
||||||
|
while (h < innerBlocks.length && innerZones[h] === zg) h++;
|
||||||
|
const bodyPart = innerBlocks.slice(g, h).map((b) => b.text).join('');
|
||||||
|
const copy = `${wrapperHeader} {${bodyPart}\n }`;
|
||||||
|
ZONES[zg].push(copy);
|
||||||
|
g = h;
|
||||||
|
}
|
||||||
|
// mark handled: skip the shared push below
|
||||||
|
block.zone = 'SPLIT';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
zone = zoneOfSelector(block.header);
|
zone = zoneOfSelector(block.header);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1999,7 +1999,7 @@ export const MUTANTS = [
|
|||||||
because: 'unavailable keeps click/keyboard access but must never regain the blue visual hover '
|
because: 'unavailable keeps click/keyboard access but must never regain the blue visual hover '
|
||||||
+ 'which makes an offline device look live',
|
+ 'which makes an offline device look live',
|
||||||
patches: [{
|
patches: [{
|
||||||
file: 'src/styles/base.styles.ts',
|
file: 'src/styles/devices.styles.ts',
|
||||||
find: '.dev:not(.unavail):hover {',
|
find: '.dev:not(.unavail):hover {',
|
||||||
replace: '.dev.unavail:hover {',
|
replace: '.dev.unavail:hover {',
|
||||||
}],
|
}],
|
||||||
@@ -2165,7 +2165,7 @@ export const MUTANTS = [
|
|||||||
because: 'touch must not leave a browser-matched device hover painted after the JS tooltip '
|
because: 'touch must not leave a browser-matched device hover painted after the JS tooltip '
|
||||||
+ 'has already been cleared',
|
+ 'has already been cleared',
|
||||||
patches: [{
|
patches: [{
|
||||||
file: 'src/styles/base.styles.ts',
|
file: 'src/styles/devices.styles.ts',
|
||||||
find: ' :host([data-pointer-hover]) .dev:not(.unavail):hover {',
|
find: ' :host([data-pointer-hover]) .dev:not(.unavail):hover {',
|
||||||
replace: ' .dev:not(.unavail):hover {',
|
replace: ' .dev:not(.unavail):hover {',
|
||||||
}],
|
}],
|
||||||
|
|||||||
@@ -61,15 +61,9 @@ export const baseStyles = css`
|
|||||||
@keyframes fixedfloor-spin {
|
@keyframes fixedfloor-spin {
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
:host([data-pointer-hover]) .tab:hover {
|
|
||||||
color: var(--hp-txt);
|
|
||||||
}
|
|
||||||
.spacer {
|
.spacer {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
:host([data-pointer-hover]) .btn:hover {
|
|
||||||
border-color: var(--hp-accent);
|
|
||||||
}
|
|
||||||
.bootveil {
|
.bootveil {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -102,30 +96,6 @@ export const baseStyles = css`
|
|||||||
animation: none;
|
animation: none;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
.recoveryoverlay {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
.stage.hpsettle {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .stage.mode-view .room.overlay:not(.styled):hover {
|
|
||||||
stroke: var(--hp-accent);
|
|
||||||
stroke-opacity: 1;
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .stage.mode-view .room.yard:not(.styled):hover {
|
|
||||||
stroke: var(--hp-accent);
|
|
||||||
stroke-opacity: 1;
|
|
||||||
}
|
|
||||||
/* The explicit late room-hover layer owns the wash and halo. Keeping CSS
|
|
||||||
filters off room paths prevents Chromium from recompositing the sibling
|
|
||||||
screen-blended Glow layer for one bright frame on every hover. */
|
|
||||||
:host([data-pointer-hover]) .stage.mode-view .room.styled:hover {
|
|
||||||
stroke: var(--hp-accent);
|
|
||||||
stroke-opacity: 1;
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .stage.markup g.opening:hover .op-outline {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
}
|
||||||
@keyframes hp-pulse-short {
|
@keyframes hp-pulse-short {
|
||||||
0% { transform: scale(1); opacity: 0.55; }
|
0% { transform: scale(1); opacity: 0.55; }
|
||||||
@@ -157,22 +127,6 @@ export const baseStyles = css`
|
|||||||
0% { transform: translateX(-4%); opacity: 0.72; }
|
0% { transform: translateX(-4%); opacity: 0.72; }
|
||||||
100% { transform: translateX(0); opacity: 1; }
|
100% { transform: translateX(0); opacity: 1; }
|
||||||
}
|
}
|
||||||
:host([data-pointer-hover]) .rlgearbtn:hover { opacity: 1; filter: brightness(1.18); }
|
|
||||||
:host([data-pointer-hover]) .rlgear:hover { opacity: 1; }
|
|
||||||
:host([data-pointer-hover]) .stage.mode-view .rlgo:hover { opacity: 1; }
|
|
||||||
:host([data-pointer-hover]) .ctrlopt:hover {
|
|
||||||
background: var(--secondary-background-color, rgba(128,128,128,0.15));
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .stage.markup .roomlabel:hover .rlhandle { display: block; }
|
|
||||||
:host([data-pointer-hover]) .bdframe .bdhandle:hover + .bdknob {
|
|
||||||
fill: #fff;
|
|
||||||
stroke: var(--hp-accent);
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .dtframe .dthandle:hover + .dtknob {
|
|
||||||
fill: #fff;
|
|
||||||
stroke: var(--hp-accent);
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .modetab .closex:hover { opacity: 1; }
|
|
||||||
@keyframes hp-sunfade-in {
|
@keyframes hp-sunfade-in {
|
||||||
from { opacity: 0; }
|
from { opacity: 0; }
|
||||||
to { opacity: 1; }
|
to { opacity: 1; }
|
||||||
@@ -181,37 +135,6 @@ export const baseStyles = css`
|
|||||||
from { opacity: 1; }
|
from { opacity: 1; }
|
||||||
to { opacity: 0; }
|
to { opacity: 0; }
|
||||||
}
|
}
|
||||||
/* Interaction wins ordinary state colours. Alarm keeps priority through
|
|
||||||
the more-specific rule below. Unavailable has no visual hover. */
|
|
||||||
:host([data-pointer-hover]) .dev:not(.unavail):hover {
|
|
||||||
--device-face-bg: #0C82F0;
|
|
||||||
--device-face-fg: light-dark(#fff, #252525);
|
|
||||||
--device-shell-stroke: var(--device-shell-base-stroke);
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .dev.theme-light:not(.unavail):hover { --device-face-fg: #fff; }
|
|
||||||
:host([data-pointer-hover]) .dev.theme-dark:not(.unavail):hover { --device-face-fg: #252525; }
|
|
||||||
:host([data-pointer-hover]) .dev:hover,
|
|
||||||
.dev:focus-visible { z-index: 5; }
|
|
||||||
/* Alert stays above focus, selection, hover and ordinary semantic paint. */
|
|
||||||
.dev.alarm,
|
|
||||||
:host([data-pointer-hover]) .dev.alarm:hover,
|
|
||||||
.dev.alarm:focus-visible {
|
|
||||||
--device-face-bg: #F0410C;
|
|
||||||
--device-face-fg: light-dark(#fff, #252525);
|
|
||||||
--device-shell-stroke: #F0410C;
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .tab:hover .tabedit {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .rszhandle:hover + .rszicon .rszink { stroke-width: 3; }
|
|
||||||
:host([data-pointer-hover]) .rszhandle.disabled:hover + .rszicon .rszink { stroke-width: 2; }
|
|
||||||
:host([data-pointer-hover]) .furnitem:hover { background: rgba(127, 127, 127, 0.18); }
|
|
||||||
:host([data-pointer-hover]) .cand:hover {
|
|
||||||
background: rgba(127, 127, 127, 0.15);
|
|
||||||
}
|
|
||||||
:host([data-pointer-hover]) .pdftag .x:hover {
|
|
||||||
color: #ff7a5c;
|
|
||||||
}
|
|
||||||
.sr-only {
|
.sr-only {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
width: 1px !important;
|
width: 1px !important;
|
||||||
@@ -223,12 +146,6 @@ export const baseStyles = css`
|
|||||||
white-space: nowrap !important;
|
white-space: nowrap !important;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
}
|
}
|
||||||
:host([data-pointer-hover]) .rrow .ract:hover { color: var(--hp-txt); }
|
|
||||||
:host([data-pointer-hover]) .rrow .ract.del:hover { color: #ff7a5c; }
|
|
||||||
:host([data-pointer-hover]) .aboutlink:hover { text-decoration: underline; }
|
|
||||||
:host([data-pointer-hover]) .menu .it:hover {
|
|
||||||
background: rgba(127, 127, 127, 0.15);
|
|
||||||
}
|
|
||||||
.toast {
|
.toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ export const chromeStyles = css`
|
|||||||
transition: 0.15s;
|
transition: 0.15s;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .tab:hover {
|
||||||
|
color: var(--hp-txt);
|
||||||
|
}
|
||||||
.tab.active {
|
.tab.active {
|
||||||
background: var(--hp-accent);
|
background: var(--hp-accent);
|
||||||
color: var(--text-primary-color, #fff);
|
color: var(--text-primary-color, #fff);
|
||||||
@@ -166,6 +169,7 @@ export const chromeStyles = css`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: var(--rad-s);
|
border-radius: var(--rad-s);
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .modetab .closex:hover { opacity: 1; }
|
||||||
.editbar .barclose {
|
.editbar .barclose {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@@ -218,6 +222,9 @@ export const chromeStyles = css`
|
|||||||
margin-left: var(--sp-3);
|
margin-left: var(--sp-3);
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .tab:hover .tabedit {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
.tab.tabadd {
|
.tab.tabadd {
|
||||||
padding: var(--sp-3) var(--sp-4);
|
padding: var(--sp-3) var(--sp-4);
|
||||||
}
|
}
|
||||||
@@ -356,6 +363,9 @@ export const chromeStyles = css`
|
|||||||
--mdc-icon-size: 16px;
|
--mdc-icon-size: 16px;
|
||||||
color: var(--hp-muted);
|
color: var(--hp-muted);
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .menu .it:hover {
|
||||||
|
background: rgba(127, 127, 127, 0.15);
|
||||||
|
}
|
||||||
.menu .it.all {
|
.menu .it.all {
|
||||||
color: var(--hp-accent);
|
color: var(--hp-accent);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -324,6 +324,17 @@ export const devicesStyles = css`
|
|||||||
--device-face-fg: #252525;
|
--device-face-fg: #252525;
|
||||||
--device-shell-stroke-ratio: .025;
|
--device-shell-stroke-ratio: .025;
|
||||||
}
|
}
|
||||||
|
/* Interaction wins ordinary state colours. Alarm keeps priority through
|
||||||
|
the more-specific rule below. Unavailable has no visual hover. */
|
||||||
|
:host([data-pointer-hover]) .dev:not(.unavail):hover {
|
||||||
|
--device-face-bg: #0C82F0;
|
||||||
|
--device-face-fg: light-dark(#fff, #252525);
|
||||||
|
--device-shell-stroke: var(--device-shell-base-stroke);
|
||||||
|
}
|
||||||
|
:host([data-pointer-hover]) .dev.theme-light:not(.unavail):hover { --device-face-fg: #fff; }
|
||||||
|
:host([data-pointer-hover]) .dev.theme-dark:not(.unavail):hover { --device-face-fg: #252525; }
|
||||||
|
:host([data-pointer-hover]) .dev:hover,
|
||||||
|
.dev:focus-visible { z-index: 5; }
|
||||||
.dev.unavail {
|
.dev.unavail {
|
||||||
opacity: 0.35;
|
opacity: 0.35;
|
||||||
--device-face-bg: #B5BAC1;
|
--device-face-bg: #B5BAC1;
|
||||||
@@ -391,6 +402,14 @@ export const devicesStyles = css`
|
|||||||
.dev:not(.on):not(.open):not(.alarm):not(.lock-locked):not(.lock-unlocked):not(.unavail):focus-visible {
|
.dev:not(.on):not(.open):not(.alarm):not(.lock-locked):not(.lock-unlocked):not(.unavail):focus-visible {
|
||||||
--device-face-fg: #0C82F0;
|
--device-face-fg: #0C82F0;
|
||||||
}
|
}
|
||||||
|
/* Alert stays above focus, selection, hover and ordinary semantic paint. */
|
||||||
|
.dev.alarm,
|
||||||
|
:host([data-pointer-hover]) .dev.alarm:hover,
|
||||||
|
.dev.alarm:focus-visible {
|
||||||
|
--device-face-bg: #F0410C;
|
||||||
|
--device-face-fg: light-dark(#fff, #252525);
|
||||||
|
--device-shell-stroke: #F0410C;
|
||||||
|
}
|
||||||
.dev.theme-light.alarm { --device-face-fg: #fff; }
|
.dev.theme-light.alarm { --device-face-fg: #fff; }
|
||||||
.dev.theme-dark.alarm {
|
.dev.theme-dark.alarm {
|
||||||
--device-face-fg: #252525;
|
--device-face-fg: #252525;
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ export const dialogsStyles = css`
|
|||||||
.btn ha-icon {
|
.btn ha-icon {
|
||||||
--mdc-icon-size: 17px;
|
--mdc-icon-size: 17px;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .btn:hover {
|
||||||
|
border-color: var(--hp-accent);
|
||||||
|
}
|
||||||
.btn.on {
|
.btn.on {
|
||||||
background: var(--hp-accent);
|
background: var(--hp-accent);
|
||||||
color: var(--text-primary-color, #fff);
|
color: var(--text-primary-color, #fff);
|
||||||
@@ -111,6 +114,11 @@ export const dialogsStyles = css`
|
|||||||
--mdc-icon-size: 44px;
|
--mdc-icon-size: 44px;
|
||||||
color: var(--hp-accent);
|
color: var(--hp-accent);
|
||||||
}
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.recoveryoverlay {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
.oplock {
|
.oplock {
|
||||||
--oplock-size: calc(var(--icon-size, 2.5cqw) * 0.62);
|
--oplock-size: calc(var(--icon-size, 2.5cqw) * 0.62);
|
||||||
--oplock-core-size: calc(var(--oplock-size) / 1.26875);
|
--oplock-core-size: calc(var(--oplock-size) / 1.26875);
|
||||||
@@ -239,6 +247,9 @@ export const dialogsStyles = css`
|
|||||||
border: 0; background: transparent; color: var(--hp-txt);
|
border: 0; background: transparent; color: var(--hp-txt);
|
||||||
padding: var(--sp-3) var(--sp-4); border-radius: var(--rad-s); cursor: pointer; font-family: inherit; font-size: var(--fs-m);
|
padding: var(--sp-3) var(--sp-4); border-radius: var(--rad-s); cursor: pointer; font-family: inherit; font-size: var(--fs-m);
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .ctrlopt:hover {
|
||||||
|
background: var(--secondary-background-color, rgba(128,128,128,0.15));
|
||||||
|
}
|
||||||
.ctrlopt .sub { color: var(--hp-muted); font-size: var(--fs-s); margin-left: auto; }
|
.ctrlopt .sub { color: var(--hp-muted); font-size: var(--fs-s); margin-left: auto; }
|
||||||
.ctrlopt ha-icon { --mdc-icon-size: 16px; }
|
.ctrlopt ha-icon { --mdc-icon-size: 16px; }
|
||||||
.ctrlstates { display: flex; flex-direction: column; gap: var(--sp-2); }
|
.ctrlstates { display: flex; flex-direction: column; gap: var(--sp-2); }
|
||||||
@@ -669,6 +680,7 @@ export const dialogsStyles = css`
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .furnitem:hover { background: rgba(127, 127, 127, 0.18); }
|
||||||
.furnitem.on {
|
.furnitem.on {
|
||||||
border-color: var(--hp-accent);
|
border-color: var(--hp-accent);
|
||||||
background: rgba(38, 166, 154, 0.18);
|
background: rgba(38, 166, 154, 0.18);
|
||||||
@@ -749,6 +761,9 @@ export const dialogsStyles = css`
|
|||||||
border-radius: var(--rad-s);
|
border-radius: var(--rad-s);
|
||||||
font-size: var(--fs-m);
|
font-size: var(--fs-m);
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .cand:hover {
|
||||||
|
background: rgba(127, 127, 127, 0.15);
|
||||||
|
}
|
||||||
.cand.sel {
|
.cand.sel {
|
||||||
background: var(--hp-accent);
|
background: var(--hp-accent);
|
||||||
color: var(--text-primary-color, #fff);
|
color: var(--text-primary-color, #fff);
|
||||||
@@ -807,6 +822,9 @@ export const dialogsStyles = css`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--hp-muted);
|
color: var(--hp-muted);
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .pdftag .x:hover {
|
||||||
|
color: #ff7a5c;
|
||||||
|
}
|
||||||
.entlist {
|
.entlist {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -919,6 +937,8 @@ export const dialogsStyles = css`
|
|||||||
color: var(--hp-muted);
|
color: var(--hp-muted);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .rrow .ract:hover { color: var(--hp-txt); }
|
||||||
|
:host([data-pointer-hover]) .rrow .ract.del:hover { color: #ff7a5c; }
|
||||||
.gsrow .gsl {
|
.gsrow .gsl {
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
font-size: var(--fs-m);
|
font-size: var(--fs-m);
|
||||||
@@ -1037,6 +1057,7 @@ export const dialogsStyles = css`
|
|||||||
font-size: var(--fs-m);
|
font-size: var(--fs-m);
|
||||||
padding: var(--sp-1) 0;
|
padding: var(--sp-1) 0;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .aboutlink:hover { text-decoration: underline; }
|
||||||
.aboutlink ha-icon { --mdc-icon-size: 18px; line-height: 1; }
|
.aboutlink ha-icon { --mdc-icon-size: 18px; line-height: 1; }
|
||||||
hp-dialog .body {
|
hp-dialog .body {
|
||||||
padding: var(--sp-5) var(--sp-6);
|
padding: var(--sp-5) var(--sp-6);
|
||||||
|
|||||||
@@ -159,6 +159,11 @@ export const planStyles = css`
|
|||||||
.stage.hpsettle {
|
.stage.hpsettle {
|
||||||
transition: height 0.25s ease;
|
transition: height 0.25s ease;
|
||||||
}
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.stage.hpsettle {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
.zoomctl {
|
.zoomctl {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
gap: var(--sp-1);
|
gap: var(--sp-1);
|
||||||
@@ -359,11 +364,19 @@ export const planStyles = css`
|
|||||||
stroke: transparent;
|
stroke: transparent;
|
||||||
stroke-width: calc(2px * var(--hp-cell-visual-scale, 1));
|
stroke-width: calc(2px * var(--hp-cell-visual-scale, 1));
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .stage.mode-view .room.overlay:not(.styled):hover {
|
||||||
|
stroke: var(--hp-accent);
|
||||||
|
stroke-opacity: 1;
|
||||||
|
}
|
||||||
.room.yard {
|
.room.yard {
|
||||||
fill: rgba(75, 140, 90, 0.14);
|
fill: rgba(75, 140, 90, 0.14);
|
||||||
stroke: #4b8c5a;
|
stroke: #4b8c5a;
|
||||||
stroke-width: calc(2px * var(--hp-cell-visual-scale, 1));
|
stroke-width: calc(2px * var(--hp-cell-visual-scale, 1));
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .stage.mode-view .room.yard:not(.styled):hover {
|
||||||
|
stroke: var(--hp-accent);
|
||||||
|
stroke-opacity: 1;
|
||||||
|
}
|
||||||
.room.styled {
|
.room.styled {
|
||||||
stroke: var(--room-stroke, transparent);
|
stroke: var(--room-stroke, transparent);
|
||||||
stroke-opacity: var(--room-stroke-op, 0);
|
stroke-opacity: var(--room-stroke-op, 0);
|
||||||
@@ -405,6 +418,13 @@ export const planStyles = css`
|
|||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* The explicit late room-hover layer owns the wash and halo. Keeping CSS
|
||||||
|
filters off room paths prevents Chromium from recompositing the sibling
|
||||||
|
screen-blended Glow layer for one bright frame on every hover. */
|
||||||
|
:host([data-pointer-hover]) .stage.mode-view .room.styled:hover {
|
||||||
|
stroke: var(--hp-accent);
|
||||||
|
stroke-opacity: 1;
|
||||||
|
}
|
||||||
/* doors, windows & gates */
|
/* doors, windows & gates */
|
||||||
.op-leaf {
|
.op-leaf {
|
||||||
transition: transform 0.6s ease;
|
transition: transform 0.6s ease;
|
||||||
@@ -422,6 +442,9 @@ export const planStyles = css`
|
|||||||
transition: opacity 0.15s;
|
transition: opacity 0.15s;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .stage.markup g.opening:hover .op-outline {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
/* openings are pure status graphics outside Plan mode: no cursor, no hover,
|
/* openings are pure status graphics outside Plan mode: no cursor, no hover,
|
||||||
no hit target — View must not interact with them at all */
|
no hit target — View must not interact with them at all */
|
||||||
.op-hit {
|
.op-hit {
|
||||||
@@ -532,6 +555,7 @@ export const planStyles = css`
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.rlgearbtn { transition: opacity 0.15s, filter 0.15s; }
|
.rlgearbtn { transition: opacity 0.15s, filter 0.15s; }
|
||||||
|
:host([data-pointer-hover]) .rlgearbtn:hover { opacity: 1; filter: brightness(1.18); }
|
||||||
.rlgearbtn ha-icon { --mdc-icon-size: calc(var(--gear-h) * 0.55); display: inline-flex; }
|
.rlgearbtn ha-icon { --mdc-icon-size: calc(var(--gear-h) * 0.55); display: inline-flex; }
|
||||||
.rlgear {
|
.rlgear {
|
||||||
--mdc-icon-size: 0.9em;
|
--mdc-icon-size: 0.9em;
|
||||||
@@ -541,6 +565,7 @@ export const planStyles = css`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .rlgear:hover { opacity: 1; }
|
||||||
.rlgo {
|
.rlgo {
|
||||||
--mdc-icon-size: 0.85em;
|
--mdc-icon-size: 0.85em;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -550,6 +575,7 @@ export const planStyles = css`
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .stage.mode-view .rlgo:hover { opacity: 1; }
|
||||||
.roomlabel .rlmetrics {
|
.roomlabel .rlmetrics {
|
||||||
position: absolute; /* below the name, outside the centring math */
|
position: absolute; /* below the name, outside the centring math */
|
||||||
top: calc(100% + 0.15em);
|
top: calc(100% + 0.15em);
|
||||||
@@ -588,6 +614,7 @@ export const planStyles = css`
|
|||||||
.rlhandle.br { right: -6px; bottom: -6px; cursor: nwse-resize; }
|
.rlhandle.br { right: -6px; bottom: -6px; cursor: nwse-resize; }
|
||||||
.rlhandle.tr { right: -6px; top: -6px; cursor: nesw-resize; }
|
.rlhandle.tr { right: -6px; top: -6px; cursor: nesw-resize; }
|
||||||
.rlhandle.bl { left: -6px; bottom: -6px; cursor: nesw-resize; }
|
.rlhandle.bl { left: -6px; bottom: -6px; cursor: nesw-resize; }
|
||||||
|
:host([data-pointer-hover]) .stage.markup .roomlabel:hover .rlhandle { display: block; }
|
||||||
.stage.markup .roomlabel { pointer-events: auto; }
|
.stage.markup .roomlabel { pointer-events: auto; }
|
||||||
.roomlabel:active { cursor: grabbing; }
|
.roomlabel:active { cursor: grabbing; }
|
||||||
.measurelayer {
|
.measurelayer {
|
||||||
@@ -749,6 +776,10 @@ export const planStyles = css`
|
|||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .bdframe .bdhandle:hover + .bdknob {
|
||||||
|
fill: #fff;
|
||||||
|
stroke: var(--hp-accent);
|
||||||
|
}
|
||||||
.bdframe .bd-nwse { cursor: nwse-resize; }
|
.bdframe .bd-nwse { cursor: nwse-resize; }
|
||||||
.bdframe .bd-nesw { cursor: nesw-resize; }
|
.bdframe .bd-nesw { cursor: nesw-resize; }
|
||||||
/* the picture itself is the drag target for a move (grab, then grabbing) */
|
/* the picture itself is the drag target for a move (grab, then grabbing) */
|
||||||
@@ -795,6 +826,10 @@ export const planStyles = css`
|
|||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
:host([data-pointer-hover]) .dtframe .dthandle:hover + .dtknob {
|
||||||
|
fill: #fff;
|
||||||
|
stroke: var(--hp-accent);
|
||||||
|
}
|
||||||
.dtframe .dt-nwse { cursor: nwse-resize; }
|
.dtframe .dt-nwse { cursor: nwse-resize; }
|
||||||
.dtframe .dt-nesw { cursor: nesw-resize; }
|
.dtframe .dt-nesw { cursor: nesw-resize; }
|
||||||
.dtframe .dtrot { cursor: grab; }
|
.dtframe .dtrot { cursor: grab; }
|
||||||
@@ -1311,7 +1346,9 @@ export const planStyles = css`
|
|||||||
}
|
}
|
||||||
.rszhalo { stroke: var(--hp-bg); stroke-width: 6; }
|
.rszhalo { stroke: var(--hp-bg); stroke-width: 6; }
|
||||||
.rszink { stroke: var(--hp-accent); stroke-width: 2; }
|
.rszink { stroke: var(--hp-accent); stroke-width: 2; }
|
||||||
|
:host([data-pointer-hover]) .rszhandle:hover + .rszicon .rszink { stroke-width: 3; }
|
||||||
.rszicon.disabled { opacity: 0.38; }
|
.rszicon.disabled { opacity: 0.38; }
|
||||||
|
:host([data-pointer-hover]) .rszhandle.disabled:hover + .rszicon .rszink { stroke-width: 2; }
|
||||||
.rszmeasurelayer,
|
.rszmeasurelayer,
|
||||||
.rszmeasurelayer * { pointer-events: none; }
|
.rszmeasurelayer * { pointer-events: none; }
|
||||||
.rszmeasurehalo,
|
.rszmeasurehalo,
|
||||||
|
|||||||
+10
-13
@@ -56,23 +56,20 @@ test('issue 266 surface files do not share a single selector', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Spec §1.3.2: every allowed exception is named with its reason.
|
// Spec §1.3.2: the exception list is empty — a leading :host(...) gate is
|
||||||
const allowed = [
|
// not ownership, so gated device groups live with their surface and no
|
||||||
// The `:host([data-pointer-hover]) .dev:hover, .dev:focus-visible` GROUP
|
// selector is shared between files.
|
||||||
// is cross-surface (host gate + device selector) and therefore lives in
|
assert.deepEqual(clashes, []);
|
||||||
// base per spec §1.1, while the plain `.dev:focus-visible` outline rule
|
|
||||||
// stays with its surface. Their source order (group first, outline
|
|
||||||
// second) is preserved by the [base, …, devices] assembly order — golden
|
|
||||||
// holds the cascade.
|
|
||||||
'.dev:focus-visible (base ∩ devices)',
|
|
||||||
];
|
|
||||||
assert.deepEqual(clashes, allowed);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('issue 266 the media wrappers survived the move', () => {
|
test('issue 266 the media wrappers survived the move', () => {
|
||||||
const all = FILES.map((name) => sourceOf(name)).join('\n');
|
const all = FILES.map((name) => sourceOf(name)).join('\n');
|
||||||
assert.equal((all.match(/@media \(forced-colors: active\)/g) || []).length, 2,
|
assert.equal((all.match(/@media \(forced-colors: active\)/g) || []).length, 2,
|
||||||
'both forced-colors blocks must survive — golden never emulates them');
|
'both forced-colors blocks must survive — golden never emulates them');
|
||||||
assert.equal((all.match(/@media \(prefers-reduced-motion: reduce\)/g) || []).length, 10,
|
// 10 source wrappers; two of them were MIXED-zone and are split into
|
||||||
'all ten reduced-motion blocks must survive — golden always shoots reduced');
|
// per-zone copies by the generator (cascade fix), hence 12 wrappers over
|
||||||
|
// the same 10 wrappers' worth of rules — the scope-keyed refactor diff
|
||||||
|
// proves no rule lost its wrapper.
|
||||||
|
assert.equal((all.match(/@media \(prefers-reduced-motion: reduce\)/g) || []).length, 12,
|
||||||
|
'reduced-motion wrappers must survive — golden always shoots reduced');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user