@@ -34,6 +34,7 @@ export function VTabBar({ tabs, activeTabId, width, className, onSelectTab, onCl
3434 const [ orderedTabs , setOrderedTabs ] = useState < VTabItem [ ] > ( tabs ) ;
3535 const [ dragTabId , setDragTabId ] = useState < string | null > ( null ) ;
3636 const [ dropIndex , setDropIndex ] = useState < number | null > ( null ) ;
37+ const [ dropLineTop , setDropLineTop ] = useState < number | null > ( null ) ;
3738 const dragSourceRef = useRef < string | null > ( null ) ;
3839
3940 useEffect ( ( ) => {
@@ -46,6 +47,7 @@ export function VTabBar({ tabs, activeTabId, width, className, onSelectTab, onCl
4647 dragSourceRef . current = null ;
4748 setDragTabId ( null ) ;
4849 setDropIndex ( null ) ;
50+ setDropLineTop ( null ) ;
4951 } ;
5052
5153 const reorder = ( targetIndex : number ) => {
@@ -69,24 +71,18 @@ export function VTabBar({ tabs, activeTabId, width, className, onSelectTab, onCl
6971 onReorderTabs ?.( nextTabs . map ( ( tab ) => tab . id ) ) ;
7072 } ;
7173
72- const getDropLineClass = ( index : number ) =>
73- cn (
74- "h-0 border-t-2" ,
75- dragTabId != null && dropIndex === index ? "border-accent/80" : "border-transparent" ,
76- index > 0 && index < orderedTabs . length && "my-px"
77- ) ;
78-
7974 return (
8075 < div
8176 className = { cn ( "flex h-full min-w-[100px] max-w-[400px] flex-col overflow-hidden border-r border-border bg-panel" , className ) }
8277 style = { { width : barWidth } }
8378 >
8479 < div
85- className = "flex min-h-0 flex-1 flex-col overflow-y-auto"
80+ className = "relative flex min-h-0 flex-1 flex-col overflow-y-auto"
8681 onDragOver = { ( event ) => {
8782 event . preventDefault ( ) ;
8883 if ( event . target === event . currentTarget ) {
8984 setDropIndex ( orderedTabs . length ) ;
85+ setDropLineTop ( event . currentTarget . scrollHeight ) ;
9086 }
9187 } }
9288 onDrop = { ( event ) => {
@@ -97,42 +93,53 @@ export function VTabBar({ tabs, activeTabId, width, className, onSelectTab, onCl
9793 clearDragState ( ) ;
9894 } }
9995 >
100- < div className = { getDropLineClass ( 0 ) } />
10196 { orderedTabs . map ( ( tab , index ) => (
102- < div key = { tab . id } className = "flex flex-col" >
103- < VTab
104- tab = { tab }
105- active = { tab . id === activeTabId }
106- isDragging = { dragTabId === tab . id }
107- isReordering = { dragTabId != null }
108- onSelect = { ( ) => onSelectTab ?.( tab . id ) }
109- onClose = { onCloseTab ? ( ) => onCloseTab ( tab . id ) : undefined }
110- onRename = { onRenameTab ? ( newName ) => onRenameTab ( tab . id , newName ) : undefined }
111- onDragStart = { ( event ) => {
112- dragSourceRef . current = tab . id ;
113- event . dataTransfer . effectAllowed = "move" ;
114- event . dataTransfer . setData ( "text/plain" , tab . id ) ;
115- setDragTabId ( tab . id ) ;
116- setDropIndex ( index ) ;
117- } }
118- onDragOver = { ( event ) => {
119- event . preventDefault ( ) ;
120- const rect = event . currentTarget . getBoundingClientRect ( ) ;
121- const insertBefore = event . clientY < rect . top + rect . height / 2 ;
122- setDropIndex ( insertBefore ? index : index + 1 ) ;
123- } }
124- onDrop = { ( event ) => {
125- event . preventDefault ( ) ;
126- if ( dropIndex != null ) {
127- reorder ( dropIndex ) ;
128- }
129- clearDragState ( ) ;
130- } }
131- onDragEnd = { clearDragState }
132- />
133- < div className = { getDropLineClass ( index + 1 ) } />
134- </ div >
97+ < VTab
98+ key = { tab . id }
99+ tab = { tab }
100+ active = { tab . id === activeTabId }
101+ isDragging = { dragTabId === tab . id }
102+ isReordering = { dragTabId != null }
103+ onSelect = { ( ) => onSelectTab ?.( tab . id ) }
104+ onClose = { onCloseTab ? ( ) => onCloseTab ( tab . id ) : undefined }
105+ onRename = { onRenameTab ? ( newName ) => onRenameTab ( tab . id , newName ) : undefined }
106+ onDragStart = { ( event ) => {
107+ dragSourceRef . current = tab . id ;
108+ event . dataTransfer . effectAllowed = "move" ;
109+ event . dataTransfer . setData ( "text/plain" , tab . id ) ;
110+ setDragTabId ( tab . id ) ;
111+ setDropIndex ( index ) ;
112+ setDropLineTop ( event . currentTarget . offsetTop ) ;
113+ } }
114+ onDragOver = { ( event ) => {
115+ event . preventDefault ( ) ;
116+ const rect = event . currentTarget . getBoundingClientRect ( ) ;
117+ const relativeY = event . clientY - rect . top ;
118+ const midpoint = event . currentTarget . offsetHeight / 2 ;
119+ const insertBefore = relativeY < midpoint ;
120+ setDropIndex ( insertBefore ? index : index + 1 ) ;
121+ setDropLineTop (
122+ insertBefore
123+ ? event . currentTarget . offsetTop
124+ : event . currentTarget . offsetTop + event . currentTarget . offsetHeight
125+ ) ;
126+ } }
127+ onDrop = { ( event ) => {
128+ event . preventDefault ( ) ;
129+ if ( dropIndex != null ) {
130+ reorder ( dropIndex ) ;
131+ }
132+ clearDragState ( ) ;
133+ } }
134+ onDragEnd = { clearDragState }
135+ />
135136 ) ) }
137+ { dragTabId != null && dropIndex != null && dropLineTop != null && (
138+ < div
139+ className = "pointer-events-none absolute left-0 right-0 border-t-2 border-accent/80"
140+ style = { { top : dropLineTop , transform : "translateY(-1px)" } }
141+ />
142+ ) }
136143 </ div >
137144 </ div >
138145 ) ;
0 commit comments