전략 편집기 수정

This commit is contained in:
Macbook
2026-06-15 00:43:19 +09:00
parent 78d9dca870
commit 2a2fe74933
2 changed files with 83 additions and 21 deletions
+74 -14
View File
@@ -4,6 +4,7 @@
import { elementsUnderDragPoint } from './flowScreenProjection';
import {
bindWindowDrag,
clientXYFromNative,
isPrimaryPointerButton,
} from './pointerDrag';
import { isDesktop } from './platform';
@@ -47,13 +48,16 @@ let activePointerId: number | null = null;
let activeTouchId: number | null = null;
let overlayController: OverlayController | null = null;
let overlayElement: HTMLElement | null = null;
let gestureCaptureEl: HTMLElement | null = null;
let listeners = new Set<PaletteDragListener>();
let dropHandler: PaletteDropHandler | null = null;
let moveRafId: number | null = null;
let pendingMove: { x: number; y: number } | null = null;
let unbindActiveDrag: (() => void) | null = null;
let unbindActiveMove: (() => void) | null = null;
let unbindActiveEnd: (() => void) | null = null;
let pendingPointerListeners: (() => void) | null = null;
let lastDragClientXY: { x: number; y: number } | null = null;
let endListenersArmed = false;
function hasCoarsePointer(): boolean {
if (typeof window === 'undefined') return false;
@@ -108,6 +112,12 @@ function dispatchEnd(xy: { x: number; y: number }, payload: PaletteDragPayload)
function releasePointerCapture(pointerId: number | null) {
if (pointerId == null) return;
try {
gestureCaptureEl?.releasePointerCapture(pointerId);
} catch {
/* ignore */
}
gestureCaptureEl = null;
try {
overlayElement?.releasePointerCapture(pointerId);
} catch {
@@ -127,10 +137,17 @@ function clearPendingPointerListeners() {
pendingPointerListeners = null;
}
function unbindActiveDragListeners() {
unbindActiveMove?.();
unbindActiveMove = null;
unbindActiveEnd?.();
unbindActiveEnd = null;
endListenersArmed = false;
}
function cleanupDrag() {
const pointerId = activePointerId;
unbindActiveDrag?.();
unbindActiveDrag = null;
unbindActiveDragListeners();
clearPendingPointerListeners();
releasePointerCapture(pointerId);
activePayload = null;
@@ -177,10 +194,12 @@ export function notifyPaletteDragMove(clientX: number, clientY: number) {
}
export function notifyPaletteDragEnd(clientX: number, clientY: number) {
if (!endListenersArmed) return;
finishDrag('end', { x: clientX, y: clientY });
}
export function notifyPaletteDragCancel(clientX: number, clientY: number) {
if (!endListenersArmed) return;
finishDrag('cancel', { x: clientX, y: clientY });
}
@@ -203,24 +222,43 @@ function eventMatchesActiveInput(ev: Event): boolean {
return true;
}
function bindActiveDragWindowListeners(pointerId: number) {
unbindActiveDrag?.();
function bindActiveDragMoveListeners(pointerId: number) {
unbindActiveMove?.();
unbindActiveDrag = bindWindowDrag(
unbindActiveMove = bindWindowDrag(
(xy, ev) => {
if (!activePayload || activePointerId !== pointerId) return;
if (!eventMatchesActiveInput(ev)) return;
notifyPaletteDragMove(xy.x, xy.y);
},
(xy, ev) => {
if (!activePayload || activePointerId !== pointerId) return;
if (!eventMatchesActiveInput(ev)) return;
finishDrag('end', xy);
},
() => { /* end 는 별도 등록 */ },
{ preventTouchScroll: true, useCapture: true },
);
}
function bindActiveDragEndListeners(pointerId: number) {
unbindActiveEnd?.();
const onEnd = (ev: Event) => {
if (!endListenersArmed) return;
if (!activePayload || activePointerId !== pointerId) return;
if (!eventMatchesActiveInput(ev)) return;
const xy = clientXYFromNative(ev) ?? lastDragClientXY;
if (!xy) return;
finishDrag('end', xy);
};
window.addEventListener('pointerup', onEnd, true);
window.addEventListener('mouseup', onEnd, true);
window.addEventListener('touchend', onEnd, true);
unbindActiveEnd = () => {
window.removeEventListener('pointerup', onEnd, true);
window.removeEventListener('mouseup', onEnd, true);
window.removeEventListener('touchend', onEnd, true);
};
}
function captureOnOverlay(pointerId: number) {
try {
overlayElement?.setPointerCapture(pointerId);
@@ -236,6 +274,11 @@ function captureOnOverlay(pointerId: number) {
}
}
function armDragEndListeners(pointerId: number) {
endListenersArmed = true;
bindActiveDragEndListeners(pointerId);
}
function beginDrag(
payload: PaletteDragPayload,
xy: { x: number; y: number },
@@ -250,14 +293,23 @@ function beginDrag(
activePointerId = pointerId;
activeTouchId = touchId;
lastDragClientXY = { x: xy.x, y: xy.y };
endListenersArmed = false;
document.body.classList.add('se-palette-drag-active');
bindActiveDragWindowListeners(pointerId);
bindActiveDragMoveListeners(pointerId);
overlayController?.mount({ payload, pointerId, x: xy.x, y: xy.y });
requestAnimationFrame(() => captureOnOverlay(pointerId));
emit({ phase: 'start', x: xy.x, y: xy.y, payload });
notifyPaletteDragMove(xy.x, xy.y);
requestAnimationFrame(() => {
if (activePointerId !== pointerId) return;
captureOnOverlay(pointerId);
requestAnimationFrame(() => {
if (activePointerId !== pointerId) return;
armDragEndListeners(pointerId);
});
});
}
export function bindPaletteDragOverlayElement(el: HTMLElement | null) {
@@ -315,6 +367,14 @@ export function handlePalettePointerDown(
e.preventDefault();
const sourceEl = e.currentTarget as HTMLElement;
try {
sourceEl.setPointerCapture(e.pointerId);
gestureCaptureEl = sourceEl;
} catch {
/* overlay/body capture later */
}
startPaletteDragGesture(
e.clientX,
e.clientY,