Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_TreePanel_Drag.ts
T
eomsangdonandClaude Opus 5.5 fb56d7c294 fix(M02): 좌측 두 컨테이너 펼친 채 진입 · 끌기 뒤 놓을 자리 강조 지움 (PLAN 31-5)
- 「일위대가 조합」 · 「구조물 조합」 둘 다 펼친 채 들어옴
- 끌어 놓은 뒤 나무 칸 자신(맨 위 자리) 강조가 남던 흠 — 놓음 · 끝 · 칸 밖 나감에서 모두 지움(칸 안 자식 위로는 그대로)
- 끌기 코드를 TreePanel_Drag 로 나눔(700줄)
- 시험 한 건

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:27:02 +09:00

75 lines
3.2 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_TreePanel_Drag.ts
* 공용 나무 끌어다 옮기기(PLAN 30-2 · 31-5) — 같은 층 안 · 폴더 줄 위 = 그 안 · 빈 자리 = 맨 위 ·
* 제 안으로는 안 됨 · 고른 것 안의 줄을 끌면 고른 것 전부. 옮기는 일은 부르는 쪽(`move`).
* 놓을 자리 강조는 한 곳만 · 놓음 · 끝 · 칸 밖으로 나감에서 모두 지움(나무 칸 자신 포함).
* ========================================================================== */
import type { Target } from "./M02_MasterTemplete_TreePanel_Pick";
import { isUnder } from "./M02_MasterTemplete_TreePanel_Model";
export interface TreeDragOptions {
/** 나무 칸(줄들의 부모 · 빈 자리 = 맨 위) */
body: HTMLElement;
targetOf: (id: string) => Target | undefined;
/** 이 줄을 끌 때 옮길 것 — 고른 것 안이면 고른 것 전부 */
listFor: (id: string) => Target[] | null;
move: (list: Target[], dest: string[]) => void;
}
/** 칸에 놓기를 달고 줄마다 끌기를 다는 손잡이(`arm`)를 돌려줌 */
export function attachTreeDrag(opt: TreeDragOptions): {
arm: (row: HTMLElement, id: string) => void;
} {
const { body } = opt;
let dragging: Target[] | null = null;
const dropDest = (event: DragEvent): string[] | null => {
const row = (event.target as HTMLElement).closest<HTMLElement>("[data-node]");
const target = row ? opt.targetOf(row.dataset.node ?? "") : undefined;
const dest = !row ? [] : target?.kind === "folder" ? target.path : null;
const blocked = dragging?.some((t) => t.kind === "folder" && dest && isUnder(dest, t.path));
return dest && !blocked ? dest : null;
};
/** 놓을 자리 강조 — 한 곳만 · null = 모두 지움(나무 칸 자신 = 맨 위 자리 포함 · PLAN 31-5 흠) */
const dropMark = (row: HTMLElement | null): void => {
body.classList.remove("is-drop");
body.querySelectorAll(".is-drop").forEach((b) => b.classList.remove("is-drop"));
row?.classList.add("is-drop");
};
// 나무 칸 밖으로 나가면 강조를 지움 — 칸 안 자식 위로 옮겨 갈 때는 그대로(깜빡임 막음)
body.addEventListener("dragleave", (event) => {
const to = event.relatedTarget as Node | null;
if (!to || !body.contains(to)) dropMark(null);
});
body.addEventListener("dragover", (event) => {
if (!dragging || !dropDest(event)) return;
event.preventDefault();
dropMark((event.target as HTMLElement).closest<HTMLElement>("[data-node]") ?? body);
});
body.addEventListener("drop", (event) => {
const dest = dragging && dropDest(event);
const list = dragging;
dragging = null;
dropMark(null);
if (!dest || !list) return;
event.preventDefault();
opt.move(list, dest);
});
return {
arm: (row, id) => {
row.draggable = true;
row.addEventListener("dragstart", (event) => {
dragging = opt.listFor(id);
event.dataTransfer?.setData("text/plain", id);
if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
});
row.addEventListener("dragend", () => {
dragging = null;
dropMark(null);
});
},
};
}