전략편집기 전략컨트롤 드래그

This commit is contained in:
Macbook
2026-06-15 01:28:56 +09:00
parent b0e2efd7cb
commit 3150dc8af6
7 changed files with 35 additions and 22 deletions
@@ -51,6 +51,10 @@ export default function PaletteChip({
}); });
const onDragStart = (e: React.DragEvent) => { const onDragStart = (e: React.DragEvent) => {
if (needsPointerPaletteDrag()) {
e.preventDefault();
return;
}
writePaletteHtmlDragData(e, buildPayload()); writePaletteHtmlDragData(e, buildPayload());
}; };
@@ -95,7 +99,7 @@ export default function PaletteChip({
return ( return (
<div <div
draggable={!needsPointerPaletteDrag()} draggable={!needsPointerPaletteDrag()}
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${composite ? ' se-palette-card--composite' : ''}${selected ? ' se-palette-card--selected' : ''}`} className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${composite ? ' se-palette-card--composite' : ''}${selected ? ' se-palette-card--selected' : ''}${needsPointerPaletteDrag() ? ' se-palette-card--pointer' : ''}`}
onDragStart={onDragStart} onDragStart={onDragStart}
onDragEnd={onDragEnd} onDragEnd={onDragEnd}
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
@@ -78,6 +78,10 @@ function SidewaysFilterChip({
const onDragStart = (e: React.DragEvent) => { const onDragStart = (e: React.DragEvent) => {
hideHint(); hideHint();
if (needsPointerPaletteDrag()) {
e.preventDefault();
return;
}
const payload: SidewaysFilterDragPayload = { const payload: SidewaysFilterDragPayload = {
type: 'sidewaysFilter', type: 'sidewaysFilter',
value: item.id, value: item.id,
@@ -127,7 +131,7 @@ function SidewaysFilterChip({
return ( return (
<div <div
draggable={!needsPointerPaletteDrag()} draggable={!needsPointerPaletteDrag()}
className={`se-palette-card se-palette-card--range se-palette-card--range-${item.mode}${selected ? ' se-palette-card--selected' : ''}`} className={`se-palette-card se-palette-card--range se-palette-card--range-${item.mode}${selected ? ' se-palette-card--selected' : ''}${needsPointerPaletteDrag() ? ' se-palette-card--pointer' : ''}`}
onDragStart={onDragStart} onDragStart={onDragStart}
onDragEnd={onDragEnd} onDragEnd={onDragEnd}
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
@@ -1336,20 +1336,10 @@ function StrategyEditorCanvasInner({
if (!needsPointerPaletteDrag()) return; if (!needsPointerPaletteDrag()) return;
return subscribePaletteDrag((ev: PaletteDragEvent) => { return subscribePaletteDrag((ev: PaletteDragEvent) => {
if (ev.phase === 'move') { if (ev.phase === 'move') {
if (!isOverStrategyBuilder(ev.x, ev.y)) {
if (lastPreviewAnchorRef.current != null) {
lastPreviewAnchorRef.current = null;
lastPreviewKeyRef.current = null;
clearPreviewRef.current();
}
return;
}
const { flowPos, anchorId } = resolveDropFromClientRef.current(ev.x, ev.y); const { flowPos, anchorId } = resolveDropFromClientRef.current(ev.x, ev.y);
if (anchorId) { if (anchorId) {
const sideKey = `${anchorId}:${Math.round(flowPos.x / 8)}:${Math.round(flowPos.y / 8)}`;
if (sideKey === lastPreviewKeyRef.current) return;
lastPreviewKeyRef.current = sideKey;
lastPreviewAnchorRef.current = anchorId; lastPreviewAnchorRef.current = anchorId;
lastPreviewKeyRef.current = `${anchorId}:${Math.round(flowPos.x / 8)}:${Math.round(flowPos.y / 8)}`;
updatePreviewRef.current(anchorId, flowPos); updatePreviewRef.current(anchorId, flowPos);
return; return;
} }
+5
View File
@@ -2033,6 +2033,11 @@
.desktop-client .se-palette-card:active { .desktop-client .se-palette-card:active {
cursor: grabbing; cursor: grabbing;
} }
.se-palette-card--pointer {
touch-action: none;
-webkit-user-drag: none;
user-select: none;
}
.se-palette-card:hover { .se-palette-card:hover {
transform: translateY(-2px); transform: translateY(-2px);
} }
+15 -3
View File
@@ -1,5 +1,9 @@
import type { ReactFlowInstance } from '@xyflow/react'; import type { ReactFlowInstance } from '@xyflow/react';
import { getLastHtmlPaletteDragClientXY, isPaletteHtmlDragActive } from './paletteDragSession'; import {
getActivePaletteDrag,
getLastHtmlPaletteDragClientXY,
isPaletteHtmlDragActive,
} from './paletteDragSession';
type FlowProjector = Pick<ReactFlowInstance, 'screenToFlowPosition' | 'getViewport'>; type FlowProjector = Pick<ReactFlowInstance, 'screenToFlowPosition' | 'getViewport'>;
@@ -93,8 +97,9 @@ export function isOverStrategyBuilder(clientX: number, clientY: number): boolean
/** DOM hit-test — react-flow 노드 id (Safari HTML5 drag: elementsFromPoint 불가 → rect 우선) */ /** DOM hit-test — react-flow 노드 id (Safari HTML5 drag: elementsFromPoint 불가 → rect 우선) */
export function findFlowNodeIdAtClientPoint(clientX: number, clientY: number): string | null { export function findFlowNodeIdAtClientPoint(clientX: number, clientY: number): string | null {
const pad = 10; const pad = 10;
const nodeEls = document.querySelectorAll<HTMLElement>('.react-flow__node'); const nodeEls = Array.from(document.querySelectorAll<HTMLElement>('.react-flow__node'));
for (const nodeEl of nodeEls) { for (let i = nodeEls.length - 1; i >= 0; i--) {
const nodeEl = nodeEls[i];
const rect = nodeEl.getBoundingClientRect(); const rect = nodeEl.getBoundingClientRect();
if ( if (
clientX >= rect.left - pad && clientX <= rect.right + pad clientX >= rect.left - pad && clientX <= rect.right + pad
@@ -166,6 +171,13 @@ export function resolvePaletteDropClientPoint(
return { flowPos, anchorId: domId }; return { flowPos, anchorId: domId };
} }
if (getActivePaletteDrag()) {
const flowHit = findAtFlow(flowPos, nodes);
if (flowHit && isConnectTarget(flowHit.id)) {
return { flowPos, anchorId: flowHit.id };
}
}
for (const node of nodes) { for (const node of nodes) {
if (!isConnectTarget(node.id)) continue; if (!isConnectTarget(node.id)) continue;
const escaped = typeof CSS !== 'undefined' && CSS.escape const escaped = typeof CSS !== 'undefined' && CSS.escape
+3 -5
View File
@@ -7,7 +7,7 @@ import {
bindWindowDrag, bindWindowDrag,
isPrimaryPointerButton, isPrimaryPointerButton,
} from './pointerDrag'; } from './pointerDrag';
import { isDesktop, isMacDesktop } from './platform'; import { isDesktop } from './platform';
export type PaletteDragPayload = Record<string, unknown> & { export type PaletteDragPayload = Record<string, unknown> & {
type: string; type: string;
@@ -96,10 +96,8 @@ function hasCoarsePointer(): boolean {
} }
} }
/** HTML5 DnD 대신 pointer/touch overlay 드래그 사용 (Windows Desktop 등) */ /** HTML5 DnD 대신 pointer/touch overlay 드래그 (Tauri Desktop 전체 — macOS WKWebView HTML5 DnD 불안정) */
export function needsPointerPaletteDrag(): boolean { export function needsPointerPaletteDrag(): boolean {
// macOS Tauri WKWebView — Safari 웹과 동일하게 HTML5 DnD 사용
if (isMacDesktop()) return false;
return isDesktop() || hasCoarsePointer(); return isDesktop() || hasCoarsePointer();
} }
@@ -279,7 +277,7 @@ function startPaletteDragGesture(
finishDrag('end', xy); finishDrag('end', xy);
} }
}, },
{ preventTouchScroll: true }, { preventTouchScroll: true, useCapture: isDesktop() },
); );
} }
+1 -1
View File
@@ -368,7 +368,7 @@ export type EdgeHandleBinding = {
export const FLOW_NODE_W = 200; export const FLOW_NODE_W = 200;
export const FLOW_NODE_H = 72; export const FLOW_NODE_H = 72;
export const FLOW_START_W = 200; export const FLOW_START_W = 200;
export const FLOW_START_H = 78; export const FLOW_START_H = 168;
function nodeCenter( function nodeCenter(
id: string, id: string,