Skip to content

Commit 3423f2a

Browse files
committed
fix(studio): the Shift+drag edit popover stays on screen when the timeline sits low
EditPopover positions itself with position: fixed and viewport coordinates, but rendered inline inside the timeline's dockview panel, whose transformed overlay becomes the containing block. The popover landed relative to the panel and was clipped below the window. Portal it to document.body like the other timeline menus. Fixes #5376
1 parent bb9e507 commit 3423f2a

2 files changed

Lines changed: 38 additions & 2 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// @vitest-environment happy-dom
2+
import { act } from "react";
3+
import { createRoot } from "react-dom/client";
4+
import { afterEach, describe, expect, it } from "vitest";
5+
import { EditPopover } from "./EditModal";
6+
7+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
8+
9+
afterEach(() => {
10+
document.body.innerHTML = "";
11+
});
12+
13+
describe("EditPopover", () => {
14+
// The timeline panel's dockview overlay is transformed, which makes it the containing
15+
// block for position: fixed. Rendered inside it, the popover's viewport coordinates are
16+
// resolved against the panel, so a Shift+drag low on the page puts it out of view.
17+
it("renders on document.body, outside a transformed panel", () => {
18+
const panel = document.createElement("div");
19+
panel.style.transform = "translateZ(0)";
20+
document.body.appendChild(panel);
21+
const root = createRoot(panel);
22+
act(() =>
23+
root.render(
24+
<EditPopover rangeStart={1} rangeEnd={2} anchorX={400} anchorY={900} onClose={() => {}} />,
25+
),
26+
);
27+
28+
const textarea = document.querySelector("textarea");
29+
expect(textarea).not.toBeNull();
30+
expect(panel.contains(textarea)).toBe(false);
31+
32+
act(() => root.unmount());
33+
});
34+
});

‎packages/studio/src/player/components/EditModal.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useCallback, useMemo, useRef } from "react";
2+
import { createPortal } from "react-dom";
23
import { useMountEffect } from "../../hooks/useMountEffect";
34
import { usePlayerStore } from "../store/playerStore";
45
import { formatTime } from "../lib/time";
@@ -126,7 +127,7 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
126127
zIndex: 200,
127128
};
128129

129-
return (
130+
return createPortal(
130131
<div ref={popoverRef} style={style}>
131132
<div className="w-80 bg-neutral-900 border border-neutral-700/60 rounded-xl shadow-2xl shadow-black/40 overflow-hidden">
132133
{/* Header */}
@@ -205,6 +206,7 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
205206
</button>
206207
</div>
207208
</div>
208-
</div>
209+
</div>,
210+
document.body,
209211
);
210212
}

0 commit comments

Comments
 (0)