@@ -335,7 +335,6 @@ export class MultiEditorTabsControl extends EditorTabsControl {
335
335
336
336
// Return if the target is not on the tabs container
337
337
if ( e . target !== tabsContainer ) {
338
- this . updateDropFeedback ( tabsContainer , false ) ; // fixes https://github.com/microsoft/vscode/issues/52093
339
338
return ;
340
339
}
341
340
@@ -348,49 +347,31 @@ export class MultiEditorTabsControl extends EditorTabsControl {
348
347
return ;
349
348
}
350
349
351
- // Return if dragged editor is last tab because then this is a no-op
352
- let isLocalDragAndDrop = false ;
353
- if ( this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
354
- isLocalDragAndDrop = true ;
355
-
356
- const data = this . editorTransfer . getData ( DraggedEditorIdentifier . prototype ) ;
357
- if ( Array . isArray ( data ) ) {
358
- const localDraggedEditor = data [ 0 ] . identifier ;
359
- if ( this . groupView . id === localDraggedEditor . groupId && this . tabsModel . isLast ( localDraggedEditor . editor ) ) {
360
- if ( e . dataTransfer ) {
361
- e . dataTransfer . dropEffect = 'none' ;
362
- }
363
-
364
- return ;
365
- }
366
- }
367
- }
368
-
369
350
// Update the dropEffect to "copy" if there is no local data to be dragged because
370
351
// in that case we can only copy the data into and not move it from its source
371
- if ( ! isLocalDragAndDrop ) {
352
+ if ( ! this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
372
353
if ( e . dataTransfer ) {
373
354
e . dataTransfer . dropEffect = 'copy' ;
374
355
}
375
356
}
376
357
377
- this . updateDropFeedback ( tabsContainer , true ) ;
358
+ this . updateDropFeedback ( tabsContainer , true , e ) ;
378
359
} ,
379
360
380
361
onDragLeave : e => {
381
- this . updateDropFeedback ( tabsContainer , false ) ;
362
+ this . updateDropFeedback ( tabsContainer , false , e ) ;
382
363
tabsContainer . classList . remove ( 'scroll' ) ;
383
364
} ,
384
365
385
366
onDragEnd : e => {
386
- this . updateDropFeedback ( tabsContainer , false ) ;
367
+ this . updateDropFeedback ( tabsContainer , false , e ) ;
387
368
tabsContainer . classList . remove ( 'scroll' ) ;
388
369
389
370
this . onGroupDragEnd ( e , lastDragEvent , tabsContainer , isNewWindowOperation ) ;
390
371
} ,
391
372
392
373
onDrop : e => {
393
- this . updateDropFeedback ( tabsContainer , false ) ;
374
+ this . updateDropFeedback ( tabsContainer , false , e ) ;
394
375
tabsContainer . classList . remove ( 'scroll' ) ;
395
376
396
377
if ( e . target === tabsContainer ) {
@@ -1038,14 +1019,13 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1038
1019
1039
1020
if ( e . dataTransfer ) {
1040
1021
e . dataTransfer . effectAllowed = 'copyMove' ;
1022
+ e . dataTransfer . setDragImage ( tab , 0 , 0 ) ; // top left corner of dragged tab set to cursor position to make room for drop-border feedback
1041
1023
}
1042
1024
1043
1025
// Apply some datatransfer types to allow for dragging the element outside of the application
1044
1026
this . doFillResourceDataTransfers ( [ editor ] , e , isNewWindowOperation ) ;
1045
1027
1046
- // Fixes https://github.com/microsoft/vscode/issues/18733
1047
- tab . classList . add ( 'dragged' ) ;
1048
- scheduleAtNextAnimationFrame ( getWindow ( this . parent ) , ( ) => tab . classList . remove ( 'dragged' ) ) ;
1028
+ scheduleAtNextAnimationFrame ( getWindow ( this . parent ) , ( ) => this . updateDropFeedback ( tab , false , e , tabIndex ) ) ;
1049
1029
} ,
1050
1030
1051
1031
onDrag : e => {
@@ -1054,9 +1034,6 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1054
1034
1055
1035
onDragEnter : e => {
1056
1036
1057
- // Update class to signal drag operation
1058
- tab . classList . add ( 'dragged-over' ) ;
1059
-
1060
1037
// Return if transfer is unsupported
1061
1038
if ( ! this . isSupportedDropTransfer ( e ) ) {
1062
1039
if ( e . dataTransfer ) {
@@ -1066,52 +1043,30 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1066
1043
return ;
1067
1044
}
1068
1045
1069
- // Return if dragged editor is the current tab dragged over
1070
- let isLocalDragAndDrop = false ;
1071
- if ( this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
1072
- isLocalDragAndDrop = true ;
1073
-
1074
- const data = this . editorTransfer . getData ( DraggedEditorIdentifier . prototype ) ;
1075
- if ( Array . isArray ( data ) ) {
1076
- const localDraggedEditor = data [ 0 ] . identifier ;
1077
- if ( localDraggedEditor . editor === this . tabsModel . getEditorByIndex ( tabIndex ) && localDraggedEditor . groupId === this . groupView . id ) {
1078
- if ( e . dataTransfer ) {
1079
- e . dataTransfer . dropEffect = 'none' ;
1080
- }
1081
-
1082
- return ;
1083
- }
1084
- }
1085
- }
1086
-
1087
1046
// Update the dropEffect to "copy" if there is no local data to be dragged because
1088
1047
// in that case we can only copy the data into and not move it from its source
1089
- if ( ! isLocalDragAndDrop ) {
1048
+ if ( ! this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
1090
1049
if ( e . dataTransfer ) {
1091
1050
e . dataTransfer . dropEffect = 'copy' ;
1092
1051
}
1093
1052
}
1094
1053
1095
- this . updateDropFeedback ( tab , true , tabIndex ) ;
1054
+ this . updateDropFeedback ( tab , true , e , tabIndex ) ;
1096
1055
} ,
1097
1056
1098
- onDragOver : ( _ , dragDuration ) => {
1057
+ onDragOver : ( e , dragDuration ) => {
1099
1058
if ( dragDuration >= MultiEditorTabsControl . DRAG_OVER_OPEN_TAB_THRESHOLD ) {
1100
1059
const draggedOverTab = this . tabsModel . getEditorByIndex ( tabIndex ) ;
1101
1060
if ( draggedOverTab && this . tabsModel . activeEditor !== draggedOverTab ) {
1102
1061
this . groupView . openEditor ( draggedOverTab , { preserveFocus : true } ) ;
1103
1062
}
1104
1063
}
1105
- } ,
1106
1064
1107
- onDragLeave : ( ) => {
1108
- tab . classList . remove ( 'dragged-over' ) ;
1109
- this . updateDropFeedback ( tab , false , tabIndex ) ;
1065
+ this . updateDropFeedback ( tab , true , e , tabIndex ) ;
1110
1066
} ,
1111
1067
1112
1068
onDragEnd : async e => {
1113
- tab . classList . remove ( 'dragged-over' ) ;
1114
- this . updateDropFeedback ( tab , false , tabIndex ) ;
1069
+ this . updateDropFeedback ( tab , false , e , tabIndex ) ;
1115
1070
1116
1071
this . editorTransfer . clearData ( DraggedEditorIdentifier . prototype ) ;
1117
1072
@@ -1140,10 +1095,31 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1140
1095
} ,
1141
1096
1142
1097
onDrop : e => {
1143
- tab . classList . remove ( 'dragged-over' ) ;
1144
- this . updateDropFeedback ( tab , false , tabIndex ) ;
1098
+ this . updateDropFeedback ( tab , false , e , tabIndex ) ;
1099
+
1100
+ // compute the target index
1101
+ let targetIndex = tabIndex ;
1102
+ if ( this . getTabDragOverLocation ( e , tab ) === 'right' ) {
1103
+ targetIndex ++ ;
1104
+ }
1105
+
1106
+ // If we are moving an editor inside the same group and it is
1107
+ // located before the target index we need to reduce the index
1108
+ // by one to account for the fact that the move will cause all
1109
+ // subsequent tabs to move one to the left.
1110
+ const editorIdentifiers = this . editorTransfer . getData ( DraggedEditorIdentifier . prototype ) ;
1111
+ if ( editorIdentifiers !== undefined ) {
1112
+ const draggedEditorIdentifier = editorIdentifiers [ 0 ] . identifier ;
1113
+ const sourceGroup = this . editorPartsView . getGroup ( draggedEditorIdentifier . groupId ) ;
1114
+ if ( sourceGroup ?. id === this . groupView . id ) {
1115
+ const editorIndex = sourceGroup . getIndexOfEditor ( draggedEditorIdentifier . editor ) ;
1116
+ if ( editorIndex < targetIndex ) {
1117
+ targetIndex -- ;
1118
+ }
1119
+ }
1120
+ }
1145
1121
1146
- this . onDrop ( e , tabIndex , tabsContainer ) ;
1122
+ this . onDrop ( e , targetIndex , tabsContainer ) ;
1147
1123
}
1148
1124
} ) ) ;
1149
1125
@@ -1174,28 +1150,73 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1174
1150
return false ;
1175
1151
}
1176
1152
1177
- private updateDropFeedback ( element : HTMLElement , isDND : boolean , tabIndex ?: number ) : void {
1153
+ private updateDropFeedback ( element : HTMLElement , isDND : boolean , e : DragEvent , tabIndex ?: number ) : void {
1178
1154
const isTab = ( typeof tabIndex === 'number' ) ;
1179
- const editor = typeof tabIndex === 'number' ? this . tabsModel . getEditorByIndex ( tabIndex ) : undefined ;
1180
- const isActiveTab = isTab && ! ! editor && this . tabsModel . isActive ( editor ) ;
1181
-
1182
- // Background
1183
- const noDNDBackgroundColor = isTab ? this . getColor ( isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND ) : '' ;
1184
- element . style . backgroundColor = ( isDND ? this . getColor ( EDITOR_DRAG_AND_DROP_BACKGROUND ) : noDNDBackgroundColor ) || '' ;
1185
-
1186
- // Outline
1187
- const activeContrastBorderColor = this . getColor ( activeContrastBorder ) ;
1188
- if ( activeContrastBorderColor && isDND ) {
1189
- element . style . outlineWidth = '2px' ;
1190
- element . style . outlineStyle = 'dashed' ;
1191
- element . style . outlineColor = activeContrastBorderColor ;
1192
- element . style . outlineOffset = isTab ? '-5px' : '-3px' ;
1155
+
1156
+ let dropTarget ;
1157
+ if ( isDND ) {
1158
+ if ( isTab ) {
1159
+ dropTarget = this . computeDropTarget ( e , tabIndex , element ) ;
1160
+ } else {
1161
+ dropTarget = { leftElement : element . lastElementChild as HTMLElement , rightElement : undefined } ;
1162
+ }
1193
1163
} else {
1194
- element . style . outlineWidth = '' ;
1195
- element . style . outlineStyle = '' ;
1196
- element . style . outlineColor = activeContrastBorderColor || '' ;
1197
- element . style . outlineOffset = '' ;
1164
+ dropTarget = undefined ;
1198
1165
}
1166
+
1167
+ this . updateDropTarget ( dropTarget ) ;
1168
+ }
1169
+
1170
+ private dropTarget : { leftElement : HTMLElement | undefined ; rightElement : HTMLElement | undefined } | undefined ;
1171
+ private updateDropTarget ( newTarget : { leftElement : HTMLElement | undefined ; rightElement : HTMLElement | undefined } | undefined ) : void {
1172
+ const oldTargets = this . dropTarget ;
1173
+ if ( oldTargets === newTarget || oldTargets && newTarget && oldTargets . leftElement === newTarget . leftElement && oldTargets . rightElement === newTarget . rightElement ) {
1174
+ return ;
1175
+ }
1176
+
1177
+ const dropClassLeft = 'drop-target-left' ;
1178
+ const dropClassRight = 'drop-target-right' ;
1179
+
1180
+ if ( oldTargets ) {
1181
+ oldTargets . leftElement ?. classList . remove ( dropClassLeft ) ;
1182
+ oldTargets . rightElement ?. classList . remove ( dropClassRight ) ;
1183
+ }
1184
+
1185
+ if ( newTarget ) {
1186
+ newTarget . leftElement ?. classList . add ( dropClassLeft ) ;
1187
+ newTarget . rightElement ?. classList . add ( dropClassRight ) ;
1188
+ }
1189
+
1190
+ this . dropTarget = newTarget ;
1191
+ }
1192
+
1193
+ private getTabDragOverLocation ( e : DragEvent , tab : HTMLElement ) : 'left' | 'right' {
1194
+ const rect = tab . getBoundingClientRect ( ) ;
1195
+ const offsetXRelativeToParent = e . clientX - rect . left ;
1196
+
1197
+ return offsetXRelativeToParent <= rect . width / 2 ? 'left' : 'right' ;
1198
+ }
1199
+
1200
+ private computeDropTarget ( e : DragEvent , tabIndex : number , targetTab : HTMLElement ) : { leftElement : HTMLElement | undefined ; rightElement : HTMLElement | undefined } | undefined {
1201
+ const isLeftSideOfTab = this . getTabDragOverLocation ( e , targetTab ) === 'left' ;
1202
+ const isLastTab = tabIndex === this . tabsModel . count - 1 ;
1203
+ const isFirstTab = tabIndex === 0 ;
1204
+
1205
+ // Before first tab
1206
+ if ( isLeftSideOfTab && isFirstTab ) {
1207
+ return { leftElement : undefined , rightElement : targetTab } ;
1208
+ }
1209
+
1210
+ // After last tab
1211
+ if ( ! isLeftSideOfTab && isLastTab ) {
1212
+ return { leftElement : targetTab , rightElement : undefined } ;
1213
+ }
1214
+
1215
+ // Between two tabs
1216
+ const tabBefore = isLeftSideOfTab ? targetTab . previousElementSibling : targetTab ;
1217
+ const tabAfter = isLeftSideOfTab ? targetTab : targetTab . nextElementSibling ;
1218
+
1219
+ return { leftElement : tabBefore as HTMLElement , rightElement : tabAfter as HTMLElement } ;
1199
1220
}
1200
1221
1201
1222
private computeTabLabels ( ) : void {
@@ -1729,6 +1750,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
1729
1750
// positioned editor actions container when tabs wrap. The margin needs to
1730
1751
// be the width of the editor actions container to avoid screen cheese.
1731
1752
tabsContainer . style . setProperty ( '--last-tab-margin-right' , tabsWrapMultiLine ? `${ editorToolbarContainer . offsetWidth } px` : '0' ) ;
1753
+
1754
+ // Remove old css classes that are not needed anymore
1755
+ for ( const tab of tabsContainer . children ) {
1756
+ tab . classList . remove ( 'last-in-row' ) ;
1757
+ }
1732
1758
}
1733
1759
1734
1760
// Setting enabled: selectively enable wrapping if possible
@@ -2044,7 +2070,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
2044
2070
private async onDrop ( e : DragEvent , targetTabIndex : number , tabsContainer : HTMLElement ) : Promise < void > {
2045
2071
EventHelper . stop ( e , true ) ;
2046
2072
2047
- this . updateDropFeedback ( tabsContainer , false ) ;
2073
+ this . updateDropFeedback ( tabsContainer , false , e , targetTabIndex ) ;
2048
2074
tabsContainer . classList . remove ( 'scroll' ) ;
2049
2075
2050
2076
const targetEditorIndex = this . tabsModel instanceof UnstickyEditorGroupModel ? targetTabIndex + this . groupView . stickyCount : targetTabIndex ;
0 commit comments