Freies Layout: Board-Drag als Free-Move neu (widgets.js-Vorbild), .board.dragging auf z-index umgewidmet, Reorder-CSS (placeholder/ghost) raus

This commit is contained in:
2026-06-14 14:58:08 +02:00
parent 390a9b2f94
commit 8401535900
2 changed files with 41 additions and 86 deletions
+2 -13
View File
@@ -893,14 +893,8 @@ body.show-desc .bm-desc { display: block; }
.modal-body { padding: 14px 16px; } .modal-body { padding: 14px 16px; }
.modal-footer { padding: 10px 16px 14px; display: flex; justify-content: flex-end; } .modal-footer { padding: 10px 16px 14px; display: flex; justify-content: flex-end; }
.board.dragging { opacity: 0.35; } /* Free-Move: das gezogene Board nach vorne heben (frueher Reorder-Opacity). */
.board.dragging { z-index: 50; cursor: grabbing; }
.board-placeholder {
border: 2px dashed var(--border-accent);
border-radius: var(--radius);
background: var(--accent-dim);
flex-shrink: 0;
}
} }
@@ -2484,11 +2478,6 @@ body.show-desc .bm-desc { display: block; }
.hidden { display: none; } .hidden { display: none; }
.accent-text { color: var(--accent); } .accent-text { color: var(--accent); }
.dim { opacity: 0.4; } .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.drag-over { background: rgba(255,160,50,0.07); }
.bm-item.dragging-source { opacity: 0.4; } .bm-item.dragging-source { opacity: 0.4; }
.about-info-label-block { display: block; margin-bottom: 6px; } .about-info-label-block { display: block; margin-bottom: 6px; }
+39 -73
View File
@@ -5,95 +5,61 @@
Bookmarks: Reihenfolge innerhalb eines Boards 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() { function initBoardDragDrop() {
const wrapper = document.getElementById('boardsWrapper'); 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 => { wrapper.querySelectorAll('.board').forEach(boardEl => {
const handle = boardEl.querySelector('.board-drag-handle'); const handle = boardEl.querySelector('.board-drag-handle');
if (!handle) return; if (!handle) return;
handle.style.cursor = 'grab'; handle.addEventListener('pointerdown', function onDown(e) {
// Auf Mobil ist .board position:static (Stapel) -> kein Free-Move.
handle.addEventListener('pointerdown', e => { if (getComputedStyle(boardEl).position !== 'absolute') return;
e.preventDefault(); e.preventDefault();
handle.setPointerCapture(e.pointerId); handle.setPointerCapture(e.pointerId);
handle.style.cursor = 'grabbing'; // .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
const rect = boardEl.getBoundingClientRect(); // Drag sonst abreissen wuerde). Der Guard prueft genau diese Klasse (Phase-4-Review 2a).
// 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);
boardEl.classList.add('dragging'); boardEl.classList.add('dragging');
dragging = { el: boardEl, ghost, const rect = boardEl.getBoundingClientRect();
offsetX: e.clientX - rect.left, const offX = e.clientX - rect.left;
offsetY: e.clientY - rect.top const offY = e.clientY - rect.top;
};
});
handle.addEventListener('pointermove', e => { function onMove(ev) {
if (!dragging || dragging.el !== boardEl) return; const maxX = window.innerWidth - boardEl.offsetWidth;
e.preventDefault(); const maxY = window.innerHeight - boardEl.offsetHeight;
dragging.ghost.style.left = (e.clientX - dragging.offsetX) + 'px'; const x = Math.max(0, Math.min(maxX, ev.clientX - offX));
dragging.ghost.style.top = (e.clientY - dragging.offsetY) + 'px'; const y = Math.max(48, Math.min(maxY, ev.clientY - offY)); // 48px = Header-Hoehe
boardEl.style.setProperty('--board-x', x + 'px');
const target = getInsertTarget(e.clientX, e.clientY); boardEl.style.setProperty('--board-y', y + 'px');
if (target && target.el !== boardEl) {
target.before
? target.el.parentNode.insertBefore(placeholder, target.el)
: target.el.parentNode.insertBefore(placeholder, target.el.nextSibling);
} }
});
handle.addEventListener('pointerup', async () => { async function onUp() {
if (!dragging || dragging.el !== boardEl) return; handle.releasePointerCapture(e.pointerId);
handle.style.cursor = 'grab'; handle.removeEventListener('pointermove', onMove);
placeholder.parentNode.insertBefore(boardEl, placeholder); handle.removeEventListener('pointerup', onUp);
placeholder.remove(); placeholder = null; boardEl.classList.remove('dragging'); // z-index zurueck + Live-Sync-Guard freigeben
boardEl.classList.remove('dragging');
dragging.ghost.remove();
dragging = null;
// Neue Reihenfolge aus DOM ablesen const id = boardEl.dataset.boardId;
const newOrder = Array.from(wrapper.querySelectorAll('.board')) const board = boards.find(b => b.id === id);
.map(el => el.dataset.boardId).filter(Boolean); if (board) {
boards.sort((a, b) => newOrder.indexOf(a.id) - newOrder.indexOf(b.id)); board.pos = {
await saveBoards(); x: parseFloat(boardEl.style.getPropertyValue('--board-x')),
}); y: parseFloat(boardEl.style.getPropertyValue('--board-y'))
};
await saveBoards();
}
}
handle.addEventListener('pointercancel', () => { handle.addEventListener('pointermove', onMove);
if (!dragging) return; handle.addEventListener('pointerup', onUp);
dragging.ghost.remove();
if (placeholder) { placeholder.remove(); placeholder = null; }
boardEl.classList.remove('dragging');
dragging = null;
handle.style.cursor = 'grab';
}); });
}); });
} }