@@ -65,6 +65,49 @@ function getMainTrackIndex({ tracks }: { tracks: TimelineTrack[] }): number {
6565 return tracks . findIndex ( ( track ) => isMainTrack ( track ) ) ;
6666}
6767
68+ function findBestCompatibleTrack ( {
69+ elementType,
70+ tracks,
71+ xPosition,
72+ elementDuration,
73+ excludeElementId,
74+ } : {
75+ elementType : ElementType ;
76+ tracks : TimelineTrack [ ] ;
77+ xPosition : number ;
78+ elementDuration : number ;
79+ excludeElementId ?: string ;
80+ } ) : number | null {
81+ const endTime = xPosition + elementDuration ;
82+
83+ // video/image → prefer main track first
84+ if ( elementType === "video" || elementType === "image" ) {
85+ const mainIdx = getMainTrackIndex ( { tracks } ) ;
86+ if ( mainIdx >= 0 ) {
87+ const hasOverlap = wouldElementOverlap ( {
88+ elements : tracks [ mainIdx ] . elements ,
89+ startTime : xPosition ,
90+ endTime,
91+ excludeElementId,
92+ } ) ;
93+ if ( ! hasOverlap ) return mainIdx ;
94+ }
95+ }
96+
97+ for ( let i = 0 ; i < tracks . length ; i ++ ) {
98+ if ( ! isCompatible ( { elementType, trackType : tracks [ i ] . type } ) ) continue ;
99+ const hasOverlap = wouldElementOverlap ( {
100+ elements : tracks [ i ] . elements ,
101+ startTime : xPosition ,
102+ endTime,
103+ excludeElementId,
104+ } ) ;
105+ if ( ! hasOverlap ) return i ;
106+ }
107+
108+ return null ;
109+ }
110+
68111function findInsertIndex ( {
69112 elementType,
70113 tracks,
@@ -138,6 +181,31 @@ export function computeDropTarget({
138181 const trackAtMouse = getTrackAtY ( { mouseY, tracks, verticalDragDirection } ) ;
139182
140183 if ( ! trackAtMouse ) {
184+ const compatibleIndex = findBestCompatibleTrack ( {
185+ elementType,
186+ tracks,
187+ xPosition,
188+ elementDuration,
189+ excludeElementId,
190+ } ) ;
191+
192+ if ( compatibleIndex !== null ) {
193+ const targetTrack = tracks [ compatibleIndex ] ;
194+ const adjustedXPosition = enforceMainTrackStart ( {
195+ tracks,
196+ targetTrackId : targetTrack . id ,
197+ requestedStartTime : xPosition ,
198+ excludeElementId,
199+ } ) ;
200+
201+ return {
202+ trackIndex : compatibleIndex ,
203+ isNewTrack : false ,
204+ insertPosition : null ,
205+ xPosition : adjustedXPosition ,
206+ } ;
207+ }
208+
141209 const isAboveAllTracks = mouseY < 0 ;
142210
143211 if ( elementType === "audio" ) {
@@ -203,6 +271,31 @@ export function computeDropTarget({
203271 } ;
204272 }
205273
274+ const fallbackIndex = findBestCompatibleTrack ( {
275+ elementType,
276+ tracks,
277+ xPosition,
278+ elementDuration,
279+ excludeElementId,
280+ } ) ;
281+
282+ if ( fallbackIndex !== null ) {
283+ const fallbackTrack = tracks [ fallbackIndex ] ;
284+ const adjustedXPosition = enforceMainTrackStart ( {
285+ tracks,
286+ targetTrackId : fallbackTrack . id ,
287+ requestedStartTime : xPosition ,
288+ excludeElementId,
289+ } ) ;
290+
291+ return {
292+ trackIndex : fallbackIndex ,
293+ isNewTrack : false ,
294+ insertPosition : null ,
295+ xPosition : adjustedXPosition ,
296+ } ;
297+ }
298+
206299 let insertAbove = isInUpperHalf ;
207300 if ( ! isTrackCompatible && verticalDragDirection ) {
208301 insertAbove = verticalDragDirection === "up" ;
0 commit comments