summaryrefslogtreecommitdiffhomepage
path: root/src/features/workspaces/logic
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/workspaces/logic')
-rw-r--r--src/features/workspaces/logic/tile-layout.test.ts17
-rw-r--r--src/features/workspaces/logic/tile-layout.ts19
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);