mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
Merge upstream/main into pr/panel-resize
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
commit
11603729dc
234 files changed
+45360
-944
No files matched your search
+64
-43
@@ -7,6 +7,8 @@ import {
|
||||
shouldHideCollapsedRightPanels,
|
||||
} from './panelRailGeometry.js';
|
||||
|
||||
const pendingCollapseRetries = new WeakSet();
|
||||
|
||||
/**
|
||||
* Measure and place the right panel rail for one synchronous layout pass.
|
||||
* The caller owns scheduling, obstacle selection, disclosure preferences and
|
||||
@@ -40,11 +42,14 @@ export function layoutRightPanelRail({
|
||||
getComputedStyle = (element) => windowRef.getComputedStyle(element),
|
||||
}) {
|
||||
if (!stack) return;
|
||||
// A collapse may schedule one follow-up, which only measures and allocates.
|
||||
const isCollapseRetry = pendingCollapseRetries.delete(stack);
|
||||
|
||||
// A panel lifted out of the rail (PanelPositionControls' portable mode)
|
||||
// is a fixed window the rail neither measures nor allocates.
|
||||
const panels = [...stack.children].filter((panel) =>
|
||||
panel.matches('[data-panel-id]:not(.panel-floating)'),
|
||||
const panels = [...stack.children].filter(
|
||||
(panel) =>
|
||||
panel.matches('[data-panel-id]:not(.panel-floating)') && !panel.hidden,
|
||||
);
|
||||
if (!hud.visible || hud.variant !== 'tactical') {
|
||||
for (const panel of panels.filter((item) =>
|
||||
@@ -115,33 +120,53 @@ export function layoutRightPanelRail({
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-collapsed headers contribute the same need even when exclusive
|
||||
// presentation hides them. Manually collapsed launchers keep their policy.
|
||||
const measuredPanels = panels.filter(
|
||||
(panel) =>
|
||||
!exclusive ||
|
||||
!panel.classList.contains('collapsed') ||
|
||||
panel.classList.contains('layout-auto-collapsed'),
|
||||
);
|
||||
const visiblePanels = panels.filter(
|
||||
(panel) => !exclusive || !panel.classList.contains('collapsed'),
|
||||
);
|
||||
|
||||
const displayScrollTop = readDisplayScrollTop();
|
||||
// Measure intrinsic content, not the allocation written by the previous
|
||||
// layout pass. Display is the exception: its own scrollHeight already
|
||||
// exposes every control, and removing its live allocation can reset the
|
||||
// user's scroll position while HUD or preset content is settling.
|
||||
for (const panel of visiblePanels) {
|
||||
if (!panel.classList.contains('collapsed') && panel !== displayPanel) {
|
||||
panel.style.removeProperty('--right-panel-allocated-height');
|
||||
}
|
||||
}
|
||||
const gap = parseFloat(getComputedStyle(stack).rowGap) || 0;
|
||||
const naturalHeight =
|
||||
visiblePanels.reduce(
|
||||
(total, panel) =>
|
||||
total +
|
||||
// Measuring lifts each opted-in scroller's max-height, which clamps its
|
||||
// scroll offset; remember the offsets to put back afterwards.
|
||||
const scrollers = [
|
||||
...(stack.querySelectorAll?.('[data-rail-scroller]') || []),
|
||||
]
|
||||
.map((node) => [node, node.scrollTop || 0])
|
||||
.filter(([, top]) => top > 0);
|
||||
const naturalHeights = new Map();
|
||||
// Remove live height allocations through a synchronous CSS override. Keeping
|
||||
// the stored properties intact avoids REMOVE/SET churn on unchanged panels.
|
||||
// Do not toggle layout classes here: the class observer schedules new passes.
|
||||
stack.setAttribute('data-rail-measuring', '');
|
||||
try {
|
||||
for (const panel of measuredPanels) {
|
||||
naturalHeights.set(
|
||||
panel,
|
||||
Math.max(
|
||||
panel.getBoundingClientRect().height,
|
||||
panel.scrollHeight || 0,
|
||||
panel.classList.contains('collapsed') ? 42 : 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
stack.removeAttribute('data-rail-measuring');
|
||||
for (const [node, top] of scrollers)
|
||||
if (node.scrollTop !== top) node.scrollTop = top;
|
||||
}
|
||||
const gap = parseFloat(getComputedStyle(stack).rowGap) || 0;
|
||||
const naturalHeight =
|
||||
measuredPanels.reduce(
|
||||
(total, panel) => total + naturalHeights.get(panel),
|
||||
0,
|
||||
) +
|
||||
gap * Math.max(0, visiblePanels.length - 1);
|
||||
gap * Math.max(0, measuredPanels.length - 1);
|
||||
const layout = resolveHudRailLayout({
|
||||
viewportHeight,
|
||||
panelHeight: naturalHeight,
|
||||
@@ -155,16 +180,16 @@ export function layoutRightPanelRail({
|
||||
});
|
||||
if (!layout) return;
|
||||
const { safeTop, safeBottom, maxHeight: availableHeight } = layout;
|
||||
const stabilityBand = viewportHeight * 0.01;
|
||||
const stabilityBand = Math.max(48, viewportHeight * 0.06);
|
||||
const wasFocused = stack.classList.contains('layout-focus');
|
||||
const shouldFocus = wasFocused
|
||||
? naturalHeight > availableHeight - stabilityBand * 2
|
||||
: naturalHeight > availableHeight - stabilityBand;
|
||||
? naturalHeight >= availableHeight - stabilityBand
|
||||
: naturalHeight > availableHeight;
|
||||
const layoutTop = shouldFocus ? safeTop : layout.top;
|
||||
const collapsedHeight = visiblePanels.reduce(
|
||||
(total, panel) =>
|
||||
panel.classList.contains('collapsed')
|
||||
? total + panel.getBoundingClientRect().height
|
||||
? total + naturalHeights.get(panel)
|
||||
: total,
|
||||
0,
|
||||
);
|
||||
@@ -196,38 +221,34 @@ export function layoutRightPanelRail({
|
||||
gap * Math.max(0, visiblePanels.length - 1),
|
||||
);
|
||||
const expandedHeights = allocatePanelStackHeights({
|
||||
naturalHeights: expandedPanels.map((panel) =>
|
||||
Math.max(panel.getBoundingClientRect().height, panel.scrollHeight || 0),
|
||||
),
|
||||
naturalHeights: expandedPanels.map((panel) => naturalHeights.get(panel)),
|
||||
availableHeight: expandedAvailableHeight,
|
||||
});
|
||||
const autoCollapseIndices = hud.visible
|
||||
? panelStackAutoCollapseIndices({
|
||||
naturalHeights: expandedPanels.map((panel) =>
|
||||
Math.max(
|
||||
panel.getBoundingClientRect().height,
|
||||
panel.scrollHeight || 0,
|
||||
// Other HUD variants restore automatic collapse at the start of each pass.
|
||||
// Do not immediately collapse those panels again and schedule a loop.
|
||||
const autoCollapseIndices =
|
||||
hud.visible && hud.variant === 'tactical'
|
||||
? panelStackAutoCollapseIndices({
|
||||
naturalHeights: expandedPanels.map((panel) =>
|
||||
naturalHeights.get(panel),
|
||||
),
|
||||
),
|
||||
allocatedHeights: expandedHeights,
|
||||
collapseLaterPanels: shouldFocus && hud.variant === 'tactical',
|
||||
})
|
||||
: [];
|
||||
if (autoCollapseIndices.length) {
|
||||
allocatedHeights: expandedHeights,
|
||||
collapseLaterPanels: shouldFocus && hud.variant === 'tactical',
|
||||
})
|
||||
: [];
|
||||
if (!isCollapseRetry && autoCollapseIndices.length) {
|
||||
for (const index of autoCollapseIndices) {
|
||||
const panel = expandedPanels[index];
|
||||
panel.classList.add('collapsed', 'layout-auto-collapsed');
|
||||
onCollapse(panel);
|
||||
}
|
||||
pendingCollapseRetries.add(stack);
|
||||
onRetry();
|
||||
return;
|
||||
}
|
||||
// Write-if-changed. This pass runs on the 500 ms stats cadence, and an
|
||||
// unconditional REMOVE-then-SET of an unchanged allocation is two style
|
||||
// mutations per tick on `#pp-toggles` (the one panel the measure-strip
|
||||
// above deliberately skips) — churn that reads as a genuine panel move to
|
||||
// the world-overlay host's occluder observer and defeats parked-idle
|
||||
// render savings. Only a real allocation change may touch the attribute.
|
||||
// Write-if-changed. This pass runs on the 500 ms stats cadence. Rewriting
|
||||
// an unchanged allocation wakes the world-overlay occluder observer and
|
||||
// defeats parked-idle render savings. Measurement leaves these values intact.
|
||||
expandedPanels.forEach((panel, index) => {
|
||||
const next = `${expandedHeights[index].toFixed(1)}px`;
|
||||
if (
|
||||
|
||||
Reference in new issue
Block a user