mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-07 01:54:24 +08:00
feat(canvas): update canvas tool behavior to allow temporary switching between Select and Move modes using Control or Space
This commit is contained in:
@@ -154,7 +154,7 @@ export function CanvasTopBar({
|
||||
</div>
|
||||
<Modal title={t("canvas.shortcuts")} open={shortcutsOpen} onCancel={() => setShortcutsOpen(false)} footer={null} centered>
|
||||
<div className="space-y-2 border-t pt-4 text-sm" style={{ borderColor: theme.node.stroke }}>
|
||||
<Shortcut keys={["Ctrl / Space", t("canvas.shortcut.drag")]} value={t("canvas.shortcut.pan")} />
|
||||
<Shortcut keys={["Ctrl / Space", t("canvas.shortcut.drag")]} value={t("canvas.shortcut.toggleTool")} />
|
||||
<Shortcut keys={[t("canvas.shortcut.wheel")]} value={t("canvas.shortcut.zoom")} />
|
||||
<Shortcut keys={[t("canvas.shortcut.zoomSlider")]} value={t("canvas.shortcut.preciseZoom")} />
|
||||
<Shortcut keys={[t("canvas.shortcut.drag")]} value={t("canvas.shortcut.boxSelect")} />
|
||||
|
||||
@@ -61,7 +61,7 @@ export function CanvasZoomControls({ scale, onScaleChange, onReset, isMiniMapOpe
|
||||
</div>
|
||||
<Modal title={t("canvas.shortcuts")} open={shortcutsOpen} onCancel={() => setShortcutsOpen(false)} footer={null} centered>
|
||||
<div className="space-y-3 border-t pt-4 text-sm" style={{ borderColor: theme.node.stroke }}>
|
||||
<Shortcut label={`Ctrl / Space + ${t("canvas.shortcut.drag")}`} value={t("canvas.shortcut.pan")} />
|
||||
<Shortcut label={`Ctrl / Space + ${t("canvas.shortcut.drag")}`} value={t("canvas.shortcut.toggleTool")} />
|
||||
<Shortcut label={t("canvas.shortcut.wheel")} value={t("canvas.shortcut.zoom")} />
|
||||
<Shortcut label={t("canvas.shortcut.drag")} value={t("canvas.shortcut.boxSelect")} />
|
||||
<Shortcut label={`Shift / Cmd + ${t("canvas.shortcut.click")}`} value={t("canvas.shortcut.addSelection")} />
|
||||
|
||||
@@ -50,11 +50,19 @@ export function InfiniteCanvas({ containerRef, viewport, tool, backgroundMode =
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Control") setIsControlPressed(true);
|
||||
if (event.code === "Space" && !(event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement)) setIsSpacePressed(true);
|
||||
if (event.code !== "Space") return;
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLSelectElement || target?.closest("[contenteditable='true']")) return;
|
||||
event.preventDefault();
|
||||
setIsSpacePressed(true);
|
||||
};
|
||||
|
||||
const handleKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.code === "Space") setIsSpacePressed(false);
|
||||
if (event.code === "Space") {
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLSelectElement || target?.closest("[contenteditable='true']"))) event.preventDefault();
|
||||
setIsSpacePressed(false);
|
||||
}
|
||||
if (event.key === "Control") setIsControlPressed(false);
|
||||
};
|
||||
|
||||
@@ -103,7 +111,9 @@ export function InfiniteCanvas({ containerRef, viewport, tool, backgroundMode =
|
||||
if (target?.closest("[data-canvas-no-zoom]")) return;
|
||||
if (target?.closest("[data-connection-create-menu]")) return;
|
||||
const isBackgroundClick = !target?.closest("[data-node-id],[data-connection-id]");
|
||||
const shouldPan = event.button === 1 || (event.button === 0 && (tool === "pan" || event.ctrlKey || isSpacePressed));
|
||||
const temporaryTool = event.ctrlKey || isSpacePressed;
|
||||
const activeTool = temporaryTool ? (tool === "select" ? "pan" : "select") : tool;
|
||||
const shouldPan = event.button === 1 || (event.button === 0 && activeTool === "pan");
|
||||
|
||||
if (shouldPan) {
|
||||
event.preventDefault();
|
||||
@@ -193,7 +203,9 @@ export function InfiniteCanvas({ containerRef, viewport, tool, backgroundMode =
|
||||
return () => container.removeEventListener("wheel", preventWheelScroll);
|
||||
}, [containerRef]);
|
||||
|
||||
const cursor = isPanning ? "grabbing" : tool === "pan" || isControlPressed || isSpacePressed ? "grab" : undefined;
|
||||
const temporaryTool = isControlPressed || isSpacePressed;
|
||||
const activeTool = temporaryTool ? (tool === "select" ? "pan" : "select") : tool;
|
||||
const cursor = isPanning ? "grabbing" : activeTool === "pan" ? "grab" : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -290,6 +290,7 @@ export default {
|
||||
shortcut: {
|
||||
dragCanvas: "Drag canvas",
|
||||
pan: "Pan view",
|
||||
toggleTool: "Temporarily switch Select / Move",
|
||||
wheel: "Mouse wheel",
|
||||
zoom: "Zoom canvas",
|
||||
zoomSlider: "Zoom slider",
|
||||
|
||||
@@ -290,6 +290,7 @@ export default {
|
||||
shortcut: {
|
||||
dragCanvas: "拖动画布",
|
||||
pan: "平移视图",
|
||||
toggleTool: "临时切换选择 / 移动",
|
||||
wheel: "滚轮",
|
||||
zoom: "缩放画布",
|
||||
zoomSlider: "缩放滑杆",
|
||||
|
||||
@@ -2867,7 +2867,7 @@ function InfiniteCanvasPage() {
|
||||
height: Math.abs(selectionBox.currentWorldY - selectionBox.startWorldY),
|
||||
}}
|
||||
>
|
||||
<rect width="100%" height="100%" fill={theme.canvas.selectionFill} stroke={theme.canvas.selectionStroke} strokeWidth={1.5 / viewport.k} strokeDasharray={`${10 / viewport.k} ${6 / viewport.k}`} />
|
||||
<rect width="100%" height="100%" fill={theme.canvas.selectionFill} stroke={theme.canvas.selectionStroke} strokeOpacity={0.55} strokeWidth={1 / viewport.k} strokeDasharray={`${6 / viewport.k} ${4 / viewport.k}`} />
|
||||
</svg>
|
||||
) : null}
|
||||
{pendingConnectionCreate ? <ConnectionCreateMenu pending={pendingConnectionCreate} onCreate={(type) => createConnectedNode(type, pendingConnectionCreate)} onClose={cancelPendingConnectionCreate} /> : null}
|
||||
|
||||
Reference in New Issue
Block a user