From 8401535900535acae0aca8448a2a2248f445e3bb Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Sun, 14 Jun 2026 14:58:08 +0200 Subject: [PATCH] Freies Layout: Board-Drag als Free-Move neu (widgets.js-Vorbild), .board.dragging auf z-index umgewidmet, Reorder-CSS (placeholder/ghost) raus --- src/css/main.css | 15 +------ src/js/drag.js | 112 +++++++++++++++++------------------------------ 2 files changed, 41 insertions(+), 86 deletions(-) diff --git a/src/css/main.css b/src/css/main.css index dd370f5..d31ead4 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -893,14 +893,8 @@ body.show-desc .bm-desc { display: block; } .modal-body { padding: 14px 16px; } .modal-footer { padding: 10px 16px 14px; display: flex; justify-content: flex-end; } -.board.dragging { opacity: 0.35; } - -.board-placeholder { - border: 2px dashed var(--border-accent); - border-radius: var(--radius); - background: var(--accent-dim); - flex-shrink: 0; -} +/* Free-Move: das gezogene Board nach vorne heben (frueher Reorder-Opacity). */ +.board.dragging { z-index: 50; cursor: grabbing; } } @@ -2484,11 +2478,6 @@ body.show-desc .bm-desc { display: block; } .hidden { display: none; } .accent-text { color: var(--accent); } .dim { opacity: 0.4; } -.drag-ghost { - position: fixed; opacity: 0.75; pointer-events: none; z-index: 9999; - transform: rotate(1.5deg) scale(1.02); - box-shadow: 0 12px 40px rgba(0,0,0,0.6); -} .bm-item.drag-over { background: rgba(255,160,50,0.07); } .bm-item.dragging-source { opacity: 0.4; } .about-info-label-block { display: block; margin-bottom: 6px; } diff --git a/src/js/drag.js b/src/js/drag.js index c7864a4..bd853c0 100644 --- a/src/js/drag.js +++ b/src/js/drag.js @@ -5,95 +5,61 @@ Bookmarks: Reihenfolge innerhalb eines Boards ============================================= */ -// ---- BOARD DRAG (Pointer Events) ---- +// ---- 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'); - let dragging = null; - let placeholder = null; - - function getInsertTarget(clientX, clientY) { - const boardEls = Array.from(wrapper.querySelectorAll('.board:not(.dragging)')); - for (const b of boardEls) { - const r = b.getBoundingClientRect(); - if (clientX >= r.left && clientX <= r.right && clientY >= r.top && clientY <= r.bottom) { - return { el: b, before: clientX < r.left + r.width / 2 }; - } - } - return null; - } wrapper.querySelectorAll('.board').forEach(boardEl => { const handle = boardEl.querySelector('.board-drag-handle'); if (!handle) return; - handle.style.cursor = 'grab'; - - handle.addEventListener('pointerdown', e => { + 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); - handle.style.cursor = 'grabbing'; - - const rect = boardEl.getBoundingClientRect(); - - // Ghost - const ghost = boardEl.cloneNode(true); - ghost.className += ' drag-ghost'; - ghost.style.left = rect.left + 'px'; - ghost.style.top = rect.top + 'px'; - ghost.style.width = rect.width + 'px'; - ghost.style.height = rect.height + 'px'; - document.body.appendChild(ghost); - - // Placeholder - placeholder = document.createElement('div'); - placeholder.className = 'board-placeholder'; - placeholder.style.cssText = `width:${rect.width}px; height:${rect.height}px;`; - boardEl.parentNode.insertBefore(placeholder, boardEl); + // .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'); - dragging = { el: boardEl, ghost, - offsetX: e.clientX - rect.left, - offsetY: e.clientY - rect.top - }; - }); + const rect = boardEl.getBoundingClientRect(); + const offX = e.clientX - rect.left; + const offY = e.clientY - rect.top; - handle.addEventListener('pointermove', e => { - if (!dragging || dragging.el !== boardEl) return; - e.preventDefault(); - dragging.ghost.style.left = (e.clientX - dragging.offsetX) + 'px'; - dragging.ghost.style.top = (e.clientY - dragging.offsetY) + 'px'; - - const target = getInsertTarget(e.clientX, e.clientY); - if (target && target.el !== boardEl) { - target.before - ? target.el.parentNode.insertBefore(placeholder, target.el) - : target.el.parentNode.insertBefore(placeholder, target.el.nextSibling); + 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'); } - }); - handle.addEventListener('pointerup', async () => { - if (!dragging || dragging.el !== boardEl) return; - handle.style.cursor = 'grab'; - placeholder.parentNode.insertBefore(boardEl, placeholder); - placeholder.remove(); placeholder = null; - boardEl.classList.remove('dragging'); - dragging.ghost.remove(); - dragging = null; + 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 - // Neue Reihenfolge aus DOM ablesen - const newOrder = Array.from(wrapper.querySelectorAll('.board')) - .map(el => el.dataset.boardId).filter(Boolean); - boards.sort((a, b) => newOrder.indexOf(a.id) - newOrder.indexOf(b.id)); - await saveBoards(); - }); + 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('pointercancel', () => { - if (!dragging) return; - dragging.ghost.remove(); - if (placeholder) { placeholder.remove(); placeholder = null; } - boardEl.classList.remove('dragging'); - dragging = null; - handle.style.cursor = 'grab'; + handle.addEventListener('pointermove', onMove); + handle.addEventListener('pointerup', onUp); }); }); }