mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Keep panel titles inside their cards and scope the hover
Collapsed Recent Imagery (English) and Data Layers (Portuguese) pushed their toggles out of the card. Collapsed rail launchers now drop the divider and pack closer, translated collapsed titles use compact type, and any title that still does not fit ends in an ellipsis. The hover now matches the dock trays' hover rather than their pinned look, only on pointers that can hover, and recolours only the hovered panel's own header, so Radio and body labels keep their colours. The cyber theme keeps main's gutters and Display toggle size, the dock and rails share --dock-label-* tokens, and the dimmed open Context title rule is gone.
This commit is contained in:
1 parent
1252dacf07
commit
44f9138d1e
8 files changed
+345
-94
No files matched your search
@@ -0,0 +1,193 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { readStylesheet } from '../testSupport/readStylesheet.mjs';
|
||||
|
||||
const css = readStylesheet(new URL('../../style.css', import.meta.url));
|
||||
const clean = (selector) =>
|
||||
selector
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
const rules = [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map(
|
||||
([, selector, body]) => ({ selector: clean(selector), body }),
|
||||
);
|
||||
const railRules = rules.filter(({ selector }) =>
|
||||
selector.includes('#left-panel-stack, #right-context-rail'),
|
||||
);
|
||||
const hoverBlock =
|
||||
css.match(
|
||||
/@media \(hover: hover\) and \(pointer: fine\) \{((?:[^{}]*\{[^{}]*\})*)\s*\}/g,
|
||||
) || [];
|
||||
|
||||
test('a side panel lights up on hover only where a pointer can hover', () => {
|
||||
const lit = railRules.filter(({ selector }) => selector.includes(':hover'));
|
||||
assert.ok(lit.length > 0, 'the rail hover rule is missing');
|
||||
for (const { selector } of lit) {
|
||||
assert.ok(
|
||||
hoverBlock.some((block) => clean(block).includes(selector)),
|
||||
`${selector} must sit inside @media (hover: hover) and (pointer: fine)`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('hover matches the dock tray hover, not its pinned look', () => {
|
||||
const [hover] = railRules.filter(({ selector }) =>
|
||||
selector.endsWith(':hover'),
|
||||
);
|
||||
assert.ok(hover, 'the rail surface hover rule is missing');
|
||||
assert.match(hover.body, /border-color:\s*var\(--glass-border-hover\)/);
|
||||
assert.doesNotMatch(hover.body, /box-shadow/);
|
||||
assert.match(hover.body, /--rail-label-hover:\s*var\(--accent\)/);
|
||||
// A low-specificity list: no ID or state selector inside it lifts the rule.
|
||||
assert.match(hover.selector, /:where\(/);
|
||||
});
|
||||
|
||||
test('hover recolours only the hovered panel header, never nested sections', () => {
|
||||
for (const { selector } of railRules) {
|
||||
assert.ok(
|
||||
!/\.radio-panel-inner/.test(selector),
|
||||
`Radio sits inside Context and must not light up as its own panel: ${selector}`,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
selector,
|
||||
/:hover\s+:is\(/,
|
||||
'a descendant colour list would reach labels in the panel body',
|
||||
);
|
||||
}
|
||||
const colour = railRules.find(
|
||||
({ body }) => /--rail-label-hover/.test(body) && /color:/.test(body),
|
||||
);
|
||||
assert.ok(colour, 'the rail title colour rule is missing');
|
||||
assert.match(colour.selector, /\.panel-header > \.panel-title/);
|
||||
const radio = rules.find(({ selector }) => selector === '#radio-panel');
|
||||
assert.match(radio?.body || '', /--rail-label-hover:\s*initial/);
|
||||
});
|
||||
|
||||
test('no later rule dims the open Context title below its siblings', () => {
|
||||
const dimmed = rules.filter(
|
||||
({ selector, body }) =>
|
||||
/#global-context-panel:not\(\.collapsed\) \.panel-title$/.test(
|
||||
selector,
|
||||
) && /color:/.test(body),
|
||||
);
|
||||
assert.deepEqual(dimmed, []);
|
||||
});
|
||||
|
||||
test('collapsed launchers on both rails keep titles inside the card', () => {
|
||||
const ellipsis = railRules.find(
|
||||
({ selector, body }) =>
|
||||
selector.endsWith(
|
||||
':is(.panel-header > .panel-title, .pp-header-label)',
|
||||
) && /text-overflow:\s*ellipsis/.test(body),
|
||||
);
|
||||
assert.ok(
|
||||
ellipsis,
|
||||
'rail titles must end in an ellipsis rather than overflow',
|
||||
);
|
||||
assert.match(ellipsis.body, /min-width:\s*0/);
|
||||
|
||||
const translated = rules.find(
|
||||
({ selector }) =>
|
||||
selector.startsWith("html:not([lang='en'])") &&
|
||||
selector.includes('#left-panel-stack, #right-context-rail') &&
|
||||
selector.includes('.panel-collapsible.collapsed'),
|
||||
);
|
||||
assert.ok(translated, 'translated collapsed titles must fit on both rails');
|
||||
assert.match(translated.body, /font-size:\s*8px/);
|
||||
});
|
||||
|
||||
test('the default theme geometry change leaves cyber gutters alone', () => {
|
||||
for (const surface of ['.data-panel-inner', '.global-context-panel-inner']) {
|
||||
const base = rules.find(
|
||||
({ selector, body }) => selector === surface && /padding:/.test(body),
|
||||
);
|
||||
assert.match(base?.body || '', /padding:\s*14px 16px 12px/, surface);
|
||||
}
|
||||
const display = rules.find(
|
||||
({ selector }) =>
|
||||
selector === '#pp-toggles > .pp-header-row .pp-collapse-btn',
|
||||
);
|
||||
assert.match(display?.body || '', /font-size:\s*12px/);
|
||||
});
|
||||
|
||||
test('rail titles and dock labels share one set of label tokens', () => {
|
||||
// command-dock-trays.css sets the dock's label type; earlier dock rules
|
||||
// for the same selector are overridden by it.
|
||||
const dock = rules.find(
|
||||
({ selector, body }) =>
|
||||
selector ===
|
||||
'#command-dock .panel-title, #command-dock .location-toolbar-label' &&
|
||||
/--dock-label-/.test(body),
|
||||
);
|
||||
const rail = railRules.find(({ body }) =>
|
||||
/--dock-label-font-size/.test(body),
|
||||
);
|
||||
for (const body of [dock?.body, rail?.body]) {
|
||||
assert.match(body || '', /font-size:\s*var\(--dock-label-font-size\)/);
|
||||
assert.match(
|
||||
body || '',
|
||||
/letter-spacing:\s*var\(--dock-label-letter-spacing\)/,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('default theme overrides never reach the cyber theme', () => {
|
||||
const notCyber = ":root:not([data-ui-theme='cyber'])";
|
||||
const padding = rules.find(
|
||||
({ selector, body }) =>
|
||||
selector.endsWith(
|
||||
':is(.data-panel-inner, .global-context-panel-inner)',
|
||||
) && /padding-left:\s*14px/.test(body),
|
||||
);
|
||||
assert.ok(
|
||||
padding?.selector.startsWith(notCyber),
|
||||
'the 14px gutters must skip cyber',
|
||||
);
|
||||
const display = rules.find(
|
||||
({ selector, body }) =>
|
||||
selector.endsWith('#pp-toggles > .pp-header-row .pp-collapse-btn') &&
|
||||
/font-size:\s*13px/.test(body),
|
||||
);
|
||||
assert.ok(
|
||||
display?.selector.startsWith(notCyber),
|
||||
"Display's 22px toggle must skip cyber",
|
||||
);
|
||||
// Cyber draws its title marker at left: -8px; a clip on the title would cut it.
|
||||
for (const { selector, body } of rules) {
|
||||
if (!/\.panel-title/.test(selector) || !/overflow:\s*hidden/.test(body))
|
||||
continue;
|
||||
if (!/#left-panel-stack|#right-context-rail/.test(selector)) continue;
|
||||
assert.ok(
|
||||
selector.startsWith(notCyber),
|
||||
`${selector} clips the cyber title marker`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('launcher compaction and header colour stay on the rail panels themselves', () => {
|
||||
const compact = railRules.filter(({ selector }) =>
|
||||
/\.collapsed > \[class\$='-panel-inner'\] > \.panel-header/.test(selector),
|
||||
);
|
||||
assert.equal(compact.length, 3);
|
||||
for (const { selector } of compact) {
|
||||
// Each launcher is a direct child of its rail: Radio, nested inside
|
||||
// Context, is not a launcher. The header must not itself be required to
|
||||
// be a rail child.
|
||||
assert.match(
|
||||
selector,
|
||||
/#right-context-rail\) > \.panel-collapsible\.collapsed > \[class\$='-panel-inner'\] > \.panel-header/,
|
||||
);
|
||||
assert.match(
|
||||
selector,
|
||||
/#right-context-rail\) > #pp-toggles\.collapsed > \.pp-header-row/,
|
||||
);
|
||||
}
|
||||
const colour = railRules.find(({ body }) =>
|
||||
/--rail-label-hover, /.test(body),
|
||||
);
|
||||
assert.match(
|
||||
colour.selector,
|
||||
/:is\(\.panel-header, \.pp-header-row\) > \.panel-collapse-btn/,
|
||||
);
|
||||
assert.doesNotMatch(colour.selector, /, \.panel-collapse-btn\)/);
|
||||
});
|
||||
Reference in new issue
Block a user