clamp dropped images to grid bounds instead of wrapping to 0,0
Hunter
Wednesday, July 29th, 2026 at 3:17 PM
79 imageDataArray.push({ idx, ...data });
80 }
81
82 + // Shift the whole fan back onto the grid before placing anything, so a drop near an edge keeps
83 + // its diagonal spacing instead of collapsing image by image
84 + const fanMaxX = Math.max(...imageDataArray.map(({ idx, widthCells }) => baseXCell + idx + widthCells));
85 + const fanMaxY = Math.max(...imageDataArray.map(({ idx, heightCells }) => baseYCell + idx + heightCells));
86 + const fanShiftX = Math.max(Math.min(0, GRID_COLS - fanMaxX), -baseXCell);
87 + const fanShiftY = Math.max(Math.min(0, GRID_ROWS - fanMaxY), -baseYCell);
88 +
89 let wrappedOffset = 0;
90 + const placed = [];
91
92 for (const { idx, dataUrl, widthCells, heightCells, naturalWidth, naturalHeight } of imageDataArray) {
- // Calculate position with diagonal offset
- let xCell = baseXCell + idx;
- let yCell = baseYCell + idx;
-
- // If out of bounds or would overlap with wrapped images, wrap to next diagonal position
- const outOfBounds = xCell < 0 || yCell < 0 ||
- xCell + widthCells > GRID_COLS ||
- yCell + heightCells > GRID_ROWS;
- const overlapsWrapped = xCell < wrappedOffset || yCell < wrappedOffset;
-
- if (outOfBounds || overlapsWrapped) {
- xCell = wrappedOffset;
- yCell = wrappedOffset;
93 + // Calculate position with diagonal offset, kept as close to the drop point as the grid allows
94 + const maxXCell = GRID_COLS - widthCells;
95 + const maxYCell = GRID_ROWS - heightCells;
96 + let xCell = Math.max(0, Math.min(baseXCell + idx + fanShiftX, maxXCell));
97 + let yCell = Math.max(0, Math.min(baseYCell + idx + fanShiftY, maxYCell));
98 +
99 + // Clamping can pile a multi-image drop onto one cell; cascade those from the top-left instead
100 + const stacked = placed.some(p => p.xCell === xCell && p.yCell === yCell);
101 + if (stacked) {
102 + xCell = Math.min(wrappedOffset, maxXCell);
103 + yCell = Math.min(wrappedOffset, maxYCell);
104 wrappedOffset++;
105 }
106 + placed.push({ xCell, yCell });
107
108 const imageData = addImage(dataUrl, xCell, yCell, widthCells, heightCells, { naturalWidth, naturalHeight });
109 imageData.container.style.zIndex = baseZIndex + idx;
26 return;
27 }
28
- // Use last known mouse position relative to grid, fall back to (0, 0)
29 + // Use last known mouse position relative to grid; off-grid positions are clamped downstream,
30 + // so only a cursor we've never seen falls back to (0, 0)
31 const gridRect = grid.getBoundingClientRect();
- const mouseXInGrid = lastMouseX - gridRect.left;
- const mouseYInGrid = lastMouseY - gridRect.top;
- const isOnGrid = mouseXInGrid >= 0 && mouseYInGrid >= 0 &&
- mouseXInGrid <= gridRect.width && mouseYInGrid <= gridRect.height;
32 + const hasCursor = lastMouseX >= 0 && lastMouseY >= 0;
33
- const dropX = isOnGrid ? mouseXInGrid : 0;
- const dropY = isOnGrid ? mouseYInGrid : 0;
34 + const dropX = hasCursor ? lastMouseX - gridRect.left : 0;
35 + const dropY = hasCursor ? lastMouseY - gridRect.top : 0;
36
37 await processAndAddImages(imageFiles, dropX, dropY);
38 });