import { Injectable, signal } from '@angular/core'; interface IndicatorRect { top: number; left: number; width: number; height?: number; mode: 'horizontal' | 'vertical'; // horizontal = change line, vertical = change column position?: 'left' | 'right'; // for vertical mode } @Injectable({ providedIn: 'root' }) export class DragDropService { readonly dragging = signal(false); readonly sourceId = signal(null); readonly fromIndex = signal(-1); readonly overIndex = signal(-1); readonly indicator = signal(null); readonly dropMode = signal<'line' | 'column-left' | 'column-right'>('line'); private containerEl: HTMLElement | null = null; private startY = 0; private moved = false; setContainer(el: HTMLElement) { this.containerEl = el; } isMoved() { return this.moved; } beginDrag(id: string, index: number, clientY: number) { this.sourceId.set(id); this.fromIndex.set(index); this.dragging.set(true); this.startY = clientY; this.moved = false; } updatePointer(clientY: number, clientX?: number) { if (!this.dragging()) return; if (Math.abs(clientY - this.startY) > 3) this.moved = true; this.computeOverIndex(clientY, clientX); } endDrag() { const result = { from: this.fromIndex(), to: this.overIndex(), mode: this.dropMode() }; this.dragging.set(false); this.sourceId.set(null); this.fromIndex.set(-1); this.overIndex.set(-1); this.indicator.set(null); this.dropMode.set('line'); this.startY = 0; const moved = this.moved; this.moved = false; return { ...result, moved }; } private computeOverIndex(clientY: number, clientX?: number) { if (!this.containerEl) return; const nodes = Array.from(this.containerEl.querySelectorAll('.block-wrapper')); if (nodes.length === 0) return; let targetIndex = 0; let indicatorTop = 0; const containerRect = this.containerEl.getBoundingClientRect(); let mode: 'horizontal' | 'vertical' = 'horizontal'; let position: 'left' | 'right' | undefined = undefined; // Check if hovering near left or right edge of a block (for column mode) if (clientX !== undefined) { for (let i = 0; i < nodes.length; i++) { const r = nodes[i].getBoundingClientRect(); const isHoveringBlock = clientY >= r.top && clientY <= r.bottom; if (isHoveringBlock) { const relativeX = clientX - r.left; const edgeThreshold = 100; // pixels from edge to trigger column mode (increased for better detection) if (relativeX < edgeThreshold) { // Near left edge - create column on left mode = 'vertical'; position = 'left'; targetIndex = i; this.dropMode.set('column-left'); this.overIndex.set(targetIndex); this.indicator.set({ top: r.top - containerRect.top, left: r.left - containerRect.left - 2, // Offset for better visibility width: 4, height: r.height, mode: 'vertical', position: 'left' }); return; } else if (relativeX > r.width - edgeThreshold) { // Near right edge - create column on right mode = 'vertical'; position = 'right'; targetIndex = i; this.dropMode.set('column-right'); this.overIndex.set(targetIndex); this.indicator.set({ top: r.top - containerRect.top, left: r.right - containerRect.left - 2, // Offset for better visibility width: 4, height: r.height, mode: 'vertical', position: 'right' }); return; } } } } // Default horizontal mode (line change) - improved detection this.dropMode.set('line'); // Find which block we're hovering over or between let found = false; for (let i = 0; i < nodes.length; i++) { const r = nodes[i].getBoundingClientRect(); // Define drop zones: top half = insert before, bottom half = insert after const dropZoneHeight = r.height / 2; const topZoneEnd = r.top + dropZoneHeight; if (clientY <= topZoneEnd) { // Insert BEFORE this block targetIndex = i; indicatorTop = r.top - containerRect.top; found = true; break; } else if (clientY <= r.bottom) { // Insert AFTER this block targetIndex = i + 1; indicatorTop = r.bottom - containerRect.top; found = true; break; } } // If cursor is below all blocks, insert at end if (!found && nodes.length > 0) { targetIndex = nodes.length; const lastRect = nodes[nodes.length - 1].getBoundingClientRect(); indicatorTop = lastRect.bottom - containerRect.top; } this.overIndex.set(targetIndex); this.indicator.set({ top: indicatorTop, left: 0, width: containerRect.width, mode: 'horizontal' }); } }