mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
refactor(ui): drop the dead right rail policy and unused tab join hooks
shouldHideCollapsedRightPanels had become a constant: collapsed right-rail panels always hide behind the tab strip on desktop, so the function, its re-exports, the hud option of layoutRightPanelRail and the test of the constant go. The rail takes the theme's width as its content box instead of restating it per theme. Remove the unread --rail-tab-top property, dead CSS (an unset clip-path variable, an invisible Cyber highlight, redundant radius rules), an unused strip class, per-tab resize observation and guards that only served the test double, which now carries a style object. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
cf37e80b7f
commit
ff017c99ca
12 files changed
+58
-137
No files matched your search
+5
-11
@@ -1511,17 +1511,11 @@ the rail and the strip with it.
|
||||
|
||||
The open tab and its panel are one surface, like a folder tab; the other tabs
|
||||
are borderless labels. The strip measures the open panel and tab, traces their
|
||||
joined silhouette as an SVG path (`railJoinPath`: rounded corners, concave
|
||||
fillets at the junction; Cyber's chamfered frame with its calibration bay and a
|
||||
square tab) and keeps a join element under the panels: one layer blurs and
|
||||
tints the backdrop inside the path, one SVG strokes the outline and another
|
||||
casts the drop shadow, so the junction has no seam or second blur region. The
|
||||
open panel's wrapper and card and the tab draw no surface of their own. The
|
||||
join is re-measured through a ResizeObserver and on strip scroll;
|
||||
`--rail-tab-bottom` on the rail sets the open panel's minimum height, so a
|
||||
short panel such as Context still reaches its tab. A first tab level with the
|
||||
panel's top shares its top edge; Cyber's strip starts below the frame's upper
|
||||
chamfer.
|
||||
joined silhouette as an SVG path (`railJoinPath`; Cyber gets its chamfered
|
||||
frame) and shapes a join element under the panels with it: glass, outline and
|
||||
shadow in one piece, so the junction has no seam. The panel and the tab draw
|
||||
no surface of their own. `--rail-tab-bottom` on the rail sets the open panel's
|
||||
minimum height, so a short panel such as Context still reaches its tab.
|
||||
|
||||
A first-time visitor on a desktop viewport finds Display open
|
||||
(`src/ui/panelPositionControls.js`): with every panel closed nothing shows that
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export { shouldHideCollapsedRightPanels } from './ui/panelRailGeometry.js';
|
||||
|
||||
const GLOBAL_CONTEXT_EXPLICIT_ACTIONS = new Set([
|
||||
'contacts',
|
||||
'space-missions',
|
||||
|
||||
@@ -2,35 +2,13 @@ import { readStylesheet } from './testSupport/readStylesheet.mjs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
shouldExpandGlobalContextPanel,
|
||||
shouldHideCollapsedRightPanels,
|
||||
} from './rightRailPolicy.js';
|
||||
import { shouldExpandGlobalContextPanel } from './rightRailPolicy.js';
|
||||
|
||||
test('the tab strip is the launcher in every HUD layout, so collapsed right-rail panels hide', () => {
|
||||
for (const hudVariant of ['tactical', 'operator', 'minimal', 'cyber', 'full']) {
|
||||
for (const hasExpandedPanel of [true, false]) {
|
||||
assert.equal(
|
||||
shouldHideCollapsedRightPanels({ hudVariant, hasExpandedPanel }),
|
||||
true,
|
||||
`${hudVariant} with${hasExpandedPanel ? '' : 'out'} an expanded panel`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('desktop Display participates in Tactical exclusivity without changing mobile Display behavior', () => {
|
||||
test('the desktop right rail hides collapsed panels behind the tab strip and marks them aria-hidden', () => {
|
||||
const ui = readFileSync(new URL('./ui/rightPanelRail.js', import.meta.url), 'utf8');
|
||||
const css = readStylesheet(new URL('../style.css', import.meta.url));
|
||||
assert.match(ui, /const isMobile = windowRef\.matchMedia\('\(max-width: 720px\)'\)\.matches/);
|
||||
assert.match(
|
||||
ui,
|
||||
/!panel\.classList\.contains\('collapsed'\)\s*&&\s*\(!isMobile \|\| panel\.id !== 'pp-toggles'\)/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
ui,
|
||||
/panel\.id !== 'pp-toggles' && !panel\.classList\.contains\('collapsed'\)/,
|
||||
);
|
||||
assert.match(ui, /const exclusive = !isMobile;/);
|
||||
assert.match(ui, /if \(exclusive && panel\.classList\.contains\('collapsed'\)\)\s*panel\.setAttribute\('aria-hidden', 'true'\)/);
|
||||
assert.match(css, /#right-context-rail\.layout-exclusive > \[data-panel-id\]\.collapsed \{/);
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ export function collapsePanelOnEscape(
|
||||
);
|
||||
const escapedFromDisclosure =
|
||||
event.target === disclosure || disclosure?.contains?.(event.target);
|
||||
if (tab) tab.focus?.({ preventScroll: true });
|
||||
if (tab) tab.focus({ preventScroll: true });
|
||||
else if (escapedFromDisclosure) disclosure?.blur?.();
|
||||
else disclosure?.focus?.({ preventScroll: true });
|
||||
return true;
|
||||
|
||||
@@ -301,7 +301,6 @@ export class PanelLayoutController {
|
||||
stack: this._rightPanelStack,
|
||||
obstacles: document.querySelectorAll(RIGHT_STACK_OBSTACLE_SELECTOR),
|
||||
windowRef: window,
|
||||
hud: this.readHud(),
|
||||
preferredPanelId: this._rightStackPreferredPanelId,
|
||||
onCollapse: (panel) => this._syncPanelCollapseButton(panel),
|
||||
leftStack: this._leftPanelStack,
|
||||
|
||||
@@ -60,15 +60,3 @@ export function resolveHudRailLayout({
|
||||
constrained: panelHeight > availableHeight,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The tab strip (rightRailTabs.js) is the launcher in every HUD layout, so
|
||||
* collapsed right-rail panels always hide. Callers still pass the rail state
|
||||
* they lay out.
|
||||
* @param {{hudVariant?: string, hasExpandedPanel?: boolean}} [input]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldHideCollapsedRightPanels(input = {}) {
|
||||
void input;
|
||||
return true;
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
export { layoutLeftPanelRail } from './leftPanelRail.js';
|
||||
export { layoutRightPanelRail } from './rightPanelRail.js';
|
||||
export { measurePanelNaturalHeight } from './panelMeasurement.js';
|
||||
export {
|
||||
resolveHudRailLayout,
|
||||
shouldHideCollapsedRightPanels,
|
||||
} from './panelRailGeometry.js';
|
||||
export { resolveHudRailLayout } from './panelRailGeometry.js';
|
||||
export {
|
||||
allocatePanelStackHeights,
|
||||
panelStackAutoCollapseIndices,
|
||||
|
||||
@@ -306,18 +306,6 @@ test('right layout lets a stored or shared choice outrank a panel open only by d
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('rail-owner-collapsed'), true);
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
const g = fixture('right');
|
||||
g.expand(g.first, 900);
|
||||
g.expand(g.second, 900);
|
||||
g.first.dataset.collapsedPreference = 'default';
|
||||
g.second.dataset.collapsedPreference = 'default';
|
||||
g.run();
|
||||
assert.equal(
|
||||
g.first.classList.contains('collapsed'),
|
||||
false,
|
||||
'all default: first wins',
|
||||
);
|
||||
assert.equal(g.second.classList.contains('rail-owner-collapsed'), true);
|
||||
});
|
||||
|
||||
for (const collapsed of [true, false]) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { allocatePanelStackHeights } from '../panelStackLayout.js';
|
||||
import {
|
||||
resolveHudRailLayout,
|
||||
shouldHideCollapsedRightPanels,
|
||||
} from './panelRailGeometry.js';
|
||||
import { resolveHudRailLayout } from './panelRailGeometry.js';
|
||||
|
||||
import { displayPanelScroller } from './displayPanelScroll.js';
|
||||
|
||||
@@ -38,7 +35,6 @@ function settledLeftRailTop(leftStack, viewportHeight, getComputedStyle) {
|
||||
* @param {HTMLElement} options.stack Rail element.
|
||||
* @param {Iterable<HTMLElement>} options.obstacles Caller-selected obstacle nodes.
|
||||
* @param {Window} options.windowRef Viewport and style reader.
|
||||
* @param {{visible: boolean, variant: string}} options.hud Current HUD presentation.
|
||||
* @param {string} options.preferredPanelId Most recently opened panel.
|
||||
* @param {Function} options.onCollapse Update a panel's disclosure chrome.
|
||||
* @param {Function} [options.getComputedStyle] Optional DOM style reader override.
|
||||
@@ -51,7 +47,6 @@ export function layoutRightPanelRail({
|
||||
stack,
|
||||
obstacles,
|
||||
windowRef,
|
||||
hud,
|
||||
preferredPanelId,
|
||||
onCollapse,
|
||||
leftStack,
|
||||
@@ -104,19 +99,10 @@ export function layoutRightPanelRail({
|
||||
onCollapse(panel);
|
||||
}
|
||||
}
|
||||
const hasExpandedPanel = panels.some(
|
||||
(panel) =>
|
||||
!panel.classList.contains('collapsed') &&
|
||||
(!isMobile || panel.id !== 'pp-toggles'),
|
||||
);
|
||||
// Phones present the rail as tab sheets (mobileShell.js), which choose what
|
||||
// shows; hiding collapsed siblings would empty a sheet and its a11y tree.
|
||||
const exclusive =
|
||||
!isMobile &&
|
||||
shouldHideCollapsedRightPanels({
|
||||
hudVariant: hud.variant,
|
||||
hasExpandedPanel,
|
||||
});
|
||||
// The tab strip is the launcher, so collapsed panels hide. Phones present
|
||||
// the rail as tab sheets (mobileShell.js), which choose what shows; hiding
|
||||
// collapsed siblings would empty a sheet and its a11y tree.
|
||||
const exclusive = !isMobile;
|
||||
stack.classList.toggle('layout-exclusive', exclusive);
|
||||
for (const panel of panels) {
|
||||
if (exclusive && panel.classList.contains('collapsed'))
|
||||
|
||||
+10
-21
@@ -185,7 +185,6 @@ export function createRightRailTabs({
|
||||
}
|
||||
const strip = documentRef.createElement('div');
|
||||
strip.id = RIGHT_RAIL_TABS_ID;
|
||||
strip.className = 'right-rail-tabs';
|
||||
strip.setAttribute('role', 'tablist');
|
||||
strip.setAttribute('aria-orientation', 'vertical');
|
||||
strip.setAttribute('aria-label', t('Panels'));
|
||||
@@ -228,7 +227,6 @@ export function createRightRailTabs({
|
||||
const label = documentRef.createElement('span');
|
||||
label.className = 'right-rail-tab-label';
|
||||
tab.append(label);
|
||||
resizer?.observe(tab);
|
||||
entry = { tab, label };
|
||||
entries.set(panel.id, entry);
|
||||
return entry;
|
||||
@@ -239,8 +237,8 @@ export function createRightRailTabs({
|
||||
const observer =
|
||||
typeof Observer === 'function' ? new Observer(() => sync()) : null;
|
||||
// Tab heights follow their labels (fonts, locale) and panel heights their
|
||||
// content and allocation: re-measure the join on any size change and on
|
||||
// strip scroll.
|
||||
// content and allocation: re-measure the join when the strip or a panel
|
||||
// changes size and on strip scroll.
|
||||
const Resize = documentRef.defaultView?.ResizeObserver;
|
||||
const resizer =
|
||||
typeof Resize === 'function' ? new Resize(() => sync()) : null;
|
||||
@@ -262,28 +260,20 @@ export function createRightRailTabs({
|
||||
};
|
||||
|
||||
/**
|
||||
* Publish the join: the open tab's extent on the rail (--rail-tab-top and
|
||||
* --rail-tab-bottom, rail pixels; the stylesheet sets the panel's minimum
|
||||
* height from them) and the joined silhouette on the join's layers. The
|
||||
* rail can be taller than its panel in the focus layout, so the join is
|
||||
* sized from the panel, extended down to cover the tab if that is shorter.
|
||||
* Publish the join: the open tab's bottom on the rail (--rail-tab-bottom,
|
||||
* rail pixels; the stylesheet sets the panel's minimum height from it) and
|
||||
* the joined silhouette on the join's layers. The rail can be taller than
|
||||
* its panel in the focus layout, so the join is sized from the panel,
|
||||
* extended down to cover the tab if that is shorter.
|
||||
*/
|
||||
function placeJoin(tab, panel) {
|
||||
const style = rail.style;
|
||||
if (typeof style?.setProperty !== 'function') return;
|
||||
if (!tab || !Number.isFinite(tab.offsetTop)) {
|
||||
style.removeProperty('--rail-tab-top');
|
||||
style.removeProperty('--rail-tab-bottom');
|
||||
if (!tab) {
|
||||
rail.style.removeProperty('--rail-tab-bottom');
|
||||
join.hidden = true;
|
||||
return;
|
||||
}
|
||||
const top = tab.offsetTop - (strip.scrollTop || 0);
|
||||
style.setProperty('--rail-tab-top', `${top}px`);
|
||||
style.setProperty('--rail-tab-bottom', `${top + tab.offsetHeight}px`);
|
||||
if (!Number.isFinite(panel?.offsetWidth) || !join.style) {
|
||||
join.hidden = true;
|
||||
return;
|
||||
}
|
||||
rail.style.setProperty('--rail-tab-bottom', `${top + tab.offsetHeight}px`);
|
||||
const computed = documentRef.defaultView?.getComputedStyle?.(rail);
|
||||
const corner = token(computed, '--right-rail-corner', 16);
|
||||
const tabRadius = token(computed, '--right-rail-tab-radius', 10);
|
||||
@@ -359,7 +349,6 @@ export function createRightRailTabs({
|
||||
const live = new Set(panels.map((panel) => panel.id));
|
||||
for (const [id, entry] of entries) {
|
||||
if (live.has(id)) continue;
|
||||
resizer?.unobserve(entry.tab);
|
||||
entry.tab.remove();
|
||||
entries.delete(id);
|
||||
}
|
||||
|
||||
@@ -24,12 +24,18 @@ function makeDocument() {
|
||||
const el = new EventTarget();
|
||||
const attrs = new Map();
|
||||
const classes = new Set();
|
||||
const props = new Map();
|
||||
Object.assign(el, {
|
||||
tagName: tag.toUpperCase(),
|
||||
ownerDocument: doc,
|
||||
children: [],
|
||||
parentElement: null,
|
||||
dataset: {},
|
||||
style: {
|
||||
props,
|
||||
setProperty: (key, value) => props.set(key, value),
|
||||
removeProperty: (key) => props.delete(key),
|
||||
},
|
||||
hidden: false,
|
||||
textContent: '',
|
||||
tabIndex: -1,
|
||||
@@ -325,11 +331,7 @@ test('a sync that changes nothing keeps the same tab elements in place', () => {
|
||||
|
||||
test('publishes the open tab extent on the rail and keeps a join element under the panels', () => {
|
||||
const f = fixture();
|
||||
const props = new Map();
|
||||
f.rail.style = {
|
||||
setProperty: (key, value) => props.set(key, value),
|
||||
removeProperty: (key) => props.delete(key),
|
||||
};
|
||||
const { props } = f.rail.style;
|
||||
assert.equal(f.tabs.join.parentElement, f.rail);
|
||||
assert.equal(f.tabs.join.getAttribute('aria-hidden'), 'true');
|
||||
assert.equal(f.tabs.join.hidden, true, 'nothing open, nothing to join');
|
||||
@@ -342,28 +344,39 @@ test('publishes the open tab extent on the rail and keeps a join element under t
|
||||
],
|
||||
);
|
||||
const tab = f.tabFor('cctv-panel');
|
||||
tab.offsetTop = 86;
|
||||
tab.offsetHeight = 56;
|
||||
Object.assign(tab, {
|
||||
offsetTop: 86,
|
||||
offsetLeft: 0,
|
||||
offsetHeight: 56,
|
||||
offsetWidth: 42,
|
||||
});
|
||||
Object.assign(f.cctv, {
|
||||
offsetTop: 0,
|
||||
offsetLeft: 0,
|
||||
offsetHeight: 300,
|
||||
offsetWidth: 330,
|
||||
});
|
||||
f.strip.scrollTop = 0;
|
||||
f.cctv.classList.remove('collapsed');
|
||||
f.tabs.sync();
|
||||
assert.deepEqual(
|
||||
[...props],
|
||||
[
|
||||
['--rail-tab-top', '86px'],
|
||||
['--rail-tab-bottom', '142px'],
|
||||
],
|
||||
assert.deepEqual([...props], [['--rail-tab-bottom', '142px']]);
|
||||
assert.equal(f.tabs.join.hidden, false);
|
||||
const [, glass] = f.tabs.join.children;
|
||||
assert.match(
|
||||
glass.style.clipPath,
|
||||
/^path\('M 16 0 L 314 0 A 16 16 0 0 1 330 16 L 330 78 /,
|
||||
);
|
||||
f.strip.scrollTop = 20;
|
||||
f.tabs.sync();
|
||||
assert.equal(
|
||||
props.get('--rail-tab-top'),
|
||||
'66px',
|
||||
props.get('--rail-tab-bottom'),
|
||||
'122px',
|
||||
'strip scroll is subtracted',
|
||||
);
|
||||
f.cctv.classList.add('collapsed');
|
||||
f.tabs.sync();
|
||||
assert.deepEqual([...props], [], 'no open panel, no join');
|
||||
assert.equal(f.tabs.join.hidden, true);
|
||||
f.tabs.destroy();
|
||||
assert.equal(f.tabs.join.parentElement, null);
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
selectors carry it to outrank the Cyber rail rules that follow this sheet. */
|
||||
|
||||
#right-context-rail[data-rail-tabs] {
|
||||
--right-rail-panel-width: 330px;
|
||||
--right-rail-tabs-width: 34px;
|
||||
/* The bridge the open tab reaches across to its panel. */
|
||||
--right-rail-tabs-gap: 8px;
|
||||
@@ -34,7 +33,9 @@
|
||||
var(--rail-tab-bottom, 0px) + var(--right-rail-fillet) + 4px +
|
||||
var(--right-rail-corner)
|
||||
);
|
||||
width: calc(var(--right-rail-panel-width) + var(--right-rail-tabs-inset));
|
||||
/* The theme's rail width stays the panel width; the strip's column is
|
||||
padding beside it. */
|
||||
box-sizing: content-box;
|
||||
padding-right: var(--right-rail-tabs-inset);
|
||||
}
|
||||
|
||||
@@ -146,7 +147,6 @@ body.mobile-shell #right-context-rail > .right-rail-join {
|
||||
background-image: var(--panel-surface-detail);
|
||||
backdrop-filter: blur(24px) saturate(1.4);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.4);
|
||||
clip-path: var(--rail-join-path, none);
|
||||
}
|
||||
|
||||
.right-rail-join-outline path {
|
||||
@@ -219,15 +219,13 @@ body:not(.mobile-shell)
|
||||
/* Cyber panels are chamfered frames (--cyber-panel-frame), not rounded
|
||||
cards: square joins, no fillets, no blur, and the strip starts below the
|
||||
frame's upper chamfer so the first tab meets a straight edge. The frame's
|
||||
red edge bar and faint right highlight move onto the join's glass. */
|
||||
red edge bar moves onto the join's glass. */
|
||||
:root[data-ui-theme='cyber'] #right-context-rail[data-rail-tabs] {
|
||||
--right-rail-panel-width: 312px;
|
||||
--right-rail-tab-radius: 0px;
|
||||
--right-rail-fillet: 0px;
|
||||
--right-rail-corner: 10px;
|
||||
--right-rail-join-style: chamfer;
|
||||
--right-rail-join-shadow: 0 10px 28px rgba(0, 0, 0, 0.42);
|
||||
width: calc(var(--right-rail-panel-width) + var(--right-rail-tabs-inset));
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber'] #right-rail-tabs {
|
||||
@@ -236,10 +234,6 @@ body:not(.mobile-shell)
|
||||
|
||||
/* Cyber's generic hover/selected button treatment (cyber.css) would paint
|
||||
the tabs; the open tab stays a label on the shared surface. */
|
||||
:root[data-ui-theme='cyber'] .right-rail-tab {
|
||||
border-radius: var(--btn-radius);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber'] .right-rail-tab:not([aria-selected='true']):hover {
|
||||
background-color: rgba(226, 235, 236, 0.08);
|
||||
box-shadow: none;
|
||||
@@ -247,7 +241,6 @@ body:not(.mobile-shell)
|
||||
|
||||
:root[data-ui-theme='cyber'] .right-rail-tab[aria-selected='true'],
|
||||
:root[data-ui-theme='cyber'] .right-rail-tab[aria-selected='true']:hover {
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
box-shadow: -3px 0 0 var(--cyber-red) inset;
|
||||
color: var(--cyber-red-bright);
|
||||
@@ -265,9 +258,7 @@ body:not(.mobile-shell)
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber'] .right-rail-join-glass {
|
||||
box-shadow:
|
||||
3px 0 0 rgba(196, 84, 84, 0.2) inset,
|
||||
-1px 0 0 rgba(226, 235, 236, 0.05) inset;
|
||||
box-shadow: 3px 0 0 rgba(196, 84, 84, 0.2) inset;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user