From a2f12ca96179c47186592f5192196291690b4bb5 Mon Sep 17 00:00:00 2001 From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com> Date: Mon, 4 May 2026 18:34:29 -0700 Subject: [PATCH] fix: minor updated the region lasso selection behavior --- src/components/MainContent.tsx | 11 +++++++ src/components/track/TrackGridPanel.test.tsx | 11 +++++++ src/components/track/TrackGridPanel.tsx | 32 ++++++++++++++++++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/components/MainContent.tsx b/src/components/MainContent.tsx index 5009d9d..5376320 100644 --- a/src/components/MainContent.tsx +++ b/src/components/MainContent.tsx @@ -94,6 +94,7 @@ const MainContent: React.FC = ({ // Refs to track pending updates for verification const pendingUpdates = useRef>(new Map()); + const preventEmptyMainContentDeselectRef = useRef(false); // Refs for auto-scroll during playback const mainContentRef = useRef(null); @@ -632,9 +633,18 @@ const MainContent: React.FC = ({ return; } + if (preventEmptyMainContentDeselectRef.current) { + preventEmptyMainContentDeselectRef.current = false; + return; + } + handleRegionLassoSelection([], { shiftKey: false }); }; + const handleRegionLassoCommit = () => { + preventEmptyMainContentDeselectRef.current = true; + }; + // Handle region single click: selection only (no piano roll opening) const handleRegionClick = (regionId: string, options: RegionClickOptions = { shiftKey: false }) => { if (DEBUG_MODE.MAIN_CONTENT) { @@ -990,6 +1000,7 @@ const MainContent: React.FC = ({ onRegionUpdated={handleRegionUpdated} onRegionClick={handleRegionClick} onRegionLassoSelection={handleRegionLassoSelection} + onRegionLassoCommit={handleRegionLassoCommit} onOpenPianoRoll={handleOpenPianoRoll} onOpenSpectrogram={handleOpenSpectrogram} showHybridButtonForAudio={showHybridButtonForAudio} diff --git a/src/components/track/TrackGridPanel.test.tsx b/src/components/track/TrackGridPanel.test.tsx index d900ef2..af80b75 100644 --- a/src/components/track/TrackGridPanel.test.tsx +++ b/src/components/track/TrackGridPanel.test.tsx @@ -119,6 +119,17 @@ describe('TrackGridPanel lasso selection', () => { expect(onRegionLassoSelection).toHaveBeenCalledWith(['region-b', 'region-a'], { shiftKey: false }); }); + it('uses the closest intersected region as primary when release is outside all regions', () => { + const { container, onRegionLassoSelection } = renderPanel(); + const firstTrackGrid = container.querySelector('[data-test-id="track-grid-1"]') as HTMLDivElement; + + fireEvent.mouseDown(firstTrackGrid, { clientX: 10, clientY: 10, button: 0 }); + fireEvent.mouseMove(document, { clientX: 150, clientY: 170 }); + fireEvent.mouseUp(document, { clientX: 150, clientY: 170 }); + + expect(onRegionLassoSelection).toHaveBeenCalledWith(['region-a', 'region-b'], { shiftKey: false }); + }); + it('clears selection on a plain empty-space click', () => { const { container, onRegionLassoSelection } = renderPanel(); const firstTrackGrid = container.querySelector('[data-test-id="track-grid-1"]') as HTMLDivElement; diff --git a/src/components/track/TrackGridPanel.tsx b/src/components/track/TrackGridPanel.tsx index ba13681..a14020f 100644 --- a/src/components/track/TrackGridPanel.tsx +++ b/src/components/track/TrackGridPanel.tsx @@ -32,6 +32,7 @@ interface TrackGridPanelProps { onRegionUpdated?: (regionId: string, updates: Partial, expectedModelUpdates?: { startBeat: number, length: number }) => void; onRegionClick?: (regionId: string, options: RegionClickOptions) => void; onRegionLassoSelection?: (regionIds: string[], options: RegionClickOptions) => void; + onRegionLassoCommit?: () => void; onOpenPianoRoll?: (regionId: string) => void; onOpenSpectrogram?: (regionId: string) => void; showHybridButtonForAudio?: boolean; @@ -53,6 +54,7 @@ const TrackGridPanel: React.FC = ({ onRegionUpdated, onRegionClick, onRegionLassoSelection, + onRegionLassoCommit, onOpenPianoRoll, onOpenSpectrogram, showHybridButtonForAudio, @@ -160,12 +162,36 @@ const TrackGridPanel: React.FC = ({ }); const intersectedRegionIds = intersectedRegions.map(region => region.id); - if (releaseRegionIndex > -1) { - const [releaseRegionId] = intersectedRegionIds.splice(releaseRegionIndex, 1); - intersectedRegionIds.push(releaseRegionId); + if (intersectedRegionIds.length > 0) { + const primaryRegionIndex = releaseRegionIndex > -1 + ? releaseRegionIndex + : intersectedRegions.reduce((closestIndex, region, index, allRegions) => { + const regionLeft = (region.barNumber - 1) * barWidth; + const regionRight = regionLeft + (region.length * barWidth); + const regionTop = region.trackIndex * 120; + const regionBottom = regionTop + 120; + const regionCenterX = (regionLeft + regionRight) / 2; + const regionCenterY = (regionTop + regionBottom) / 2; + const regionDistance = Math.hypot(releasePointX - regionCenterX, releasePointY - regionCenterY); + + const closestRegion = allRegions[closestIndex]; + const closestLeft = (closestRegion.barNumber - 1) * barWidth; + const closestRight = closestLeft + (closestRegion.length * barWidth); + const closestTop = closestRegion.trackIndex * 120; + const closestBottom = closestTop + 120; + const closestCenterX = (closestLeft + closestRight) / 2; + const closestCenterY = (closestTop + closestBottom) / 2; + const closestDistance = Math.hypot(releasePointX - closestCenterX, releasePointY - closestCenterY); + + return regionDistance < closestDistance ? index : closestIndex; + }, 0); + + const [primaryRegionId] = intersectedRegionIds.splice(primaryRegionIndex, 1); + intersectedRegionIds.push(primaryRegionId); } onRegionLassoSelection?.(intersectedRegionIds, { shiftKey: isLassoShiftPressedRef.current }); + onRegionLassoCommit?.(); } isLassoSelectingRef.current = false;