ultisuite-client/lib/sidebar-nav-dnd.ts
2026-05-16 20:30:50 +02:00

65 lines
2.0 KiB
TypeScript

/** Drag-and-drop MIME + helpers for reordering sidebar labels / folders. */
export const SIDEBAR_NAV_DND_MIME = "application/x-ultimail-sidebar-nav"
export type SidebarNavDragPayload =
| { kind: "label"; id: string }
| { kind: "folder"; id: string }
export type SidebarNavDropPlacement = "before" | "after" | "inside"
export function setSidebarNavDragData(
e: React.DragEvent,
payload: SidebarNavDragPayload
) {
const encoded = JSON.stringify(payload)
e.dataTransfer.setData(SIDEBAR_NAV_DND_MIME, encoded)
/** Fallback for browsers that hide custom types until drop. */
e.dataTransfer.setData("text/plain", encoded)
e.dataTransfer.effectAllowed = "move"
}
export function readSidebarNavDragData(
e: React.DragEvent,
active: SidebarNavDragPayload | null = null
): SidebarNavDragPayload | null {
for (const mime of [SIDEBAR_NAV_DND_MIME, "text/plain"]) {
const raw = e.dataTransfer.getData(mime)
if (!raw) continue
try {
const parsed = JSON.parse(raw) as SidebarNavDragPayload
if (parsed?.kind === "label" || parsed?.kind === "folder") {
if (typeof parsed.id === "string" && parsed.id.length > 0) return parsed
}
} catch {
/* ignore */
}
}
return active
}
export function hasSidebarNavDrag(e: React.DragEvent): boolean {
return e.dataTransfer.types.includes(SIDEBAR_NAV_DND_MIME)
}
/** Prefer React state — `dataTransfer.types` is unreliable during `dragover`. */
export function isSidebarNavDragEvent(
e: React.DragEvent,
active: SidebarNavDragPayload | null
): boolean {
return active !== null || hasSidebarNavDrag(e)
}
/** Top/bottom thirds = reorder; middle third = nest (folders only). */
export function resolveNavDropPlacement(
e: React.DragEvent<Element>,
allowNest: boolean
): SidebarNavDropPlacement {
const rect = e.currentTarget.getBoundingClientRect()
const ratio = (e.clientY - rect.top) / Math.max(rect.height, 1)
if (!allowNest) return ratio < 0.5 ? "before" : "after"
if (ratio < 0.25) return "before"
if (ratio > 0.75) return "after"
return "inside"
}