diff --git a/app/javascript/controllers/dashboard_sortable_controller.js b/app/javascript/controllers/dashboard_sortable_controller.js index 31ba4725b..2adac0f3e 100644 --- a/app/javascript/controllers/dashboard_sortable_controller.js +++ b/app/javascript/controllers/dashboard_sortable_controller.js @@ -259,31 +259,56 @@ export default class extends Controller { } getDragAfterElement(pointerX, pointerY) { - const draggableElements = this.sectionTargets.filter( + const siblings = this.sectionTargets.filter( (section) => section !== this.draggedElement, ); - if (draggableElements.length === 0) return null; + if (siblings.length === 0) return null; - let closest = null; - let minDistance = Number.POSITIVE_INFINITY; + // On 2xl grid (2 columns), filter to sections in the same column as pointer + const column = this.getSameColumnSiblings(siblings, pointerX); - draggableElements.forEach((child) => { - const rect = child.getBoundingClientRect(); - const centerX = rect.left + rect.width / 2; - const centerY = rect.top + rect.height / 2; + // Walk top-to-bottom through gaps between sections. + // Return value is passed to insertBefore(), so we return the element + // the dragged section should be placed IN FRONT OF, or null for end. + for (let i = 0; i < column.length; i++) { + const rect = column[i].getBoundingClientRect(); - const dx = pointerX - centerX; - const dy = pointerY - centerY; - const distance = Math.sqrt(dx * dx + dy * dy); - - if (distance < minDistance) { - minDistance = distance; - closest = child; + // Pointer is above the first section — insert before it + if (i === 0 && pointerY < rect.top) { + return column[0]; } - }); - return closest; + // Crossing line = midpoint of gap between this section and the next + if (i < column.length - 1) { + const nextRect = column[i + 1].getBoundingClientRect(); + const crossingLine = (rect.bottom + nextRect.top) / 2; + + // Pointer is above the crossing line — it belongs before the next section + if (pointerY < crossingLine) return column[i + 1]; + } + } + + // Pointer is below all crossing lines — append to end + return null; + } + + getSameColumnSiblings(siblings, pointerX) { + if (siblings.length <= 1) return siblings; + + // Check if we're in a multi-column layout by comparing left positions + const firstRect = siblings[0].getBoundingClientRect(); + const hasMultipleColumns = siblings.some( + (s) => Math.abs(s.getBoundingClientRect().left - firstRect.left) > 50, + ); + + if (!hasMultipleColumns) return siblings; + + // Filter to siblings in the same column as the pointer + return siblings.filter((s) => { + const rect = s.getBoundingClientRect(); + return pointerX >= rect.left && pointerX <= rect.right; + }); } showPlaceholder(element, position) {