This commit is contained in:
Macbook
2026-06-14 23:39:51 +09:00
parent b2788ee629
commit 0045f0ef2d
6 changed files with 221 additions and 70 deletions
+33 -17
View File
@@ -2,6 +2,8 @@ import type { ReactFlowInstance } from '@xyflow/react';
type FlowProjector = Pick<ReactFlowInstance, 'screenToFlowPosition' | 'getViewport'>;
const DRAG_LAYER_SELECTOR = '.se-palette-drag-overlay, .se-palette-drag-ghost';
function manualProject(
clientX: number,
clientY: number,
@@ -20,6 +22,27 @@ function manualProject(
};
}
/** 드래그 overlay 아래 DOM 탐색 (overlay pointer-events 잠시 비활성) */
export function elementsUnderDragPoint(clientX: number, clientY: number): Element[] {
const overlays = document.querySelectorAll<HTMLElement>(DRAG_LAYER_SELECTOR);
const prev: string[] = [];
overlays.forEach(el => {
prev.push(el.style.pointerEvents);
el.style.pointerEvents = 'none';
});
try {
if (typeof document.elementsFromPoint === 'function') {
return document.elementsFromPoint(clientX, clientY);
}
const hit = document.elementFromPoint(clientX, clientY);
return hit ? [hit] : [];
} finally {
overlays.forEach((el, i) => {
el.style.pointerEvents = prev[i] ?? '';
});
}
}
/** screen(client) → React Flow 좌표 (Tauri WebView screenToFlowPosition 보정) */
export function projectScreenToFlowPosition(
clientX: number,
@@ -31,7 +54,6 @@ export function projectScreenToFlowPosition(
try {
const pos = rf.screenToFlowPosition({ x: clientX, y: clientY });
if (Number.isFinite(pos.x) && Number.isFinite(pos.y)) {
// WebView 버그: 화면 픽셀을 그대로 반환하는 경우
const looksLikeScreenPx =
Math.abs(pos.x - clientX) < 2 && Math.abs(pos.y - clientY) < 2;
if (!looksLikeScreenPx) return pos;
@@ -50,26 +72,18 @@ export function projectScreenToFlowPosition(
);
}
/** 팔레트 드롭 — 전략 빌더 캔버스 위인지 (우측 팔레트 위 release 무시) */
/** 팔레트 드롭 — 전략 빌더 캔버스 위인지 */
export function isOverStrategyBuilder(clientX: number, clientY: number): boolean {
const elements = typeof document.elementsFromPoint === 'function'
? document.elementsFromPoint(clientX, clientY)
: [document.elementFromPoint(clientX, clientY)].filter(Boolean) as Element[];
for (const el of elements) {
if (el.closest('.se-palette-panel, .se-side-wrap--right, .se-palette-drag-ghost')) continue;
if (el.closest('.se-canvas-wrap')) return true;
for (const el of elementsUnderDragPoint(clientX, clientY)) {
if (el.closest('.se-palette-panel, .se-side-wrap--right')) continue;
if (el.closest('.se-canvas-wrap, .react-flow__pane, .react-flow__renderer')) return true;
}
return false;
}
/** DOM hit-test — elementsFromPoint 로 ghost·overlay 아래 노드까지 탐색 */
/** DOM hit-test — overlay 아래 react-flow 노드 탐색 */
export function findFlowNodeIdAtClientPoint(clientX: number, clientY: number): string | null {
const elements = typeof document.elementsFromPoint === 'function'
? document.elementsFromPoint(clientX, clientY)
: [document.elementFromPoint(clientX, clientY)].filter(Boolean) as Element[];
for (const el of elements) {
for (const el of elementsUnderDragPoint(clientX, clientY)) {
const nodeEl = el.closest('.react-flow__node') as HTMLElement | null;
if (nodeEl) {
const id = nodeEl.getAttribute('data-id');
@@ -109,10 +123,12 @@ export function resolvePaletteDropClientPoint(
return { flowPos, anchorId: domId };
}
// DOM hit-test 실패 시 START 노드 bounding rect 로 보조
for (const node of nodes) {
if (!isConnectTarget(node.id)) continue;
const el = document.querySelector(`.react-flow__node[data-id="${CSS.escape(node.id)}"]`);
const escaped = typeof CSS !== 'undefined' && CSS.escape
? CSS.escape(node.id)
: node.id.replace(/"/g, '\\"');
const el = document.querySelector(`.react-flow__node[data-id="${escaped}"]`);
if (!el) continue;
const rect = el.getBoundingClientRect();
if (