@aiquants/drag-drop-panels 0.7.2 → 0.8.0
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/LICENSE +21 -0
- package/README.md +32 -10
- package/dist/drag-drop/hooks/useCustomDragHandlers.d.ts.map +1 -1
- package/dist/index.es.js +88 -88
- package/dist/index.umd.js +2 -2
- package/dist/styles/drag-drop-panels.standalone.css +3 -0
- package/package.json +11 -4
- package/src/DragDropLayout.tsx +751 -0
- package/src/drag-drop/ColumnControlPanel.tsx +123 -0
- package/src/drag-drop/ColumnInsertPanel.tsx +106 -0
- package/src/drag-drop/DebugComponents.tsx +222 -0
- package/src/drag-drop/DebugControlPanel.tsx +200 -0
- package/src/drag-drop/DebugOverlay.tsx +250 -0
- package/src/drag-drop/DragDropLayout.css +130 -0
- package/src/drag-drop/ResizeHandle.tsx +208 -0
- package/src/drag-drop/ResponsiveControl.tsx +33 -0
- package/src/drag-drop/hooks/dragGhostScale.spec.tsx +153 -0
- package/src/drag-drop/hooks/useColumnLayout.ts +457 -0
- package/src/drag-drop/hooks/useCustomDragHandlers.tsx +333 -0
- package/src/drag-drop/hooks/useDragDropColumns.tsx +446 -0
- package/src/drag-drop/hooks/useDragDropState.tsx +200 -0
- package/src/drag-drop/hooks/useFitScale.ts +48 -0
- package/src/drag-drop/hooks/useMouseTracking.tsx +102 -0
- package/src/drag-drop/hooks/useNormalDragHandlers.tsx +427 -0
- package/src/drag-drop/hooks/usePanelManagement.tsx +238 -0
- package/src/drag-drop/hooks/useResponsiveColumns.ts +210 -0
- package/src/drag-drop/resize-handle.css +71 -0
- package/src/drag-drop/types.ts +248 -0
- package/src/drag-drop/utils/columnIdUtils.ts +104 -0
- package/src/drag-drop/utils/edgeScrollUtils.ts +124 -0
- package/src/drag-drop/utils/simple-logger.ts +162 -0
- package/src/index.ts +60 -0
- package/src/styles/standalone.entry.css +27 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview This file defines the `useCustomDragHandlers` custom hook.
|
|
3
|
+
* This hook encapsulates the complex logic for a custom drag-and-drop implementation
|
|
4
|
+
* that does not rely on the standard HTML5 Drag and Drop API. It manages drag initiation,
|
|
5
|
+
* movement, and completion using mouse events (`mousedown`, `mousemove`, `mouseup`).
|
|
6
|
+
*
|
|
7
|
+
* このファイルは `useCustomDragHandlers` カスタムフックを定義します。
|
|
8
|
+
* このフックは、標準のHTML5ドラッグ&ドロップAPIに依存しない、カスタムのドラッグ&ドロップ実装の
|
|
9
|
+
* 複雑なロジックをカプセル化します。マウスイベント(`mousedown`, `mousemove`, `mouseup`)を
|
|
10
|
+
* 使用して、ドラッグの開始、移動、完了を管理します。
|
|
11
|
+
*/
|
|
12
|
+
import { type DragEvent, type MouseEvent, useCallback, useEffect, useRef } from "react"
|
|
13
|
+
import { debugLog } from "../DebugComponents"
|
|
14
|
+
import type { ClickState, CustomDragState, DebugConfig, DragOverPosition, DragState, OriginalPanelPosition } from "../types"
|
|
15
|
+
import { DEFAULT_EDGE_SCROLL_CONFIG, EdgeScrollManager } from "../utils/edgeScrollUtils"
|
|
16
|
+
|
|
17
|
+
interface UseCustomDragHandlersProps {
|
|
18
|
+
debugConfig: DebugConfig
|
|
19
|
+
dragState: DragState
|
|
20
|
+
customDrag: CustomDragState
|
|
21
|
+
clickState: ClickState
|
|
22
|
+
dragModes: Record<string, "normal" | "custom">
|
|
23
|
+
isMouseDown: boolean
|
|
24
|
+
originalPanelPosition: OriginalPanelPosition | null
|
|
25
|
+
dragOverPosition: DragOverPosition | null
|
|
26
|
+
setCustomDrag: (state: CustomDragState) => void
|
|
27
|
+
setDragState: (state: DragState | ((prev: DragState) => DragState)) => void
|
|
28
|
+
setDragOverPosition: (position: DragOverPosition | null) => void
|
|
29
|
+
setOriginalPanelPosition: (position: OriginalPanelPosition | null) => void
|
|
30
|
+
setClickState: (state: ClickState) => void
|
|
31
|
+
setIsMouseDown: (isDown: boolean) => void
|
|
32
|
+
findPanelPosition: (panelId: string) => OriginalPanelPosition | null
|
|
33
|
+
performPanelMove: (panelId: string, targetColumn: string, insertIndex: number) => void
|
|
34
|
+
showMousePosition: (x: number, y: number) => void
|
|
35
|
+
updateMousePosition: (x: number, y: number) => void
|
|
36
|
+
recordMouseDown: (x: number, y: number) => void
|
|
37
|
+
recordClickPosition: (x: number, y: number) => void
|
|
38
|
+
isVirtualClick: (x: number, y: number, maxTime?: number, maxDistance?: number) => boolean
|
|
39
|
+
calculateMoveDistance: (x: number, y: number) => number
|
|
40
|
+
clearMouseTracking: () => void
|
|
41
|
+
resetDragState: () => void
|
|
42
|
+
resetClickState: () => void
|
|
43
|
+
createDragImage: (panelId: string, offsetX: number, offsetY: number) => void
|
|
44
|
+
updateDragImagePosition: (x: number, y: number) => void
|
|
45
|
+
removeDragImage: () => void
|
|
46
|
+
handlePlaceholderDragEnter: (columnIndex: number, insertIndex: number) => void
|
|
47
|
+
handlePlaceholderDragLeave: (e?: DragEvent<Element>, columnIndex?: number, insertIndex?: number) => void
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const useCustomDragHandlers = ({
|
|
51
|
+
debugConfig,
|
|
52
|
+
dragState,
|
|
53
|
+
customDrag,
|
|
54
|
+
clickState,
|
|
55
|
+
dragModes,
|
|
56
|
+
isMouseDown,
|
|
57
|
+
dragOverPosition,
|
|
58
|
+
setCustomDrag,
|
|
59
|
+
setDragState,
|
|
60
|
+
setDragOverPosition,
|
|
61
|
+
setOriginalPanelPosition,
|
|
62
|
+
setClickState,
|
|
63
|
+
setIsMouseDown,
|
|
64
|
+
findPanelPosition,
|
|
65
|
+
performPanelMove,
|
|
66
|
+
showMousePosition,
|
|
67
|
+
updateMousePosition,
|
|
68
|
+
recordMouseDown,
|
|
69
|
+
recordClickPosition,
|
|
70
|
+
isVirtualClick,
|
|
71
|
+
calculateMoveDistance,
|
|
72
|
+
clearMouseTracking,
|
|
73
|
+
resetDragState,
|
|
74
|
+
resetClickState,
|
|
75
|
+
createDragImage,
|
|
76
|
+
updateDragImagePosition,
|
|
77
|
+
removeDragImage,
|
|
78
|
+
handlePlaceholderDragEnter,
|
|
79
|
+
handlePlaceholderDragLeave,
|
|
80
|
+
}: UseCustomDragHandlersProps) => {
|
|
81
|
+
const debugConfigRef = useRef(debugConfig)
|
|
82
|
+
debugConfigRef.current = debugConfig
|
|
83
|
+
|
|
84
|
+
const clickStateTimeoutRef = useRef<number | null>(null)
|
|
85
|
+
const dragStartMouseCoordRef = useRef<{ x: number; y: number } | null>(null)
|
|
86
|
+
const clickCountRef = useRef(0)
|
|
87
|
+
|
|
88
|
+
const edgeScrollManagerRef = useRef<EdgeScrollManager>(new EdgeScrollManager(DEFAULT_EDGE_SCROLL_CONFIG))
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
// DragDropLayout が出力する data 属性で特定する (クラスセレクタは utility 変更で壊れるため禁止)
|
|
92
|
+
const scrollElement = document.querySelector<HTMLElement>("main[data-aqdd-scroll-root]")
|
|
93
|
+
if (scrollElement) {
|
|
94
|
+
edgeScrollManagerRef.current.setScrollElement(scrollElement)
|
|
95
|
+
}
|
|
96
|
+
}, [])
|
|
97
|
+
|
|
98
|
+
const dragOverPositionRef = useRef(dragOverPosition)
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
dragOverPositionRef.current = dragOverPosition
|
|
101
|
+
debugLog(debugConfigRef.current, 2, "dragOverPositionRef更新:", dragOverPosition)
|
|
102
|
+
}, [dragOverPosition])
|
|
103
|
+
|
|
104
|
+
const handleGlobalMouseMove = useCallback(
|
|
105
|
+
(e: globalThis.MouseEvent) => {
|
|
106
|
+
if (customDrag.isActive || clickState.isClicked) {
|
|
107
|
+
e.preventDefault()
|
|
108
|
+
const selection = window.getSelection()
|
|
109
|
+
if (selection && selection.rangeCount > 0) {
|
|
110
|
+
selection.removeAllRanges()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (customDrag.isActive) {
|
|
115
|
+
updateMousePosition(e.clientX, e.clientY)
|
|
116
|
+
updateDragImagePosition(e.clientX, e.clientY)
|
|
117
|
+
edgeScrollManagerRef.current.update(e.clientX, e.clientY)
|
|
118
|
+
|
|
119
|
+
const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY)
|
|
120
|
+
const dropZone = elementAtPoint?.closest<HTMLElement>("[data-drop-zone]")
|
|
121
|
+
|
|
122
|
+
if (dropZone) {
|
|
123
|
+
document.body.classList.add("droppable")
|
|
124
|
+
const columnIndexAttr = dropZone.getAttribute("data-column-index")
|
|
125
|
+
const columnIndex = columnIndexAttr ? parseInt(columnIndexAttr, 10) : null
|
|
126
|
+
|
|
127
|
+
if (columnIndex !== null && columnIndex !== dragState.dragOverColumn) {
|
|
128
|
+
setDragState((prev) => ({ ...prev, dragOverColumn: columnIndex }))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const placeholder = elementAtPoint?.closest<HTMLElement>("[data-placeholder-index]")
|
|
132
|
+
if (placeholder) {
|
|
133
|
+
const placeholderIndexAttr = placeholder.getAttribute("data-placeholder-index")
|
|
134
|
+
const placeholderColumnAttr = placeholder.getAttribute("data-placeholder-column")
|
|
135
|
+
|
|
136
|
+
if (placeholderIndexAttr && placeholderColumnAttr) {
|
|
137
|
+
const insertIndex = parseInt(placeholderIndexAttr, 10)
|
|
138
|
+
const targetColumnIndex = parseInt(placeholderColumnAttr, 10)
|
|
139
|
+
handlePlaceholderDragEnter(targetColumnIndex, insertIndex)
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
handlePlaceholderDragLeave()
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
document.body.classList.remove("droppable")
|
|
146
|
+
setDragOverPosition(null)
|
|
147
|
+
setDragState((prev) => ({ ...prev, dragOverColumn: null }))
|
|
148
|
+
}
|
|
149
|
+
} else if (isMouseDown && clickState.isClicked && clickState.panelId) {
|
|
150
|
+
if (dragModes[clickState.panelId] !== "custom") return
|
|
151
|
+
|
|
152
|
+
const distance = calculateMoveDistance(e.clientX, e.clientY)
|
|
153
|
+
if (distance < 10) return
|
|
154
|
+
|
|
155
|
+
debugLog(debugConfigRef.current, 1, "🚀 カスタムドラッグ開始 -", clickState.panelId)
|
|
156
|
+
|
|
157
|
+
const element = document.querySelector<HTMLElement>(`[data-panel-id="${clickState.panelId}"]`)
|
|
158
|
+
if (element && dragStartMouseCoordRef.current) {
|
|
159
|
+
const rect = element.getBoundingClientRect()
|
|
160
|
+
const offsetX = dragStartMouseCoordRef.current.x - rect.left
|
|
161
|
+
const offsetY = dragStartMouseCoordRef.current.y - rect.top
|
|
162
|
+
|
|
163
|
+
createDragImage(clickState.panelId, offsetX, offsetY)
|
|
164
|
+
document.body.classList.add("custom-dragging")
|
|
165
|
+
|
|
166
|
+
setCustomDrag({
|
|
167
|
+
isActive: true,
|
|
168
|
+
panelId: clickState.panelId,
|
|
169
|
+
startX: offsetX,
|
|
170
|
+
startY: offsetY,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
setDragState({ isDragging: true, draggedPanel: clickState.panelId, dragOverColumn: null })
|
|
175
|
+
setOriginalPanelPosition(findPanelPosition(clickState.panelId))
|
|
176
|
+
showMousePosition(e.clientX, e.clientY)
|
|
177
|
+
|
|
178
|
+
setClickState({ isClicked: false, panelId: null })
|
|
179
|
+
setIsMouseDown(false)
|
|
180
|
+
clearMouseTracking()
|
|
181
|
+
dragStartMouseCoordRef.current = null
|
|
182
|
+
|
|
183
|
+
if (clickStateTimeoutRef.current) {
|
|
184
|
+
clearTimeout(clickStateTimeoutRef.current)
|
|
185
|
+
clickStateTimeoutRef.current = null
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
[
|
|
190
|
+
customDrag.isActive,
|
|
191
|
+
clickState,
|
|
192
|
+
dragState.dragOverColumn,
|
|
193
|
+
dragModes,
|
|
194
|
+
isMouseDown,
|
|
195
|
+
updateMousePosition,
|
|
196
|
+
setDragState,
|
|
197
|
+
setDragOverPosition,
|
|
198
|
+
calculateMoveDistance,
|
|
199
|
+
findPanelPosition,
|
|
200
|
+
setCustomDrag,
|
|
201
|
+
setOriginalPanelPosition,
|
|
202
|
+
showMousePosition,
|
|
203
|
+
setClickState,
|
|
204
|
+
setIsMouseDown,
|
|
205
|
+
clearMouseTracking,
|
|
206
|
+
createDragImage,
|
|
207
|
+
updateDragImagePosition,
|
|
208
|
+
handlePlaceholderDragEnter,
|
|
209
|
+
handlePlaceholderDragLeave,
|
|
210
|
+
],
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
const handleGlobalMouseUp = useCallback(
|
|
214
|
+
(e: globalThis.MouseEvent) => {
|
|
215
|
+
debugLog(debugConfigRef.current, 1, "🐭 カスタムドラッグハンドラー - グローバルマウスアップ", {
|
|
216
|
+
isActive: customDrag.isActive,
|
|
217
|
+
panelId: customDrag.panelId,
|
|
218
|
+
dragMode: customDrag.panelId ? dragModes[customDrag.panelId] : "unknown",
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
if (customDrag.isActive) {
|
|
222
|
+
edgeScrollManagerRef.current.stop()
|
|
223
|
+
|
|
224
|
+
const currentDragOverPosition = dragOverPositionRef.current
|
|
225
|
+
if (currentDragOverPosition) {
|
|
226
|
+
const elementUnderMouse = document.elementFromPoint(e.clientX, e.clientY)
|
|
227
|
+
const dropZone = elementUnderMouse?.closest<HTMLElement>("[data-drop-zone]")
|
|
228
|
+
|
|
229
|
+
if (dropZone) {
|
|
230
|
+
const targetColumnKey = dropZone.getAttribute("data-column-key")
|
|
231
|
+
if (targetColumnKey && dragModes[customDrag.panelId] === "custom") {
|
|
232
|
+
debugLog(debugConfigRef.current, 1, "🔄 カスタムドラッグパネル移動実行:", customDrag.panelId, "→", targetColumnKey)
|
|
233
|
+
performPanelMove(customDrag.panelId, targetColumnKey, currentDragOverPosition.insertIndex)
|
|
234
|
+
} else {
|
|
235
|
+
debugLog(debugConfigRef.current, 1, "🚫 カスタムドラッグパネル移動スキップ:", customDrag.panelId, "モード:", dragModes[customDrag.panelId])
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
removeDragImage()
|
|
240
|
+
document.body.classList.remove("custom-dragging", "droppable")
|
|
241
|
+
resetDragState()
|
|
242
|
+
clickCountRef.current = 0
|
|
243
|
+
} else if (isVirtualClick(e.clientX, e.clientY)) {
|
|
244
|
+
const elementUnderMouse = document.elementFromPoint(e.clientX, e.clientY)
|
|
245
|
+
const panelElement = elementUnderMouse?.closest<HTMLElement>("[data-panel-id]")
|
|
246
|
+
const panelId = panelElement?.getAttribute("data-panel-id") ?? null
|
|
247
|
+
|
|
248
|
+
if (!(panelElement && panelId) || dragModes[panelId] !== "custom") {
|
|
249
|
+
if (clickStateTimeoutRef.current) {
|
|
250
|
+
clearTimeout(clickStateTimeoutRef.current)
|
|
251
|
+
clickStateTimeoutRef.current = null
|
|
252
|
+
}
|
|
253
|
+
resetClickState()
|
|
254
|
+
dragStartMouseCoordRef.current = null
|
|
255
|
+
clickCountRef.current = 0
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
clickCountRef.current += 1
|
|
260
|
+
|
|
261
|
+
if (clickState.isClicked === false && clickCountRef.current === 1) {
|
|
262
|
+
debugLog(debugConfigRef.current, 1, "🖱️ 仮想クリック検出 - ドラッグ待機状態へ:", panelId)
|
|
263
|
+
setClickState({ isClicked: true, panelId })
|
|
264
|
+
clickStateTimeoutRef.current = window.setTimeout(() => {
|
|
265
|
+
debugLog(debugConfigRef.current, 1, "⏳ クリック待機状態を自動解除:", panelId)
|
|
266
|
+
resetClickState()
|
|
267
|
+
dragStartMouseCoordRef.current = null
|
|
268
|
+
clickCountRef.current = 0
|
|
269
|
+
}, 2000)
|
|
270
|
+
} else {
|
|
271
|
+
debugLog(debugConfigRef.current, 1, "🚫 クリック待機状態を解除 - 仮想クリック検出停止:", panelId)
|
|
272
|
+
if (clickStateTimeoutRef.current) {
|
|
273
|
+
clearTimeout(clickStateTimeoutRef.current)
|
|
274
|
+
clickStateTimeoutRef.current = null
|
|
275
|
+
}
|
|
276
|
+
resetClickState()
|
|
277
|
+
dragStartMouseCoordRef.current = null
|
|
278
|
+
clickCountRef.current = 0
|
|
279
|
+
clickStateTimeoutRef.current = window.setTimeout(() => {
|
|
280
|
+
debugLog(debugConfigRef.current, 1, "⏳ 仮想クリック検出再開:", panelId)
|
|
281
|
+
}, 2000)
|
|
282
|
+
}
|
|
283
|
+
} else if (clickState.isClicked) {
|
|
284
|
+
resetClickState()
|
|
285
|
+
dragStartMouseCoordRef.current = null
|
|
286
|
+
clickCountRef.current = 0
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (isMouseDown) {
|
|
290
|
+
setIsMouseDown(false)
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
[customDrag.isActive, customDrag.panelId, clickState, dragModes, isMouseDown, performPanelMove, resetDragState, resetClickState, isVirtualClick, setClickState, setIsMouseDown, removeDragImage],
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
const handleMouseDown = useCallback(
|
|
297
|
+
(e: MouseEvent, panelId: string) => {
|
|
298
|
+
if (dragModes[panelId] !== "custom") {
|
|
299
|
+
debugLog(debugConfigRef.current, 1, "🚫 カスタムドラッグモードではない - スキップ:", panelId)
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
recordMouseDown(e.clientX, e.clientY)
|
|
304
|
+
|
|
305
|
+
if (clickState.isClicked && clickState.panelId === panelId) {
|
|
306
|
+
setIsMouseDown(true)
|
|
307
|
+
|
|
308
|
+
if (clickStateTimeoutRef.current !== null) {
|
|
309
|
+
clearTimeout(clickStateTimeoutRef.current)
|
|
310
|
+
clickStateTimeoutRef.current = null
|
|
311
|
+
}
|
|
312
|
+
recordClickPosition(e.clientX, e.clientY)
|
|
313
|
+
dragStartMouseCoordRef.current = { x: e.clientX, y: e.clientY }
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
[clickState, dragModes, recordMouseDown, setIsMouseDown, recordClickPosition],
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
return () => {
|
|
321
|
+
if (clickStateTimeoutRef.current !== null) {
|
|
322
|
+
clearTimeout(clickStateTimeoutRef.current)
|
|
323
|
+
}
|
|
324
|
+
edgeScrollManagerRef.current.stop()
|
|
325
|
+
}
|
|
326
|
+
}, [])
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
handleGlobalMouseMove,
|
|
330
|
+
handleGlobalMouseUp,
|
|
331
|
+
handleMouseDown,
|
|
332
|
+
}
|
|
333
|
+
}
|