refactor: Ctrl+click toggles selection, Ctrl+click+drag copies group
Changed behavior: - Ctrl+Click on item (no drag): toggle selection (add/remove) - Ctrl+Click+Drag on item (movement > 5px): copy selected group - Alt+Click: move item immediately - No modifier + click selected item: move group Added pendingDragRef + useEffect to detect mousemove threshold before starting copy-drag. onAddToSelection + onSetPendingDrag props wired through WaveformLane.
This commit is contained in:
+57
-8
@@ -388,6 +388,8 @@ const WaveformLane = ({
|
||||
getLocalAnchor,
|
||||
onClearLocalSelection,
|
||||
onDeselectItem,
|
||||
onAddToSelection,
|
||||
onSetPendingDrag,
|
||||
onSetSelectionMode,
|
||||
onSetSelectionStart,
|
||||
onSetSelectionEnd,
|
||||
@@ -1080,10 +1082,18 @@ const WaveformLane = ({
|
||||
if (hitItem && !e.altKey && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && selectedItemIds && selectedItemIds.has(hitItem.id)) {
|
||||
if (e.ctrlKey) {
|
||||
// Ctrl+Click: toggle selection only, never start drag immediately
|
||||
if (selectedItemIds && selectedItemIds.has(hitItem.id)) {
|
||||
if (onDeselectItem) onDeselectItem(hitItem.id);
|
||||
} else if (onAddToSelection) {
|
||||
onAddToSelection(hitItem.id);
|
||||
}
|
||||
// Store pending drag so mousemove can start copy-drag
|
||||
if (onSetPendingDrag) onSetPendingDrag(track.id, hitItem.type, hitItem.id, time - hitItem.start, e.nativeEvent || e);
|
||||
} else {
|
||||
if (onSectionItemDragStart) onSectionItemDragStart(track.id, hitItem.type, hitItem.id, time - hitItem.start, e.ctrlKey);
|
||||
// No modifier: start drag immediately (single or multi move)
|
||||
if (onSectionItemDragStart) onSectionItemDragStart(track.id, hitItem.type, hitItem.id, time - hitItem.start, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1139,15 +1149,18 @@ const WaveformLane = ({
|
||||
if (clickedClip && (e.altKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && selectedItemIds) {
|
||||
if (e.ctrlKey) {
|
||||
// Ctrl+Click: toggle selection, store pending drag
|
||||
var clipCanonicalId = clickedClip.id === 'default' ? 'default_' + track.id : clickedClip.id;
|
||||
if (selectedItemIds.has(clipCanonicalId)) {
|
||||
if (selectedItemIds && selectedItemIds.has(clipCanonicalId)) {
|
||||
if (onDeselectItem) onDeselectItem(clipCanonicalId);
|
||||
return;
|
||||
} else if (onAddToSelection) {
|
||||
onAddToSelection(clipCanonicalId);
|
||||
}
|
||||
}
|
||||
if (onClipDragStart) {
|
||||
onClipDragStart(track.id, clickedClip.id, time - clickedClip.startTime, e.ctrlKey);
|
||||
if (onSetPendingDrag) onSetPendingDrag(track.id, 'clip', clipCanonicalId, time - clickedClip.startTime, e.nativeEvent || e);
|
||||
} else {
|
||||
// Alt+Click: move immediately
|
||||
if (onClipDragStart) onClipDragStart(track.id, clickedClip.id, time - clickedClip.startTime, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -6934,6 +6947,7 @@ const App = () => {
|
||||
const sweepStartRef = useRef(0);
|
||||
const sweepTrackIdRef = useRef(null);
|
||||
const sweepSelectRef = useRef(null);
|
||||
const pendingDragRef = useRef(null); // { trackId, itemType, itemId, clickOffset, startX, startY }
|
||||
const [localSelectionTrackId, setLocalSelectionTrackId] = useState(null);
|
||||
const [localSelectionStart, setLocalSelectionStart] = useState(null);
|
||||
const [localSelectionEnd, setLocalSelectionEnd] = useState(null);
|
||||
@@ -11641,6 +11655,18 @@ const App = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// ── Add item to selectedItemIds ──
|
||||
const handleAddToSelection = itemId => {
|
||||
setSelectedItemIds(prev => {
|
||||
var next = new Set(prev);
|
||||
next.add(itemId);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
const handleSetPendingDrag = (trackId, itemType, itemId, clickOffset, e) => {
|
||||
pendingDragRef.current = { trackId, itemType, itemId, clickOffset, startX: e.clientX, startY: e.clientY };
|
||||
};
|
||||
|
||||
// ── Sweep Select ──
|
||||
const handleSweepSelectStart = (trackId, startTime) => {
|
||||
isSweepingRef.current = true;
|
||||
@@ -11841,6 +11867,27 @@ const App = () => {
|
||||
};
|
||||
}, [zoom, activeTab, sessionTabs]);
|
||||
|
||||
// ── Pending drag: Ctrl+click toggles selection; mousemove > threshold starts copy-drag ──
|
||||
useEffect(() => {
|
||||
const handleMouseMove = e => {
|
||||
var pd = pendingDragRef.current;
|
||||
if (!pd) return;
|
||||
var dx = e.clientX - pd.startX;
|
||||
if (Math.abs(dx) > 5) {
|
||||
pendingDragRef.current = null;
|
||||
handleSectionItemDragStart(pd.trackId, pd.itemType, pd.itemId, pd.clickOffset, true);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
var handleMouseUp = function() { pendingDragRef.current = null; };
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
return function() {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
pendingDragRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ── Document-level mousemove/mouseup for Section/MIDI item resize ──
|
||||
useEffect(() => {
|
||||
const handleMouseMove = e => {
|
||||
@@ -16522,6 +16569,8 @@ const App = () => {
|
||||
onTrackLaneMouseDown: handleTrackLaneMouseDown,
|
||||
onSweepSelectStart: handleSweepSelectStart,
|
||||
onDeselectItem: handleDeselectItem,
|
||||
onAddToSelection: handleAddToSelection,
|
||||
onSetPendingDrag: handleSetPendingDrag,
|
||||
onContextMenu: handleContextMenu,
|
||||
onClipDragStart: handleClipDragStart,
|
||||
onClipStretchStart: handleClipStretchStart,
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user