fix(project): 달력 드래그를 포인터 기반으로 재구현
HTML5 네이티브 DnD 가 절대배치 grid 오버레이 달력에서 dragstart 를 잡지 못해(콘솔 에러 없이 무동작) 드래그가 안 됐다. mousedown→mousemove→mouseup 포인터 기반으로 직접 구현: - 5px 이상 움직이면 드래그 시작, 고스트 라벨이 커서 따라다님 - 드래그 중 bar pointer-events 해제 → elementFromPoint 가 날짜 셀 반환 - 놓은 칸의 날짜로 기간 유지 이동, 안 움직이면 클릭=모달 - 네이티브 draggable 속성 제거 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+50
-51
@@ -142,22 +142,16 @@
|
||||
return Math.round((new Date(b + "T00:00:00") - new Date(a + "T00:00:00")) / 86400000);
|
||||
}
|
||||
|
||||
let dragTaskId = null;
|
||||
|
||||
// 한 주 안에서 clientX → 0..6 열 인덱스 → 그 칸의 날짜.
|
||||
function dateAtPointer(week, clientX) {
|
||||
const cells = week.querySelectorAll(".pj-week-days .pj-day[data-date]");
|
||||
if (!cells.length) return null;
|
||||
const rect = week.getBoundingClientRect();
|
||||
let col = Math.floor((clientX - rect.left) / (rect.width / 7));
|
||||
col = Math.max(0, Math.min(cells.length - 1, col));
|
||||
return { date: cells[col].dataset.date, cell: cells[col] };
|
||||
}
|
||||
|
||||
function clearDropHL() {
|
||||
document.querySelectorAll(".pj-day.pj-drop-over").forEach(function (d) { d.classList.remove("pj-drop-over"); });
|
||||
}
|
||||
|
||||
// 화면 좌표 → 그 위치의 날짜 셀(있으면).
|
||||
function dayCellAt(x, y) {
|
||||
const elAt = document.elementFromPoint(x, y);
|
||||
return elAt ? elAt.closest(".pj-day[data-date]") : null;
|
||||
}
|
||||
|
||||
async function moveTaskTo(taskId, dropDate) {
|
||||
const t = tasks.find(function (x) { return String(x.id) === String(taskId); });
|
||||
if (!t) return;
|
||||
@@ -175,52 +169,57 @@
|
||||
} catch (err) { alert("이동 실패: " + err.message); }
|
||||
}
|
||||
|
||||
// 네이티브 HTML5 드래그는 이 절대배치 grid 오버레이에서 불안정해서,
|
||||
// 포인터(mousedown→mousemove→mouseup) 기반으로 직접 구현한다.
|
||||
function wireCalendar() {
|
||||
if (calendarWired) return;
|
||||
calendarWired = true;
|
||||
const cal = document.querySelector(".pj-cal");
|
||||
|
||||
document.querySelectorAll(".pj-bar[data-task-id]").forEach(function (bar) {
|
||||
bar.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
if (!canManage) return;
|
||||
const t = tasks.find(function (x) { return String(x.id) === bar.dataset.taskId; });
|
||||
if (t) openTaskModal(t);
|
||||
});
|
||||
if (canManage) {
|
||||
bar.draggable = true;
|
||||
bar.addEventListener("dragstart", function (e) {
|
||||
dragTaskId = bar.dataset.taskId;
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
try { e.dataTransfer.setData("text/plain", bar.dataset.taskId); } catch (_) {}
|
||||
if (cal) cal.classList.add("pj-cal-dragging");
|
||||
});
|
||||
bar.addEventListener("dragend", function () {
|
||||
dragTaskId = null;
|
||||
if (cal) cal.classList.remove("pj-cal-dragging");
|
||||
clearDropHL();
|
||||
});
|
||||
}
|
||||
});
|
||||
bar.addEventListener("mousedown", function (e) {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault(); // 텍스트 선택/네이티브 드래그 방지
|
||||
const taskId = bar.dataset.taskId;
|
||||
const sx = e.clientX, sy = e.clientY;
|
||||
let dragging = false, ghost = null;
|
||||
|
||||
if (!canManage) return;
|
||||
// 드롭 대상은 주(week) 단위로 받고, 마우스 X 로 어느 날짜 칸인지 계산.
|
||||
// (bar 오버레이/pointer-events 간섭 없이 안정적으로 드롭됨)
|
||||
document.querySelectorAll(".pj-week").forEach(function (week) {
|
||||
week.addEventListener("dragover", function (e) {
|
||||
if (!dragTaskId) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
const hit = dateAtPointer(week, e.clientX);
|
||||
if (hit) { clearDropHL(); hit.cell.classList.add("pj-drop-over"); }
|
||||
});
|
||||
week.addEventListener("drop", function (e) {
|
||||
e.preventDefault();
|
||||
const taskId = dragTaskId || e.dataTransfer.getData("text/plain");
|
||||
clearDropHL();
|
||||
if (!taskId) return;
|
||||
const hit = dateAtPointer(week, e.clientX);
|
||||
if (hit) moveTaskTo(taskId, hit.date);
|
||||
function onMove(ev) {
|
||||
if (!dragging) {
|
||||
if (Math.abs(ev.clientX - sx) + Math.abs(ev.clientY - sy) < 5) return;
|
||||
dragging = true;
|
||||
if (cal) cal.classList.add("pj-cal-dragging");
|
||||
if (canManage) {
|
||||
ghost = document.createElement("div");
|
||||
ghost.className = "pj-bar-ghost";
|
||||
ghost.textContent = (bar.textContent || "").trim();
|
||||
document.body.appendChild(ghost);
|
||||
}
|
||||
}
|
||||
if (ghost) { ghost.style.left = (ev.clientX + 10) + "px"; ghost.style.top = (ev.clientY + 12) + "px"; }
|
||||
clearDropHL();
|
||||
const cell = dayCellAt(ev.clientX, ev.clientY);
|
||||
if (cell) cell.classList.add("pj-drop-over");
|
||||
}
|
||||
|
||||
function onUp(ev) {
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", onUp);
|
||||
if (cal) cal.classList.remove("pj-cal-dragging");
|
||||
if (ghost) ghost.remove();
|
||||
clearDropHL();
|
||||
const t = tasks.find(function (x) { return String(x.id) === String(taskId); });
|
||||
if (!dragging) { // 안 움직였으면 클릭 = 모달
|
||||
if (t && canManage) openTaskModal(t);
|
||||
return;
|
||||
}
|
||||
if (!canManage) return;
|
||||
const cell = dayCellAt(ev.clientX, ev.clientY);
|
||||
if (cell) moveTaskTo(taskId, cell.dataset.date);
|
||||
}
|
||||
|
||||
document.addEventListener("mousemove", onMove);
|
||||
document.addEventListener("mouseup", onUp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user