fix(web): Editor-Layout ab 1024 px und Scroll-Reset beim Seitenwechsel

Editor: Seitenspalten geben nach (minmax), die Vorschau bleibt ab 1024 px
mindestens 360 px breit, einspaltig erst unter 1024 px. Router: navigate()
springt nach dem Rendern nach oben, Dokumenttitel je Route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-04 10:55:56 +00:00
1 parent 09832c2cf4
commit 09de06e797
3 files changed
+83 -5

No files matched your search

+47
View File
@@ -125,6 +125,53 @@ describe('startRouter', () => {
expect(location.pathname).toBe('/');
});
describe('Scrollposition und Titel', () => {
it('navigate springt nach dem Rendern an den Seitenanfang', () => {
const order: string[] = [];
const scrollTo = vi.spyOn(window, 'scrollTo').mockImplementation(() => order.push('scroll'));
try {
startRouter(() => {
order.push('render');
});
order.length = 0;
navigate('/karten');
expect(scrollTo).toHaveBeenCalledTimes(1);
expect(scrollTo).toHaveBeenCalledWith(0, 0);
expect(order).toEqual(['render', 'scroll']);
} finally {
scrollTo.mockRestore();
}
});
it('beim Start und bei popstate wird nicht gescrollt (das übernimmt der Browser)', () => {
const scrollTo = vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
try {
startRouter(vi.fn());
history.pushState(null, '', '/karten');
window.dispatchEvent(new PopStateEvent('popstate'));
expect(scrollTo).not.toHaveBeenCalled();
} finally {
scrollTo.mockRestore();
}
});
it('setzt den Dokumenttitel je Route', () => {
vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
try {
startRouter(vi.fn());
expect(document.title).toBe('AirCard-IOS – Kartenmotiv gestalten');
navigate('/karten');
expect(document.title).toBe('Meine Karten – AirCard-IOS');
navigate('/passwort-neu');
expect(document.title).toBe('Passwort neu setzen – AirCard-IOS');
navigate('/');
expect(document.title).toBe('AirCard-IOS – Kartenmotiv gestalten');
} finally {
vi.restoreAllMocks();
}
});
});
it('rendert bei popstate die dann aktuelle Route', () => {
const render = vi.fn();
startRouter(render);
+11 -1
View File
@@ -9,6 +9,12 @@ export function matchRoute(pathname: string): RouteId {
return 'editor';
}
const TITLES: Record<RouteId, string> = {
editor: 'AirCard-IOS – Kartenmotiv gestalten',
cards: 'Meine Karten – AirCard-IOS',
reset: 'Passwort neu setzen – AirCard-IOS',
};
// Beendet den laufenden Router (Seiten-Cleanup, Listener); null, solange keiner läuft.
let stopCurrent: (() => void) | null = null;
let renderCurrent: (() => void) | null = null;
@@ -20,6 +26,8 @@ export function navigate(path: string): void {
history.pushState(null, '', target);
}
renderCurrent?.();
// Eine neue Seite beginnt oben (pushState setzt die Scrollposition nicht zurück); bei popstate bleibt es dem Browser überlassen.
if (renderCurrent) window.scrollTo(0, 0);
}
export function startRouter(render: Render): void {
@@ -33,7 +41,9 @@ export function startRouter(render: Render): void {
const renderPage = () => {
cleanup?.();
cleanup = undefined;
cleanup = render(matchRoute(location.pathname), root);
const route = matchRoute(location.pathname);
document.title = TITLES[route];
cleanup = render(route, root);
};
const onClick = (event: MouseEvent) => {