diff options
Diffstat (limited to 'src/features/workspaces/logic')
| -rw-r--r-- | src/features/workspaces/logic/tile-layout.test.ts | 17 | ||||
| -rw-r--r-- | src/features/workspaces/logic/tile-layout.ts | 19 |
2 files changed, 24 insertions, 12 deletions
diff --git a/src/features/workspaces/logic/tile-layout.test.ts b/src/features/workspaces/logic/tile-layout.test.ts index 81156e7..a5d58c0 100644 --- a/src/features/workspaces/logic/tile-layout.test.ts +++ b/src/features/workspaces/logic/tile-layout.test.ts @@ -350,11 +350,12 @@ describe("displacePlace", () => { { id: "Y", col: 2, row: 1, colSpan: 1, rowSpan: 1 }, ]; const next = displacePlace(placements, "Y", { col: 1, row: 1 }, WIDE); - // Y@1; X (was@1) pushed into the gap at 2; X2 (was@3) stays. + // Y@1; X (was@1) pushed into the gap at 2; X2 (was@3) stays. The array + // keeps the INPUT render order (only coordinates change). expect(next.map((p) => [p.id, p.col, p.row])).toEqual([ - ["Y", 1, 1], ["X", 1, 2], ["X2", 1, 3], + ["Y", 1, 1], ]); }); @@ -366,10 +367,11 @@ describe("displacePlace", () => { { id: "Y", col: 2, row: 1, colSpan: 1, rowSpan: 1 }, ]; const next = displacePlace(placements, "Y", { col: 1, row: 2 }, WIDE); + // Same coordinates, input render order: X, Z, then the dragged Y. expect(next.map((p) => [p.id, p.col, p.row])).toEqual([ - ["Y", 1, 2], ["X", 1, 3], ["Z", 1, 4], + ["Y", 1, 2], ]); }); @@ -380,9 +382,10 @@ describe("displacePlace", () => { ]; // Drag a (cell 1) to cell 3 — b stays at 2; a lands at 3. const next = displacePlace(placements, "a", { col: 1, row: 3 }, WIDE); + // The array keeps the input render order (a first) — only a's coords move. expect(next.map((p) => [p.id, p.col, p.row])).toEqual([ - ["b", 1, 2], ["a", 1, 3], + ["b", 1, 2], ]); }); @@ -432,12 +435,12 @@ describe("displacePlace", () => { { id: "d", col: 1, row: 4, colSpan: 1, rowSpan: 1 }, ]; const next = displacePlace(placements, "a", { col: 1, row: 4 }, WIDE); - // d is pushed past the column's end → column 2 row 1. Stream order: - // b(1) c(2) a(3) d(4). + // d is pushed past the column's end → column 2 row 1. The array keeps the + // INPUT render order (a,b,c,d) — only coordinates change. expect(next.map((p) => [p.id, p.col, p.row])).toEqual([ + ["a", 1, 4], ["b", 1, 2], ["c", 1, 3], - ["a", 1, 4], ["d", 2, 1], ]); }); diff --git a/src/features/workspaces/logic/tile-layout.ts b/src/features/workspaces/logic/tile-layout.ts index 4b5c89c..c4720d3 100644 --- a/src/features/workspaces/logic/tile-layout.ts +++ b/src/features/workspaces/logic/tile-layout.ts @@ -413,7 +413,9 @@ function firstFitFrom( * one; the displaced re-anchor at the next fitting slot forward (spans ride * along; a span that can't fit the fixed cross-axis anywhere forward wraps to * the first fitting slot — it must land somewhere). Pure: placements + drop - * in, a NEW placements array out (stream order). + * in, a NEW placements array out — in the INPUT array order (only coordinates + * change). A keyed `{#each}` over the result therefore never moves DOM nodes; + * moving a captured element drops its pointer capture — the "stuck tile" bug. */ export function displacePlace( placements: readonly Placement[], @@ -447,9 +449,12 @@ export function displacePlace( occupy(occupied, slot.col, slot.row, colSpan, rowSpan); placed.push({ ...placement, col: slot.col, row: slot.row }); } - return placed.sort( - (a, b) => fillOrderKey(a.col, a.row, geometry) - fillOrderKey(b.col, b.row, geometry), - ); + // Return in the INPUT render order (coordinates change only): the live drag + // preview feeds a keyed `{#each}`, and a reordered array moves the DOM nodes + // — a moved captured element silently loses pointer capture (the stuck-tile + // bug). The commit path re-sorts to stream order itself (`placeAtCell`). + const byId = new Map(placed.map((placement) => [placement.id, placement])); + return placements.map((placement) => byId.get(placement.id) ?? placement); } /** @@ -468,7 +473,11 @@ export function placeAtCell( // An unknown dragged id changes nothing (order-sorted copy — the op's // canonical no-op shape, like `moveTile` before it). if (!tiles.some((tile) => tile.id === id)) return sortTiles(tiles); - const displaced = displacePlace(pack(tiles, geometry), id, cell, geometry); + // displacePlace preserves the input RENDER order for the live preview; the + // commit pins + renumbers in STREAM order, so re-sort before pinning. + const displaced = displacePlace(pack(tiles, geometry), id, cell, geometry) + .slice() + .sort((a, b) => fillOrderKey(a.col, a.row, geometry) - fillOrderKey(b.col, b.row, geometry)); const result: TileLayoutEntry[] = []; for (const placement of displaced) { const entry = tiles.find((tile) => tile.id === placement.id); |
