feat: Live-Vorschau beim Kalender-Sortieren

Beim Ziehen wandert die Zeile jetzt live zwischen die anderen, die Liste
macht sichtbar Platz an der Zielposition. Container-dragover wird nur einmal
gebunden (kein Listener-Stacking pro Render). Version v30.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-05-31 17:29:41 +02:00
parent c62b3df33a
commit cc4ccc7d81
2 changed files with 34 additions and 18 deletions

View File

@@ -575,35 +575,51 @@ function saveCalOrder(keys) {
}
// Drag & drop reordering of the flat calendar list (persisted per device).
// The dragged row is moved live among its siblings during dragover, so the
// list visibly "makes space" and you can see where it will land.
function bindCalDragReorder(container) {
let dragKey = null;
// Find the row the cursor is currently above (by vertical midpoint), so we
// know before which sibling to insert the dragged row.
const rowAfter = (y) => {
const rows = [...container.querySelectorAll('.cal-item:not(.cal-dragging)')];
return rows.reduce((closest, row) => {
const box = row.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) return { offset, el: row };
return closest;
}, { offset: Number.NEGATIVE_INFINITY, el: null }).el;
};
container.querySelectorAll('.cal-item').forEach(item => {
item.addEventListener('dragstart', e => {
// Don't start a drag from interactive children (checkbox, color dot, buttons).
if (e.target.closest('input, button, .cal-item-dot')) { e.preventDefault(); return; }
dragKey = item.dataset.key;
e.dataTransfer.effectAllowed = 'move';
item.classList.add('cal-dragging');
// Defer the class so the drag image is the full opaque row, then dim it.
requestAnimationFrame(() => item.classList.add('cal-dragging'));
});
item.addEventListener('dragend', () => {
dragKey = null;
item.classList.remove('cal-dragging');
// Persist the final DOM order; no full re-render needed.
saveCalOrder([...container.querySelectorAll('.cal-item')].map(el => el.dataset.key));
});
item.addEventListener('dragover', e => { e.preventDefault(); });
item.addEventListener('drop', e => {
});
// Live reorder: move the dragged row to the hovered position as the cursor
// moves. Bound once on the (stable) container to avoid stacking listeners on
// every re-render.
if (!container.__dragBound) {
container.__dragBound = true;
container.addEventListener('dragover', e => {
e.preventDefault();
const targetKey = item.dataset.key;
if (!dragKey || dragKey === targetKey) return;
const keys = [...container.querySelectorAll('.cal-item')].map(el => el.dataset.key);
const from = keys.indexOf(dragKey);
const to = keys.indexOf(targetKey);
if (from === -1 || to === -1) return;
keys.splice(to, 0, keys.splice(from, 1)[0]);
saveCalOrder(keys);
renderCalendarList();
});
const dragging = container.querySelector('.cal-dragging');
if (!dragging) return;
const after = rowAfter(e.clientY);
if (after == null) container.appendChild(dragging);
else if (after !== dragging) container.insertBefore(dragging, after);
});
}
}
function renderCalendarList() {
const container = document.getElementById('cal-list-items');

View File

@@ -1,2 +1,2 @@
// Increment APP_VERSION with every code change
export const APP_VERSION = 'v29';
export const APP_VERSION = 'v30';