Skip to content

Commit aa2c2c6

Browse files
fix(studio): a timeline at 00:00 stays at 00:00 when the window resizes or the zoom is set (#4411)
The centre-anchor step ran on every scale change, including a window resize and a zoom set by a host, and from scroll 0 it always moved the view right, hiding 00:00 under the track header. A view at the start now stays there; a scrolled view still keeps its centre time.
1 parent 55bd0ce commit aa2c2c6

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// @vitest-environment happy-dom
2+
3+
import { act, createRef } from "react";
4+
import { createRoot } from "react-dom/client";
5+
import { describe, expect, it } from "vitest";
6+
import { useTimelinePlayhead } from "./useTimelinePlayhead";
7+
8+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
9+
10+
function scrollBox(scrollLeft: number) {
11+
const el = document.createElement("div");
12+
let left = scrollLeft;
13+
Object.defineProperties(el, {
14+
clientWidth: { value: 800 },
15+
scrollWidth: { value: 20_000 },
16+
scrollLeft: { get: () => left, set: (v: number) => (left = v) },
17+
});
18+
return el;
19+
}
20+
21+
function Harness({ pps, scroll }: { pps: number; scroll: HTMLDivElement }) {
22+
useTimelinePlayhead({
23+
playheadRef: createRef(),
24+
scrollRef: { current: scroll },
25+
ppsRef: { current: pps },
26+
durationRef: { current: 60 },
27+
isDragging: { current: false },
28+
currentTime: 0,
29+
zoomMode: "manual",
30+
manualZoomPercent: 100,
31+
zoomModeRef: { current: "manual" },
32+
manualZoomPercentRef: { current: 100 },
33+
fitPps: pps,
34+
fitPpsRef: { current: pps },
35+
effectiveDuration: 60,
36+
pps,
37+
timelineReady: true,
38+
elementsLength: 1,
39+
setZoomMode: () => {},
40+
setManualZoomPercent: () => {},
41+
contentOrigin: 32,
42+
});
43+
return null;
44+
}
45+
46+
function zoom(scrollLeft: number, fromPps: number, toPps: number) {
47+
const scroll = scrollBox(scrollLeft);
48+
const host = document.createElement("div");
49+
const root = createRoot(host);
50+
act(() => root.render(<Harness pps={fromPps} scroll={scroll} />));
51+
act(() => root.render(<Harness pps={toPps} scroll={scroll} />));
52+
act(() => root.unmount());
53+
return scroll.scrollLeft;
54+
}
55+
56+
describe("useTimelinePlayhead centre anchor", () => {
57+
it("keeps a view at the start at the start when the scale changes", () => {
58+
expect(zoom(0, 100, 114)).toBe(0);
59+
});
60+
61+
it("keeps the time at the viewport centre when the view is scrolled", () => {
62+
// Centre time (400 + 400 - 32) / 100 = 7.68s lands at 32 + 7.68 * 200 - 400.
63+
expect(zoom(400, 100, 200)).toBe(1168);
64+
});
65+
});

‎packages/studio/src/player/components/useTimelinePlayhead.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ export function useTimelinePlayhead({
7575
// it true and the next toolbar zoom would wrongly skip center-anchoring.
7676
const skip = skipCenterAnchorRef.current;
7777
skipCenterAnchorRef.current = false;
78-
if (!scroll || pps === prevPps || skip) return;
78+
// A view at the start stays there: a resize or a host zoom must not hide 00:00.
79+
if (!scroll || pps === prevPps || skip || scroll.scrollLeft < 1) return;
7980
const nextScrollLeft = getTimelineScrollLeftForZoomAnchor({
8081
pointerX: scroll.clientWidth / 2,
8182
currentScrollLeft: scroll.scrollLeft,

0 commit comments

Comments
 (0)