Skip to content

Commit dfc8483

Browse files
committed
Partial mobile support
1 parent ae44fde commit dfc8483

File tree

5 files changed

+335
-31
lines changed

5 files changed

+335
-31
lines changed

src/components/GGanttBar.vue

+38-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import useBarDragManagement from "../composables/useBarDragManagement"
55
import useTimePositionMapping from "../composables/useTimePositionMapping"
66
import useBarDragLimit from "../composables/useBarDragLimit"
77
import { useBarKeyboardControl } from "../composables/useBarKeyboardControl"
8+
import { useTouchEvents } from "../composables/useTouchEvents"
89
import type { GanttBarObject } from "../types"
910
import provideEmitBarEvent from "../provider/provideEmitBarEvent"
1011
import provideConfig from "../provider/provideConfig"
@@ -46,11 +47,10 @@ const prepareForDrag = () => {
4647
4748
window.addEventListener("mousemove", firstMousemoveCallback, {
4849
once: true
49-
}) // on first mousemove event
50+
})
5051
window.addEventListener(
5152
"mouseup",
5253
() => {
53-
// in case user does not move the mouse after mousedown at all
5454
window.removeEventListener("mousemove", firstMousemoveCallback)
5555
isDragging.value = false
5656
},
@@ -78,6 +78,38 @@ const { barStart, barEnd, width, chartStart, chartEnd, chartSize } = config
7878
const xStart = ref(0)
7979
const xEnd = ref(0)
8080
81+
const { handleTouchStart, handleTouchMove, handleTouchEnd, handleTouchCancel } = useTouchEvents(
82+
(_draggedBar, e) => {
83+
firstMousemoveCallback(e)
84+
isDragging.value = true
85+
}
86+
)
87+
const onTouchEvent = (e: TouchEvent) => {
88+
console.log(e)
89+
if (bar.value.ganttBarConfig.immobile) return
90+
91+
let mouseEvent: MouseEvent | undefined
92+
93+
switch (e.type) {
94+
case "touchstart":
95+
mouseEvent = handleTouchStart(e, bar.value)!
96+
break
97+
case "touchmove":
98+
mouseEvent = handleTouchMove(e)!
99+
break
100+
case "touchend":
101+
mouseEvent = handleTouchEnd(e)!
102+
break
103+
case "touchcancel":
104+
mouseEvent = handleTouchCancel(e)!
105+
break
106+
}
107+
108+
if (mouseEvent) {
109+
onMouseEvent(mouseEvent)
110+
}
111+
}
112+
81113
onMounted(() => {
82114
xStart.value = mapTimeToPosition(bar.value[barStart.value])
83115
xEnd.value = mapTimeToPosition(bar.value[barEnd.value])
@@ -133,6 +165,10 @@ const getGroupBarPath = (width: number, height: number) => {
133165
@mouseenter="onMouseEvent"
134166
@mouseleave="onMouseEvent"
135167
@contextmenu="onMouseEvent"
168+
@touchstart="onTouchEvent"
169+
@touchmove="onTouchEvent"
170+
@touchend="onTouchEvent"
171+
@touchcancel="onTouchEvent"
136172
@keydown="onBarKeyDown"
137173
role="listitem"
138174
:aria-label="`Activity ${barConfig.label}`"

src/components/GGanttLabelColumn.vue

+40-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import useDayjsHelper from "../composables/useDayjsHelper"
2222
import type { UseRowsReturn } from "../composables/useRows"
2323
import { useRowDragAndDrop } from "../composables/useRowDragAndDrop"
24+
import { useColumnTouchResize } from "../composables/useColumnTouchResize"
2425
2526
interface LabelColumnRowProps extends ChartRow {
2627
indentLevel?: number
@@ -67,6 +68,24 @@ const {
6768
(_event, payload) => emit("row-drop", payload)
6869
)
6970
71+
const { touchState, handleTouchStart, handleTouchMove, handleTouchEnd, handleTouchCancel } =
72+
useColumnTouchResize()
73+
74+
const handleColumnTouchStart = (e: TouchEvent, column: string) => {
75+
if (!labelResizable) return
76+
77+
const currentWidth = columnWidths.get(column) || labelColumnWidth.value
78+
handleTouchStart(e, column, currentWidth)
79+
}
80+
81+
const handleColumnTouchMove = (e: TouchEvent) => {
82+
if (!labelResizable) return
83+
84+
handleTouchMove(e, (column: string, newWidth: number) => {
85+
columnWidths.set(column, newWidth)
86+
})
87+
}
88+
7089
const columnWidths = reactive<Map<string, number>>(new Map())
7190
const isDragging = ref(false)
7291
const dragStartX = ref(0)
@@ -397,7 +416,15 @@ defineExpose({
397416
v-if="labelResizable"
398417
class="column-resizer"
399418
@mousedown="(e) => handleDragStart(e, column.field)"
400-
:class="{ 'is-dragging': isDragging && draggedColumn === column.field }"
419+
@touchstart="(e) => handleColumnTouchStart(e, column.field)"
420+
@touchmove="handleColumnTouchMove"
421+
@touchend="handleTouchEnd"
422+
@touchcancel="handleTouchCancel"
423+
:class="{
424+
'is-dragging': isDragging && draggedColumn === column.field,
425+
'is-touch-resizing':
426+
touchState.isResizing && touchState.currentColumn === column.field
427+
}"
401428
></div>
402429
</div>
403430
</template>
@@ -611,10 +638,21 @@ defineExpose({
611638
}
612639
613640
.column-resizer:hover,
614-
.column-resizer.is-dragging {
641+
.column-resizer.is-dragging,
642+
.column-resizer.is-touch-resizing {
615643
background: rgba(0, 0, 0, 0.1);
616644
}
617645
646+
.g-label-column-header-cell:has(.column-resizer.is-touch-resizing) {
647+
background-color: rgba(0, 0, 0, 0.05);
648+
}
649+
650+
@media (max-width: 768px) {
651+
.column-resizer {
652+
width: 16px;
653+
}
654+
}
655+
618656
.g-label-column {
619657
user-select: none;
620658
}
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { ref } from "vue"
2+
3+
interface TouchResizeState {
4+
isResizing: boolean
5+
startX: number
6+
currentColumn: string | null
7+
initialWidth: number
8+
}
9+
10+
export function useColumnTouchResize() {
11+
const touchState = ref<TouchResizeState>({
12+
isResizing: false,
13+
startX: 0,
14+
currentColumn: null,
15+
initialWidth: 0
16+
})
17+
18+
const resetTouchState = () => {
19+
touchState.value = {
20+
isResizing: false,
21+
startX: 0,
22+
currentColumn: null,
23+
initialWidth: 0
24+
}
25+
}
26+
27+
const handleTouchStart = (e: TouchEvent, column: string, currentWidth: number) => {
28+
const touch = e.touches[0]
29+
if (!touch) return
30+
31+
e.preventDefault()
32+
33+
touchState.value = {
34+
isResizing: true,
35+
startX: touch.clientX,
36+
currentColumn: column,
37+
initialWidth: currentWidth
38+
}
39+
}
40+
41+
const handleTouchMove = (e: TouchEvent, onResize: (column: string, newWidth: number) => void) => {
42+
const touch = e.touches[0]
43+
if (!touch || !touchState.value.isResizing) return
44+
45+
e.preventDefault()
46+
47+
const deltaX = touch.clientX - touchState.value.startX
48+
const newWidth = Math.max(50, touchState.value.initialWidth + deltaX)
49+
50+
if (touchState.value.currentColumn) {
51+
onResize(touchState.value.currentColumn, newWidth)
52+
}
53+
}
54+
55+
const handleTouchEnd = () => {
56+
if (touchState.value.isResizing) {
57+
resetTouchState()
58+
}
59+
}
60+
61+
const handleTouchCancel = handleTouchEnd
62+
63+
return {
64+
touchState,
65+
handleTouchStart,
66+
handleTouchMove,
67+
handleTouchEnd,
68+
handleTouchCancel
69+
}
70+
}

src/composables/useRowDragAndDrop.ts

+28-27
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,31 @@ export function useRowDragAndDrop(
112112
}
113113
}
114114

115+
const isDescendant = (parent: ChartRow, potentialChild: ChartRow): boolean => {
116+
if (!parent.children) {
117+
return false
118+
}
119+
120+
return parent.children.some(
121+
(child) => child.id === potentialChild.id || isDescendant(child, potentialChild)
122+
)
123+
}
124+
125+
const findRowIndexById = (rows: ChartRow[], id: string | number): [number, ChartRow[]] => {
126+
for (let i = 0; i < rows.length; i++) {
127+
if (rows[i]!.id === id) {
128+
return [i, rows]
129+
}
130+
if (rows[i]!.children?.length) {
131+
const [index, parentRows] = findRowIndexById(rows[i]!.children!, id)
132+
if (index !== -1) {
133+
return [index, parentRows]
134+
}
135+
}
136+
}
137+
return [-1, rows]
138+
}
139+
115140
/**
116141
* Handles the completion of a drag operation
117142
* Updates row positions and emits drop event
@@ -122,40 +147,14 @@ export function useRowDragAndDrop(
122147
return
123148
}
124149

125-
const isDescendant = (parent: ChartRow, potentialChild: ChartRow): boolean => {
126-
if (!parent.children) {
127-
return false
128-
}
129-
130-
return parent.children.some(
131-
(child) => child.id === potentialChild.id || isDescendant(child, potentialChild)
132-
)
133-
}
134-
135150
if (isDescendant(draggedRow, dropTarget.row)) {
136151
return
137152
}
138153

139-
const findRowIndexById = (rows: ChartRow[], id: string | number): [number, ChartRow[]] => {
140-
for (let i = 0; i < rows.length; i++) {
141-
if (rows[i]!.id === id) {
142-
return [i, rows]
143-
}
144-
if (rows[i]!.children?.length) {
145-
const [index, parentRows] = findRowIndexById(rows[i]!.children!, id)
146-
if (index !== -1) {
147-
return [index, parentRows]
148-
}
149-
}
150-
}
151-
return [-1, rows]
152-
}
153-
154154
const [sourceIndex, sourceParentRows] = findRowIndexById(rows.value, draggedRow.id!)
155155
const [targetIndex, targetParentRows] = findRowIndexById(rows.value, dropTarget.row.id!)
156156

157157
if (sourceIndex === -1 || targetIndex === -1) {
158-
console.log("Row non trovate")
159158
return
160159
}
161160

@@ -200,6 +199,8 @@ export function useRowDragAndDrop(
200199
handleDragStart,
201200
handleDragOver,
202201
handleDrop,
203-
resetOrder
202+
resetOrder,
203+
isDescendant,
204+
findRowIndexById
204205
}
205206
}

0 commit comments

Comments
 (0)