fix(ui): reorder dragged tabs live with a placeholder; drop terminal tabs on a group to join or split (#1054)

Dragging a tab showed no floating copy: the ghost cloned the tab's classes, and .tab { position: relative } loaded after .ghost { position: fixed }. The ghost is now positioned inline, and a tab dragged within its strip stays as an empty slot the other tabs move around; the insertion line for tabs from another strip is drawn inside the edge tabs so the scroller doesn't clip it. A terminal group is now a drop target like an editor pane: the middle joins the group, an edge splits off a new group (dropZoneAt moves to ui and is shared).

Closes #1052

Co-authored-by: fuqiuluo <[email protected]>
Цей коміт міститься в:
fuqiuluoтаfuqiuluo authored and GitHub committed 2026-11-04 00:41:57 +08:00
1 джерело e20eb1acf8
коміт 2b20c0f38e
11 files changed
+423 -55

No files matched your search

+12 -1
Переглянути файл
@@ -1,8 +1,19 @@
import { describe, expect, it } from "vitest";
import { isOutsideWindow } from "./pointerDrag";
import { dropZoneAt, isOutsideWindow } from "./pointerDrag";
const viewport = { left: 0, top: 0, right: 1280, bottom: 800 };
describe("dropZoneAt", () => {
const rect = { left: 100, top: 100, width: 400, height: 200 };
it("picks the nearest edge within its outer quarter, else the middle", () => {
expect(dropZoneAt(300, 200, rect)).toBe("center");
expect(dropZoneAt(110, 200, rect)).toBe("left");
expect(dropZoneAt(490, 200, rect)).toBe("right");
expect(dropZoneAt(300, 110, rect)).toBe("top");
expect(dropZoneAt(300, 290, rect)).toBe("bottom");
});
});
describe("isOutsideWindow", () => {
it("treats the viewport interior as inside (issue #454: drag a tab out)", () => {
expect(isOutsideWindow(viewport, 640, 400)).toBe(false);