diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx
index b4d3d351..b2a97a1e 100644
--- a/src/components/video-editor/timeline/TimelineEditor.tsx
+++ b/src/components/video-editor/timeline/TimelineEditor.tsx
@@ -60,6 +60,10 @@ import Item from "./Item";
import KeyframeMarkers from "./KeyframeMarkers";
import Row from "./Row";
import TimelineWrapper from "./TimelineWrapper";
+import {
+ getTimelineContentMinHeightPx,
+ getTimelineRowsMinHeightPx,
+} from "./timelineLayout";
import { type AudioPeaksData, useAudioPeaks } from "./useAudioPeaks";
import { buildInteractionZoomSuggestions } from "./zoomSuggestionUtils";
@@ -702,12 +706,18 @@ function Timeline({
).sort((left, right) => getAnnotationTrackIndex(left) - getAnnotationTrackIndex(right)),
[annotationItems],
);
+ const timelineRowCount = 2 + annotationRowIds.length + audioRowIds.length;
+ const timelineRowsMinHeightPx = getTimelineRowsMinHeightPx(timelineRowCount);
+ const timelineContentMinHeightPx = getTimelineContentMinHeightPx(timelineRowCount);
return (
@@ -720,7 +730,10 @@ function Timeline({
keyframes={keyframes}
/>
-
+
{audioPeaks && }
diff --git a/src/components/video-editor/timeline/TimelineWrapper.tsx b/src/components/video-editor/timeline/TimelineWrapper.tsx
index 32a94565..fed0431c 100644
--- a/src/components/video-editor/timeline/TimelineWrapper.tsx
+++ b/src/components/video-editor/timeline/TimelineWrapper.tsx
@@ -375,7 +375,7 @@ export default function TimelineWrapper({
autoScroll={{ enabled: false }}
resizeHandleWidth={28}
>
-
+
{children}
{/* Floating tooltip shown during drag/resize */}
{
+ it("reserves vertical space for every rendered timeline row", () => {
+ expect(getTimelineRowsMinHeightPx(5)).toBe(5 * TIMELINE_ROW_MIN_HEIGHT_PX);
+ expect(getTimelineContentMinHeightPx(5)).toBe(
+ TIMELINE_AXIS_HEIGHT_PX + 5 * TIMELINE_ROW_MIN_HEIGHT_PX,
+ );
+ });
+
+ it("ignores invalid row counts", () => {
+ expect(getTimelineRowsMinHeightPx(-1)).toBe(0);
+ expect(getTimelineRowsMinHeightPx(Number.NaN)).toBe(0);
+ expect(getTimelineContentMinHeightPx(Number.POSITIVE_INFINITY)).toBe(
+ TIMELINE_AXIS_HEIGHT_PX,
+ );
+ });
+
+ it("floors fractional row counts", () => {
+ expect(getTimelineRowsMinHeightPx(2.9)).toBe(2 * TIMELINE_ROW_MIN_HEIGHT_PX);
+ expect(getTimelineContentMinHeightPx(2.9)).toBe(
+ TIMELINE_AXIS_HEIGHT_PX + 2 * TIMELINE_ROW_MIN_HEIGHT_PX,
+ );
+ });
+});
diff --git a/src/components/video-editor/timeline/timelineLayout.ts b/src/components/video-editor/timeline/timelineLayout.ts
new file mode 100644
index 00000000..bdf03a17
--- /dev/null
+++ b/src/components/video-editor/timeline/timelineLayout.ts
@@ -0,0 +1,18 @@
+export const TIMELINE_AXIS_HEIGHT_PX = 32;
+export const TIMELINE_ROW_MIN_HEIGHT_PX = 28;
+
+function normalizeRowCount(rowCount: number) {
+ if (!Number.isFinite(rowCount)) {
+ return 0;
+ }
+
+ return Math.max(0, Math.floor(rowCount));
+}
+
+export function getTimelineRowsMinHeightPx(rowCount: number) {
+ return normalizeRowCount(rowCount) * TIMELINE_ROW_MIN_HEIGHT_PX;
+}
+
+export function getTimelineContentMinHeightPx(rowCount: number) {
+ return TIMELINE_AXIS_HEIGHT_PX + getTimelineRowsMinHeightPx(rowCount);
+}