@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,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ドラッグ&ドロップ機能の型定義
|
|
3
|
+
* DragDropLayout と useDragDropColumns で共通で使用される型を統合して定義
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentType, DragEvent, MouseEvent, RefObject, TouchEvent } from "react"
|
|
7
|
+
|
|
8
|
+
export interface ElementWithResizeObserver extends HTMLElement {
|
|
9
|
+
__resizeObserver?: ResizeObserver
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ReactMouseEvent = import("react").MouseEvent<Element>
|
|
13
|
+
export type ReactDragEvent = import("react").DragEvent<Element>
|
|
14
|
+
export type ReactTouchEvent = import("react").TouchEvent<Element>
|
|
15
|
+
|
|
16
|
+
export type DOMMouseEvent = globalThis.MouseEvent
|
|
17
|
+
export type DOMDragEvent = globalThis.DragEvent
|
|
18
|
+
export type DOMTouchEvent = globalThis.TouchEvent
|
|
19
|
+
|
|
20
|
+
export interface DebugConfig {
|
|
21
|
+
showDebugPanel?: boolean
|
|
22
|
+
showMousePosition?: boolean
|
|
23
|
+
showPanelSizes?: boolean
|
|
24
|
+
showPlaceholderInfo?: boolean
|
|
25
|
+
showDropZoneOutlines?: boolean
|
|
26
|
+
logLevel?: 0 | 1 | 2
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_DEBUG_CONFIG: Required<DebugConfig> = {
|
|
30
|
+
showDebugPanel: false,
|
|
31
|
+
showMousePosition: false,
|
|
32
|
+
showPanelSizes: false,
|
|
33
|
+
showPlaceholderInfo: false,
|
|
34
|
+
showDropZoneOutlines: false,
|
|
35
|
+
logLevel: 0,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DragOverPosition {
|
|
39
|
+
columnIndex: number
|
|
40
|
+
insertIndex: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface OriginalPanelPosition {
|
|
44
|
+
columnIndex: number
|
|
45
|
+
panelIndex: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DragState {
|
|
49
|
+
isDragging: boolean
|
|
50
|
+
draggedPanel: string | null
|
|
51
|
+
dragOverColumn: number | null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ColumnConfig {
|
|
55
|
+
id: string
|
|
56
|
+
key: string
|
|
57
|
+
minWidth?: number
|
|
58
|
+
maxWidth?: number
|
|
59
|
+
className?: string
|
|
60
|
+
label?: string
|
|
61
|
+
title?: string
|
|
62
|
+
initialWidth?: number
|
|
63
|
+
resizable?: boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PanelConfig {
|
|
67
|
+
id: string
|
|
68
|
+
component: ComponentType<Record<string, unknown>>
|
|
69
|
+
title: string
|
|
70
|
+
props?: Record<string, unknown>
|
|
71
|
+
dragMode?: "normal" | "custom"
|
|
72
|
+
minHeight?: string
|
|
73
|
+
maxHeight?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface PanelSizeInfo {
|
|
77
|
+
width: number
|
|
78
|
+
height: number
|
|
79
|
+
x: number
|
|
80
|
+
y: number
|
|
81
|
+
minHeight: string
|
|
82
|
+
maxHeight: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TouchDragState {
|
|
86
|
+
isTouchDragging: boolean
|
|
87
|
+
panelId?: string | null
|
|
88
|
+
startX: number
|
|
89
|
+
startY: number
|
|
90
|
+
currentX: number
|
|
91
|
+
currentY: number
|
|
92
|
+
draggedElement: HTMLElement | null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ClickState {
|
|
96
|
+
isClicked: boolean
|
|
97
|
+
panelId: string | null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface MousePosition {
|
|
101
|
+
x: number
|
|
102
|
+
y: number
|
|
103
|
+
isVisible: boolean
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface CustomDragState {
|
|
107
|
+
isActive: boolean
|
|
108
|
+
panelId: string
|
|
109
|
+
startX: number
|
|
110
|
+
startY: number
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface DragDropHookReturn {
|
|
114
|
+
dragState: DragState
|
|
115
|
+
dragOverPosition: DragOverPosition | null
|
|
116
|
+
originalPanelPosition: OriginalPanelPosition | null
|
|
117
|
+
touchDragState: TouchDragState
|
|
118
|
+
customDrag: CustomDragState
|
|
119
|
+
clickState: ClickState
|
|
120
|
+
mousePosition: MousePosition
|
|
121
|
+
dragModes: Record<string, "normal" | "custom">
|
|
122
|
+
panelSizes: Record<string, PanelSizeInfo>
|
|
123
|
+
handleDragStart: (e: ReactDragEvent, panelId: string) => void
|
|
124
|
+
handleDragEnd: (e: ReactDragEvent) => void
|
|
125
|
+
handleClick: (e: ReactMouseEvent, panelId: string) => void
|
|
126
|
+
handleMouseDown: (e: ReactMouseEvent, panelId: string) => void
|
|
127
|
+
handleTouchStart: (e: ReactTouchEvent, panelId: string) => void
|
|
128
|
+
handleTouchMove: (e: ReactTouchEvent) => void
|
|
129
|
+
handleTouchEnd: (e: ReactTouchEvent) => void
|
|
130
|
+
handlePlaceholderDragEnter: (columnIndex: number, insertIndex: number) => void
|
|
131
|
+
handlePlaceholderDragLeave: (e: ReactDragEvent, columnIndex: number, insertIndex: number) => void
|
|
132
|
+
toggleDragMode: (panelId: string) => void
|
|
133
|
+
setDragOverPosition: (position: DragOverPosition | null) => void
|
|
134
|
+
setPanelRef: (panelId: string, el: ElementWithResizeObserver | null) => void
|
|
135
|
+
findPanelPosition: (panelId: string) => OriginalPanelPosition | null
|
|
136
|
+
dragOverPositionRef: RefObject<DragOverPosition | null>
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface DragDropEventHandlers {
|
|
140
|
+
onDragStart: (e: DragEvent<Element>, panelId: string) => void
|
|
141
|
+
onDragEnd: () => void
|
|
142
|
+
onPlaceholderDragEnter: (columnIndex: number, insertIndex: number) => void
|
|
143
|
+
onPlaceholderDragLeave: (e?: DragEvent<Element>, columnIndex?: number, insertIndex?: number) => void
|
|
144
|
+
onDragOverWithInsert: (e: DragEvent<Element>, columnIndex: number) => void
|
|
145
|
+
onDropWithInsert: (e: DragEvent<Element>, targetColumn: string) => void
|
|
146
|
+
onColumnDragLeave: (e: DragEvent<Element>) => void
|
|
147
|
+
onPanelClick?: (e: MouseEvent<Element>, panelId: string) => void
|
|
148
|
+
onPanelMouseDown?: (e: MouseEvent<Element>, panelId: string) => void
|
|
149
|
+
onTouchStart?: (e: TouchEvent<Element>, panelId: string) => void
|
|
150
|
+
onTouchMove?: (e: TouchEvent<Element>) => void
|
|
151
|
+
onTouchEnd?: (e: TouchEvent<Element>) => void
|
|
152
|
+
onPanelSizeInfoClick?: (e: MouseEvent<Element> | TouchEvent<Element>, panelId: string) => void
|
|
153
|
+
onToggleDragMode?: (panelId: string) => void
|
|
154
|
+
/**
|
|
155
|
+
* Optional close handler. When provided, the panel header shows an X button that the
|
|
156
|
+
* consumer uses to REMOVE the panel (unlike the EyeOff button, which only hides the
|
|
157
|
+
* panel into the restore bar).
|
|
158
|
+
*
|
|
159
|
+
* 指定時のみパネルヘッダに ✕(完全クローズ)ボタンを表示する。
|
|
160
|
+
* EyeOff(非表示 + 復元バー)とは別の「パネルを取り除く」意味論。
|
|
161
|
+
*/
|
|
162
|
+
onPanelClose?: (panelId: string) => void
|
|
163
|
+
/**
|
|
164
|
+
* Optional maximize handler (controlled). When provided, the panel header shows a
|
|
165
|
+
* maximize/restore button and the panel whose id equals `maximizedPanel` is rendered
|
|
166
|
+
* as a viewport-filling fixed overlay (OS-window-like maximize). Called with the
|
|
167
|
+
* panelId to maximize, or null to restore. Escape and header double-click also restore.
|
|
168
|
+
*
|
|
169
|
+
* 指定時のみパネルヘッダに最大化/復元ボタンを表示する (制御型)。`maximizedPanel` に
|
|
170
|
+
* 一致するパネルはビューポート全面の fixed オーバーレイとして描画される (OS ウインドウの
|
|
171
|
+
* 最大化と同様)。最大化するときは panelId、復元するときは null で呼ばれる。
|
|
172
|
+
* Escape キーとヘッダのダブルクリックでも復元/最大化をトグルする。
|
|
173
|
+
*/
|
|
174
|
+
onPanelMaximizeChange?: (panelId: string | null) => void
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Where the HTML5 drag can be initiated from (dragMode "normal" only).
|
|
179
|
+
* - "panel" (default): the whole panel is draggable — current behavior.
|
|
180
|
+
* - "header": only the panel header is draggable, so text inside the panel body
|
|
181
|
+
* can be selected/copied (essential for document-viewer panels).
|
|
182
|
+
*
|
|
183
|
+
* HTML5 ドラッグの開始領域。"header" 指定でパネル本文のテキスト選択が可能になる
|
|
184
|
+
* (ドキュメントビューア用途で必須)。既定は従来どおり "panel" 全体。
|
|
185
|
+
*/
|
|
186
|
+
export type PanelDragHandle = "panel" | "header"
|
|
187
|
+
|
|
188
|
+
export interface DragDropState {
|
|
189
|
+
columnPanels: Record<string, string[]>
|
|
190
|
+
panelVisibility: Record<string, boolean>
|
|
191
|
+
dragModes: Record<string, "normal" | "custom">
|
|
192
|
+
dragState: DragState
|
|
193
|
+
dragOverPosition: DragOverPosition | null
|
|
194
|
+
originalPanelPosition: OriginalPanelPosition | null
|
|
195
|
+
panelSizes: Record<string, PanelSizeInfo>
|
|
196
|
+
customDrag?: CustomDragState
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface DragDropStateUpdaters {
|
|
200
|
+
onColumnPanelsChange: (newColumnPanels: Record<string, string[]>) => void
|
|
201
|
+
onPanelVisibilityChange: (newVisibility: Record<string, boolean>) => void
|
|
202
|
+
setPanelRef?: (panelId: string, el: HTMLDivElement | null) => void
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface UseDragDropColumnsReturn {
|
|
206
|
+
columnPanels: Record<string, string[]>
|
|
207
|
+
panelVisibility: Record<string, boolean>
|
|
208
|
+
dragModes: Record<string, "normal" | "custom">
|
|
209
|
+
dragState: DragState
|
|
210
|
+
dragOverPosition: DragOverPosition | null
|
|
211
|
+
originalPanelPosition: OriginalPanelPosition | null
|
|
212
|
+
touchDragState: TouchDragState
|
|
213
|
+
customDrag: CustomDragState
|
|
214
|
+
clickState: ClickState
|
|
215
|
+
mousePosition: MousePosition
|
|
216
|
+
panelSizes: Record<string, PanelSizeInfo>
|
|
217
|
+
setColumnPanels: (newColumnPanels: Record<string, string[]>) => void
|
|
218
|
+
setPanelVisibility: (newVisibility: Record<string, boolean>) => void
|
|
219
|
+
setDragModes: (newDragModes: Record<string, "normal" | "custom">) => void
|
|
220
|
+
setPanelRef: (panelId: string, el: HTMLDivElement | null) => void
|
|
221
|
+
handleDragStart: (e: DragEvent<Element>, panelId: string) => void
|
|
222
|
+
handleDragEnd: () => void
|
|
223
|
+
handleMouseDown: (e: MouseEvent<Element>, panelId: string) => void
|
|
224
|
+
handleTouchStart: (e: TouchEvent<Element>, panelId: string) => void
|
|
225
|
+
handleTouchMove: (e: TouchEvent) => void
|
|
226
|
+
handleTouchEnd: (e: TouchEvent) => void
|
|
227
|
+
handlePlaceholderDragEnter: (columnIndex: number, insertIndex: number) => void
|
|
228
|
+
handlePlaceholderDragLeave: (e?: DragEvent<Element>, columnIndex?: number, insertIndex?: number) => void
|
|
229
|
+
handleDragOverWithInsert: (e: DragEvent<Element>, columnIndex: number) => void
|
|
230
|
+
handleDropWithInsert: (e: DragEvent<Element>, targetColumn: string) => void
|
|
231
|
+
handleColumnDragLeave: (e: DragEvent<Element>) => void
|
|
232
|
+
handlePanelSizeInfoClick: (e: MouseEvent<Element> | TouchEvent<Element>, panelId: string) => void
|
|
233
|
+
toggleDragMode: (panelId: string) => void
|
|
234
|
+
findPanelPosition: (panelId: string) => OriginalPanelPosition | null
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface ResizeConfig {
|
|
238
|
+
enableResize?: boolean
|
|
239
|
+
columnWidths?: Record<string, number>
|
|
240
|
+
onColumnWidthChange?: (columnId: string, width: number) => void
|
|
241
|
+
onResizeStart?: (columnId: string, startX: number) => void
|
|
242
|
+
onResize?: (currentX: number) => void
|
|
243
|
+
onResizeEnd?: () => void
|
|
244
|
+
isResizing?: boolean
|
|
245
|
+
resizingColumn?: string | null
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export type DragMode = "normal" | "custom"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Column ID management utilities for consistent column identification across the application.
|
|
3
|
+
* カラムID管理ユーティリティ - アプリケーション全体でのカラム識別の一貫性を保つ。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ColumnIdConfig {
|
|
7
|
+
prefix: string
|
|
8
|
+
startIndex: number
|
|
9
|
+
separator: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_COLUMN_ID_CONFIG: ColumnIdConfig = {
|
|
13
|
+
prefix: "column",
|
|
14
|
+
startIndex: 1,
|
|
15
|
+
separator: "_",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const generateColumnId = (index: number, config: ColumnIdConfig = DEFAULT_COLUMN_ID_CONFIG): string => {
|
|
19
|
+
const { prefix, startIndex, separator } = config
|
|
20
|
+
const columnNumber = index + startIndex
|
|
21
|
+
return `${prefix}${separator}${columnNumber}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const generateColumnIds = (count: number, config: ColumnIdConfig = DEFAULT_COLUMN_ID_CONFIG): string[] => {
|
|
25
|
+
return Array.from({ length: count }, (_, index) => generateColumnId(index, config))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const parseColumnId = (columnId: string, config: ColumnIdConfig = DEFAULT_COLUMN_ID_CONFIG): number | null => {
|
|
29
|
+
const { prefix, startIndex, separator } = config
|
|
30
|
+
const expectedPrefix = `${prefix}${separator}`
|
|
31
|
+
|
|
32
|
+
if (!columnId.startsWith(expectedPrefix)) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const numberPart = columnId.slice(expectedPrefix.length)
|
|
37
|
+
const columnNumber = parseInt(numberPart, 10)
|
|
38
|
+
|
|
39
|
+
if (Number.isNaN(columnNumber) || columnNumber < startIndex) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return columnNumber - startIndex
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const isValidColumnId = (columnId: string, config: ColumnIdConfig = DEFAULT_COLUMN_ID_CONFIG): boolean => {
|
|
47
|
+
return parseColumnId(columnId, config) !== null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const getNextColumnId = (existingIds: string[], config: ColumnIdConfig = DEFAULT_COLUMN_ID_CONFIG): string => {
|
|
51
|
+
const indices = existingIds
|
|
52
|
+
.map((id) => parseColumnId(id, config))
|
|
53
|
+
.filter((index): index is number => index !== null)
|
|
54
|
+
.sort((a, b) => a - b)
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < indices.length; i++) {
|
|
57
|
+
if (indices[i] !== i) {
|
|
58
|
+
return generateColumnId(i, config)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return generateColumnId(indices.length, config)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const sortColumnIds = (columnIds: string[], config: ColumnIdConfig = DEFAULT_COLUMN_ID_CONFIG): string[] => {
|
|
66
|
+
return columnIds.sort((a, b) => {
|
|
67
|
+
const indexA = parseColumnId(a, config) ?? -1
|
|
68
|
+
const indexB = parseColumnId(b, config) ?? -1
|
|
69
|
+
return indexA - indexB
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const createColumnIdMapping = (oldIds: string[], config: ColumnIdConfig = DEFAULT_COLUMN_ID_CONFIG): Record<string, string> => {
|
|
74
|
+
const mapping: Record<string, string> = {}
|
|
75
|
+
|
|
76
|
+
oldIds.forEach((oldId, index) => {
|
|
77
|
+
const newId = generateColumnId(index, config)
|
|
78
|
+
mapping[oldId] = newId
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return mapping
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const normalizeColumnPanels = (columnPanels: Record<string, string[]>, newColumnCount: number, config: ColumnIdConfig = DEFAULT_COLUMN_ID_CONFIG): Record<string, string[]> => {
|
|
85
|
+
const allPanels: string[] = []
|
|
86
|
+
Object.values(columnPanels).forEach((panels) => {
|
|
87
|
+
allPanels.push(...panels)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const newColumnPanels: Record<string, string[]> = {}
|
|
91
|
+
const newColumnIds = generateColumnIds(newColumnCount, config)
|
|
92
|
+
|
|
93
|
+
newColumnIds.forEach((columnId) => {
|
|
94
|
+
newColumnPanels[columnId] = []
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
allPanels.forEach((panelId, index) => {
|
|
98
|
+
const targetColumnIndex = index % newColumnCount
|
|
99
|
+
const targetColumnId = newColumnIds[targetColumnIndex]
|
|
100
|
+
newColumnPanels[targetColumnId].push(panelId)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
return newColumnPanels
|
|
104
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utility functions for implementing edge scrolling during drag operations.
|
|
3
|
+
* This provides auto-scrolling functionality when the cursor reaches the edge of the viewport.
|
|
4
|
+
*
|
|
5
|
+
* ドラッグ操作中のエッジスクロール機能を実装するためのユーティリティ関数。
|
|
6
|
+
* カーソルがビューポートの端に到達した際の自動スクロール機能を提供します。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface EdgeScrollConfig {
|
|
10
|
+
triggerDistance: number
|
|
11
|
+
scrollSpeed: number
|
|
12
|
+
maxScrollSpeed: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_EDGE_SCROLL_CONFIG: EdgeScrollConfig = {
|
|
16
|
+
triggerDistance: 80,
|
|
17
|
+
scrollSpeed: 5,
|
|
18
|
+
maxScrollSpeed: 20,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ScrollVector {
|
|
22
|
+
x: number
|
|
23
|
+
y: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const calculateScrollVector = (clientX: number, clientY: number, config: EdgeScrollConfig = DEFAULT_EDGE_SCROLL_CONFIG, scrollElement?: HTMLElement): ScrollVector => {
|
|
27
|
+
const { triggerDistance, scrollSpeed, maxScrollSpeed } = config
|
|
28
|
+
|
|
29
|
+
let elementBounds: DOMRect
|
|
30
|
+
if (scrollElement) {
|
|
31
|
+
elementBounds = scrollElement.getBoundingClientRect()
|
|
32
|
+
} else {
|
|
33
|
+
elementBounds = new DOMRect(0, 0, window.innerWidth, window.innerHeight)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { left, top, right, bottom } = elementBounds
|
|
37
|
+
|
|
38
|
+
let scrollX = 0
|
|
39
|
+
let scrollY = 0
|
|
40
|
+
|
|
41
|
+
if (clientX >= left && clientX <= right && clientY >= top && clientY <= bottom) {
|
|
42
|
+
if (clientX < left + triggerDistance) {
|
|
43
|
+
const intensity = Math.max(0, (left + triggerDistance - clientX) / triggerDistance)
|
|
44
|
+
scrollX = -Math.min(maxScrollSpeed, scrollSpeed + intensity * (maxScrollSpeed - scrollSpeed))
|
|
45
|
+
} else if (clientX > right - triggerDistance) {
|
|
46
|
+
const intensity = Math.max(0, (clientX - (right - triggerDistance)) / triggerDistance)
|
|
47
|
+
scrollX = Math.min(maxScrollSpeed, scrollSpeed + intensity * (maxScrollSpeed - scrollSpeed))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (clientY < top + triggerDistance) {
|
|
51
|
+
const intensity = Math.max(0, (top + triggerDistance - clientY) / triggerDistance)
|
|
52
|
+
scrollY = -Math.min(maxScrollSpeed, scrollSpeed + intensity * (maxScrollSpeed - scrollSpeed))
|
|
53
|
+
} else if (clientY > bottom - triggerDistance) {
|
|
54
|
+
const intensity = Math.max(0, (clientY - (bottom - triggerDistance)) / triggerDistance)
|
|
55
|
+
scrollY = Math.min(maxScrollSpeed, scrollSpeed + intensity * (maxScrollSpeed - scrollSpeed))
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { x: scrollX, y: scrollY }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const performScroll = (scrollVector: ScrollVector, scrollElement?: HTMLElement): void => {
|
|
63
|
+
if (scrollVector.x !== 0 || scrollVector.y !== 0) {
|
|
64
|
+
if (scrollElement) {
|
|
65
|
+
scrollElement.scrollBy(scrollVector.x, scrollVector.y)
|
|
66
|
+
} else {
|
|
67
|
+
window.scrollBy(scrollVector.x, scrollVector.y)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class EdgeScrollManager {
|
|
73
|
+
private intervalId: number | null = null
|
|
74
|
+
private config: EdgeScrollConfig
|
|
75
|
+
private isActive = false
|
|
76
|
+
private scrollElement?: HTMLElement
|
|
77
|
+
|
|
78
|
+
constructor(config: EdgeScrollConfig = DEFAULT_EDGE_SCROLL_CONFIG, scrollElement?: HTMLElement) {
|
|
79
|
+
this.config = config
|
|
80
|
+
this.scrollElement = scrollElement
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setScrollElement(element?: HTMLElement): void {
|
|
84
|
+
this.scrollElement = element
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
start(clientX: number, clientY: number): void {
|
|
88
|
+
if (this.isActive) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.isActive = true
|
|
93
|
+
this.intervalId = window.setInterval(() => {
|
|
94
|
+
const scrollVector = calculateScrollVector(clientX, clientY, this.config, this.scrollElement)
|
|
95
|
+
performScroll(scrollVector, this.scrollElement)
|
|
96
|
+
}, 16)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
update(clientX: number, clientY: number): void {
|
|
100
|
+
const scrollVector = calculateScrollVector(clientX, clientY, this.config, this.scrollElement)
|
|
101
|
+
|
|
102
|
+
if ((scrollVector.x !== 0 || scrollVector.y !== 0) && !this.isActive) {
|
|
103
|
+
this.start(clientX, clientY)
|
|
104
|
+
} else if (scrollVector.x === 0 && scrollVector.y === 0 && this.isActive) {
|
|
105
|
+
this.stop()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
stop(): void {
|
|
110
|
+
if (this.intervalId !== null) {
|
|
111
|
+
clearInterval(this.intervalId)
|
|
112
|
+
this.intervalId = null
|
|
113
|
+
}
|
|
114
|
+
this.isActive = false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
isScrolling(): boolean {
|
|
118
|
+
return this.isActive
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
updateConfig(newConfig: EdgeScrollConfig): void {
|
|
122
|
+
this.config = newConfig
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module utils/logger
|
|
3
|
+
* @description Logger utility for consistent log management across the library.
|
|
4
|
+
* @description ライブラリ全体で一貫したログ管理を行うためのロガーユーティリティ。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @enum LogLevel
|
|
9
|
+
* @description Log levels for filtering output.
|
|
10
|
+
* @description 出力をフィルタリングするためのログレベル。
|
|
11
|
+
*/
|
|
12
|
+
export enum LogLevel {
|
|
13
|
+
DEBUG = 0,
|
|
14
|
+
INFO = 1,
|
|
15
|
+
WARN = 2,
|
|
16
|
+
ERROR = 3,
|
|
17
|
+
NONE = 4,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @interface ILogger
|
|
22
|
+
* @description Interface for a logger object compatible with Console.
|
|
23
|
+
* @description Console と互換性のあるロガーオブジェクトのインターフェース。
|
|
24
|
+
*/
|
|
25
|
+
export interface ILogger {
|
|
26
|
+
debug(message?: unknown, ...optionalParams: unknown[]): void
|
|
27
|
+
info(message?: unknown, ...optionalParams: unknown[]): void
|
|
28
|
+
warn(message?: unknown, ...optionalParams: unknown[]): void
|
|
29
|
+
error(message?: unknown, ...optionalParams: unknown[]): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @class Logger
|
|
34
|
+
* @description A wrapper class for handling logging with levels and prefixes.
|
|
35
|
+
* @description レベルとプレフィックスを使用したログ記録を処理するためのラッパークラス。
|
|
36
|
+
*/
|
|
37
|
+
export class Logger implements ILogger {
|
|
38
|
+
private level: LogLevel
|
|
39
|
+
private prefix: string
|
|
40
|
+
private impl: ILogger
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @constructor
|
|
44
|
+
* @param {LogLevel} [level=LogLevel.WARN] - The minimum log level to output.
|
|
45
|
+
* @param {string} [prefix="[drag-drop-panels]"] - The prefix to add to all log messages.
|
|
46
|
+
* @param {ILogger} [impl=console] - The implementation to use for logging.
|
|
47
|
+
*/
|
|
48
|
+
constructor(level: LogLevel = LogLevel.WARN, prefix: string = "[drag-drop-panels]", impl: ILogger = console) {
|
|
49
|
+
this.level = level
|
|
50
|
+
this.prefix = prefix
|
|
51
|
+
this.impl = impl
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static instance: Logger = new Logger(LogLevel.WARN, "[drag-drop-panels]")
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @method setLevel
|
|
58
|
+
* @description Updates the current log level for the static instance.
|
|
59
|
+
* @description 静的インスタンスの現在のログレベルを更新します。
|
|
60
|
+
* @param {LogLevel} level - The new log level.
|
|
61
|
+
*/
|
|
62
|
+
static setLevel(level: LogLevel): void {
|
|
63
|
+
Logger.instance.setLevel(level)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @method setLevel
|
|
68
|
+
* @description Updates the current log level.
|
|
69
|
+
* @description 現在のログレベルを更新します。
|
|
70
|
+
* @param {LogLevel} level - The new log level.
|
|
71
|
+
*/
|
|
72
|
+
setLevel(level: LogLevel): void {
|
|
73
|
+
this.level = level
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @method setImplementation
|
|
78
|
+
* @description Updates the logger implementation for the static instance.
|
|
79
|
+
* @description 静的インスタンスのロガーの実装を更新します。
|
|
80
|
+
* @param {ILogger} impl - The new logger implementation.
|
|
81
|
+
*/
|
|
82
|
+
static setImplementation(impl: ILogger): void {
|
|
83
|
+
Logger.instance.setImplementation(impl)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @method setImplementation
|
|
88
|
+
* @description Updates the logger implementation.
|
|
89
|
+
* @description ロガーの実装を更新します。
|
|
90
|
+
* @param {ILogger} impl - The new logger implementation.
|
|
91
|
+
*/
|
|
92
|
+
setImplementation(impl: ILogger): void {
|
|
93
|
+
this.impl = impl
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @method setPrefix
|
|
98
|
+
* @description Updates the log prefix for the static instance.
|
|
99
|
+
* @description 静的インスタンスのログのプレフィックスを更新します。
|
|
100
|
+
* @param {string} prefix - The new prefix.
|
|
101
|
+
*/
|
|
102
|
+
static setPrefix(prefix: string): void {
|
|
103
|
+
Logger.instance.setPrefix(prefix)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @method setPrefix
|
|
108
|
+
* @description Updates the log prefix.
|
|
109
|
+
* @description ログのプレフィックスを更新します。
|
|
110
|
+
* @param {string} prefix - The new prefix.
|
|
111
|
+
*/
|
|
112
|
+
setPrefix(prefix: string): void {
|
|
113
|
+
this.prefix = prefix
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private formatMessage(message: unknown): unknown[] {
|
|
117
|
+
if (typeof message === "string") {
|
|
118
|
+
return [`${this.prefix} ${message}`]
|
|
119
|
+
}
|
|
120
|
+
return [this.prefix, message]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static debug(message?: unknown, ...optionalParams: unknown[]): void {
|
|
124
|
+
Logger.instance.debug(message, ...optionalParams)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
debug(message?: unknown, ...optionalParams: unknown[]): void {
|
|
128
|
+
if (this.level <= LogLevel.DEBUG) {
|
|
129
|
+
this.impl.debug(...this.formatMessage(message), ...optionalParams)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static info(message?: unknown, ...optionalParams: unknown[]): void {
|
|
134
|
+
Logger.instance.info(message, ...optionalParams)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
info(message?: unknown, ...optionalParams: unknown[]): void {
|
|
138
|
+
if (this.level <= LogLevel.INFO) {
|
|
139
|
+
this.impl.info(...this.formatMessage(message), ...optionalParams)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static warn(message?: unknown, ...optionalParams: unknown[]): void {
|
|
144
|
+
Logger.instance.warn(message, ...optionalParams)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
warn(message?: unknown, ...optionalParams: unknown[]): void {
|
|
148
|
+
if (this.level <= LogLevel.WARN) {
|
|
149
|
+
this.impl.warn(...this.formatMessage(message), ...optionalParams)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static error(message?: unknown, ...optionalParams: unknown[]): void {
|
|
154
|
+
Logger.instance.error(message, ...optionalParams)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
error(message?: unknown, ...optionalParams: unknown[]): void {
|
|
158
|
+
if (this.level <= LogLevel.ERROR) {
|
|
159
|
+
this.impl.error(...this.formatMessage(message), ...optionalParams)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @package @aiquants/drag-drop-panels
|
|
3
|
+
* @description Reusable drag-and-drop panel layout components and utilities
|
|
4
|
+
*
|
|
5
|
+
* 再利用可能なドラッグ&ドロップパネルレイアウトコンポーネントとユーティリティ
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type { DragDropLayoutProps } from "./DragDropLayout"
|
|
9
|
+
export { DragDropLayout } from "./DragDropLayout"
|
|
10
|
+
export { ColumnControlPanel } from "./drag-drop/ColumnControlPanel"
|
|
11
|
+
export { ColumnInsertPanel } from "./drag-drop/ColumnInsertPanel"
|
|
12
|
+
export { debugLog, PanelSizeDisplay } from "./drag-drop/DebugComponents"
|
|
13
|
+
export { DebugControlPanel } from "./drag-drop/DebugControlPanel"
|
|
14
|
+
export { DropZoneDebugOverlay, InsertPlaceholder } from "./drag-drop/DebugOverlay"
|
|
15
|
+
export type { ColumnConfig, ColumnLayoutState, UseColumnLayoutProps, UseColumnLayoutReturn } from "./drag-drop/hooks/useColumnLayout"
|
|
16
|
+
export { useColumnLayout } from "./drag-drop/hooks/useColumnLayout"
|
|
17
|
+
export { useCustomDragHandlers } from "./drag-drop/hooks/useCustomDragHandlers"
|
|
18
|
+
export type { UseDragDropColumnsProps } from "./drag-drop/hooks/useDragDropColumns"
|
|
19
|
+
export { useDragDropColumns } from "./drag-drop/hooks/useDragDropColumns"
|
|
20
|
+
export { useDragDropState } from "./drag-drop/hooks/useDragDropState"
|
|
21
|
+
export { useMouseTracking } from "./drag-drop/hooks/useMouseTracking"
|
|
22
|
+
export { useNormalDragHandlers } from "./drag-drop/hooks/useNormalDragHandlers"
|
|
23
|
+
export { usePanelManagement } from "./drag-drop/hooks/usePanelManagement"
|
|
24
|
+
export type { UseResponsiveColumnsProps, UseResponsiveColumnsReturn } from "./drag-drop/hooks/useResponsiveColumns"
|
|
25
|
+
export { useResponsiveColumns } from "./drag-drop/hooks/useResponsiveColumns"
|
|
26
|
+
export type { ResizableColumnProps, ResizeHandleProps } from "./drag-drop/ResizeHandle"
|
|
27
|
+
export { ResizableColumn, ResizeHandle } from "./drag-drop/ResizeHandle"
|
|
28
|
+
export { ResponsiveControl } from "./drag-drop/ResponsiveControl"
|
|
29
|
+
export type {
|
|
30
|
+
ClickState,
|
|
31
|
+
ColumnConfig as ColumnConfigType,
|
|
32
|
+
CustomDragState,
|
|
33
|
+
DebugConfig,
|
|
34
|
+
DOMDragEvent,
|
|
35
|
+
DOMMouseEvent,
|
|
36
|
+
DOMTouchEvent,
|
|
37
|
+
DragDropEventHandlers,
|
|
38
|
+
DragDropHookReturn,
|
|
39
|
+
DragDropState,
|
|
40
|
+
DragDropStateUpdaters,
|
|
41
|
+
DragMode,
|
|
42
|
+
DragOverPosition,
|
|
43
|
+
DragState,
|
|
44
|
+
ElementWithResizeObserver,
|
|
45
|
+
MousePosition,
|
|
46
|
+
OriginalPanelPosition,
|
|
47
|
+
PanelConfig,
|
|
48
|
+
PanelSizeInfo,
|
|
49
|
+
ReactDragEvent,
|
|
50
|
+
ReactMouseEvent,
|
|
51
|
+
ReactTouchEvent,
|
|
52
|
+
ResizeConfig,
|
|
53
|
+
TouchDragState,
|
|
54
|
+
UseDragDropColumnsReturn,
|
|
55
|
+
} from "./drag-drop/types"
|
|
56
|
+
export { DEFAULT_DEBUG_CONFIG } from "./drag-drop/types"
|
|
57
|
+
export type { ColumnIdConfig } from "./drag-drop/utils/columnIdUtils"
|
|
58
|
+
export { createColumnIdMapping, DEFAULT_COLUMN_ID_CONFIG, generateColumnId, generateColumnIds, getNextColumnId, isValidColumnId, normalizeColumnPanels, parseColumnId, sortColumnIds } from "./drag-drop/utils/columnIdUtils"
|
|
59
|
+
export type { EdgeScrollConfig, ScrollVector } from "./drag-drop/utils/edgeScrollUtils"
|
|
60
|
+
export { calculateScrollVector, DEFAULT_EDGE_SCROLL_CONFIG, EdgeScrollManager, performScroll } from "./drag-drop/utils/edgeScrollUtils"
|