@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,446 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
2
|
+
import { debugLog } from "../DebugComponents"
|
|
3
|
+
import type { DebugConfig, TouchDragState, UseDragDropColumnsReturn } from "../types"
|
|
4
|
+
import { DEFAULT_COLUMN_ID_CONFIG, sortColumnIds } from "../utils/columnIdUtils"
|
|
5
|
+
import { useCustomDragHandlers } from "./useCustomDragHandlers"
|
|
6
|
+
import { useDragDropState } from "./useDragDropState"
|
|
7
|
+
import { useMouseTracking } from "./useMouseTracking"
|
|
8
|
+
import { useNormalDragHandlers } from "./useNormalDragHandlers"
|
|
9
|
+
import { usePanelManagement } from "./usePanelManagement"
|
|
10
|
+
|
|
11
|
+
// Props型定義
|
|
12
|
+
export interface UseDragDropColumnsProps {
|
|
13
|
+
// 初期設定
|
|
14
|
+
initialColumnPanels: Record<string, string[]>
|
|
15
|
+
initialPanelVisibility: Record<string, boolean>
|
|
16
|
+
initialDragModes?: Record<string, "normal" | "custom">
|
|
17
|
+
|
|
18
|
+
// デバッグ設定
|
|
19
|
+
debugConfig: DebugConfig
|
|
20
|
+
|
|
21
|
+
// アニメーション・タッチ設定
|
|
22
|
+
enableAnimation?: boolean
|
|
23
|
+
enableTouch?: boolean
|
|
24
|
+
|
|
25
|
+
// イベントコールバック
|
|
26
|
+
onPanelMove?: (panelId: string, fromColumn: string, toColumn: string, insertIndex: number) => void
|
|
27
|
+
onPanelVisibilityChange?: (panelId: string, isVisible: boolean) => void
|
|
28
|
+
onDragModeChange?: (panelId: string, mode: "normal" | "custom") => void
|
|
29
|
+
onColumnPanelsChange?: (newColumnPanels: Record<string, string[]>) => void
|
|
30
|
+
|
|
31
|
+
// 表示倍率 (ドラッグ中のゴースト表示等に反映させるために必要)
|
|
32
|
+
scale?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Main drag and drop columns hook - integrates all sub-hooks.
|
|
37
|
+
* メインドラッグ&ドロップカラムフック - 全てのサブフックを統合。
|
|
38
|
+
*/
|
|
39
|
+
export const useDragDropColumns = ({ initialColumnPanels, initialPanelVisibility, initialDragModes = {}, debugConfig, onPanelMove, onDragModeChange, onColumnPanelsChange, scale = 1 }: UseDragDropColumnsProps): UseDragDropColumnsReturn => {
|
|
40
|
+
// debugConfig を安定した参照で管理
|
|
41
|
+
const debugConfigRef = useRef(debugConfig)
|
|
42
|
+
// debugConfigRef を更新するが、useEffect の依存配列には含めない
|
|
43
|
+
debugConfigRef.current = debugConfig
|
|
44
|
+
|
|
45
|
+
// 基本状態管理
|
|
46
|
+
const [columnPanels, setColumnPanels] = useState(initialColumnPanels)
|
|
47
|
+
const [dragModes, setDragModes] = useState<Record<string, "normal" | "custom">>(initialDragModes)
|
|
48
|
+
|
|
49
|
+
// columnPanelsのrefを作成して依存関係の問題を解決
|
|
50
|
+
const columnPanelsRef = useRef(columnPanels)
|
|
51
|
+
columnPanelsRef.current = columnPanels
|
|
52
|
+
|
|
53
|
+
// 分割されたフック呼び出し
|
|
54
|
+
const dragDropState = useDragDropState()
|
|
55
|
+
|
|
56
|
+
const mouseTracking = useMouseTracking()
|
|
57
|
+
|
|
58
|
+
const panelManagement = usePanelManagement(initialPanelVisibility, scale)
|
|
59
|
+
|
|
60
|
+
// イベントハンドラーの安定した参照を提供するためのRef
|
|
61
|
+
const eventHandlersRef = useRef<{
|
|
62
|
+
mousemove: ((e: globalThis.MouseEvent) => void) | null
|
|
63
|
+
mouseup: ((e: globalThis.MouseEvent) => void) | null
|
|
64
|
+
}>({
|
|
65
|
+
mousemove: null,
|
|
66
|
+
mouseup: null,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// パネルIDから元の位置を見つけるヘルパー関数
|
|
70
|
+
const findPanelPosition = useCallback((panelId: string) => {
|
|
71
|
+
const columnKeys = Object.keys(columnPanelsRef.current)
|
|
72
|
+
const sortedColumnKeys = sortColumnIds(columnKeys, DEFAULT_COLUMN_ID_CONFIG) // カラムキーを正しい順序でソート
|
|
73
|
+
|
|
74
|
+
for (let columnIndex = 0; columnIndex < sortedColumnKeys.length; columnIndex++) {
|
|
75
|
+
const columnKey = sortedColumnKeys[columnIndex]
|
|
76
|
+
const panelIndex = columnPanelsRef.current[columnKey]?.indexOf(panelId) ?? -1
|
|
77
|
+
if (panelIndex !== -1) {
|
|
78
|
+
return { columnIndex, panelIndex }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null
|
|
82
|
+
}, [])
|
|
83
|
+
|
|
84
|
+
// 共通:パネル移動処理
|
|
85
|
+
const performPanelMove = useCallback(
|
|
86
|
+
(draggedPanelId: string, targetColumnKey: string, targetInsertIndex: number) => {
|
|
87
|
+
debugLog(debugConfigRef.current, 1, "🔄 パネル移動実行:", draggedPanelId, "→", targetColumnKey, "@", targetInsertIndex)
|
|
88
|
+
|
|
89
|
+
setColumnPanels((prevColumnPanels: Record<string, string[]>) => {
|
|
90
|
+
const newColumnPanels = { ...prevColumnPanels }
|
|
91
|
+
const sourceColumnKey = Object.keys(newColumnPanels).find((key) => newColumnPanels[key].includes(draggedPanelId))
|
|
92
|
+
|
|
93
|
+
if (!sourceColumnKey) {
|
|
94
|
+
console.error("Source column not found for panel:", draggedPanelId)
|
|
95
|
+
return prevColumnPanels
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ターゲットカラムが存在しない場合はエラー
|
|
99
|
+
if (!newColumnPanels[targetColumnKey]) {
|
|
100
|
+
console.error("Target column not found:", targetColumnKey)
|
|
101
|
+
return prevColumnPanels
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 移動元と移動先が同じ列の場合
|
|
105
|
+
if (sourceColumnKey === targetColumnKey) {
|
|
106
|
+
const columnPanels = [...newColumnPanels[sourceColumnKey]]
|
|
107
|
+
const sourceIndex = columnPanels.indexOf(draggedPanelId)
|
|
108
|
+
|
|
109
|
+
// 1. 配列からドラッグした要素を一旦削除
|
|
110
|
+
const [movedPanel] = columnPanels.splice(sourceIndex, 1)
|
|
111
|
+
|
|
112
|
+
// 2. 正しい挿入位置を計算
|
|
113
|
+
// 元の位置より前に挿入する場合はインデックスの変更なし
|
|
114
|
+
// 元の位置より後ろに挿入する場合は、spliceで1つ要素が減った分インデックスを調整
|
|
115
|
+
const adjustedInsertIndex = sourceIndex < targetInsertIndex ? targetInsertIndex - 1 : targetInsertIndex
|
|
116
|
+
|
|
117
|
+
// 3. 正しい挿入位置に要素を挿入
|
|
118
|
+
columnPanels.splice(adjustedInsertIndex, 0, movedPanel)
|
|
119
|
+
|
|
120
|
+
newColumnPanels[targetColumnKey] = columnPanels
|
|
121
|
+
} else {
|
|
122
|
+
// 移動元と移動先が異なる列の場合
|
|
123
|
+
// 1. 移動元の列からパネルを削除
|
|
124
|
+
newColumnPanels[sourceColumnKey] = newColumnPanels[sourceColumnKey].filter((id) => id !== draggedPanelId)
|
|
125
|
+
|
|
126
|
+
// 2. 移動先の列の指定位置にパネルを挿入
|
|
127
|
+
const targetPanels = [...newColumnPanels[targetColumnKey]]
|
|
128
|
+
targetPanels.splice(targetInsertIndex, 0, draggedPanelId)
|
|
129
|
+
newColumnPanels[targetColumnKey] = targetPanels
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
debugLog(debugConfigRef.current, 1, "✅ パネル移動完了:", draggedPanelId, "→", targetColumnKey)
|
|
133
|
+
|
|
134
|
+
// 外部コールバック実行(更新後の値を使用)
|
|
135
|
+
if (onColumnPanelsChange) {
|
|
136
|
+
// setStateは非同期なので、次のフレームで実行
|
|
137
|
+
requestAnimationFrame(() => {
|
|
138
|
+
onColumnPanelsChange(newColumnPanels)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
if (onPanelMove) {
|
|
142
|
+
const originalColumn = Object.keys(prevColumnPanels).find((column) => prevColumnPanels[column].includes(draggedPanelId))
|
|
143
|
+
if (originalColumn) {
|
|
144
|
+
requestAnimationFrame(() => {
|
|
145
|
+
onPanelMove(draggedPanelId, originalColumn, targetColumnKey, targetInsertIndex)
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return newColumnPanels
|
|
151
|
+
})
|
|
152
|
+
},
|
|
153
|
+
[onColumnPanelsChange, onPanelMove],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Handles the `dragenter` event on drop placeholders.
|
|
158
|
+
* Updates the drag-over position to show where the panel will be inserted.
|
|
159
|
+
*
|
|
160
|
+
* ドロッププレースホルダーでの `dragenter` イベントを処理します。
|
|
161
|
+
* ドラッグオーバー位置を更新して、パネルが挿入される場所を示します。
|
|
162
|
+
*/
|
|
163
|
+
const handlePlaceholderDragEnter = useCallback(
|
|
164
|
+
(columnIndex: number, insertIndex: number) => {
|
|
165
|
+
if (!(dragDropState.dragState.isDragging || dragDropState.customDrag.isActive)) return
|
|
166
|
+
|
|
167
|
+
// Skip if trying to drop in the original position.
|
|
168
|
+
// 元の位置にドロップしようとしている場合はスキップします。
|
|
169
|
+
const originalPos = dragDropState.originalPanelPosition
|
|
170
|
+
if (originalPos && originalPos.columnIndex === columnIndex && (originalPos.panelIndex === insertIndex || originalPos.panelIndex + 1 === insertIndex)) {
|
|
171
|
+
debugLog(debugConfigRef.current, 1, `元の位置への挿入をスキップ: カラム${columnIndex}, 位置${insertIndex}`)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Update position only if it has changed.
|
|
176
|
+
// 位置が変更された場合のみ更新します。
|
|
177
|
+
const currentPosition = dragDropState.dragOverPosition
|
|
178
|
+
if (!currentPosition || currentPosition.columnIndex !== columnIndex || currentPosition.insertIndex !== insertIndex) {
|
|
179
|
+
debugLog(debugConfigRef.current, 1, `プレースホルダーエンター: カラム${columnIndex}, 位置${insertIndex}`)
|
|
180
|
+
dragDropState.setDragOverPosition({ columnIndex, insertIndex })
|
|
181
|
+
dragDropState.setDragState((prev) => ({ ...prev, dragOverColumn: columnIndex }))
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
[dragDropState],
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Unified placeholder drag leave handler for both normal and custom drag operations.
|
|
189
|
+
* For normal drag: performs DOM event checks before clearing (when event is provided).
|
|
190
|
+
* For custom drag: simply clears the drag-over position (when no event is provided).
|
|
191
|
+
*
|
|
192
|
+
* ノーマルドラッグとカスタムドラッグ両方に対応する統一プレースホルダーリーブハンドラー。
|
|
193
|
+
* ノーマルドラッグ: DOM イベントチェック付きでクリア(イベントが提供された場合)。
|
|
194
|
+
* カスタムドラッグ: 単純にドラッグオーバー位置をクリア(イベントが提供されない場合)。
|
|
195
|
+
*/
|
|
196
|
+
const handlePlaceholderDragLeave = useCallback(
|
|
197
|
+
(e?: React.DragEvent, columnIndex?: number, insertIndex?: number) => {
|
|
198
|
+
if (!(dragDropState.dragState.isDragging || dragDropState.customDrag.isActive)) return
|
|
199
|
+
|
|
200
|
+
if (dragDropState.customDrag.isActive) {
|
|
201
|
+
// カスタムドラッグの場合:シンプルにクリア
|
|
202
|
+
debugLog(debugConfigRef.current, 1, "🚫 カスタムドラッグ - プレースホルダーリーブ: ハイライトを解除します。")
|
|
203
|
+
dragDropState.setDragOverPosition(null)
|
|
204
|
+
} else if (dragDropState.dragState.isDragging && e && columnIndex !== undefined && insertIndex !== undefined) {
|
|
205
|
+
// ノーマルドラッグの場合:DOM イベントチェック付きでクリア
|
|
206
|
+
const currentPos = dragDropState.dragOverPosition
|
|
207
|
+
if (currentPos && currentPos.columnIndex === columnIndex && currentPos.insertIndex === insertIndex) {
|
|
208
|
+
const placeholderElement = e.currentTarget as HTMLElement
|
|
209
|
+
const relatedTarget = e.relatedTarget as Node | null
|
|
210
|
+
|
|
211
|
+
// Check if the mouse has truly left the element and its children.
|
|
212
|
+
// マウスが要素とその子要素から本当に離れたかどうかを確認します。
|
|
213
|
+
if (!(relatedTarget && placeholderElement.contains(relatedTarget))) {
|
|
214
|
+
debugLog(debugConfigRef.current, 1, `ノーマルドラッグ - プレースホルダーリーブ: カラム${columnIndex}, 位置${insertIndex}。ハイライトを解除します。`)
|
|
215
|
+
dragDropState.setDragOverPosition(null)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
[dragDropState],
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
const customDragHandlers = useCustomDragHandlers({
|
|
224
|
+
debugConfig: debugConfigRef.current,
|
|
225
|
+
dragState: dragDropState.dragState,
|
|
226
|
+
customDrag: dragDropState.customDrag,
|
|
227
|
+
clickState: dragDropState.clickState,
|
|
228
|
+
dragModes,
|
|
229
|
+
isMouseDown: dragDropState.isMouseDown,
|
|
230
|
+
originalPanelPosition: dragDropState.originalPanelPosition,
|
|
231
|
+
dragOverPosition: dragDropState.dragOverPosition,
|
|
232
|
+
setCustomDrag: dragDropState.setCustomDrag,
|
|
233
|
+
setDragState: dragDropState.setDragState,
|
|
234
|
+
setDragOverPosition: dragDropState.setDragOverPosition,
|
|
235
|
+
setOriginalPanelPosition: dragDropState.setOriginalPanelPosition,
|
|
236
|
+
setClickState: dragDropState.setClickState,
|
|
237
|
+
setIsMouseDown: dragDropState.setIsMouseDown,
|
|
238
|
+
findPanelPosition,
|
|
239
|
+
performPanelMove,
|
|
240
|
+
showMousePosition: mouseTracking.showMousePosition,
|
|
241
|
+
updateMousePosition: mouseTracking.updateMousePosition,
|
|
242
|
+
recordMouseDown: mouseTracking.recordMouseDown,
|
|
243
|
+
recordClickPosition: mouseTracking.recordClickPosition,
|
|
244
|
+
isVirtualClick: mouseTracking.isVirtualClick,
|
|
245
|
+
calculateMoveDistance: mouseTracking.calculateMoveDistance,
|
|
246
|
+
clearMouseTracking: mouseTracking.clearMouseTracking,
|
|
247
|
+
resetDragState: dragDropState.resetDragState,
|
|
248
|
+
resetClickState: dragDropState.resetClickState,
|
|
249
|
+
createDragImage: panelManagement.createDragImage,
|
|
250
|
+
updateDragImagePosition: panelManagement.updateDragImagePosition,
|
|
251
|
+
removeDragImage: panelManagement.removeDragImage,
|
|
252
|
+
handlePlaceholderDragEnter,
|
|
253
|
+
handlePlaceholderDragLeave,
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
const normalDragHandlers = useNormalDragHandlers({
|
|
257
|
+
debugConfig: debugConfigRef.current,
|
|
258
|
+
dragState: dragDropState.dragState,
|
|
259
|
+
customDrag: dragDropState.customDrag,
|
|
260
|
+
dragModes,
|
|
261
|
+
columnPanels,
|
|
262
|
+
dragOverPosition: dragDropState.dragOverPosition,
|
|
263
|
+
originalPanelPosition: dragDropState.originalPanelPosition,
|
|
264
|
+
setDragState: dragDropState.setDragState,
|
|
265
|
+
setDragOverPosition: dragDropState.setDragOverPosition,
|
|
266
|
+
setOriginalPanelPosition: dragDropState.setOriginalPanelPosition,
|
|
267
|
+
performPanelMove,
|
|
268
|
+
showMousePosition: mouseTracking.showMousePosition,
|
|
269
|
+
updateMousePosition: mouseTracking.updateMousePosition,
|
|
270
|
+
resetDragState: dragDropState.resetDragState,
|
|
271
|
+
handlePlaceholderDragEnter,
|
|
272
|
+
handlePlaceholderDragLeave,
|
|
273
|
+
scale,
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// イベントリスナーの安定した参照を保持するためのラッパー関数
|
|
277
|
+
const stableMouseMoveHandler = useCallback((e: globalThis.MouseEvent) => {
|
|
278
|
+
if (eventHandlersRef.current.mousemove) {
|
|
279
|
+
eventHandlersRef.current.mousemove(e)
|
|
280
|
+
}
|
|
281
|
+
}, [])
|
|
282
|
+
|
|
283
|
+
const stableMouseUpHandler = useCallback((e: globalThis.MouseEvent) => {
|
|
284
|
+
if (eventHandlersRef.current.mouseup) {
|
|
285
|
+
eventHandlersRef.current.mouseup(e)
|
|
286
|
+
}
|
|
287
|
+
}, [])
|
|
288
|
+
|
|
289
|
+
// refベースのグローバルイベントリスナー管理
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
debugLog(debugConfigRef.current, 1, "🔧 グローバルイベントリスナー初期化 - refベース管理開始")
|
|
292
|
+
|
|
293
|
+
document.addEventListener("mousemove", stableMouseMoveHandler)
|
|
294
|
+
document.addEventListener("mouseup", stableMouseUpHandler)
|
|
295
|
+
|
|
296
|
+
return () => {
|
|
297
|
+
debugLog(debugConfigRef.current, 1, "🔴 グローバルイベントリスナー終了 - refベース管理終了")
|
|
298
|
+
document.removeEventListener("mousemove", stableMouseMoveHandler)
|
|
299
|
+
document.removeEventListener("mouseup", stableMouseUpHandler)
|
|
300
|
+
}
|
|
301
|
+
}, [stableMouseMoveHandler, stableMouseUpHandler])
|
|
302
|
+
|
|
303
|
+
// イベントハンドラー設定の統一管理
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
const needsPattern1Handler = !dragDropState.clickState.isClicked && Object.entries(dragModes).some(([, mode]) => mode === "custom")
|
|
306
|
+
const needsPattern23Handler = dragDropState.clickState.isClicked && dragDropState.clickState.panelId
|
|
307
|
+
|
|
308
|
+
if (needsPattern1Handler || needsPattern23Handler) {
|
|
309
|
+
const patternType = needsPattern1Handler ? "パターン1(仮想クリック検出)" : "パターン2,3(ドラッグ準備)"
|
|
310
|
+
debugLog(debugConfigRef.current, 1, `🔧 イベントハンドラー設定開始: ${patternType}`)
|
|
311
|
+
|
|
312
|
+
eventHandlersRef.current.mousemove = customDragHandlers.handleGlobalMouseMove
|
|
313
|
+
eventHandlersRef.current.mouseup = customDragHandlers.handleGlobalMouseUp
|
|
314
|
+
} else {
|
|
315
|
+
if (!(dragDropState.customDrag.isActive || dragDropState.dragState.isDragging)) {
|
|
316
|
+
eventHandlersRef.current.mousemove = null
|
|
317
|
+
eventHandlersRef.current.mouseup = null
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}, [dragDropState.clickState.isClicked, dragDropState.clickState.panelId, dragModes, dragDropState.customDrag.isActive, dragDropState.dragState.isDragging, customDragHandlers.handleGlobalMouseMove, customDragHandlers.handleGlobalMouseUp])
|
|
321
|
+
|
|
322
|
+
// タッチイベントハンドラー(簡略版)
|
|
323
|
+
const handleTouchStart = useCallback(
|
|
324
|
+
(e: React.TouchEvent, panelId: string) => {
|
|
325
|
+
const touch = e.touches[0]
|
|
326
|
+
const element = e.currentTarget as HTMLElement
|
|
327
|
+
|
|
328
|
+
dragDropState.setTouchDragState({
|
|
329
|
+
isTouchDragging: true,
|
|
330
|
+
startX: touch.clientX,
|
|
331
|
+
startY: touch.clientY,
|
|
332
|
+
currentX: touch.clientX,
|
|
333
|
+
currentY: touch.clientY,
|
|
334
|
+
draggedElement: element,
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
dragDropState.setDragState({
|
|
338
|
+
isDragging: true,
|
|
339
|
+
draggedPanel: panelId,
|
|
340
|
+
dragOverColumn: null,
|
|
341
|
+
})
|
|
342
|
+
},
|
|
343
|
+
[dragDropState],
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
const handleTouchMove = useCallback(
|
|
347
|
+
(e: React.TouchEvent) => {
|
|
348
|
+
if (!dragDropState.touchDragState.isTouchDragging) return
|
|
349
|
+
e.preventDefault()
|
|
350
|
+
|
|
351
|
+
const touch = e.touches[0]
|
|
352
|
+
dragDropState.setTouchDragState((prev: TouchDragState) => ({
|
|
353
|
+
...prev,
|
|
354
|
+
currentX: touch.clientX,
|
|
355
|
+
currentY: touch.clientY,
|
|
356
|
+
}))
|
|
357
|
+
},
|
|
358
|
+
[dragDropState.touchDragState.isTouchDragging, dragDropState],
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
const handleTouchEnd = useCallback(
|
|
362
|
+
(_e: React.TouchEvent) => {
|
|
363
|
+
if (!dragDropState.touchDragState.isTouchDragging) return
|
|
364
|
+
|
|
365
|
+
dragDropState.setTouchDragState({
|
|
366
|
+
isTouchDragging: false,
|
|
367
|
+
startX: 0,
|
|
368
|
+
startY: 0,
|
|
369
|
+
currentX: 0,
|
|
370
|
+
currentY: 0,
|
|
371
|
+
draggedElement: null,
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
normalDragHandlers.handleDragEnd()
|
|
375
|
+
},
|
|
376
|
+
[dragDropState.touchDragState.isTouchDragging, dragDropState, normalDragHandlers],
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
// ドラッグモード切り替え
|
|
380
|
+
const toggleDragMode = useCallback(
|
|
381
|
+
(panelId: string) => {
|
|
382
|
+
debugLog(debugConfigRef.current, 1, "🔄 toggleDragMode called:", { panelId })
|
|
383
|
+
setDragModes((prev) => {
|
|
384
|
+
const currentMode = prev[panelId] || "normal"
|
|
385
|
+
const newMode: "normal" | "custom" = currentMode === "normal" ? "custom" : "normal"
|
|
386
|
+
const newModes = { ...prev, [panelId]: newMode }
|
|
387
|
+
|
|
388
|
+
if (onDragModeChange) {
|
|
389
|
+
try {
|
|
390
|
+
onDragModeChange(panelId, newMode)
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.error("🔄 Error in onDragModeChange:", error)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return newModes
|
|
397
|
+
})
|
|
398
|
+
},
|
|
399
|
+
[onDragModeChange],
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
// パネルサイズ情報クリック処理
|
|
403
|
+
const handlePanelSizeInfoClick = useCallback((e: React.MouseEvent | React.TouchEvent, panelId: string) => {
|
|
404
|
+
e.stopPropagation()
|
|
405
|
+
debugLog(debugConfigRef.current, 2, "パネルサイズ情報クリック:", panelId)
|
|
406
|
+
}, [])
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
// 状態
|
|
410
|
+
columnPanels,
|
|
411
|
+
panelVisibility: panelManagement.panelVisibility,
|
|
412
|
+
dragModes,
|
|
413
|
+
dragState: dragDropState.dragState,
|
|
414
|
+
dragOverPosition: dragDropState.dragOverPosition,
|
|
415
|
+
originalPanelPosition: dragDropState.originalPanelPosition,
|
|
416
|
+
touchDragState: dragDropState.touchDragState,
|
|
417
|
+
customDrag: dragDropState.customDrag,
|
|
418
|
+
clickState: dragDropState.clickState,
|
|
419
|
+
mousePosition: mouseTracking.mousePosition,
|
|
420
|
+
panelSizes: panelManagement.panelSizes,
|
|
421
|
+
|
|
422
|
+
// 状態更新関数
|
|
423
|
+
setColumnPanels,
|
|
424
|
+
setPanelVisibility: panelManagement.setPanelVisibility,
|
|
425
|
+
setDragModes,
|
|
426
|
+
setPanelRef: panelManagement.setPanelRef,
|
|
427
|
+
|
|
428
|
+
// イベントハンドラー
|
|
429
|
+
handleDragStart: normalDragHandlers.handleDragStart,
|
|
430
|
+
handleDragEnd: normalDragHandlers.handleDragEnd,
|
|
431
|
+
handleMouseDown: customDragHandlers.handleMouseDown,
|
|
432
|
+
handleTouchStart,
|
|
433
|
+
handleTouchMove,
|
|
434
|
+
handleTouchEnd,
|
|
435
|
+
handlePlaceholderDragEnter,
|
|
436
|
+
handlePlaceholderDragLeave,
|
|
437
|
+
handleDragOverWithInsert: normalDragHandlers.handleDragOverWithInsert,
|
|
438
|
+
handleDropWithInsert: normalDragHandlers.handleDropWithInsert,
|
|
439
|
+
handleColumnDragLeave: normalDragHandlers.handleColumnDragLeave,
|
|
440
|
+
handlePanelSizeInfoClick,
|
|
441
|
+
|
|
442
|
+
// ユーティリティ関数
|
|
443
|
+
toggleDragMode,
|
|
444
|
+
findPanelPosition,
|
|
445
|
+
}
|
|
446
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview This file defines the `useDragDropState` custom hook, which centralizes
|
|
3
|
+
* the state management for various aspects of the drag-and-drop functionality. It provides
|
|
4
|
+
* a structured way to handle states for normal drag, custom drag, touch drag, and click events.
|
|
5
|
+
*
|
|
6
|
+
* このファイルは `useDragDropState` カスタムフックを定義します。これは、ドラッグ&ドロップ機能の
|
|
7
|
+
* 様々な側面の状態管理を一元化します。通常のドラッグ、カスタムドラッグ、タッチドラッグ、
|
|
8
|
+
* およびクリックイベントの状態を構造化された方法で処理する手段を提供します。
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useState } from "react"
|
|
11
|
+
import type { ClickState, CustomDragState, DragOverPosition, DragState, OriginalPanelPosition, TouchDragState } from "../types"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A custom hook for managing all states related to drag-and-drop operations.
|
|
15
|
+
* It encapsulates the state logic, providing a clean interface for components to use.
|
|
16
|
+
*
|
|
17
|
+
* ドラッグ&ドロップ操作に関連するすべての状態を管理するためのカスタムフック。
|
|
18
|
+
* 状態ロジックをカプセル化し、コンポーネントが使用するためのクリーンなインターフェースを提供します。
|
|
19
|
+
*/
|
|
20
|
+
export const useDragDropState = () => {
|
|
21
|
+
// --- State Definitions ---
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Manages the primary state for standard HTML5 drag-and-drop operations.
|
|
25
|
+
* isDragging: Indicates if a drag operation is globally active.
|
|
26
|
+
* draggedPanel: The ID of the panel currently being dragged.
|
|
27
|
+
* dragOverColumn: The index of the column the panel is being dragged over.
|
|
28
|
+
*
|
|
29
|
+
* 標準のHTML5ドラッグ&ドロップ操作の主要な状態を管理します。
|
|
30
|
+
* isDragging: ドラッグ操作がグローバルにアクティブかどうかを示します。
|
|
31
|
+
* draggedPanel: 現在ドラッグされているパネルのID。
|
|
32
|
+
* dragOverColumn: パネルがドラッグされているカラムのインデックス。
|
|
33
|
+
*/
|
|
34
|
+
const [dragState, setDragState] = useState<DragState>({
|
|
35
|
+
isDragging: false,
|
|
36
|
+
draggedPanel: null,
|
|
37
|
+
dragOverColumn: null,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Stores the position where a panel is being dragged over (column and insert index).
|
|
42
|
+
* Null if not over a valid drop target.
|
|
43
|
+
*
|
|
44
|
+
* パネルがドラッグされている位置(カラムと挿入インデックス)を格納します。
|
|
45
|
+
* 有効なドロップターゲット上にない場合はnullです。
|
|
46
|
+
*/
|
|
47
|
+
const [dragOverPosition, setDragOverPosition] = useState<DragOverPosition | null>(null)
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Stores the original column and panel index of the panel when a drag operation starts.
|
|
51
|
+
*
|
|
52
|
+
* ドラッグ操作が開始されたときのパネルの元のカラムとパネルインデックスを格納します。
|
|
53
|
+
*/
|
|
54
|
+
const [originalPanelPosition, setOriginalPanelPosition] = useState<OriginalPanelPosition | null>(null)
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Manages the state for the custom mouse-based drag implementation.
|
|
58
|
+
* isActive: Indicates if the custom drag is currently active.
|
|
59
|
+
* panelId: The ID of the panel being dragged.
|
|
60
|
+
* startX/startY: The initial mouse coordinates when the drag started.
|
|
61
|
+
*
|
|
62
|
+
* マウスベースのカスタムドラッグ実装の状態を管理します。
|
|
63
|
+
* isActive: カスタムドラッグが現在アクティブかどうかを示します。
|
|
64
|
+
* panelId: ドラッグされているパネルのID。
|
|
65
|
+
* startX/startY: ドラッグが開始されたときの初期マウス座標。
|
|
66
|
+
*/
|
|
67
|
+
const [customDrag, setCustomDrag] = useState<CustomDragState>({
|
|
68
|
+
isActive: false,
|
|
69
|
+
panelId: "",
|
|
70
|
+
startX: 0,
|
|
71
|
+
startY: 0,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Manages the state related to click events, used to initiate custom drags.
|
|
76
|
+
* isClicked: True if a panel has been clicked, pending a potential drag.
|
|
77
|
+
* panelId: The ID of the clicked panel.
|
|
78
|
+
*
|
|
79
|
+
* カスタムドラッグを開始するために使用されるクリックイベント関連の状態を管理します。
|
|
80
|
+
* isClicked: パネルがクリックされ、ドラッグの可能性がある場合はtrue。
|
|
81
|
+
* panelId: クリックされたパネルのID。
|
|
82
|
+
*/
|
|
83
|
+
const [clickState, setClickState] = useState<ClickState>({
|
|
84
|
+
isClicked: false,
|
|
85
|
+
panelId: null,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Manages the state for touch-based drag-and-drop.
|
|
90
|
+
* isTouchDragging: Indicates if a touch-based drag is active.
|
|
91
|
+
* startX/startY: Initial touch coordinates.
|
|
92
|
+
* currentX/currentY: Current touch coordinates.
|
|
93
|
+
* draggedElement: The DOM element being dragged.
|
|
94
|
+
*
|
|
95
|
+
* タッチベースのドラッグ&ドロップの状態を管理します。
|
|
96
|
+
* isTouchDragging: タッチベースのドラッグがアクティブかどうかを示します。
|
|
97
|
+
* startX/startY: 初期のタッチ座標。
|
|
98
|
+
* currentX/currentY: 現在のタッチ座標。
|
|
99
|
+
* draggedElement: ドラッグされているDOM要素。
|
|
100
|
+
*/
|
|
101
|
+
const [touchDragState, setTouchDragState] = useState<TouchDragState>({
|
|
102
|
+
isTouchDragging: false,
|
|
103
|
+
startX: 0,
|
|
104
|
+
startY: 0,
|
|
105
|
+
currentX: 0,
|
|
106
|
+
currentY: 0,
|
|
107
|
+
draggedElement: null,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Tracks whether the mouse button is currently pressed down.
|
|
112
|
+
* マウスボタンが現在押されているかどうかを追跡します。
|
|
113
|
+
*/
|
|
114
|
+
const [isMouseDown, setIsMouseDown] = useState<boolean>(false)
|
|
115
|
+
|
|
116
|
+
// --- Utility Functions ---
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resets all states related to drag operations (custom, standard, and position).
|
|
120
|
+
* This is typically called at the end of a drag-and-drop sequence.
|
|
121
|
+
*
|
|
122
|
+
* ドラッグ操作に関連するすべての状態(カスタム、標準、および位置)をリセットします。
|
|
123
|
+
* これは通常、ドラッグ&ドロップシーケンスの最後に呼び出されます。
|
|
124
|
+
*/
|
|
125
|
+
const resetDragState = useCallback(() => {
|
|
126
|
+
setCustomDrag({ isActive: false, panelId: "", startX: 0, startY: 0 })
|
|
127
|
+
setDragState({ isDragging: false, draggedPanel: null, dragOverColumn: null })
|
|
128
|
+
setDragOverPosition(null)
|
|
129
|
+
setOriginalPanelPosition(null)
|
|
130
|
+
}, [])
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resets the click and mouse-down states.
|
|
134
|
+
*
|
|
135
|
+
* クリックおよびマウスダウンの状態をリセットします。
|
|
136
|
+
*/
|
|
137
|
+
const resetClickState = useCallback(() => {
|
|
138
|
+
setClickState({ isClicked: false, panelId: null })
|
|
139
|
+
setIsMouseDown(false)
|
|
140
|
+
}, [])
|
|
141
|
+
|
|
142
|
+
// --- Stabilized State Setters ---
|
|
143
|
+
// By wrapping setters in useCallback, we ensure they have a stable identity across re-renders,
|
|
144
|
+
// which can help prevent unnecessary re-renders in child components that depend on them.
|
|
145
|
+
//
|
|
146
|
+
// セッターをuseCallbackでラップすることにより、再レンダリング間で安定したアイデンティティを確保します。
|
|
147
|
+
// これにより、それらに依存する子コンポーネントの不要な再レンダリングを防ぐことができます。
|
|
148
|
+
|
|
149
|
+
const setDragStateStable = useCallback((state: DragState | ((prev: DragState) => DragState)) => {
|
|
150
|
+
setDragState(state)
|
|
151
|
+
}, [])
|
|
152
|
+
|
|
153
|
+
const setDragOverPositionStable = useCallback((position: DragOverPosition | null) => {
|
|
154
|
+
setDragOverPosition(position)
|
|
155
|
+
}, [])
|
|
156
|
+
|
|
157
|
+
const setOriginalPanelPositionStable = useCallback((position: OriginalPanelPosition | null) => {
|
|
158
|
+
setOriginalPanelPosition(position)
|
|
159
|
+
}, [])
|
|
160
|
+
|
|
161
|
+
const setCustomDragStable = useCallback((state: CustomDragState | ((prev: CustomDragState) => CustomDragState)) => {
|
|
162
|
+
setCustomDrag(state)
|
|
163
|
+
}, [])
|
|
164
|
+
|
|
165
|
+
const setClickStateStable = useCallback((state: ClickState | ((prev: ClickState) => ClickState)) => {
|
|
166
|
+
setClickState(state)
|
|
167
|
+
}, [])
|
|
168
|
+
|
|
169
|
+
const setTouchDragStateStable = useCallback((state: TouchDragState | ((prev: TouchDragState) => TouchDragState)) => {
|
|
170
|
+
setTouchDragState(state)
|
|
171
|
+
}, [])
|
|
172
|
+
|
|
173
|
+
const setIsMouseDownStable = useCallback((isDown: boolean) => {
|
|
174
|
+
setIsMouseDown(isDown)
|
|
175
|
+
}, [])
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
// States / 状態
|
|
179
|
+
dragState,
|
|
180
|
+
dragOverPosition,
|
|
181
|
+
originalPanelPosition,
|
|
182
|
+
customDrag,
|
|
183
|
+
clickState,
|
|
184
|
+
touchDragState,
|
|
185
|
+
isMouseDown,
|
|
186
|
+
|
|
187
|
+
// Stabilized Setters / 安定化されたセッター
|
|
188
|
+
setDragState: setDragStateStable,
|
|
189
|
+
setDragOverPosition: setDragOverPositionStable,
|
|
190
|
+
setOriginalPanelPosition: setOriginalPanelPositionStable,
|
|
191
|
+
setCustomDrag: setCustomDragStable,
|
|
192
|
+
setClickState: setClickStateStable,
|
|
193
|
+
setTouchDragState: setTouchDragStateStable,
|
|
194
|
+
setIsMouseDown: setIsMouseDownStable,
|
|
195
|
+
|
|
196
|
+
// Utility Functions / ユーティリティ関数
|
|
197
|
+
resetDragState,
|
|
198
|
+
resetClickState,
|
|
199
|
+
}
|
|
200
|
+
}
|