fix auto size detection

This commit is contained in:
Eduard Gert
2026-05-29 17:21:45 +02:00
parent 558769e671
commit b0d8ac6489
6 changed files with 53 additions and 13 deletions
@@ -1,6 +1,6 @@
import { useLayoutEffect, useRef } from "react";
import { Window } from "@wailsio/runtime";
import {isMacOS} from "@/lib/platform.ts";
import i18next from "@/lib/i18n";
// useAutoSizeWindow resizes the current Wails window so its height matches
// the measured height of the content element the returned ref is attached
@@ -15,17 +15,42 @@ import {isMacOS} from "@/lib/platform.ts";
// Re-measures via ResizeObserver so adding/removing content (e.g. the
// SessionAboutToExpire title swapping at countdown zero) keeps the chrome
// tight to the content with no scrollbar.
//
// Also re-measures on i18next `languageChanged`. The ResizeObserver in
// theory catches the same reflow when translated strings replace each
// other (DE/HU strings often wrap to more lines than EN), but in practice
// the observer can settle on a stale size before React's commit and the
// font's glyph metrics finish updating. An explicit double-rAF after the
// language flip guarantees the final layout is the one we measure.
export function useAutoSizeWindow<T extends HTMLElement>(width: number) {
const ref = useRef<T | null>(null);
useLayoutEffect(() => {
const el = ref.current;
if (!el) return;
let shown = false;
let raf1 = 0;
let raf2 = 0;
const apply = () => {
let h = Math.ceil(el.getBoundingClientRect().height);
h = isMacOS() ? h : h - el.getBoundingClientRect().height;
const h = Math.ceil(el.getBoundingClientRect().height);
if (h <= 0) return;
void Window.SetSize(width, h)
// Wails Window.SetSize takes the *frame* size on every platform
// (Windows: SetWindowPos, macOS: setFrame:, Linux: GTK frame).
// The OS title bar lives inside the frame, so we have to add the
// chrome height before calling SetSize, or the title bar eats
// pixels from the bottom and the rendered content gets clipped.
//
// window.outerHeight / window.innerHeight are useless here:
// WebView2 (and WKWebView) report the WebView's own outer == inner
// because the WebView itself has no chrome — the OS title bar is
// outside the WebView's window object entirely. The only way to
// recover the chrome height is to compare the OS frame height
// (Wails-side Window.Size()) against the WebView viewport
// (window.innerHeight).
void Window.Size()
.then((frame) => {
const chrome = Math.max(0, frame.height - window.innerHeight);
return Window.SetSize(width, h + chrome);
})
.then(() => {
if (shown) return;
shown = true;
@@ -34,10 +59,26 @@ export function useAutoSizeWindow<T extends HTMLElement>(width: number) {
})
.catch(() => {});
};
// Double rAF: first frame lands after React commits the new
// translated strings, second frame lands after the browser has
// recomputed layout, so apply() sees the final box.
const scheduleApply = () => {
cancelAnimationFrame(raf1);
cancelAnimationFrame(raf2);
raf1 = requestAnimationFrame(() => {
raf2 = requestAnimationFrame(apply);
});
};
apply();
const ro = new ResizeObserver(apply);
ro.observe(el);
return () => ro.disconnect();
i18next.on("languageChanged", scheduleApply);
return () => {
ro.disconnect();
cancelAnimationFrame(raf1);
cancelAnimationFrame(raf2);
i18next.off("languageChanged", scheduleApply);
};
}, [width]);
return ref;
}