From 9ee37e4015002d7842c2ad714e32099c98be5156 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Tue, 10 Nov 2026 09:03:19 -0700 Subject: [PATCH] Show a dragged column's drop line at every boundary The line was hidden while a drop would leave the column where it is, so a drag showed nothing until it crossed a neighbour's middle. It now shows from the first movement, on the nearer edge of the column's own slot when the drop would change nothing, and at the first boundary over the mark column. The choice of boundary moves to a tested function in the columns module. Co-Authored-By: Claude Opus 5.5 --- apps/yaak-proxy/components/FlowListHeader.tsx | 17 +++++++------- apps/yaak-proxy/lib/columns.test.ts | 22 +++++++++++++++++++ apps/yaak-proxy/lib/columns.ts | 19 ++++++++++++++++ 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/apps/yaak-proxy/components/FlowListHeader.tsx b/apps/yaak-proxy/components/FlowListHeader.tsx index 1e002ab48..a232080f8 100644 --- a/apps/yaak-proxy/components/FlowListHeader.tsx +++ b/apps/yaak-proxy/components/FlowListHeader.tsx @@ -38,6 +38,7 @@ import { type ColumnPrefs, type CustomColumnId, canHide, + dropBoundary, hideOtherColumns, isCustomColumn, moveColumn, @@ -409,7 +410,7 @@ function useColumnDrag({ id: ColumnId; /** The pointer, across the window */ x: number; - /** The boundary it would drop at, and that boundary's line, or null where it would not move */ + /** The boundary it would move to, null where it would stay, and the line drawn on it */ to: number | null; line: number | null; frame: number; @@ -433,6 +434,8 @@ function useColumnDrag({ if (column == null) return; state.current = { id: column.id, x: pointer.current, to: null, line: null, frame: 0 }; setDragging(column); + // The line shows from the drag's first movement, not a frame later + target(state.current); const tick = () => { const s = state.current; @@ -511,18 +514,14 @@ function useColumnDrag({ /** Sets where the drag would drop, from the pointer and the labels where they are now */ function target(s: NonNullable) { - // The boundary before the first label whose middle is right of the pointer const cells = Array.from( gridRef.current?.querySelectorAll(":scope > [data-column]") ?? [], ).map((el) => el.getBoundingClientRect()); - let to = cells.findIndex((r) => s.x < r.left + r.width / 2); - if (to === -1) to = cells.length; - // Where it already is, it would not move, so no line. Its place is read each time, as the - // columns can change under a drag: Operation shows itself when a flow has one. + // Its place is read each time, as the columns can change under a drag: Operation shows + // itself when a flow has one. const from = latest.current.columns.findIndex((c) => c.id === s.id); - const moves = from !== -1 && to !== from && to !== from + 1; - s.to = moves ? to : null; - const line = moves ? (cells[to]?.left ?? cells.at(-1)?.right ?? null) : null; + const { to, line } = dropBoundary(s.x, cells, from); + s.to = to; // Told again only when it changes: another boundary, or the same one scrolled if (line !== s.line) latest.current.onDropLine(line); s.line = line; diff --git a/apps/yaak-proxy/lib/columns.test.ts b/apps/yaak-proxy/lib/columns.test.ts index 446b3c71c..cafcb4b3c 100644 --- a/apps/yaak-proxy/lib/columns.test.ts +++ b/apps/yaak-proxy/lib/columns.test.ts @@ -11,6 +11,7 @@ import { customPathProblem, customValue, defaultColumnPrefs, + dropBoundary, fieldReference, fieldReferenceChanges, gridTemplate, @@ -322,6 +323,27 @@ describe("column order", () => { expect(parseColumnPrefs({ order: "time" }).order).toEqual([]); }); + test("the drop line shows at every boundary, its own slot's edges included", () => { + // Four columns 100px wide from x 24, after the mark column; the third is dragged + const cells = [24, 124, 224, 324].map((left) => ({ left, right: left + 100 })); + const at = (x: number) => dropBoundary(x, cells, 2); + // Over the mark column or the first's left half: the first boundary + expect(at(5)).toEqual({ to: 0, line: 24 }); + expect(at(60)).toEqual({ to: 0, line: 24 }); + expect(at(80)).toEqual({ to: 1, line: 124 }); + // The left neighbour's near half and its own left half: its left edge, staying + expect(at(190)).toEqual({ to: null, line: 224 }); + expect(at(250)).toEqual({ to: null, line: 224 }); + // Its own right half and the right neighbour's near half: its right edge, staying + expect(at(290)).toEqual({ to: null, line: 324 }); + expect(at(350)).toEqual({ to: null, line: 324 }); + // Past the right neighbour's middle, and past the end + expect(at(380)).toEqual({ to: 4, line: 424 }); + expect(at(900)).toEqual({ to: 4, line: 424 }); + // The last column dragged to the end stays + expect(dropBoundary(900, cells, 3)).toEqual({ to: null, line: 424 }); + }); + test("Reset Column Order puts every column back", () => { let prefs = moveColumn(defaultColumnPrefs, "time", 0); prefs = toggleColumn(prefs, "latency"); diff --git a/apps/yaak-proxy/lib/columns.ts b/apps/yaak-proxy/lib/columns.ts index e55e2b4a2..693cd9ee4 100644 --- a/apps/yaak-proxy/lib/columns.ts +++ b/apps/yaak-proxy/lib/columns.ts @@ -298,6 +298,25 @@ export function resetOrder(prefs: ColumnPrefs): ColumnPrefs { return { ...prefs, order: [] }; } +/** + * Where a dragged column would drop, from the pointer and the shown columns' edges, left to + * right: the boundary before the first column whose middle is right of the pointer, and the + * line's place on it. Over its own column or the near half of a neighbour, that is an edge of + * its own slot, where it would stay, so `to` is null; the line still shows there. Left of the + * first column, over the fixed mark column, it is the first boundary. + */ +export function dropBoundary( + x: number, + cells: { left: number; right: number }[], + from: number, +): { to: number | null; line: number | null } { + let at = cells.findIndex((r) => x < (r.left + r.right) / 2); + if (at === -1) at = cells.length; + const line = cells[at]?.left ?? cells.at(-1)?.right ?? null; + const stays = from === -1 || at === from || at === from + 1; + return { to: stays ? null : at, line }; +} + /** Shows `id` alone */ export function hideOtherColumns(prefs: ColumnPrefs, id: ColumnId): ColumnPrefs { const visible: ColumnPrefs["visible"] = {};