per-panel content zoom for every panel
soulseek had a zoom control wired into its own panel header, persisted under its own screens/ key. move it into the framework so every panel has it, and drop the soulseek-specific copy (its header was then identical to the default, so that goes too, along with the orphan db row). the factor lives on the LayoutPanel node rather than in its own useDashboardState key: the layout is already persisted per panel, and a separate key would seed a server row per panel on mount. absent at 1, so an untouched panel adds nothing to the stored layout. uses css zoom, not transform: scale. a transform repaints at a different size without re-laying out, so the panel keeps its 100% geometry and anything anchored or percentage-sized lands wrong — chat's composer made that obvious. zoom scales used lengths instead: children reflow, h-full still resolves to the panel, and rem-based tailwind text scales with it. @container moves onto the zoomed element so container queries respond to the effective width, the way they would in a genuinely narrower panel. zoomable: false opts out the terminals (xterm measures its own cell grid) and remote desktop (novnc does its own scaling and pointer mapping). fixes chat's virtualiser under zoom: it measured bubbles with getBoundingClientRect (rendered px) but positions them with translateY (layout px), so at 70% every bubble was placed too early and they stacked. new helpers/measure-zoomed divides the element's currentCSSZoom back out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// Panel content is scaled with CSS `zoom` (see PanelSlot), and that splits measurement into two units.
|
||||
// Layout — percentages, `transform: translateY(Npx)`, anything you write into a style — is in *layout*
|
||||
// px, which zoom leaves alone. `getBoundingClientRect()` is in *rendered* px, already multiplied by the
|
||||
// zoom. Feed one into the other and every offset is wrong by the zoom factor: at 0.7 a virtualiser
|
||||
// measures each row as 70% of the height it will actually occupy, places the next row that much too
|
||||
// early, and the list collapses into itself.
|
||||
//
|
||||
// `offsetHeight` is in layout px and would also fix it, but it rounds to whole pixels, which a
|
||||
// virtualiser accumulates into visible drift. So keep the subpixel rect and divide the zoom back out.
|
||||
// `currentCSSZoom` is the element's effective zoom including every zoomed ancestor; it is absent on
|
||||
// older engines, where zoom is not applied either, so 1 is the right fallback.
|
||||
|
||||
const effectiveZoom = (element: Element) => (element as Element & { currentCSSZoom?: number }).currentCSSZoom || 1;
|
||||
|
||||
/** An element's height in layout px — the unit its own positioning styles are written in. */
|
||||
export const measureHeight = (element: Element) => element.getBoundingClientRect().height / effectiveZoom(element);
|
||||
|
||||
/** An element's width in layout px. */
|
||||
export const measureWidth = (element: Element) => element.getBoundingClientRect().width / effectiveZoom(element);
|
||||
Reference in New Issue
Block a user