Skip to content

Commit 0a147b4

Browse files
fix drag selection in editor
1 parent 702a961 commit 0a147b4

File tree

2 files changed

+88
-83
lines changed

2 files changed

+88
-83
lines changed

client/packages/lowcoder/src/comps/comps/gridLayoutComp/canvasView.tsx

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export const CanvasView = React.memo((props: ContainerBaseProps) => {
9898
const isPreviewTheme = useContext(ThemeContext)?.themeId === 'preview-theme';
9999
const editorState = useContext(EditorContext);
100100
const [dragSelectedComps, setDragSelectedComp] = useState<Set<string>>(new Set());
101+
const currentSelectionRef = useRef<Set<string>>(new Set());
101102
const scrollContainerRef = useRef<HTMLDivElement>(null);
102103
const mountedRef = useRef(true);
103104
const appSettings = editorState.getAppSettings();
@@ -108,9 +109,15 @@ export const CanvasView = React.memo((props: ContainerBaseProps) => {
108109
return () => {
109110
mountedRef.current = false;
110111
setDragSelectedComp(new Set());
112+
currentSelectionRef.current = new Set();
111113
};
112114
}, []);
113115

116+
// Keep ref in sync with state
117+
useEffect(() => {
118+
currentSelectionRef.current = dragSelectedComps;
119+
}, [dragSelectedComps]);
120+
114121
// Memoized drag selection handler
115122
const handleDragSelection = useCallback((checkSelectFunc?: CheckSelectFn) => {
116123
if (!mountedRef.current) return new Set<string>();
@@ -135,6 +142,25 @@ export const CanvasView = React.memo((props: ContainerBaseProps) => {
135142
return selectedComps;
136143
}, [props.items, props.layout]);
137144

145+
const handleMouseMove = useCallback((checkSelectFunc: CheckSelectFn) => {
146+
if (mountedRef.current) {
147+
const selectedName = handleDragSelection(checkSelectFunc);
148+
setDragSelectedComp(new Set(selectedName));
149+
}
150+
}, [handleDragSelection]);
151+
152+
const handleMouseUp = useCallback(() => {
153+
if (mountedRef.current) {
154+
const currentSelection = new Set(currentSelectionRef.current);
155+
setDragSelectedComp(new Set());
156+
editorState.setSelectedCompNames(currentSelection);
157+
}
158+
}, [editorState]);
159+
160+
const handleMouseDown = useCallback(() => {
161+
setDragSelectedComp(new Set());
162+
}, []);
163+
138164
const maxWidth = useMemo(
139165
() => appSettings.maxWidth ?? maxWidthFromHook,
140166
[appSettings, maxWidthFromHook]
@@ -277,25 +303,6 @@ export const CanvasView = React.memo((props: ContainerBaseProps) => {
277303
rowHeight: parseInt(defaultRowHeight),
278304
}), [props.positionParams, defaultGrid, defaultRowHeight]);
279305

280-
// Memoized mouse event handlers
281-
const handleMouseDown = useCallback(() => {
282-
setDragSelectedComp(new Set());
283-
}, []);
284-
285-
const handleMouseUp = useCallback(() => {
286-
if (mountedRef.current) {
287-
editorState.setSelectedCompNames(dragSelectedComps);
288-
setDragSelectedComp(new Set());
289-
}
290-
}, [editorState, dragSelectedComps]);
291-
292-
const handleMouseMove = useCallback((checkSelectFunc: CheckSelectFn) => {
293-
if (mountedRef.current) {
294-
const selectedName = handleDragSelection(checkSelectFunc);
295-
setDragSelectedComp(selectedName);
296-
}
297-
}, [handleDragSelection]);
298-
299306
if (readOnly) {
300307
return (
301308
<UICompContainer

client/packages/lowcoder/src/comps/comps/gridLayoutComp/dragSelector.tsx

Lines changed: 62 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Layers } from "constants/Layers";
2-
import React, { ReactNode, useCallback, useRef, useState, useEffect } from "react";
2+
import React, { ReactNode, useCallback, useRef, useEffect } from "react";
33

44
export type CheckSelectFn = (
55
item?: HTMLDivElement | null,
@@ -41,7 +41,7 @@ const createInitialState = (): SectionState => ({
4141

4242
export const DragSelector = React.memo((props: SectionProps) => {
4343
const selectAreaRef = useRef<HTMLDivElement>(null);
44-
const [state, setState] = useState<SectionState>(createInitialState());
44+
const stateRef = useRef<SectionState>(createInitialState());
4545
const mountedRef = useRef(true);
4646

4747
// Cleanup on unmount
@@ -54,18 +54,62 @@ export const DragSelector = React.memo((props: SectionProps) => {
5454
};
5555
}, []);
5656

57+
const rectIntersect = useCallback((
58+
selectionBox: Rect | undefined,
59+
item: HTMLElement | null | undefined
60+
): boolean => {
61+
if (!selectionBox || !item || !selectAreaRef.current) return false;
62+
63+
const containerRect = selectAreaRef.current.getBoundingClientRect();
64+
const itemBox = {
65+
top: item.getBoundingClientRect().top - containerRect.top,
66+
left: item.getBoundingClientRect().left - containerRect.left,
67+
width: item.getBoundingClientRect().width,
68+
height: item.getBoundingClientRect().height,
69+
};
70+
71+
return (
72+
selectionBox.left <= itemBox.left + itemBox.width &&
73+
selectionBox.left + selectionBox.width >= itemBox.left &&
74+
selectionBox.top <= itemBox.top + itemBox.height &&
75+
selectionBox.top + selectionBox.height >= itemBox.top
76+
);
77+
}, []);
78+
79+
const calculateSelectionBox = useCallback((startPoint: Point | undefined, endPoint: Point) => {
80+
if (!stateRef.current.mouseDown || !startPoint || !endPoint) return undefined;
81+
82+
return {
83+
left: Math.min(startPoint.x, endPoint.x),
84+
top: Math.min(startPoint.y, endPoint.y),
85+
width: Math.abs(startPoint.x - endPoint.x),
86+
height: Math.abs(startPoint.y - endPoint.y),
87+
};
88+
}, []);
89+
90+
const childrenViewCheckFunc = useCallback((
91+
item?: HTMLDivElement | null,
92+
afterCheck?: (checkResult: boolean) => void
93+
) => {
94+
const result = rectIntersect(stateRef.current.selectionBox, item);
95+
if (afterCheck) {
96+
afterCheck(result);
97+
}
98+
return result;
99+
}, [rectIntersect]);
100+
57101
const handleMouseMove = useCallback((e: MouseEvent) => {
58-
if (!mountedRef.current || !state.mouseDown) return;
102+
if (!mountedRef.current || !stateRef.current.mouseDown) return;
59103

60104
const endPoint = {
61105
x: e.pageX - (selectAreaRef.current?.getBoundingClientRect().left ?? 0),
62106
y: e.pageY - (selectAreaRef.current?.getBoundingClientRect().top ?? 0),
63107
};
64108

65-
setState(prevState => ({
66-
...prevState,
67-
selectionBox: calculateSelectionBox(prevState.startPoint, endPoint),
68-
}));
109+
stateRef.current = {
110+
...stateRef.current,
111+
selectionBox: calculateSelectionBox(stateRef.current.startPoint, endPoint),
112+
};
69113

70114
// Clean up selection properly
71115
const selection = window.getSelection();
@@ -74,83 +118,37 @@ export const DragSelector = React.memo((props: SectionProps) => {
74118
}
75119

76120
props.onMouseMove(childrenViewCheckFunc);
77-
}, [state.mouseDown, state.startPoint, props.onMouseMove]);
121+
}, [props.onMouseMove, calculateSelectionBox, childrenViewCheckFunc]);
78122

79123
const handleMouseUp = useCallback(() => {
80-
if (!mountedRef.current) return;
81-
82124
window.document.removeEventListener("mousemove", handleMouseMove);
83125
window.document.removeEventListener("mouseup", handleMouseUp);
84126
props.onMouseUp();
85-
setState(createInitialState());
127+
stateRef.current = createInitialState();
86128
}, [handleMouseMove, props.onMouseUp]);
87129

88130
const handleMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
89-
if (!mountedRef.current || e.button === 2 || e.nativeEvent.which === 2) return;
131+
if (e.button === 2 || e.nativeEvent.which === 2) return;
90132

91133
const startPoint = {
92134
x: e.pageX - (selectAreaRef.current?.getBoundingClientRect().left ?? 0),
93135
y: e.pageY - (selectAreaRef.current?.getBoundingClientRect().top ?? 0),
94136
};
95137

96-
setState({
138+
stateRef.current = {
97139
mouseDown: true,
98140
startPoint,
99141
selectionBox: undefined,
100142
appendMode: false,
101-
});
143+
};
102144

103145
window.document.addEventListener("mousemove", handleMouseMove);
104146
window.document.addEventListener("mouseup", handleMouseUp);
105147
props.onMouseDown();
106148
}, [handleMouseMove, handleMouseUp, props.onMouseDown]);
107149

108-
const rectIntersect = useCallback((
109-
selectionBox: Rect | undefined,
110-
item: HTMLElement | null | undefined
111-
): boolean => {
112-
if (!selectionBox || !item || !selectAreaRef.current) return false;
113-
114-
const containerRect = selectAreaRef.current.getBoundingClientRect();
115-
const itemBox = {
116-
top: item.getBoundingClientRect().top - containerRect.top,
117-
left: item.getBoundingClientRect().left - containerRect.left,
118-
width: item.getBoundingClientRect().width,
119-
height: item.getBoundingClientRect().height,
120-
};
121-
122-
return (
123-
selectionBox.left <= itemBox.left + itemBox.width &&
124-
selectionBox.left + selectionBox.width >= itemBox.left &&
125-
selectionBox.top <= itemBox.top + itemBox.height &&
126-
selectionBox.top + selectionBox.height >= itemBox.top
127-
);
128-
}, []);
129-
130-
const childrenViewCheckFunc = useCallback((
131-
item?: HTMLDivElement | null,
132-
afterCheck?: (checkResult: boolean) => void
133-
) => {
134-
const result = rectIntersect(state.selectionBox, item);
135-
if (afterCheck) {
136-
afterCheck(result);
137-
}
138-
return result;
139-
}, [state.selectionBox, rectIntersect]);
140-
141-
const calculateSelectionBox = useCallback((startPoint: Point | undefined, endPoint: Point) => {
142-
if (!state.mouseDown || !startPoint || !endPoint) return undefined;
143-
144-
return {
145-
left: Math.min(startPoint.x, endPoint.x),
146-
top: Math.min(startPoint.y, endPoint.y),
147-
width: Math.abs(startPoint.x - endPoint.x),
148-
height: Math.abs(startPoint.y - endPoint.y),
149-
};
150-
}, [state.mouseDown]);
151-
152150
const renderSelectionBox = useCallback(() => {
153-
if (!state.mouseDown || !state.startPoint || !state.selectionBox || !selectAreaRef.current) {
151+
if (!stateRef.current.mouseDown || !stateRef.current.startPoint || !stateRef.current.selectionBox || !selectAreaRef.current) {
154152
return null;
155153
}
156154

@@ -160,14 +158,14 @@ export const DragSelector = React.memo((props: SectionProps) => {
160158
background: "rgba(51, 119, 255, 0.1)",
161159
position: "absolute",
162160
zIndex: Layers.dragSelectBox,
163-
left: state.selectionBox.left,
164-
top: state.selectionBox.top,
165-
height: state.selectionBox.height,
166-
width: state.selectionBox.width,
161+
left: stateRef.current.selectionBox.left,
162+
top: stateRef.current.selectionBox.top,
163+
height: stateRef.current.selectionBox.height,
164+
width: stateRef.current.selectionBox.width,
167165
}}
168166
/>
169167
);
170-
}, [state.mouseDown, state.startPoint, state.selectionBox]);
168+
}, []);
171169

172170
return (
173171
<div

0 commit comments

Comments
 (0)