-
-
Notifications
You must be signed in to change notification settings - Fork 366
Closed
Labels
Description
Is there an existing issue for this?
- I have searched the existing issues and this is a new bug.
Current Behavior
Hi, followed the idea from React Flow Lasso Selection Node and Figma Select Tool.
I have tried to implement the Rect Selection Tool Feature.
I am currently stuck that when the mouse up the node that is detected selected in mouse move, is unselected.
Here is the code
<script setup>
import { ref, shallowRef, onMounted, onUnmounted } from "vue";
import {
VueFlow,
useVueFlow,
getNodesInside,
MarkerType,
ConnectionLineType,
} from "@vue-flow/core";
import { Background } from "@vue-flow/background";
import { Controls } from "@vue-flow/controls";
import { MiniMap } from "@vue-flow/minimap";
import { initialEdges, initialNodes } from "./initial-elements.js";
import CustomNode from "./CustomNode.vue";
import SelectionRectangle from "./SelectionRectangle.vue"; // Thêm component mới
// --- State từ Code 1 (Logic chính) ---
const nodes = ref(initialNodes);
const edges = ref(initialEdges);
const nodeTypes = shallowRef({
custom: CustomNode,
});
const dark = ref(true);
const deleteKeyCodes = ["Delete", "Backspace"];
const connectionLineOptions = {
markerEnd: MarkerType.ArrowClosed,
};
// --- State từ Code 2 (Rectangle Select) ---
const isDrawing = ref(false);
const startPosition = ref({ x: 0, y: 0 });
const selectionRect = ref({ x: 0, y: 0, width: 0, height: 0 });
// --- Gộp `useVueFlow` từ cả 2 code ---
const {
// Từ Code 1
onInit,
onNodeDragStop,
onConnect,
addEdges,
setViewport,
toObject,
updateEdge,
// Từ Code 2
project,
getNodes,
viewport,
//addSelectedNodes,
//nodesSelectionActive,
//removeSelectedElements,
setNodes
} = useVueFlow();
// --- Logic từ Code 1 (Event Handlers) ---
const onEdgeUpdate = ({ edge, connection }) => {
updateEdge(edge, connection);
};
onInit((vueFlowInstance) => {
vueFlowInstance.fitView();
});
// onNodeDragStop(({ event, nodes, node }) => {
// console.log("Node Drag Stop", { event, nodes, node });
// });
onConnect((connection) => {
addEdges({
...connection,
type: "step",
markerEnd: MarkerType.ArrowClosed,
updatable: true,
});
});
// --- Logic từ Code 1 (Utility Functions) ---
function updatePos() {
nodes.value = nodes.value.map((node) => {
return {
...node,
position: {
x: Math.random() * 400,
y: Math.random() * 400,
},
};
});
}
// function logToObject() {
// console.log(toObject());
// }
function resetTransform() {
setViewport({ x: 0, y: 0, zoom: 1 });
}
function toggleDarkMode() {
dark.value = !dark.value;
}
// --- Logic từ Code 2 (Rectangle Select Handlers) ---
// (Đã loại bỏ TypeScript types để phù hợp với code 1)
function handleMouseDown(event) {
// Chỉ bắt đầu vẽ nếu nhấp chuột trái
if (event.button !== 0) return;
// Bỏ chọn tất cả khi bắt đầu kéo mới
//removeSelectedElements();
//nodesSelectionActive.value = false;
isDrawing.value = true;
startPosition.value = { x: event.clientX, y: event.clientY };
selectionRect.value = {
x: event.clientX,
y: event.clientY,
width: 0,
height: 0,
};
// Ngăn chặn hành vi mặc định (như chọn văn bản)
event.preventDefault();
}
function handleMouseMove(event) {
if (!isDrawing.value) return;
const currentX = event.clientX;
const currentY = event.clientY;
const rectX = Math.min(startPosition.value.x, currentX);
const rectY = Math.min(startPosition.value.y, currentY);
const rectWidth = Math.abs(startPosition.value.x - currentX);
const rectHeight = Math.abs(startPosition.value.y - currentY);
selectionRect.value = {
x: rectX,
y: rectY,
width: rectWidth,
height: rectHeight,
};
// Chuyển đổi sang tọa độ viewport
const viewportRect = screenRectToViewportRect(selectionRect.value, project);
// Lấy mảng nodes hiện tại từ instance (getNodes là hàm)
const currentNodes = typeof getNodes === "function" ? getNodes() : (getNodes?.value ?? []);
// Tìm nodes nằm trong rectangle (getNodesInside cần mảng nodes)
const nodesInside = getNodesInside(
currentNodes,
viewportRect,
viewport.value,
true // partial selection?
);
// lấy id của nodes trong vùng
const nodesToSelectIds = nodesInside.map((n) => n.id);
// Vì bạn đang truyền `:nodes="nodes"` (controlled),
// cần cập nhật nodes.value để phản ánh selection
nodes.value = nodes.value.map((node) => ({
...node,
selected: nodesToSelectIds.includes(node.id),
}));
// nếu bạn muốn debug
// console.log('inside', nodesInside.map(n => n.id));
}
function handleMouseUp(event) {
if (isDrawing.value) {
event.stopPropagation();
event.preventDefault();
}
if (!isDrawing.value) return;
isDrawing.value = false;
// Kích hoạt hộp bao lựa chọn trực quan
// if (getNodes.value.some((n) => n.selected)) {
// nodesSelectionActive.value = true;
// }
}
// --- Logic từ Code 2 (Utility & Lifecycle) ---
/**
* Chuyển đổi một Rect từ tọa độ màn hình sang tọa độ viewport
*/
function screenRectToViewportRect(screenRect, project) {
const topLeft = project({ x: screenRect.x, y: screenRect.y });
const bottomRight = project({
x: screenRect.x + screenRect.width,
y: screenRect.y + screenRect.height,
});
return {
x: topLeft.x,
y: topLeft.y,
width: bottomRight.x - topLeft.x,
height: bottomRight.y - topLeft.y,
};
}
//Lắng nghe mouseup trên window
// onMounted(() => {
// window.addEventListener("mouseup", handleMouseUp);
// });
// onUnmounted(() => {
// window.removeEventListener("mouseup", handleMouseUp);
// });
</script>
<template>
<div @mousedown="handleMouseDown" @mousemove="handleMouseMove" @mouseup="handleMouseUp"
style="width: 100vw; height: 100vh">
<VueFlow :nodes="nodes" :node-types="nodeTypes" :edges="edges" :class="{ dark }" class="basic-flow"
:default-viewport="{ zoom: 1.5 }" :min-zoom="0.2" :max-zoom="4" :pan-on-drag="false" :pan-on-scroll="true"
:zoom-on-scrol="false" :connection-line-type="ConnectionLineType.Step" :edgesUpdatable="true"
:delete-key-code="deleteKeyCodes" @edge-update="onEdgeUpdate" :style="{ background: '#ddd' }">
<Background pattern-color="#aaa" :gap="16" />
<MiniMap />
<Controls position="top-left">
</Controls>
</VueFlow>
<SelectionRectangle v-if="isDrawing" :rect="selectionRect" />
</div>
</template>
<style>
/* Style từ Code 2 */
@import "@vue-flow/core/dist/style.css";
@import "@vue-flow/core/dist/theme-default.css";
html,
body {
margin: 0;
overflow: hidden;
}
</style>Expected Behavior
When mouse up, the node is kept selected without being unselected.
Steps To Reproduce
No response
Minimal reproduction of the issue with CodeSandbox
No response
Relevant log output
Anything else?
No response