@stonecrop/atable 0.4.23 → 0.4.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index.css +1 -1
- package/dist/atable.d.ts +511 -25
- package/dist/atable.js +1301 -1039
- package/dist/atable.js.map +1 -1
- package/dist/atable.umd.cjs +2 -2
- package/dist/atable.umd.cjs.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/stores/table.d.ts +370 -16
- package/dist/src/stores/table.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +138 -10
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/stores/table.js +111 -13
- package/package.json +3 -3
- package/src/components/AGanttCell.vue +444 -170
- package/src/components/AGanttConnection.vue +164 -0
- package/src/components/ARow.vue +1 -1
- package/src/components/ATable.vue +96 -71
- package/src/components/ATableHeader.vue +1 -1
- package/src/index.ts +4 -0
- package/src/stores/table.ts +129 -13
- package/src/types/index.ts +159 -10
|
@@ -1,46 +1,82 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<td class="aganttcell" :colspan="colspan">
|
|
3
|
-
<div ref="container" class="gantt-
|
|
3
|
+
<div ref="container" class="gantt-container">
|
|
4
4
|
<!-- Draggable gantt bar -->
|
|
5
5
|
<div
|
|
6
6
|
ref="bar"
|
|
7
|
+
:data-rowindex="rowIndex"
|
|
8
|
+
:data-colindex="colIndex"
|
|
7
9
|
class="gantt-bar"
|
|
8
|
-
:class="{ 'is-dragging':
|
|
9
|
-
:style="barStyle"
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
:class="{ 'is-dragging': isAnyDragging }"
|
|
11
|
+
:style="barStyle"
|
|
12
|
+
@mouseenter="showConnectionHandles"
|
|
13
|
+
@mouseleave="hideConnectionHandles">
|
|
14
|
+
<!-- Connection handles for linking bars -->
|
|
15
|
+
<div
|
|
16
|
+
ref="leftConnectionHandle"
|
|
17
|
+
class="connection-handle left-connection-handle"
|
|
18
|
+
:class="{ visible: isLeftConnectionVisible, 'is-dragging': isLeftConnectionDragging }"
|
|
19
|
+
@mousedown.stop="startConnectionDrag('left', $event)">
|
|
20
|
+
<div class="connection-dot"></div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div
|
|
24
|
+
ref="rightConnectionHandle"
|
|
25
|
+
class="connection-handle right-connection-handle"
|
|
26
|
+
:class="{ visible: isRightConnectionVisible, 'is-dragging': isRightConnectionDragging }"
|
|
27
|
+
@mousedown.stop="startConnectionDrag('right', $event)">
|
|
28
|
+
<div class="connection-dot"></div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Resize handles for changing bar length -->
|
|
32
|
+
<div ref="leftResizeHandle" class="resize-handle left-resize-handle" :class="{ 'is-dragging': isLeftResizing }">
|
|
12
33
|
<div class="handle-grip"></div>
|
|
13
34
|
<div class="vertical-indicator left-indicator"></div>
|
|
14
35
|
</div>
|
|
15
36
|
|
|
16
37
|
<label v-if="label" class="gantt-label">{{ label }}</label>
|
|
17
38
|
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
<div
|
|
40
|
+
ref="rightResizeHandle"
|
|
41
|
+
class="resize-handle right-resize-handle"
|
|
42
|
+
:class="{ 'is-dragging': isRightResizing }">
|
|
20
43
|
<div class="handle-grip"></div>
|
|
21
44
|
<div class="vertical-indicator right-indicator"></div>
|
|
22
45
|
</div>
|
|
23
46
|
</div>
|
|
24
47
|
</div>
|
|
48
|
+
|
|
49
|
+
<!-- Connection drag preview line -->
|
|
50
|
+
<svg v-if="showDragPreview" :style="connectionDragStyle">
|
|
51
|
+
<line
|
|
52
|
+
:x1="dragPreview.startX"
|
|
53
|
+
:y1="dragPreview.startY"
|
|
54
|
+
:x2="dragPreview.endX"
|
|
55
|
+
:y2="dragPreview.endY"
|
|
56
|
+
stroke="#2196f3"
|
|
57
|
+
stroke-width="2"
|
|
58
|
+
stroke-dasharray="5,5" />
|
|
59
|
+
</svg>
|
|
25
60
|
</td>
|
|
26
61
|
</template>
|
|
27
62
|
|
|
28
63
|
<script setup lang="ts">
|
|
29
64
|
import { useDraggable, useElementBounding } from '@vueuse/core'
|
|
30
|
-
import { ref, computed, onMounted, useTemplateRef } from 'vue'
|
|
65
|
+
import { ref, computed, onMounted, onUnmounted, useTemplateRef, type StyleValue } from 'vue'
|
|
31
66
|
|
|
32
67
|
import { createTableStore } from '../stores/table'
|
|
68
|
+
import type { ConnectionPath } from '../types'
|
|
33
69
|
|
|
34
70
|
const {
|
|
35
71
|
store,
|
|
36
72
|
columnsCount,
|
|
37
73
|
rowIndex,
|
|
38
74
|
colIndex,
|
|
39
|
-
start,
|
|
75
|
+
start = 0,
|
|
40
76
|
end,
|
|
41
77
|
colspan = 1,
|
|
42
78
|
label,
|
|
43
|
-
color,
|
|
79
|
+
color = '#cccccc',
|
|
44
80
|
} = defineProps<{
|
|
45
81
|
store: ReturnType<typeof createTableStore>
|
|
46
82
|
columnsCount: number
|
|
@@ -53,182 +89,366 @@ const {
|
|
|
53
89
|
color?: string
|
|
54
90
|
}>()
|
|
55
91
|
|
|
56
|
-
const
|
|
92
|
+
const emit = defineEmits<{
|
|
93
|
+
'connection:create': [connection: ConnectionPath]
|
|
94
|
+
}>()
|
|
57
95
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} else {
|
|
62
|
-
baseColor.value = color
|
|
63
|
-
}
|
|
64
|
-
})
|
|
96
|
+
// Core refs and state
|
|
97
|
+
const barColor = ref(color.length >= 6 ? color : '#cccccc')
|
|
98
|
+
const barId = `gantt-bar-row-${rowIndex}-col-${colIndex}`
|
|
65
99
|
|
|
100
|
+
// Template refs
|
|
66
101
|
const containerRef = useTemplateRef('container')
|
|
67
102
|
const barRef = useTemplateRef('bar')
|
|
68
|
-
const
|
|
69
|
-
const
|
|
103
|
+
const leftResizeHandleRef = useTemplateRef('leftResizeHandle')
|
|
104
|
+
const rightResizeHandleRef = useTemplateRef('rightResizeHandle')
|
|
105
|
+
const leftConnectionHandleRef = useTemplateRef('leftConnectionHandle')
|
|
106
|
+
const rightConnectionHandleRef = useTemplateRef('rightConnectionHandle')
|
|
70
107
|
|
|
108
|
+
// Position tracking
|
|
71
109
|
const { width: totalBarWidth } = useElementBounding(containerRef)
|
|
72
110
|
const { left: barLeft, right: barRight } = useElementBounding(barRef)
|
|
73
|
-
|
|
111
|
+
|
|
112
|
+
// Bar positioning
|
|
113
|
+
const currentStart = ref(start)
|
|
74
114
|
const currentEnd = ref(end || currentStart.value + colspan)
|
|
75
|
-
|
|
115
|
+
|
|
116
|
+
// Drag states
|
|
117
|
+
const isLeftConnectionVisible = ref(false)
|
|
118
|
+
const isRightConnectionVisible = ref(false)
|
|
119
|
+
const isLeftConnectionDragging = ref(false)
|
|
120
|
+
const isRightConnectionDragging = ref(false)
|
|
121
|
+
const showDragPreview = ref(false)
|
|
122
|
+
const dragPreview = ref({ startX: 0, startY: 0, endX: 0, endY: 0 })
|
|
123
|
+
|
|
124
|
+
// Computed properties
|
|
125
|
+
const isAnyDragging = computed(() => isBarDragging.value || isLeftResizing.value || isRightResizing.value)
|
|
76
126
|
|
|
77
127
|
const pixelsPerColumn = computed(() => (colspan > 0 ? totalBarWidth.value / colspan : 0))
|
|
78
128
|
|
|
79
|
-
const barStyle = computed(() => {
|
|
129
|
+
const barStyle = computed((): StyleValue => {
|
|
80
130
|
const startPercent = (currentStart.value / colspan) * 100
|
|
81
131
|
const endPercent = (currentEnd.value / colspan) * 100
|
|
82
|
-
|
|
83
132
|
return {
|
|
84
133
|
left: `${startPercent}%`,
|
|
85
134
|
width: `${endPercent - startPercent}%`,
|
|
86
|
-
backgroundColor:
|
|
135
|
+
backgroundColor: barColor.value,
|
|
87
136
|
}
|
|
88
137
|
})
|
|
89
138
|
|
|
90
|
-
const
|
|
139
|
+
const connectionDragStyle = computed(
|
|
140
|
+
(): StyleValue => ({
|
|
141
|
+
position: 'fixed',
|
|
142
|
+
top: 0,
|
|
143
|
+
left: 0,
|
|
144
|
+
width: '100vw',
|
|
145
|
+
height: '100vh',
|
|
146
|
+
pointerEvents: 'none',
|
|
147
|
+
zIndex: 1000,
|
|
148
|
+
})
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
// Drag setup data
|
|
152
|
+
const dragStartData = ref({ startX: 0, startPos: 0 })
|
|
153
|
+
|
|
154
|
+
// Left resize handle dragging
|
|
155
|
+
const { isDragging: isLeftResizing } = useDraggable(leftResizeHandleRef, {
|
|
91
156
|
axis: 'x',
|
|
92
|
-
onStart: () =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
startX: barLeft.value,
|
|
96
|
-
startPos: currentStart.value,
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
onMove: ({ x }) => {
|
|
100
|
-
if (isLeftDragging.value && barRef.value) {
|
|
101
|
-
const deltaX = x - dragStartData.value.startX
|
|
102
|
-
const deltaColumns = deltaX / pixelsPerColumn.value
|
|
103
|
-
const newStart = Math.max(0, Math.min(currentEnd.value - 1, dragStartData.value.startPos + deltaColumns))
|
|
104
|
-
barRef.value.style.left = `${(newStart / colspan) * 100}%`
|
|
105
|
-
barRef.value.style.width = `${((currentEnd.value - newStart) / colspan) * 100}%`
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
onEnd: ({ x }) => {
|
|
109
|
-
if (barRef.value) {
|
|
110
|
-
const deltaX = x - dragStartData.value.startX
|
|
111
|
-
const deltaColumns = Math.round(deltaX / pixelsPerColumn.value)
|
|
112
|
-
const oldStart = currentStart.value
|
|
113
|
-
const newStart = Math.max(0, Math.min(currentEnd.value - 1, dragStartData.value.startPos + deltaColumns))
|
|
114
|
-
currentStart.value = newStart
|
|
115
|
-
|
|
116
|
-
store.updateGanttBar({
|
|
117
|
-
rowIndex,
|
|
118
|
-
colIndex,
|
|
119
|
-
type: 'resize',
|
|
120
|
-
edge: 'start',
|
|
121
|
-
oldStart,
|
|
122
|
-
newStart,
|
|
123
|
-
end: currentEnd.value,
|
|
124
|
-
delta: deltaColumns,
|
|
125
|
-
oldColspan: currentEnd.value - oldStart,
|
|
126
|
-
newColspan: currentEnd.value - newStart,
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
},
|
|
157
|
+
onStart: () => setupDragStart(barLeft.value, currentStart.value),
|
|
158
|
+
onMove: ({ x }) => handleLeftResize(x),
|
|
159
|
+
onEnd: ({ x }) => finishLeftResize(x),
|
|
130
160
|
})
|
|
131
161
|
|
|
132
|
-
|
|
162
|
+
// Right resize handle dragging
|
|
163
|
+
const { isDragging: isRightResizing } = useDraggable(rightResizeHandleRef, {
|
|
133
164
|
axis: 'x',
|
|
134
|
-
onStart: () =>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
startX: barRight.value,
|
|
138
|
-
startPos: currentEnd.value,
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
onMove: ({ x }) => {
|
|
142
|
-
if (isRightDragging.value && barRef.value) {
|
|
143
|
-
const deltaX = x - dragStartData.value.startX
|
|
144
|
-
const deltaColumns = deltaX / pixelsPerColumn.value
|
|
145
|
-
const newEnd = Math.max(
|
|
146
|
-
currentStart.value + 1,
|
|
147
|
-
Math.min(columnsCount, dragStartData.value.startPos + deltaColumns)
|
|
148
|
-
)
|
|
149
|
-
barRef.value.style.width = `${((newEnd - currentStart.value) / colspan) * 100}%`
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
onEnd: ({ x }) => {
|
|
153
|
-
if (barRef.value) {
|
|
154
|
-
const deltaX = x - dragStartData.value.startX
|
|
155
|
-
const deltaColumns = Math.round(deltaX / pixelsPerColumn.value)
|
|
156
|
-
const oldEnd = currentEnd.value
|
|
157
|
-
const newEnd = Math.max(
|
|
158
|
-
currentStart.value + 1,
|
|
159
|
-
Math.min(columnsCount, dragStartData.value.startPos + deltaColumns)
|
|
160
|
-
)
|
|
161
|
-
currentEnd.value = newEnd
|
|
162
|
-
|
|
163
|
-
store.updateGanttBar({
|
|
164
|
-
rowIndex,
|
|
165
|
-
colIndex,
|
|
166
|
-
type: 'resize',
|
|
167
|
-
edge: 'end',
|
|
168
|
-
oldEnd,
|
|
169
|
-
newEnd,
|
|
170
|
-
start: currentStart.value,
|
|
171
|
-
delta: deltaColumns,
|
|
172
|
-
oldColspan: oldEnd - currentStart.value,
|
|
173
|
-
newColspan: newEnd - currentStart.value,
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
},
|
|
165
|
+
onStart: () => setupDragStart(barRight.value, currentEnd.value),
|
|
166
|
+
onMove: ({ x }) => handleRightResize(x),
|
|
167
|
+
onEnd: ({ x }) => finishRightResize(x),
|
|
177
168
|
})
|
|
178
169
|
|
|
170
|
+
// Bar movement dragging
|
|
179
171
|
const { isDragging: isBarDragging } = useDraggable(barRef, {
|
|
180
|
-
exact: true,
|
|
172
|
+
exact: true,
|
|
181
173
|
axis: 'x',
|
|
182
|
-
onStart: () =>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
174
|
+
onStart: () => setupDragStart(barLeft.value, currentStart.value),
|
|
175
|
+
onMove: ({ x }) => handleBarMove(x),
|
|
176
|
+
onEnd: ({ x }) => finishBarMove(x),
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// Lifecycle
|
|
180
|
+
onMounted(() => {
|
|
181
|
+
registerGanttComponents()
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
onUnmounted(() => {
|
|
185
|
+
unregisterGanttComponents()
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
// Helper functions
|
|
189
|
+
function setupDragStart(startX: number, startPos: number) {
|
|
190
|
+
if (barRef.value) barRef.value.style.transition = 'none'
|
|
191
|
+
dragStartData.value = { startX, startPos }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function handleLeftResize(x: number) {
|
|
195
|
+
if (!isLeftResizing.value || !barRef.value) return
|
|
196
|
+
|
|
197
|
+
const deltaX = x - dragStartData.value.startX
|
|
198
|
+
const deltaColumns = deltaX / pixelsPerColumn.value
|
|
199
|
+
const newStart = Math.max(0, Math.min(currentEnd.value - 1, dragStartData.value.startPos + deltaColumns))
|
|
200
|
+
|
|
201
|
+
barRef.value.style.left = `${(newStart / colspan) * 100}%`
|
|
202
|
+
barRef.value.style.width = `${((currentEnd.value - newStart) / colspan) * 100}%`
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function finishLeftResize(x: number) {
|
|
206
|
+
if (!barRef.value) return
|
|
207
|
+
|
|
208
|
+
const deltaX = x - dragStartData.value.startX
|
|
209
|
+
const deltaColumns = Math.round(deltaX / pixelsPerColumn.value)
|
|
210
|
+
const oldStart = currentStart.value
|
|
211
|
+
const newStart = Math.max(0, Math.min(currentEnd.value - 1, dragStartData.value.startPos + deltaColumns))
|
|
212
|
+
|
|
213
|
+
currentStart.value = newStart
|
|
214
|
+
store.updateGanttBar({
|
|
215
|
+
rowIndex,
|
|
216
|
+
colIndex,
|
|
217
|
+
type: 'resize',
|
|
218
|
+
edge: 'start',
|
|
219
|
+
oldStart,
|
|
220
|
+
newStart,
|
|
221
|
+
end: currentEnd.value,
|
|
222
|
+
delta: deltaColumns,
|
|
223
|
+
oldColspan: currentEnd.value - oldStart,
|
|
224
|
+
newColspan: currentEnd.value - newStart,
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function handleRightResize(x: number) {
|
|
229
|
+
if (!isRightResizing.value || !barRef.value) return
|
|
230
|
+
|
|
231
|
+
const deltaX = x - dragStartData.value.startX
|
|
232
|
+
const deltaColumns = deltaX / pixelsPerColumn.value
|
|
233
|
+
const newEnd = Math.max(currentStart.value + 1, Math.min(columnsCount, dragStartData.value.startPos + deltaColumns))
|
|
234
|
+
|
|
235
|
+
barRef.value.style.width = `${((newEnd - currentStart.value) / colspan) * 100}%`
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function finishRightResize(x: number) {
|
|
239
|
+
if (!barRef.value) return
|
|
240
|
+
|
|
241
|
+
const deltaX = x - dragStartData.value.startX
|
|
242
|
+
const deltaColumns = Math.round(deltaX / pixelsPerColumn.value)
|
|
243
|
+
const oldEnd = currentEnd.value
|
|
244
|
+
const newEnd = Math.max(currentStart.value + 1, Math.min(columnsCount, dragStartData.value.startPos + deltaColumns))
|
|
245
|
+
|
|
246
|
+
currentEnd.value = newEnd
|
|
247
|
+
store.updateGanttBar({
|
|
248
|
+
rowIndex,
|
|
249
|
+
colIndex,
|
|
250
|
+
type: 'resize',
|
|
251
|
+
edge: 'end',
|
|
252
|
+
oldEnd,
|
|
253
|
+
newEnd,
|
|
254
|
+
start: currentStart.value,
|
|
255
|
+
delta: deltaColumns,
|
|
256
|
+
oldColspan: oldEnd - currentStart.value,
|
|
257
|
+
newColspan: newEnd - currentStart.value,
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function handleBarMove(x: number) {
|
|
262
|
+
if (!isBarDragging.value || !barRef.value) return
|
|
263
|
+
|
|
264
|
+
const deltaX = x - dragStartData.value.startX
|
|
265
|
+
const deltaColumns = deltaX / pixelsPerColumn.value
|
|
266
|
+
const barWidth = currentEnd.value - currentStart.value
|
|
267
|
+
const newStart = Math.max(0, Math.min(dragStartData.value.startPos + deltaColumns, columnsCount - barWidth))
|
|
268
|
+
|
|
269
|
+
barRef.value.style.left = `${(newStart / colspan) * 100}%`
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function finishBarMove(x: number) {
|
|
273
|
+
if (!barRef.value) return
|
|
274
|
+
|
|
275
|
+
const deltaX = x - dragStartData.value.startX
|
|
276
|
+
const deltaColumns = Math.round(deltaX / pixelsPerColumn.value)
|
|
277
|
+
const barWidth = currentEnd.value - currentStart.value
|
|
278
|
+
|
|
279
|
+
const oldStart = currentStart.value
|
|
280
|
+
const oldEnd = currentEnd.value
|
|
281
|
+
let newStart = dragStartData.value.startPos + deltaColumns
|
|
282
|
+
let newEnd = newStart + barWidth
|
|
283
|
+
|
|
284
|
+
// Boundary checks
|
|
285
|
+
if (newStart < 0) {
|
|
286
|
+
newStart = 0
|
|
287
|
+
newEnd = barWidth
|
|
288
|
+
} else if (newEnd > columnsCount) {
|
|
289
|
+
newEnd = columnsCount
|
|
290
|
+
newStart = newEnd - barWidth
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
currentStart.value = newStart
|
|
294
|
+
currentEnd.value = newEnd
|
|
295
|
+
|
|
296
|
+
store.updateGanttBar({
|
|
297
|
+
rowIndex,
|
|
298
|
+
colIndex,
|
|
299
|
+
type: 'bar',
|
|
300
|
+
oldStart,
|
|
301
|
+
oldEnd,
|
|
302
|
+
newStart,
|
|
303
|
+
newEnd,
|
|
304
|
+
delta: deltaColumns,
|
|
305
|
+
colspan: newEnd - newStart,
|
|
306
|
+
})
|
|
307
|
+
}
|
|
215
308
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
309
|
+
function registerGanttComponents() {
|
|
310
|
+
const { x: barX, y: barY } = useElementBounding(barRef)
|
|
311
|
+
const { x: leftX, y: leftY } = useElementBounding(leftConnectionHandleRef)
|
|
312
|
+
const { x: rightX, y: rightY } = useElementBounding(rightConnectionHandleRef)
|
|
313
|
+
|
|
314
|
+
store.registerGanttBar({
|
|
315
|
+
id: barId,
|
|
316
|
+
rowIndex,
|
|
317
|
+
colIndex,
|
|
318
|
+
startIndex: currentStart,
|
|
319
|
+
endIndex: currentEnd,
|
|
320
|
+
color: barColor,
|
|
321
|
+
label,
|
|
322
|
+
position: { x: barX, y: barY },
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
store.registerConnectionHandle({
|
|
326
|
+
id: `${barId}-connection-left`,
|
|
327
|
+
rowIndex,
|
|
328
|
+
colIndex,
|
|
329
|
+
side: 'left',
|
|
330
|
+
position: { x: leftX, y: leftY },
|
|
331
|
+
visible: isLeftConnectionVisible,
|
|
332
|
+
barId,
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
store.registerConnectionHandle({
|
|
336
|
+
id: `${barId}-connection-right`,
|
|
337
|
+
rowIndex,
|
|
338
|
+
colIndex,
|
|
339
|
+
side: 'right',
|
|
340
|
+
position: { x: rightX, y: rightY },
|
|
341
|
+
visible: isRightConnectionVisible,
|
|
342
|
+
barId,
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function unregisterGanttComponents() {
|
|
347
|
+
store.unregisterGanttBar(barId)
|
|
348
|
+
store.unregisterConnectionHandle(`${barId}-connection-left`)
|
|
349
|
+
store.unregisterConnectionHandle(`${barId}-connection-right`)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function showConnectionHandles() {
|
|
353
|
+
isLeftConnectionVisible.value = true
|
|
354
|
+
isRightConnectionVisible.value = true
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function hideConnectionHandles() {
|
|
358
|
+
if (!isLeftConnectionDragging.value && !isRightConnectionDragging.value) {
|
|
359
|
+
isLeftConnectionVisible.value = false
|
|
360
|
+
isRightConnectionVisible.value = false
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function startConnectionDrag(side: 'left' | 'right', event: MouseEvent) {
|
|
365
|
+
event.preventDefault()
|
|
366
|
+
event.stopPropagation()
|
|
367
|
+
|
|
368
|
+
showDragPreview.value = true
|
|
369
|
+
if (side === 'left') {
|
|
370
|
+
isLeftConnectionDragging.value = true
|
|
371
|
+
} else {
|
|
372
|
+
isRightConnectionDragging.value = true
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Set initial drag preview position
|
|
376
|
+
const handle = side === 'left' ? leftConnectionHandleRef.value : rightConnectionHandleRef.value
|
|
377
|
+
if (handle) {
|
|
378
|
+
const handleRect = handle.getBoundingClientRect()
|
|
379
|
+
const centerX = handleRect.left + handleRect.width / 2
|
|
380
|
+
const centerY = handleRect.top + handleRect.height / 2
|
|
381
|
+
dragPreview.value = { startX: centerX, startY: centerY, endX: centerX, endY: centerY }
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const handleMouseMove = (moveEvent: MouseEvent) => {
|
|
385
|
+
dragPreview.value.endX = moveEvent.clientX
|
|
386
|
+
dragPreview.value.endY = moveEvent.clientY
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const handleMouseUp = (event: MouseEvent) => {
|
|
390
|
+
handleConnectionDrop(event, side)
|
|
391
|
+
cleanupConnectionDrag(handleMouseMove, handleMouseUp)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
document.addEventListener('mousemove', handleMouseMove)
|
|
395
|
+
document.addEventListener('mouseup', handleMouseUp)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function handleConnectionDrop(upEvent: MouseEvent, sourceSide: 'left' | 'right') {
|
|
399
|
+
const targetElement = document.elementFromPoint(upEvent.clientX, upEvent.clientY)
|
|
400
|
+
const targetHandle = targetElement?.closest('.connection-handle')
|
|
401
|
+
|
|
402
|
+
if (
|
|
403
|
+
targetHandle &&
|
|
404
|
+
targetHandle !== (sourceSide === 'left' ? leftConnectionHandleRef.value : rightConnectionHandleRef.value)
|
|
405
|
+
) {
|
|
406
|
+
const targetBar = targetHandle.closest('.gantt-bar')
|
|
407
|
+
|
|
408
|
+
if (targetBar) {
|
|
409
|
+
const targetRowIndex = parseInt(targetBar.getAttribute('data-rowindex') || '0')
|
|
410
|
+
const targetColIndex = parseInt(targetBar.getAttribute('data-colindex') || '0')
|
|
411
|
+
const targetSide = targetHandle.classList.contains('left-connection-handle') ? 'left' : 'right'
|
|
412
|
+
const targetBarId = `gantt-bar-row-${targetRowIndex}-col-${targetColIndex}`
|
|
413
|
+
|
|
414
|
+
const connection = store.createConnection(
|
|
415
|
+
`${barId}-connection-${sourceSide}`,
|
|
416
|
+
`${targetBarId}-connection-${targetSide}`
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
if (connection) {
|
|
420
|
+
emit('connection:create', connection)
|
|
421
|
+
}
|
|
230
422
|
}
|
|
231
|
-
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function cleanupConnectionDrag(
|
|
427
|
+
handleMouseMove: (event: MouseEvent) => void,
|
|
428
|
+
handleMouseUp: (event: MouseEvent) => void
|
|
429
|
+
) {
|
|
430
|
+
showDragPreview.value = false
|
|
431
|
+
isLeftConnectionDragging.value = false
|
|
432
|
+
isRightConnectionDragging.value = false
|
|
433
|
+
|
|
434
|
+
document.removeEventListener('mousemove', handleMouseMove)
|
|
435
|
+
document.removeEventListener('mouseup', handleMouseUp)
|
|
436
|
+
|
|
437
|
+
if (!barRef.value?.matches(':hover')) {
|
|
438
|
+
hideConnectionHandles()
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
defineExpose({
|
|
443
|
+
barStyle,
|
|
444
|
+
cleanupConnectionDrag,
|
|
445
|
+
currentEnd,
|
|
446
|
+
handleConnectionDrop,
|
|
447
|
+
isLeftConnectionDragging,
|
|
448
|
+
isLeftConnectionVisible,
|
|
449
|
+
isRightConnectionDragging,
|
|
450
|
+
isRightConnectionVisible,
|
|
451
|
+
showDragPreview,
|
|
232
452
|
})
|
|
233
453
|
</script>
|
|
234
454
|
|
|
@@ -240,17 +460,16 @@ const { isDragging: isBarDragging } = useDraggable(barRef, {
|
|
|
240
460
|
height: 100%;
|
|
241
461
|
}
|
|
242
462
|
|
|
243
|
-
.gantt-
|
|
463
|
+
.gantt-container {
|
|
244
464
|
position: relative;
|
|
245
465
|
height: 100%;
|
|
246
466
|
background-color: #f0f0f0;
|
|
247
467
|
border-radius: 4px;
|
|
248
|
-
overflow: visible;
|
|
468
|
+
overflow: visible;
|
|
249
469
|
}
|
|
250
470
|
|
|
251
471
|
.gantt-bar {
|
|
252
472
|
position: absolute;
|
|
253
|
-
height: 100%;
|
|
254
473
|
border-radius: 4px;
|
|
255
474
|
display: flex;
|
|
256
475
|
align-items: center;
|
|
@@ -259,6 +478,10 @@ const { isDragging: isBarDragging } = useDraggable(barRef, {
|
|
|
259
478
|
box-sizing: border-box;
|
|
260
479
|
border: 1px solid rgba(0, 0, 0, 0.5);
|
|
261
480
|
transition: left 0.1s ease-out, width 0.1s ease-out;
|
|
481
|
+
height: 80%;
|
|
482
|
+
top: 50%;
|
|
483
|
+
z-index: 2;
|
|
484
|
+
transform: translateY(-50%);
|
|
262
485
|
}
|
|
263
486
|
|
|
264
487
|
.gantt-bar:active {
|
|
@@ -281,7 +504,7 @@ const { isDragging: isBarDragging } = useDraggable(barRef, {
|
|
|
281
504
|
user-select: none;
|
|
282
505
|
}
|
|
283
506
|
|
|
284
|
-
.
|
|
507
|
+
.resize-handle {
|
|
285
508
|
position: relative;
|
|
286
509
|
width: 12px;
|
|
287
510
|
height: 100%;
|
|
@@ -293,10 +516,11 @@ const { isDragging: isBarDragging } = useDraggable(barRef, {
|
|
|
293
516
|
background: rgba(0, 0, 0, 0.25);
|
|
294
517
|
}
|
|
295
518
|
|
|
296
|
-
.left-handle {
|
|
519
|
+
.left-resize-handle {
|
|
297
520
|
border-right: 1px solid rgba(0, 0, 0, 0.5);
|
|
298
521
|
}
|
|
299
|
-
|
|
522
|
+
|
|
523
|
+
.right-resize-handle {
|
|
300
524
|
border-left: 1px solid rgba(0, 0, 0, 0.5);
|
|
301
525
|
}
|
|
302
526
|
|
|
@@ -307,21 +531,20 @@ const { isDragging: isBarDragging } = useDraggable(barRef, {
|
|
|
307
531
|
background: rgba(0, 0, 0, 0.8);
|
|
308
532
|
}
|
|
309
533
|
|
|
310
|
-
.
|
|
534
|
+
.resize-handle:hover {
|
|
311
535
|
background-color: rgba(255, 255, 255, 0.5);
|
|
312
536
|
}
|
|
313
537
|
|
|
314
|
-
/* Vertical indicators for handles */
|
|
315
538
|
.vertical-indicator {
|
|
316
539
|
position: absolute;
|
|
317
540
|
width: 2px;
|
|
318
541
|
opacity: 0;
|
|
319
542
|
pointer-events: none;
|
|
320
543
|
transition: opacity 0.2s ease;
|
|
321
|
-
top: -100vh;
|
|
322
|
-
height: 100vh;
|
|
544
|
+
top: -100vh;
|
|
545
|
+
height: 100vh;
|
|
323
546
|
z-index: 5;
|
|
324
|
-
background-color: v-bind(
|
|
547
|
+
background-color: v-bind(barColor);
|
|
325
548
|
}
|
|
326
549
|
|
|
327
550
|
.left-indicator {
|
|
@@ -334,11 +557,11 @@ const { isDragging: isBarDragging } = useDraggable(barRef, {
|
|
|
334
557
|
transform: translateX(50%);
|
|
335
558
|
}
|
|
336
559
|
|
|
337
|
-
.
|
|
560
|
+
.resize-handle.is-dragging .vertical-indicator {
|
|
338
561
|
opacity: 0.7;
|
|
339
562
|
}
|
|
340
563
|
|
|
341
|
-
.gantt-
|
|
564
|
+
.gantt-container::after {
|
|
342
565
|
content: '';
|
|
343
566
|
position: absolute;
|
|
344
567
|
top: 0;
|
|
@@ -350,4 +573,55 @@ const { isDragging: isBarDragging } = useDraggable(barRef, {
|
|
|
350
573
|
pointer-events: none;
|
|
351
574
|
z-index: 1;
|
|
352
575
|
}
|
|
576
|
+
|
|
577
|
+
.connection-handle {
|
|
578
|
+
position: absolute;
|
|
579
|
+
top: 50%;
|
|
580
|
+
transform: translateY(-50%);
|
|
581
|
+
width: 16px;
|
|
582
|
+
height: 16px;
|
|
583
|
+
opacity: 0;
|
|
584
|
+
transition: opacity 0.2s ease;
|
|
585
|
+
cursor: crosshair;
|
|
586
|
+
z-index: 15;
|
|
587
|
+
display: flex;
|
|
588
|
+
align-items: center;
|
|
589
|
+
justify-content: center;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.connection-handle.visible {
|
|
593
|
+
opacity: 1;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.left-connection-handle {
|
|
597
|
+
left: -16px;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.right-connection-handle {
|
|
601
|
+
right: -16px;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.connection-dot {
|
|
605
|
+
width: 8px;
|
|
606
|
+
height: 8px;
|
|
607
|
+
border-radius: 50%;
|
|
608
|
+
background-color: #2196f3;
|
|
609
|
+
border: 2px solid white;
|
|
610
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.connection-handle:hover .connection-dot {
|
|
614
|
+
background-color: #1976d2;
|
|
615
|
+
transform: scale(1.2);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.connection-handle.is-dragging {
|
|
619
|
+
opacity: 1 !important;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.connection-handle.is-dragging .connection-dot {
|
|
623
|
+
background-color: #1976d2;
|
|
624
|
+
transform: scale(1.3);
|
|
625
|
+
box-shadow: 0 2px 8px rgba(33, 150, 243, 0.4);
|
|
626
|
+
}
|
|
353
627
|
</style>
|