/* ============================================= HELLION NEWTAB — drag.js Drag & Drop via Pointer Events Boards: Reihenfolge per Handle Bookmarks: Reihenfolge innerhalb eines Boards ============================================= */ // ---- BOARD FREE-MOVE (Pointer Events) ---- // Neugebaut fuer v2.3 (frueher Reorder mit Ghost/Placeholder). Vorbild: // widgets.js _initDrag — setPointerCapture, offX/offY, onMove mit Clamping // gegen window.innerWidth/Height, onUp schreibt board.pos + saveBoards(). // Gebunden am .board-drag-handle, NICHT am ganzen .board, damit Bookmark-Drag, // Klick-Delegation und Action-Buttons frei bleiben. function initBoardDragDrop() { const wrapper = document.getElementById('boardsWrapper'); wrapper.querySelectorAll('.board').forEach(boardEl => { const handle = boardEl.querySelector('.board-drag-handle'); if (!handle) return; handle.addEventListener('pointerdown', function onDown(e) { // Auf Mobil ist .board position:static (Stapel) -> kein Free-Move. if (getComputedStyle(boardEl).position !== 'absolute') return; e.preventDefault(); handle.setPointerCapture(e.pointerId); // .board.dragging hebt das Board per CSS nach vorne (z-index) UND ist das Signal fuer den // Live-Sync-Guard in app.js (bindStorageSync verwirft ein onChanged-Re-Render, das diesen // Drag sonst abreissen wuerde). Der Guard prueft genau diese Klasse (Phase-4-Review 2a). boardEl.classList.add('dragging'); const rect = boardEl.getBoundingClientRect(); const offX = e.clientX - rect.left; const offY = e.clientY - rect.top; function onMove(ev) { const maxX = window.innerWidth - boardEl.offsetWidth; const maxY = window.innerHeight - boardEl.offsetHeight; const x = Math.max(0, Math.min(maxX, ev.clientX - offX)); const y = Math.max(48, Math.min(maxY, ev.clientY - offY)); // 48px = Header-Hoehe boardEl.style.setProperty('--board-x', x + 'px'); boardEl.style.setProperty('--board-y', y + 'px'); } async function onUp() { handle.releasePointerCapture(e.pointerId); handle.removeEventListener('pointermove', onMove); handle.removeEventListener('pointerup', onUp); boardEl.classList.remove('dragging'); // z-index zurueck + Live-Sync-Guard freigeben const id = boardEl.dataset.boardId; const board = boards.find(b => b.id === id); if (board) { board.pos = { x: parseFloat(boardEl.style.getPropertyValue('--board-x')), y: parseFloat(boardEl.style.getPropertyValue('--board-y')) }; await saveBoards(); } } handle.addEventListener('pointermove', onMove); handle.addEventListener('pointerup', onUp); }); }); } // ---- BOOKMARK DRAG (innerhalb eines Boards) ---- function initBookmarkDragDrop(listEl, board) { let dragSrcBmId = null; listEl.addEventListener('dragstart', e => { const item = e.target.closest('.bm-item'); if (!item) return; dragSrcBmId = item.dataset.bmId; e.dataTransfer.effectAllowed = 'move'; setTimeout(() => item.classList.add('dragging-source'), 0); }); listEl.addEventListener('dragend', e => { const item = e.target.closest('.bm-item'); if (item) item.classList.remove('dragging-source'); }); listEl.addEventListener('dragover', e => { e.preventDefault(); const item = e.target.closest('.bm-item'); if (item) item.classList.add('drag-over'); }); listEl.addEventListener('dragleave', e => { const item = e.target.closest('.bm-item'); if (item) item.classList.remove('drag-over'); }); listEl.addEventListener('drop', async e => { e.preventDefault(); e.stopPropagation(); const item = e.target.closest('.bm-item'); if (!item) return; item.classList.remove('drag-over'); const targetBmId = item.dataset.bmId; if (!dragSrcBmId || dragSrcBmId === targetBmId) return; const srcIdx = board.bookmarks.findIndex(b => b.id === dragSrcBmId); const tgtIdx = board.bookmarks.findIndex(b => b.id === targetBmId); const [moved] = board.bookmarks.splice(srcIdx, 1); board.bookmarks.splice(tgtIdx, 0, moved); await saveBoards(); renderBoards(); }); }