feat: preview all the notes/regions during resizing/moving multiple notes and regions

This commit is contained in:
Xiaohan-Tian
2026-05-19 17:35:09 -07:00
parent 06e2162eea
commit 4b5b5c8d76
6 changed files with 889 additions and 169 deletions
@@ -239,8 +239,8 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
const noteId = note.getId();
// Check if this note is being resized or dragged and has a temporary style
if ((resizingNoteId === noteId || draggingNoteId === noteId) && tempNoteStyles[noteId]) {
// Use preview geometry whenever this note has an active temporary style.
if (tempNoteStyles[noteId]) {
// Use the temporary style for position and size
const tempStyle = tempNoteStyles[noteId];
+302 -35
View File
@@ -1,41 +1,69 @@
import React from 'react';
import { beforeAll, describe, expect, it, vi } from 'vitest';
import { render } from '@testing-library/react';
import React, { useState } from 'react';
import { act, render } from '@testing-library/react';
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import TrackGridItem from './TrackGridItem';
import { KGAudioTrack } from '../../core/track/KGAudioTrack';
import { KGMainContentState } from '../../core/state/KGMainContentState';
import { createMockMidiRegion, createMockMidiTrack } from '../../test/utils/mock-data';
const storeState = {
selectedRegionIds: [] as string[],
activeTrackAutomationTrackId: null as string | null,
activeTrackAutomationType: null,
trackAutomationRedrawVersion: 0,
recordingMode: 'audio' as 'audio' | 'midi' | null,
recordingTargetTrackIndex: 0,
recordingCommitStartBeatAbsolute: 4,
recordingAudioPreviewCurrentBeat: 8,
recordingAudioPreviewPeaks: [{ min: -0.5, max: 0.5 }],
recordingAudioPreviewFileName: 'Recording' as string | null,
timeSignature: { numerator: 4, denominator: 4 },
};
const regionItemProps = new Map<string, Record<string, unknown>>();
vi.mock('../../stores/projectStore', () => ({
useProjectStore: (selector?: (state: {
selectedRegionIds: string[];
activeTrackAutomationTrackId: string | null;
activeTrackAutomationType: null;
trackAutomationRedrawVersion: number;
recordingMode: 'audio' | 'midi' | null;
recordingTargetTrackIndex: number | null;
recordingCommitStartBeatAbsolute: number;
recordingAudioPreviewCurrentBeat: number;
recordingAudioPreviewPeaks: Array<{ min: number; max: number }>;
recordingAudioPreviewFileName: string | null;
timeSignature: { numerator: number; denominator: number };
}) => unknown) => {
const state = {
selectedRegionIds: [],
activeTrackAutomationTrackId: null,
activeTrackAutomationType: null,
trackAutomationRedrawVersion: 0,
recordingMode: 'audio' as const,
recordingTargetTrackIndex: 0,
recordingCommitStartBeatAbsolute: 4,
recordingAudioPreviewCurrentBeat: 8,
recordingAudioPreviewPeaks: [{ min: -0.5, max: 0.5 }],
recordingAudioPreviewFileName: 'Recording',
timeSignature: { numerator: 4, denominator: 4 },
};
return selector ? selector(state) : state;
useProjectStore: (selector?: (state: typeof storeState) => unknown) => (
selector ? selector(storeState) : storeState
),
}));
vi.mock('./RegionItem', () => ({
default: (props: Record<string, unknown>) => {
regionItemProps.set(props.id as string, props);
return (
<div
data-region-id={props.id as string}
data-preview-region={(props.isPreview as boolean | undefined) ? 'true' : 'false'}
style={props.style as React.CSSProperties}
/>
);
},
}));
describe('TrackGridItem recording preview', () => {
vi.mock('./TrackAutomationLane', () => ({
default: () => null,
}));
vi.mock('../../core/audio-interface/KGAudioInterface', () => ({
KGAudioInterface: {
instance: () => ({
getAudioBuffer: () => undefined,
}),
},
}));
describe('TrackGridItem preview behavior', () => {
const getRegionItem = (regionId: string) => regionItemProps.get(regionId) as {
style: React.CSSProperties;
onResizeStart?: (regionId: string, resizeAction: 'start' | 'end', initialX: number) => void;
onResize?: (regionId: string, resizeAction: 'start' | 'end', deltaX: number) => void;
onResizeEnd?: (regionId: string, resizeAction: 'start' | 'end') => void;
onDragStart?: (regionId: string, initialX: number, initialY: number) => void;
onDrag?: (regionId: string, deltaX: number, deltaY: number) => void;
onDragEnd?: (regionId: string) => void;
};
beforeAll(() => {
Object.defineProperty(HTMLCanvasElement.prototype, 'getContext', {
value: vi.fn(() => ({
@@ -57,11 +85,97 @@ describe('TrackGridItem recording preview', () => {
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
});
beforeEach(() => {
regionItemProps.clear();
storeState.selectedRegionIds = [];
storeState.activeTrackAutomationTrackId = null;
storeState.activeTrackAutomationType = null;
storeState.trackAutomationRedrawVersion = 0;
storeState.recordingMode = 'audio';
storeState.recordingTargetTrackIndex = 0;
storeState.recordingCommitStartBeatAbsolute = 4;
storeState.recordingAudioPreviewCurrentBeat = 8;
storeState.recordingAudioPreviewPeaks = [{ min: -0.5, max: 0.5 }];
storeState.recordingAudioPreviewFileName = 'Recording';
storeState.timeSignature = { numerator: 4, denominator: 4 };
KGMainContentState.instance().setActiveTool('pointer');
KGMainContentState.instance().setSnapping(true);
});
const createGridContainerRef = () => {
const gridElement = document.createElement('div');
Object.defineProperty(gridElement, 'clientWidth', { configurable: true, value: 800 });
Object.defineProperty(gridElement, 'clientHeight', { configurable: true, value: 240 });
return { current: gridElement };
};
const renderSharedPreviewHarness = (
selectedRegionIds: string[] = [],
regionOverrides: Array<{ id: string; trackId: string; trackIndex: number; barNumber: number; length: number; name: string }> = [
{ id: 'region-a', trackId: '1', trackIndex: 0, barNumber: 1, length: 1, name: 'Region A' },
{ id: 'region-b', trackId: '2', trackIndex: 1, barNumber: 3, length: 2, name: 'Region B' },
],
) => {
storeState.selectedRegionIds = selectedRegionIds;
const regionAData = regionOverrides.find(region => region.id === 'region-a')!;
const regionBData = regionOverrides.find(region => region.id === 'region-b')!;
const regionA = createMockMidiRegion({ id: 'region-a', trackId: '1', trackIndex: 0, startFromBeat: (regionAData.barNumber - 1) * 4, length: regionAData.length * 4 });
const regionB = createMockMidiRegion({ id: 'region-b', trackId: '2', trackIndex: 1, startFromBeat: (regionBData.barNumber - 1) * 4, length: regionBData.length * 4 });
const trackA = createMockMidiTrack({ id: 1, regions: [regionA] });
const trackB = createMockMidiTrack({ id: 2, regions: [regionB] });
trackA.setTrackIndex(0);
trackB.setTrackIndex(1);
const gridContainerRef = createGridContainerRef();
const baseProps = {
isDragging: false,
isDragOver: false,
regions: regionOverrides,
maxBars: 8,
selectedRegionId: null,
gridContainerRef,
onDoubleClick: vi.fn(),
onRegionResize: vi.fn(),
onRegionResizeEnd: vi.fn(),
onRegionDrag: vi.fn(),
onRegionDragEnd: vi.fn(),
allTracks: [trackA, trackB],
};
const SharedPreviewHarness = () => {
const [previewRegionStyles, setPreviewRegionStyles] = useState<Record<string, React.CSSProperties>>({});
return (
<>
<TrackGridItem
track={trackA}
index={0}
previewRegionStyles={previewRegionStyles}
setPreviewRegionStyles={setPreviewRegionStyles}
{...baseProps}
/>
<TrackGridItem
track={trackB}
index={1}
previewRegionStyles={previewRegionStyles}
setPreviewRegionStyles={setPreviewRegionStyles}
{...baseProps}
/>
</>
);
};
render(<SharedPreviewHarness />);
return baseProps;
};
it('renders a non-interactive preview region on the recording audio track', () => {
const track = new KGAudioTrack('Audio Track', 1);
track.setTrackIndex(0);
const view = render(
render(
<TrackGridItem
track={track}
index={0}
@@ -70,12 +184,165 @@ describe('TrackGridItem recording preview', () => {
regions={[]}
maxBars={8}
selectedRegionId={null}
gridContainerRef={{ current: document.createElement('div') }}
gridContainerRef={createGridContainerRef()}
onDoubleClick={vi.fn()}
/>
);
const previewRegion = view.container.querySelector('[data-preview-region="true"]');
expect(previewRegion).toBeTruthy();
expect(regionItemProps.get('audio-recording-preview')).toBeTruthy();
});
it('previews end resize for all selected regions across track rows', () => {
renderSharedPreviewHarness(['region-a', 'region-b']);
act(() => {
getRegionItem('region-a').onResizeStart?.('region-a', 'end', 0);
getRegionItem('region-a').onResize?.('region-a', 'end', 40);
});
expect(getRegionItem('region-a').style).toEqual({
left: '0px',
width: '140px',
position: 'absolute',
});
expect(getRegionItem('region-b').style).toEqual({
left: '200px',
width: '240px',
position: 'absolute',
});
});
it('previews start resize for all selected regions across track rows', () => {
renderSharedPreviewHarness(
['region-a', 'region-b'],
[
{ id: 'region-a', trackId: '1', trackIndex: 0, barNumber: 2, length: 2, name: 'Region A' },
{ id: 'region-b', trackId: '2', trackIndex: 1, barNumber: 4, length: 3, name: 'Region B' },
],
);
act(() => {
getRegionItem('region-a').onResizeStart?.('region-a', 'start', 0);
getRegionItem('region-a').onResize?.('region-a', 'start', 40);
});
expect(getRegionItem('region-a').style).toEqual({
left: '140px',
width: '160px',
position: 'absolute',
});
expect(getRegionItem('region-b').style).toEqual({
left: '340px',
width: '260px',
position: 'absolute',
});
});
it('previews drag movement for all selected regions across track rows', () => {
renderSharedPreviewHarness(['region-a', 'region-b']);
act(() => {
getRegionItem('region-a').onDragStart?.('region-a', 0, 0);
getRegionItem('region-a').onDrag?.('region-a', 50, 60);
});
expect(getRegionItem('region-a').style).toEqual({
left: '50px',
width: '100px',
position: 'absolute',
zIndex: 100,
transform: 'translateY(0px)',
});
expect(getRegionItem('region-b').style).toEqual({
left: '250px',
width: '200px',
position: 'absolute',
zIndex: 100,
transform: 'translateY(0px)',
});
});
it('previews only the grabbed region when it is outside the current selection', () => {
renderSharedPreviewHarness(['region-a', 'region-b']);
const regionC = {
id: 'region-c',
trackId: '1',
trackIndex: 0,
barNumber: 5,
length: 1,
name: 'Region C',
};
const track = createMockMidiTrack({ id: 1, regions: [createMockMidiRegion({ id: 'region-c', trackId: '1', trackIndex: 0, startFromBeat: 16, length: 4 })] });
track.setTrackIndex(0);
const gridContainerRef = createGridContainerRef();
render(
<TrackGridItem
track={track}
index={0}
isDragging={false}
isDragOver={false}
regions={[regionC]}
maxBars={8}
selectedRegionId={null}
gridContainerRef={gridContainerRef}
onDoubleClick={vi.fn()}
/>
);
act(() => {
getRegionItem('region-c').onDragStart?.('region-c', 0, 0);
getRegionItem('region-c').onDrag?.('region-c', 50, 60);
});
expect(getRegionItem('region-c').style).toEqual({
left: '450px',
width: '100px',
position: 'absolute',
zIndex: 100,
transform: 'translateY(60px)',
});
});
it('clears preview styles for the full cohort after drag and resize end', () => {
const { onRegionResizeEnd, onRegionDragEnd } = renderSharedPreviewHarness(['region-a', 'region-b']);
act(() => {
getRegionItem('region-a').onResizeStart?.('region-a', 'end', 0);
getRegionItem('region-a').onResize?.('region-a', 'end', 40);
getRegionItem('region-a').onResizeEnd?.('region-a', 'end');
});
expect(getRegionItem('region-a').style).toEqual({
left: '0px',
width: '100px',
position: 'absolute',
});
expect(getRegionItem('region-b').style).toEqual({
left: '200px',
width: '200px',
position: 'absolute',
});
expect(onRegionResizeEnd).toHaveBeenCalledWith('region-a', 1, 1);
act(() => {
getRegionItem('region-a').onDragStart?.('region-a', 0, 0);
getRegionItem('region-a').onDrag?.('region-a', 50, 60);
getRegionItem('region-a').onDragEnd?.('region-a');
});
expect(getRegionItem('region-a').style).toEqual({
left: '0px',
width: '100px',
position: 'absolute',
});
expect(getRegionItem('region-b').style).toEqual({
left: '200px',
width: '200px',
position: 'absolute',
});
expect(onRegionDragEnd).toHaveBeenCalledWith('region-a', 2, 0);
});
});
+142 -49
View File
@@ -11,6 +11,22 @@ import { KGMainContentState } from '../../core/state/KGMainContentState';
import { isModifierKeyPressed } from '../../util/osUtil';
import { useProjectStore } from '../../stores/projectStore';
interface RegionResizePreviewBaseline {
regionId: string;
originalBarNumber: number;
originalLength: number;
originalLeft: number;
originalWidth: number;
}
interface RegionDragPreviewBaseline {
regionId: string;
originalBarNumber: number;
originalTrackIndex: number;
originalLeft: number;
originalWidth: number;
}
interface TrackGridItemProps {
track: KGTrack;
index: number;
@@ -35,6 +51,8 @@ interface TrackGridItemProps {
onOpenHybrid?: (regionId: string) => void;
allTracks?: KGTrack[]; // Added to access all tracks for drag operations
onKGOneClipDrop?: (e: React.DragEvent<HTMLDivElement>, trackIndex: number) => void;
previewRegionStyles?: Record<string, React.CSSProperties>;
setPreviewRegionStyles?: React.Dispatch<React.SetStateAction<Record<string, React.CSSProperties>>>;
}
const TrackGridItem: React.FC<TrackGridItemProps> = ({
@@ -61,6 +79,8 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
onOpenHybrid,
allTracks,
onKGOneClipDrop,
previewRegionStyles,
setPreviewRegionStyles,
}) => {
const selectedRegionIds = useProjectStore(state => state.selectedRegionIds);
const activeTrackAutomationTrackId = useProjectStore(state => state.activeTrackAutomationTrackId);
@@ -76,7 +96,7 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
const [containerWidth, setContainerWidth] = useState(0);
const [resizingRegion, setResizingRegion] = useState<string | null>(null);
const [draggingRegion, setDraggingRegion] = useState<string | null>(null);
const [tempRegionStyles, setTempRegionStyles] = useState<Record<string, React.CSSProperties>>({});
const [localTempRegionStyles, setLocalTempRegionStyles] = useState<Record<string, React.CSSProperties>>({});
const [isModifierPressed, setIsModifierPressed] = useState(false);
// Refs for resize operations
@@ -86,14 +106,41 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
const currentResizeRegion = useRef<RegionUI | null>(null);
const initialBarNumberRef = useRef<number | null>(null);
const initialLengthRef = useRef<number | null>(null);
const resizePreviewBaselinesRef = useRef<RegionResizePreviewBaseline[]>([]);
const resizePreviewRegionIdsRef = useRef<string[]>([]);
// Refs for drag operations
const currentDragLeft = useRef<number | null>(null);
const currentDragTop = useRef<number | null>(null);
const currentDragRegion = useRef<RegionUI | null>(null);
const dragPreviewBaselinesRef = useRef<RegionDragPreviewBaseline[]>([]);
const dragPreviewRegionIdsRef = useRef<string[]>([]);
const trackElementRef = useRef<HTMLDivElement | null>(null);
const isBulkRegionEdit = (regionId: string) => selectedRegionIds.length > 1 && selectedRegionIds.includes(regionId);
const tempRegionStyles = previewRegionStyles ?? localTempRegionStyles;
const setTempRegionStyles = setPreviewRegionStyles ?? setLocalTempRegionStyles;
const getPreviewRegionIds = (regionId: string) => (
selectedRegionIds.length > 1 && selectedRegionIds.includes(regionId)
? selectedRegionIds
: [regionId]
);
const clearTempRegionStyles = (regionIds?: string[]) => {
if (!regionIds || regionIds.length === 0) {
setTempRegionStyles({});
return;
}
setTempRegionStyles(prev => {
const updated = { ...prev };
regionIds.forEach(id => {
delete updated[id];
});
return updated;
});
};
// Update container width when the grid container changes size
useEffect(() => {
@@ -144,7 +191,7 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
// Calculate region position and style
const getRegionStyle = (region: RegionUI) => {
// Check if there's a temporary style for this region during resize or drag
if ((resizingRegion === region.id || draggingRegion === region.id) && tempRegionStyles[region.id]) {
if (tempRegionStyles[region.id]) {
return tempRegionStyles[region.id];
}
@@ -195,17 +242,30 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
// Store the initial width and left position
currentResizeWidth.current = region.length * barWidth;
currentResizeLeft.current = (region.barNumber - 1) * barWidth;
// Set initial style to current position/size
const initialStyle = {
left: `${currentResizeLeft.current}px`,
width: `${currentResizeWidth.current}px`,
position: 'absolute' as const, // Fixed: Use const assertion
};
const previewRegionIds = getPreviewRegionIds(regionId);
resizePreviewBaselinesRef.current = previewRegionIds
.map(id => regions.find(candidate => candidate.id === id))
.filter((candidate): candidate is RegionUI => candidate !== undefined)
.map(candidate => ({
regionId: candidate.id,
originalBarNumber: candidate.barNumber,
originalLength: candidate.length,
originalLeft: (candidate.barNumber - 1) * barWidth,
originalWidth: candidate.length * barWidth,
}));
resizePreviewRegionIdsRef.current = resizePreviewBaselinesRef.current.map(baseline => baseline.regionId);
setTempRegionStyles(prev => ({
...prev,
[regionId]: initialStyle
...Object.fromEntries(resizePreviewBaselinesRef.current.map(baseline => [
baseline.regionId,
{
left: `${baseline.originalLeft}px`,
width: `${baseline.originalWidth}px`,
position: 'absolute' as const,
},
])),
}));
};
@@ -254,16 +314,28 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
console.log(`RESIZE: regionId=${regionId}, action=${resizeAction}, deltaX=${deltaX}, newBarNumber=${newBarNumber}, newLength=${newLength}`);
}
// Update the temporary style for this region
const newStyle = {
left: `${newLeft}px`,
width: `${newWidth}px`,
position: 'absolute' as const, // Fixed: Use const assertion
};
const leftDelta = newLeft - originalLeft;
const widthDelta = newWidth - originalWidth;
const previewBaselines = resizePreviewBaselinesRef.current.length > 0
? resizePreviewBaselinesRef.current
: [{
regionId,
originalBarNumber: region.barNumber,
originalLength: region.length,
originalLeft,
originalWidth,
}];
setTempRegionStyles(prev => ({
...prev,
[regionId]: newStyle
...Object.fromEntries(previewBaselines.map(baseline => [
baseline.regionId,
{
left: `${resizeAction === 'start' ? baseline.originalLeft + leftDelta : baseline.originalLeft}px`,
width: `${baseline.originalWidth + widthDelta}px`,
position: 'absolute' as const,
},
])),
}));
// Notify parent about resize
@@ -328,16 +400,14 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
// Clear resizing state
setResizingRegion(null);
setTempRegionStyles(prev => {
const updated = { ...prev };
delete updated[regionId];
return updated;
});
clearTempRegionStyles(resizePreviewRegionIdsRef.current);
currentResizeWidth.current = null;
currentResizeLeft.current = null;
currentResizeRegion.current = null;
initialBarNumberRef.current = null;
initialLengthRef.current = null;
resizePreviewBaselinesRef.current = [];
resizePreviewRegionIdsRef.current = [];
// Notify parent about resize end with rounded values
if (onRegionResizeEnd) {
@@ -378,18 +448,32 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
// Store the initial position
currentDragLeft.current = left;
currentDragTop.current = 0; // Initially at the top of the current track
// Set initial style
const initialStyle = {
left: `${left}px`,
width: `${width}px`,
position: 'absolute' as const, // Fixed: Use const assertion
zIndex: 100, // Bring to front during drag
};
const previewRegionIds = getPreviewRegionIds(regionId);
dragPreviewBaselinesRef.current = previewRegionIds
.map(id => regions.find(candidate => candidate.id === id))
.filter((candidate): candidate is RegionUI => candidate !== undefined)
.map(candidate => ({
regionId: candidate.id,
originalBarNumber: candidate.barNumber,
originalTrackIndex: candidate.trackIndex,
originalLeft: (candidate.barNumber - 1) * barWidth,
originalWidth: candidate.length * barWidth,
}));
dragPreviewRegionIdsRef.current = dragPreviewBaselinesRef.current.map(baseline => baseline.regionId);
setTempRegionStyles(prev => ({
...prev,
[regionId]: initialStyle
...Object.fromEntries(dragPreviewBaselinesRef.current.map(baseline => [
baseline.regionId,
{
left: `${baseline.originalLeft}px`,
width: `${baseline.originalWidth}px`,
position: 'absolute' as const,
zIndex: 100,
transform: 'translateY(0px)',
},
])),
}));
};
@@ -425,18 +509,29 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
console.log(`DRAG: regionId=${regionId}, deltaX=${deltaX}, deltaY=${deltaY}, newBarNumber=${newBarNumber}`);
}
// Update the temporary style for this region
const newStyle = {
left: `${newLeft}px`,
width: `${region.length * barWidth}px`,
position: 'absolute' as const,
zIndex: 100, // Keep on top during drag
transform: `translateY(${appliedDeltaY}px)`,
};
const leftDelta = newLeft - initialLeft;
const previewBaselines = dragPreviewBaselinesRef.current.length > 0
? dragPreviewBaselinesRef.current
: [{
regionId,
originalBarNumber: region.barNumber,
originalTrackIndex: region.trackIndex,
originalLeft: initialLeft,
originalWidth: region.length * barWidth,
}];
setTempRegionStyles(prev => ({
...prev,
[regionId]: newStyle
...Object.fromEntries(previewBaselines.map(baseline => [
baseline.regionId,
{
left: `${baseline.originalLeft + leftDelta}px`,
width: `${baseline.originalWidth}px`,
position: 'absolute' as const,
zIndex: 100,
transform: `translateY(${appliedDeltaY}px)`,
},
])),
}));
// We'll calculate the track index on drag end, but still notify parent about the drag
@@ -506,14 +601,12 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
// Clear dragging state
setDraggingRegion(null);
setTempRegionStyles(prev => {
const updated = { ...prev };
delete updated[regionId];
return updated;
});
clearTempRegionStyles(dragPreviewRegionIdsRef.current);
currentDragLeft.current = null;
currentDragTop.current = null;
currentDragRegion.current = null;
dragPreviewBaselinesRef.current = [];
dragPreviewRegionIdsRef.current = [];
if (finalBarNumber === region.barNumber && finalTrackIndex === region.trackIndex) {
if (DEBUG_MODE.TRACK_GRID_ITEM) {
+3
View File
@@ -66,6 +66,7 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
const refreshProjectState = useProjectStore(state => state.refreshProjectState);
const gridContainerRef = useRef<HTMLDivElement>(null);
const [showAudioImportModal, setShowAudioImportModal] = useState(false);
const [previewRegionStyles, setPreviewRegionStyles] = useState<Record<string, React.CSSProperties>>({});
const pendingAudioImportRef = useRef<{ barNumber: number; trackIndex: number } | null>(null);
const isLassoSelectingRef = useRef(false);
const isLassoShiftPressedRef = useRef(false);
@@ -907,6 +908,8 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
onOpenHybrid={onOpenHybrid}
allTracks={tracks}
onKGOneClipDrop={handleExternalDrop}
previewRegionStyles={previewRegionStyles}
setPreviewRegionStyles={setPreviewRegionStyles}
/>
))}