수정
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* Tauri WebView — HTML5 DnD 대신 pointer capture 기반 팔레트 드래그
|
||||
* Desktop(Tauri) — HTML5 DnD 대신 pointer + 전체화면 overlay 드래그
|
||||
*/
|
||||
import { clientXYFromNative } from './pointerDrag';
|
||||
import { isDesktop } from './platform';
|
||||
|
||||
export type PaletteDragPayload = Record<string, unknown> & {
|
||||
@@ -19,16 +18,29 @@ export interface PaletteDragEvent {
|
||||
payload: PaletteDragPayload;
|
||||
}
|
||||
|
||||
export interface PaletteDragOverlaySession {
|
||||
payload: PaletteDragPayload;
|
||||
pointerId: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
type PaletteDragListener = (event: PaletteDragEvent) => void;
|
||||
type PaletteDropHandler = (event: PaletteDragEvent) => void;
|
||||
|
||||
type OverlayController = {
|
||||
mount: (session: PaletteDragOverlaySession) => void;
|
||||
unmount: () => void;
|
||||
bindElement: (el: HTMLElement | null) => void;
|
||||
};
|
||||
|
||||
let suppressClickUntil = 0;
|
||||
const DRAG_THRESHOLD_PX = 6;
|
||||
|
||||
let activePayload: PaletteDragPayload | null = null;
|
||||
let activePointerId: number | null = null;
|
||||
let unbindMove: (() => void) | null = null;
|
||||
let pendingPointerId: number | null = null;
|
||||
let overlayController: OverlayController | null = null;
|
||||
let overlayElement: HTMLElement | null = null;
|
||||
let listeners = new Set<PaletteDragListener>();
|
||||
let dropHandler: PaletteDropHandler | null = null;
|
||||
|
||||
@@ -41,11 +53,14 @@ export function subscribePaletteDrag(listener: PaletteDragListener): () => void
|
||||
return () => listeners.delete(listener);
|
||||
}
|
||||
|
||||
/** Canvas 가 mount 직후 drop 콜백 등록 (subscribe 누락 방지) */
|
||||
export function setPaletteDragDropHandler(handler: PaletteDropHandler | null): void {
|
||||
dropHandler = handler;
|
||||
}
|
||||
|
||||
export function registerPaletteDragOverlay(controller: OverlayController | null): void {
|
||||
overlayController = controller;
|
||||
}
|
||||
|
||||
export function getActivePaletteDrag(): PaletteDragPayload | null {
|
||||
return activePayload;
|
||||
}
|
||||
@@ -71,12 +86,10 @@ function dispatchEnd(xy: { x: number; y: number }, payload: PaletteDragPayload)
|
||||
}
|
||||
|
||||
function cleanupDrag() {
|
||||
unbindMove?.();
|
||||
unbindMove = null;
|
||||
activePayload = null;
|
||||
activePointerId = null;
|
||||
pendingPointerId = null;
|
||||
document.body.classList.remove('se-palette-drag-active');
|
||||
overlayController?.unmount();
|
||||
document.querySelectorAll('.se-palette-drag-ghost').forEach(el => el.remove());
|
||||
}
|
||||
|
||||
@@ -92,13 +105,40 @@ function finishDrag(phase: 'end' | 'cancel', xy: { x: number; y: number }) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 캔버스 pointerup 백업 — WebView 에서 window 이벤트 누락 시 */
|
||||
export function notifyPaletteDragMove(clientX: number, clientY: number) {
|
||||
if (!activePayload) return;
|
||||
emit({ phase: 'move', x: clientX, y: clientY, payload: activePayload });
|
||||
}
|
||||
|
||||
export function notifyPaletteDragEnd(clientX: number, clientY: number) {
|
||||
finishDrag('end', { x: clientX, y: clientY });
|
||||
}
|
||||
|
||||
export function notifyPaletteDragCancel(clientX: number, clientY: number) {
|
||||
finishDrag('cancel', { x: clientX, y: clientY });
|
||||
}
|
||||
|
||||
export function completePaletteDragAt(clientX: number, clientY: number): boolean {
|
||||
if (!activePayload) return false;
|
||||
finishDrag('end', { x: clientX, y: clientY });
|
||||
return true;
|
||||
}
|
||||
|
||||
function captureOnOverlay(pointerId: number) {
|
||||
try {
|
||||
overlayElement?.setPointerCapture(pointerId);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
if (!overlayElement?.hasPointerCapture(pointerId)) {
|
||||
document.body.setPointerCapture(pointerId);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function beginDrag(
|
||||
payload: PaletteDragPayload,
|
||||
xy: { x: number; y: number },
|
||||
@@ -108,48 +148,18 @@ function beginDrag(
|
||||
activePointerId = pointerId;
|
||||
document.body.classList.add('se-palette-drag-active');
|
||||
|
||||
try {
|
||||
document.body.setPointerCapture(pointerId);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
overlayController?.mount({ payload, pointerId, x: xy.x, y: xy.y });
|
||||
requestAnimationFrame(() => captureOnOverlay(pointerId));
|
||||
|
||||
emit({ phase: 'start', x: xy.x, y: xy.y, payload });
|
||||
}
|
||||
|
||||
const onMove = (ev: Event) => {
|
||||
if (!(ev instanceof PointerEvent) || ev.pointerId !== pointerId || !activePayload) return;
|
||||
if (ev.cancelable) ev.preventDefault();
|
||||
emit({ phase: 'move', x: ev.clientX, y: ev.clientY, payload: activePayload });
|
||||
};
|
||||
|
||||
const onUp = (ev: Event) => {
|
||||
if (!(ev instanceof PointerEvent) || ev.pointerId !== pointerId) return;
|
||||
const xy = clientXYFromNative(ev) ?? { x: ev.clientX, y: ev.clientY };
|
||||
finishDrag('end', xy);
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', onMove, { passive: false });
|
||||
window.addEventListener('pointerup', onUp, true);
|
||||
window.addEventListener('pointercancel', onUp, true);
|
||||
window.addEventListener('mouseup', onUp, true);
|
||||
document.addEventListener('pointerup', onUp, true);
|
||||
document.addEventListener('lostpointercapture', onUp, true);
|
||||
|
||||
unbindMove = () => {
|
||||
window.removeEventListener('pointermove', onMove);
|
||||
window.removeEventListener('pointerup', onUp, true);
|
||||
window.removeEventListener('pointercancel', onUp, true);
|
||||
window.removeEventListener('mouseup', onUp, true);
|
||||
document.removeEventListener('pointerup', onUp, true);
|
||||
document.removeEventListener('lostpointercapture', onUp, true);
|
||||
try {
|
||||
if (document.body.hasPointerCapture(pointerId)) {
|
||||
document.body.releasePointerCapture(pointerId);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
};
|
||||
export function bindPaletteDragOverlayElement(el: HTMLElement | null) {
|
||||
overlayElement = el;
|
||||
overlayController?.bindElement(el);
|
||||
if (el && activePointerId != null) {
|
||||
captureOnOverlay(activePointerId);
|
||||
}
|
||||
}
|
||||
|
||||
export function handlePalettePointerDown(
|
||||
@@ -161,10 +171,11 @@ export function handlePalettePointerDown(
|
||||
if (e.button !== 0) return;
|
||||
if ((e.target as HTMLElement).closest('input, textarea, select, button')) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const startX = e.clientX;
|
||||
const startY = e.clientY;
|
||||
const pointerId = e.pointerId;
|
||||
pendingPointerId = pointerId;
|
||||
|
||||
const onPointerMove = (ev: PointerEvent) => {
|
||||
if (ev.pointerId !== pointerId) return;
|
||||
@@ -185,12 +196,11 @@ export function handlePalettePointerDown(
|
||||
window.removeEventListener('pointermove', onPointerMove);
|
||||
window.removeEventListener('pointerup', onPointerUp);
|
||||
window.removeEventListener('pointercancel', onPointerCancel);
|
||||
pendingPointerId = null;
|
||||
};
|
||||
|
||||
const onPointerCancel = onPointerUp;
|
||||
|
||||
window.addEventListener('pointermove', onPointerMove);
|
||||
window.addEventListener('pointermove', onPointerMove, { passive: false });
|
||||
window.addEventListener('pointerup', onPointerUp);
|
||||
window.addEventListener('pointercancel', onPointerCancel);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user