1
- import type { GanttBarObject } from "../types"
2
-
3
- import createBarDrag from "./createBarDrag"
4
- import useDayjsHelper from "./useDayjsHelper"
5
1
import provideConfig from "../provider/provideConfig"
2
+ import type { GanttBarObject } from "../types/bar"
6
3
import provideGetChartRows from "../provider/provideGetChartRows"
7
4
import provideEmitBarEvent from "../provider/provideEmitBarEvent"
5
+ import useDayjsHelper from "./useDayjsHelper"
6
+ import createBarDrag from "./createBarDrag"
7
+ // Types for better typing and code organization
8
+ type DragState = {
9
+ movedBars : Map < GanttBarObject , { oldStart : string ; oldEnd : string } >
10
+ isDragging : boolean
11
+ }
12
+
13
+ type BarMovement = {
14
+ bar : GanttBarObject
15
+ minutes : number
16
+ direction : "left" | "right"
17
+ }
8
18
9
- export default function useBarDragManagement ( ) {
19
+ type OverlapResult = {
20
+ overlapBar ?: GanttBarObject
21
+ overlapType : "left" | "right" | "between" | null
22
+ }
23
+
24
+ // Main hook for drag and drop management
25
+ const useBarDragManagement = ( ) => {
10
26
const config = provideConfig ( )
11
27
const getChartRows = provideGetChartRows ( )
12
28
const emitBarEvent = provideEmitBarEvent ( )
13
29
const { pushOnOverlap, barStart, barEnd, noOverlap, dateFormat } = config
14
30
15
- const movedBarsInDrag = new Map < GanttBarObject , { oldStart : string ; oldEnd : string } > ( )
31
+ // Central state management for drag operations
32
+ const dragState : DragState = {
33
+ movedBars : new Map ( ) ,
34
+ isDragging : false
35
+ }
16
36
17
37
const { toDayjs, format } = useDayjsHelper ( )
18
38
39
+ // Initialize drag for a single bar
19
40
const initDragOfBar = ( bar : GanttBarObject , e : MouseEvent ) => {
20
- const { initDrag } = createBarDrag ( bar , onDrag , onEndDrag , config )
21
- emitBarEvent ( { ...e , type : "dragstart" } , bar )
22
- initDrag ( e )
41
+ const dragHandler = createDragHandler ( bar )
42
+ dragHandler . initiateDrag ( e )
23
43
addBarToMovedBars ( bar )
44
+ emitBarEvent ( { ...e , type : "dragstart" } , bar )
24
45
}
25
46
47
+ // Initialize drag for a bundle of bars
26
48
const initDragOfBundle = ( mainBar : GanttBarObject , e : MouseEvent ) => {
27
49
const bundle = mainBar . ganttBarConfig . bundle
28
- if ( bundle == null ) {
29
- return
30
- }
31
- getChartRows ( ) . forEach ( ( row ) => {
32
- row . bars . forEach ( ( bar ) => {
33
- if ( bar . ganttBarConfig . bundle === bundle ) {
34
- const dragEndHandler = bar === mainBar ? onEndDrag : ( ) => null
35
- const { initDrag } = createBarDrag ( bar , onDrag , dragEndHandler , config )
36
- initDrag ( e )
37
- addBarToMovedBars ( bar )
38
- }
39
- } )
50
+ if ( ! bundle ) return
51
+
52
+ const getBundleBars = ( ) =>
53
+ getChartRows ( )
54
+ . flatMap ( ( row ) => row . bars )
55
+ . filter ( ( bar ) => bar . ganttBarConfig . bundle === bundle )
56
+
57
+ getBundleBars ( ) . forEach ( ( bar ) => {
58
+ const isMainBar = bar === mainBar
59
+ const dragHandler = createDragHandler ( bar , isMainBar )
60
+ dragHandler . initiateDrag ( e )
61
+ addBarToMovedBars ( bar )
40
62
} )
63
+
41
64
emitBarEvent ( { ...e , type : "dragstart" } , mainBar )
42
65
}
43
66
44
- const onDrag = ( e : MouseEvent , bar : GanttBarObject ) => {
67
+ // Create handler for drag events
68
+ const createDragHandler = ( bar : GanttBarObject , isMainBar = true ) => ( {
69
+ initiateDrag : ( e : MouseEvent ) => {
70
+ const { initDrag } = createBarDrag (
71
+ bar ,
72
+ ( e ) => handleDrag ( e , bar ) ,
73
+ isMainBar ? handleDragEnd : ( ) => null ,
74
+ config
75
+ )
76
+ initDrag ( e )
77
+ }
78
+ } )
79
+
80
+ // Handle ongoing drag operations
81
+ const handleDrag = ( e : MouseEvent , bar : GanttBarObject ) => {
45
82
emitBarEvent ( { ...e , type : "drag" } , bar )
46
- fixOverlaps ( bar )
83
+ if ( pushOnOverlap ?. value ) {
84
+ handleOverlaps ( bar )
85
+ }
47
86
}
48
87
49
- const fixOverlaps = ( ganttBar : GanttBarObject ) => {
50
- if ( ! pushOnOverlap ?. value ) {
51
- return
88
+ // Manage overlapping bars during drag
89
+ const handleOverlaps = ( bar : GanttBarObject ) => {
90
+ let currentBar = bar
91
+ let overlap = detectOverlap ( currentBar )
92
+
93
+ while ( overlap . overlapBar ) {
94
+ const adjustedBar = adjustOverlappingBar ( currentBar , overlap )
95
+ if ( ! adjustedBar ) break
96
+
97
+ currentBar = adjustedBar
98
+ overlap = detectOverlap ( currentBar )
52
99
}
53
- let currentBar = ganttBar
54
- let { overlapBar, overlapType } = getOverlapBarAndType ( currentBar )
55
- while ( overlapBar ) {
56
- addBarToMovedBars ( overlapBar )
57
- const currentBarStart = toDayjs ( currentBar [ barStart . value ] )
58
- const currentBarEnd = toDayjs ( currentBar [ barEnd . value ] )
59
- const overlapBarStart = toDayjs ( overlapBar [ barStart . value ] )
60
- const overlapBarEnd = toDayjs ( overlapBar [ barEnd . value ] )
61
- let minuteDiff : number
62
- switch ( overlapType ) {
63
- case "left" :
64
- minuteDiff = overlapBarEnd . diff ( currentBarStart , "minutes" , true )
65
- overlapBar [ barEnd . value ] = format ( currentBar [ barStart . value ] , dateFormat . value )
66
- overlapBar [ barStart . value ] = format (
67
- overlapBarStart . subtract ( minuteDiff , "minutes" ) ,
68
- dateFormat . value
69
- )
70
- break
71
- case "right" :
72
- minuteDiff = currentBarEnd . diff ( overlapBarStart , "minutes" , true )
73
- overlapBar [ barStart . value ] = format ( currentBarEnd , dateFormat . value )
74
- overlapBar [ barEnd . value ] = format (
75
- overlapBarEnd . add ( minuteDiff , "minutes" ) ,
76
- dateFormat . value
77
- )
78
- break
79
- default :
80
- console . warn (
81
- "Vue-Ganttastic: One bar is inside of the other one! This should never occur while push-on-overlap is active!"
82
- )
83
- return
84
- }
85
- if ( overlapBar && ( overlapType === "left" || overlapType === "right" ) ) {
86
- moveBundleOfPushedBarByMinutes ( overlapBar , minuteDiff , overlapType )
100
+ }
101
+
102
+ // Detect if and how bars overlap
103
+ const detectOverlap = ( bar : GanttBarObject ) : OverlapResult => {
104
+ const barsInRow = getChartRows ( ) . find ( ( row ) => row . bars . includes ( bar ) ) ?. bars || [ ]
105
+
106
+ for ( const otherBar of barsInRow ) {
107
+ if ( otherBar === bar ) continue
108
+
109
+ const overlapType = determineOverlapType ( bar , otherBar )
110
+ if ( overlapType ) {
111
+ return { overlapBar : otherBar , overlapType }
87
112
}
88
- currentBar = overlapBar
89
- ; ( { overlapBar, overlapType } = getOverlapBarAndType ( overlapBar ) )
90
113
}
114
+
115
+ return { overlapType : null }
116
+ }
117
+
118
+ // Determine the type of overlap between two bars
119
+ const determineOverlapType = ( bar1 : GanttBarObject , bar2 : GanttBarObject ) => {
120
+ const start1 = toDayjs ( bar1 [ barStart . value ] )
121
+ const end1 = toDayjs ( bar1 [ barEnd . value ] )
122
+ const start2 = toDayjs ( bar2 [ barStart . value ] )
123
+ const end2 = toDayjs ( bar2 [ barEnd . value ] )
124
+
125
+ if ( start1 . isBetween ( start2 , end2 ) ) return "left"
126
+ if ( end1 . isBetween ( start2 , end2 ) ) return "right"
127
+ if ( start2 . isBetween ( start1 , end1 ) || end2 . isBetween ( start1 , end1 ) ) return "between"
128
+
129
+ return null
130
+ }
131
+
132
+ // Adjust bar positions when overlap is detected
133
+ const adjustOverlappingBar = ( currentBar : GanttBarObject , overlap : OverlapResult ) => {
134
+ if ( ! overlap . overlapBar || ! overlap . overlapType ) return null
135
+
136
+ const adjustment = calculateBarMovement ( currentBar , overlap . overlapBar , overlap . overlapType )
137
+ applyBarAdjustment ( overlap . overlapBar , adjustment )
138
+
139
+ if ( overlap . overlapBar . ganttBarConfig . bundle ) {
140
+ adjustBundleBars ( overlap . overlapBar , adjustment )
141
+ }
142
+
143
+ return overlap . overlapBar
91
144
}
92
145
93
- const getOverlapBarAndType = ( ganttBar : GanttBarObject ) => {
94
- let overlapLeft , overlapRight , overlapInBetween
95
- const allBarsInRow = getChartRows ( ) . find ( ( row ) => row . bars . includes ( ganttBar ) ) ?. bars ?? [ ]
96
- const ganttBarStart = toDayjs ( ganttBar [ barStart . value ] )
97
- const ganttBarEnd = toDayjs ( ganttBar [ barEnd . value ] )
98
- const overlapBar = allBarsInRow . find ( ( otherBar ) => {
99
- if ( otherBar === ganttBar ) {
100
- return false
101
- }
102
- const otherBarStart = toDayjs ( otherBar [ barStart . value ] )
103
- const otherBarEnd = toDayjs ( otherBar [ barEnd . value ] )
104
- overlapLeft = ganttBarStart . isBetween ( otherBarStart , otherBarEnd )
105
- overlapRight = ganttBarEnd . isBetween ( otherBarStart , otherBarEnd )
106
- overlapInBetween =
107
- otherBarStart . isBetween ( ganttBarStart , ganttBarEnd ) ||
108
- otherBarEnd . isBetween ( ganttBarStart , ganttBarEnd )
109
- return overlapLeft || overlapRight || overlapInBetween
110
- } )
111
- const overlapType = overlapLeft
112
- ? "left"
113
- : overlapRight
114
- ? "right"
115
- : overlapInBetween
116
- ? "between"
117
- : null
118
- return { overlapBar, overlapType }
119
- }
120
-
121
- const moveBundleOfPushedBarByMinutes = (
122
- pushedBar : GanttBarObject ,
123
- minutes : number ,
124
- direction : "left" | "right"
125
- ) => {
126
- addBarToMovedBars ( pushedBar )
127
- if ( ! pushedBar . ganttBarConfig . bundle ) {
128
- return
146
+ // Calculate the required movement to resolve overlap
147
+ const calculateBarMovement = (
148
+ currentBar : GanttBarObject ,
149
+ overlapBar : GanttBarObject ,
150
+ type : string
151
+ ) : BarMovement => {
152
+ const currentStart = toDayjs ( currentBar [ barStart . value ] )
153
+ const currentEnd = toDayjs ( currentBar [ barEnd . value ] )
154
+ const overlapStart = toDayjs ( overlapBar [ barStart . value ] )
155
+ const overlapEnd = toDayjs ( overlapBar [ barEnd . value ] )
156
+
157
+ if ( type === "left" ) {
158
+ const minutes = overlapEnd . diff ( currentStart , "minutes" , true )
159
+ return { bar : overlapBar , minutes, direction : "left" }
129
160
}
161
+
162
+ const minutes = currentEnd . diff ( overlapStart , "minutes" , true )
163
+ return { bar : overlapBar , minutes, direction : "right" }
164
+ }
165
+
166
+ // Apply movement adjustment to a bar
167
+ const applyBarAdjustment = ( bar : GanttBarObject , adjustment : BarMovement ) => {
168
+ addBarToMovedBars ( bar )
169
+
170
+ const timeAdjustment =
171
+ adjustment . direction === "left"
172
+ ? ( time : string ) => toDayjs ( time ) . subtract ( adjustment . minutes , "minutes" )
173
+ : ( time : string ) => toDayjs ( time ) . add ( adjustment . minutes , "minutes" )
174
+
175
+ bar [ barStart . value ] = format ( timeAdjustment ( bar [ barStart . value ] ) , dateFormat . value )
176
+ bar [ barEnd . value ] = format ( timeAdjustment ( bar [ barEnd . value ] ) , dateFormat . value )
177
+ }
178
+
179
+ // Adjust all bars in a bundle
180
+ const adjustBundleBars = ( sourceBar : GanttBarObject , adjustment : BarMovement ) => {
181
+ const bundle = sourceBar . ganttBarConfig . bundle
182
+ if ( ! bundle ) return
183
+
130
184
getChartRows ( ) . forEach ( ( row ) => {
131
185
row . bars . forEach ( ( bar ) => {
132
- if ( bar . ganttBarConfig . bundle === pushedBar . ganttBarConfig . bundle && bar !== pushedBar ) {
186
+ if ( bar . ganttBarConfig . bundle === bundle && bar !== sourceBar ) {
133
187
addBarToMovedBars ( bar )
134
- moveBarByMinutes ( bar , minutes , direction )
188
+ applyBarAdjustment ( bar , adjustment )
135
189
}
136
190
} )
137
191
} )
138
192
}
139
193
140
- const moveBarByMinutes = ( bar : GanttBarObject , minutes : number , direction : "left" | "right" ) => {
141
- switch ( direction ) {
142
- case "left" :
143
- bar [ barStart . value ] = format (
144
- toDayjs ( bar , "start" ) . subtract ( minutes , "minutes" ) ,
145
- dateFormat . value
146
- )
147
- bar [ barEnd . value ] = format (
148
- toDayjs ( bar , "end" ) . subtract ( minutes , "minutes" ) ,
149
- dateFormat . value
150
- )
151
- break
152
- case "right" :
153
- bar [ barStart . value ] = format (
154
- toDayjs ( bar , "start" ) . add ( minutes , "minutes" ) ,
155
- dateFormat . value
156
- )
157
- bar [ barEnd . value ] = format ( toDayjs ( bar , "end" ) . add ( minutes , "minutes" ) , dateFormat . value )
194
+ // Handle the end of a drag operation
195
+ const handleDragEnd = ( e : MouseEvent , bar : GanttBarObject ) => {
196
+ if ( shouldSnapBack ( ) ) {
197
+ snapBackMovedBars ( )
158
198
}
159
- fixOverlaps ( bar )
160
- }
161
199
162
- const onEndDrag = ( e : MouseEvent , bar : GanttBarObject ) => {
163
- snapBackAllMovedBarsIfNeeded ( )
164
- const ev = {
165
- ...e ,
166
- type : "dragend"
167
- }
168
- emitBarEvent ( ev , bar , undefined , new Map ( movedBarsInDrag ) )
169
- movedBarsInDrag . clear ( )
200
+ emitBarEvent ( { ...e , type : "dragend" } , bar , undefined , new Map ( dragState . movedBars ) )
201
+
202
+ dragState . movedBars . clear ( )
203
+ dragState . isDragging = false
170
204
}
171
205
206
+ // Add a bar to the moved bars tracking
172
207
const addBarToMovedBars = ( bar : GanttBarObject ) => {
173
- if ( ! movedBarsInDrag . has ( bar ) ) {
174
- const oldStart = bar [ barStart . value ]
175
- const oldEnd = bar [ barEnd . value ]
176
- movedBarsInDrag . set ( bar , { oldStart, oldEnd } )
208
+ if ( ! dragState . movedBars . has ( bar ) ) {
209
+ dragState . movedBars . set ( bar , {
210
+ oldStart : bar [ barStart . value ] ,
211
+ oldEnd : bar [ barEnd . value ]
212
+ } )
177
213
}
178
214
}
179
215
180
- const snapBackAllMovedBarsIfNeeded = ( ) => {
181
- if ( pushOnOverlap . value || ! noOverlap . value ) {
182
- return
183
- }
216
+ // Check if bars should snap back to original positions
217
+ const shouldSnapBack = ( ) => {
218
+ if ( pushOnOverlap . value || ! noOverlap . value ) return false
184
219
185
- let isAnyOverlap = false
186
- movedBarsInDrag . forEach ( ( _ , bar ) => {
187
- const { overlapBar } = getOverlapBarAndType ( bar )
188
- if ( overlapBar != null ) {
189
- isAnyOverlap = true
190
- }
220
+ return Array . from ( dragState . movedBars . keys ( ) ) . some ( ( bar ) => {
221
+ const { overlapBar } = detectOverlap ( bar )
222
+ return overlapBar != null
191
223
} )
192
- if ( ! isAnyOverlap ) {
193
- return
194
- }
195
- movedBarsInDrag . forEach ( ( { oldStart, oldEnd } , bar ) => {
224
+ }
225
+
226
+ // Reset all moved bars to their original positions
227
+ const snapBackMovedBars = ( ) => {
228
+ dragState . movedBars . forEach ( ( { oldStart, oldEnd } , bar ) => {
196
229
bar [ barStart . value ] = oldStart
197
230
bar [ barEnd . value ] = oldEnd
198
231
} )
@@ -203,3 +236,5 @@ export default function useBarDragManagement() {
203
236
initDragOfBundle
204
237
}
205
238
}
239
+
240
+ export default useBarDragManagement
0 commit comments