@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.
Files changed (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -10
  3. package/dist/drag-drop/hooks/useCustomDragHandlers.d.ts.map +1 -1
  4. package/dist/index.es.js +88 -88
  5. package/dist/index.umd.js +2 -2
  6. package/dist/styles/drag-drop-panels.standalone.css +3 -0
  7. package/package.json +11 -4
  8. package/src/DragDropLayout.tsx +751 -0
  9. package/src/drag-drop/ColumnControlPanel.tsx +123 -0
  10. package/src/drag-drop/ColumnInsertPanel.tsx +106 -0
  11. package/src/drag-drop/DebugComponents.tsx +222 -0
  12. package/src/drag-drop/DebugControlPanel.tsx +200 -0
  13. package/src/drag-drop/DebugOverlay.tsx +250 -0
  14. package/src/drag-drop/DragDropLayout.css +130 -0
  15. package/src/drag-drop/ResizeHandle.tsx +208 -0
  16. package/src/drag-drop/ResponsiveControl.tsx +33 -0
  17. package/src/drag-drop/hooks/dragGhostScale.spec.tsx +153 -0
  18. package/src/drag-drop/hooks/useColumnLayout.ts +457 -0
  19. package/src/drag-drop/hooks/useCustomDragHandlers.tsx +333 -0
  20. package/src/drag-drop/hooks/useDragDropColumns.tsx +446 -0
  21. package/src/drag-drop/hooks/useDragDropState.tsx +200 -0
  22. package/src/drag-drop/hooks/useFitScale.ts +48 -0
  23. package/src/drag-drop/hooks/useMouseTracking.tsx +102 -0
  24. package/src/drag-drop/hooks/useNormalDragHandlers.tsx +427 -0
  25. package/src/drag-drop/hooks/usePanelManagement.tsx +238 -0
  26. package/src/drag-drop/hooks/useResponsiveColumns.ts +210 -0
  27. package/src/drag-drop/resize-handle.css +71 -0
  28. package/src/drag-drop/types.ts +248 -0
  29. package/src/drag-drop/utils/columnIdUtils.ts +104 -0
  30. package/src/drag-drop/utils/edgeScrollUtils.ts +124 -0
  31. package/src/drag-drop/utils/simple-logger.ts +162 -0
  32. package/src/index.ts +60 -0
  33. package/src/styles/standalone.entry.css +27 -0
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @fileoverview Defines the ColumnControlPanel component.
3
+ * This component provides UI controls for managing the number of columns,
4
+ * auto-balancing panels, and resetting column widths in a drag-and-drop layout.
5
+ *
6
+ * ColumnControlPanel コンポーネントを定義します。
7
+ * このコンポーネントは、ドラッグ&ドロップレイアウトにおいて、カラム数の管理、
8
+ * パネルの自動バランス調整、およびカラム幅のリセットを行うためのUIコントロールを提供します。
9
+ */
10
+ import { Grid3X3, Minus, Plus, RotateCcw } from "lucide-react"
11
+
12
+ /**
13
+ * Props for the ColumnControlPanel component.
14
+ *
15
+ * ColumnControlPanel コンポーネントのプロパティ。
16
+ */
17
+ export type ColumnControlPanelProps = {
18
+ /** The current number of columns. / 現在のカラム数。 */
19
+ columnCount: number
20
+ /** Callback function when the column count changes. / カラム数が変更されたときのコールバック関数。 */
21
+ onColumnCountChange: (count: number) => void
22
+ /** Callback function to auto-balance panels across columns. / パネルをカラム間で自動的にバランス調整するためのコールバック関数。 */
23
+ onAutoBalance: () => void
24
+ /** Callback function to reset column widths to their initial state. / カラム幅を初期状態にリセットするためのコールバック関数。 */
25
+ onResetWidths: () => void
26
+ /** The minimum number of columns allowed. Defaults to 2. / 許可される最小カラム数。デフォルトは2。 */
27
+ minColumns?: number
28
+ /** The maximum number of columns allowed. Defaults to 6. / 許可される最大カラム数。デフォルトは6。 */
29
+ maxColumns?: number
30
+ /** A flag indicating if a resize operation is in progress. / リサイズ操作が進行中かどうかを示すフラグ。 */
31
+ isResizing?: boolean
32
+ /** A flag indicating if responsive mode is active (restricts manual column adjustments). / レスポンシブモードが有効かどうか(手動カラム調整を制限)。 */
33
+ isResponsiveMode?: boolean
34
+ /** The responsive column count when responsive mode is active. / レスポンシブモード有効時のレスポンシブカラム数。 */
35
+ responsiveColumnCount?: number
36
+ }
37
+
38
+ /**
39
+ * A UI component that provides controls for managing layout columns.
40
+ * It allows users to add or remove columns, balance panels, and reset widths.
41
+ *
42
+ * レイアウトカラムを管理するためのコントロールを提供するUIコンポーネント。
43
+ * ユーザーはカラムの追加・削除、パネルのバランス調整、幅のリセットができます。
44
+ * @param {ColumnControlPanelProps} props - The props for the component.
45
+ * @returns {JSX.Element} The rendered column control panel.
46
+ */
47
+ export const ColumnControlPanel = ({ columnCount, onColumnCountChange, onAutoBalance, onResetWidths, minColumns = 2, maxColumns = 6, isResizing = false, isResponsiveMode = false, responsiveColumnCount }: ColumnControlPanelProps) => {
48
+ // カラムを減らせるかどうかのフラグ(レスポンシブモード時は無効)
49
+ const canDecreaseColumns = columnCount > minColumns && !isResponsiveMode
50
+ // カラムを増やせるかどうかのフラグ(レスポンシブモード時は無効)
51
+ const canIncreaseColumns = columnCount < maxColumns && !isResponsiveMode
52
+
53
+ return (
54
+ <div className="flex items-center gap-3 rounded-lg border border-slate-200 bg-white/80 px-4 py-2 shadow-sm backdrop-blur-lg">
55
+ {/* カラム数制御 */}
56
+ <div className="flex items-center gap-2">
57
+ <span className="font-medium text-slate-700 text-sm">カラム数:</span>
58
+
59
+ {/* カラム減少ボタン */}
60
+ <button
61
+ type="button"
62
+ onClick={() => canDecreaseColumns && onColumnCountChange(columnCount - 1)}
63
+ disabled={!canDecreaseColumns || isResizing}
64
+ className={`rounded p-1 text-xs transition-colors ${canDecreaseColumns && !isResizing ? "border border-slate-300 text-slate-600 hover:border-red-300 hover:bg-red-50 hover:text-red-600" : "cursor-not-allowed border border-slate-200 bg-slate-50 text-slate-300"}`}
65
+ title={isResponsiveMode ? "レスポンシブモード中:カラム数は自動調整されます" : `カラムを削除 (最小: ${minColumns}列)`}>
66
+ <Minus className="h-3 w-3" />
67
+ </button>
68
+
69
+ {/* 現在のカラム数表示 */}
70
+ <div className={`min-w-[2rem] rounded border px-3 py-1 text-center font-mono text-sm ${isResponsiveMode ? "border-blue-300 bg-blue-50 text-blue-700" : "border-slate-300 bg-slate-100 text-slate-700"}`}>
71
+ {isResponsiveMode ? (responsiveColumnCount ?? columnCount) : columnCount}
72
+ {isResponsiveMode && <span className="ml-1 text-xs">自動</span>}
73
+ </div>
74
+
75
+ {/* カラム増加ボタン */}
76
+ <button
77
+ type="button"
78
+ onClick={() => canIncreaseColumns && onColumnCountChange(columnCount + 1)}
79
+ disabled={!canIncreaseColumns || isResizing}
80
+ className={`rounded p-1 text-xs transition-colors ${canIncreaseColumns && !isResizing ? "border border-slate-300 text-slate-600 hover:border-blue-300 hover:bg-blue-50 hover:text-blue-600" : "cursor-not-allowed border border-slate-200 bg-slate-50 text-slate-300"}`}
81
+ title={isResponsiveMode ? "レスポンシブモード中:カラム数は自動調整されます" : `カラムを追加 (最大: ${maxColumns}列)`}>
82
+ <Plus className="h-3 w-3" />
83
+ </button>
84
+ </div>
85
+
86
+ {/* 区切り線 */}
87
+ <div className="h-6 w-px bg-slate-300" />
88
+
89
+ {/* レイアウト制御ボタン */}
90
+ <div className="flex items-center gap-2">
91
+ {/* 自動バランス調整 */}
92
+ <button
93
+ type="button"
94
+ onClick={onAutoBalance}
95
+ disabled={isResizing}
96
+ className={`flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors ${!isResizing ? "border border-slate-300 text-slate-600 hover:border-green-300 hover:bg-green-50 hover:text-green-600" : "cursor-not-allowed border border-slate-200 bg-slate-50 text-slate-300"}`}
97
+ title="パネルを全カラムに均等分散">
98
+ <Grid3X3 className="h-3 w-3" />
99
+ <span>バランス</span>
100
+ </button>
101
+
102
+ {/* 幅リセット */}
103
+ <button
104
+ type="button"
105
+ onClick={onResetWidths}
106
+ disabled={isResizing}
107
+ className={`flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors ${!isResizing ? "border border-slate-300 text-slate-600 hover:border-orange-300 hover:bg-orange-50 hover:text-orange-600" : "cursor-not-allowed border border-slate-200 bg-slate-50 text-slate-300"}`}
108
+ title="カラム幅を初期値にリセット">
109
+ <RotateCcw className="h-3 w-3" />
110
+ <span>幅リセット</span>
111
+ </button>
112
+ </div>
113
+
114
+ {/* リサイズ中インジケーター */}
115
+ {isResizing && (
116
+ <div className="flex items-center gap-1 text-blue-600 text-xs">
117
+ <div className="h-2 w-2 animate-pulse rounded-full bg-blue-500" />
118
+ <span>リサイズ中...</span>
119
+ </div>
120
+ )}
121
+ </div>
122
+ )
123
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @fileoverview Defines the ColumnInsertPanel component.
3
+ * This component provides UI controls for inserting and deleting columns at specific positions
4
+ * in a drag-and-drop layout.
5
+ *
6
+ * ColumnInsertPanel コンポーネントを定義します。
7
+ * このコンポーネントは、ドラッグ&ドロップレイアウトにおいて、
8
+ * 特定の位置にカラムを挿入・削除するためのUIコントロールを提供します。
9
+ */
10
+ import { Plus, X } from "lucide-react"
11
+
12
+ /**
13
+ * Props for the ColumnInsertPanel component。
14
+ *
15
+ * ColumnInsertPanel コンポーネントのプロパティ。
16
+ */
17
+ export type ColumnInsertPanelProps = {
18
+ /** The current number of columns. / 現在のカラム数。 */
19
+ columnCount: number
20
+ /** Callback function to insert a column at a specific position. / 特定の位置にカラムを挿入するためのコールバック関数。 */
21
+ onInsertColumn: (position: number) => void
22
+ /** Callback function to delete a column at a specific position. / 特定の位置のカラムを削除するためのコールバック関数。 */
23
+ onDeleteColumn: (position: number) => void
24
+ /** The minimum number of columns allowed. Defaults to 1. / 許可される最小カラム数。デフォルトは1。 */
25
+ minColumns?: number
26
+ /** The maximum number of columns allowed. Defaults to 6. / 許可される最大カラム数。デフォルトは6。 */
27
+ maxColumns?: number
28
+ /** A flag indicating if a resize operation is in progress. / リサイズ操作が進行中かどうかを示すフラグ。 */
29
+ isResizing?: boolean
30
+ /** A flag to disable the entire panel (e.g., during responsive mode). / パネル全体を無効化するフラグ(レスポンシブモード時など)。 */
31
+ disabled?: boolean
32
+ }
33
+
34
+ /**
35
+ * A UI component that provides controls for inserting and deleting columns at specific positions.
36
+ * It shows visual indicators between columns where new columns can be inserted,
37
+ * and delete buttons on each column.
38
+ *
39
+ * 特定の位置でカラムの挿入・削除を行うためのコントロールを提供するUIコンポーネント。
40
+ * 新しいカラムを挿入できる位置にビジュアルインジケーターを表示し、
41
+ * 各カラムに削除ボタンを表示します。
42
+ * @param {ColumnInsertPanelProps} props - The props for the component.
43
+ * @returns {JSX.Element} The rendered column insert panel。
44
+ */
45
+ export const ColumnInsertPanel = ({ columnCount, onInsertColumn, onDeleteColumn, minColumns = 1, maxColumns = 6, isResizing = false, disabled = false }: ColumnInsertPanelProps) => {
46
+ // カラムを増やせるかどうかのフラグ
47
+ const canInsertColumns = columnCount < maxColumns && !disabled
48
+ // カラムを減らせるかどうかのフラグ
49
+ const canDeleteColumns = columnCount > minColumns && !disabled
50
+
51
+ return (
52
+ <div className={`flex items-center justify-center gap-1 rounded-lg border border-slate-200 px-4 py-2 shadow-sm backdrop-blur-lg ${disabled ? "bg-slate-100/80 opacity-60" : "bg-white/80"}`}>
53
+ {disabled && (
54
+ <div className="flex items-center gap-2 text-slate-500 text-xs">
55
+ <span>レスポンシブモード有効中:カラム数は自動調整されます</span>
56
+ </div>
57
+ )}
58
+
59
+ {!disabled && (
60
+ <>
61
+ {/* 最初の位置に挿入ボタン */}
62
+ <button
63
+ type="button"
64
+ onClick={() => canInsertColumns && onInsertColumn(0)}
65
+ disabled={!canInsertColumns || isResizing}
66
+ className={`flex h-8 w-8 items-center justify-center rounded border-2 border-dashed text-xs transition-colors ${
67
+ canInsertColumns && !isResizing ? "border-blue-300 text-blue-600 hover:border-blue-500 hover:bg-blue-50" : "cursor-not-allowed border-slate-200 text-slate-300"
68
+ }`}
69
+ title="この位置に新しいカラムを挿入">
70
+ <Plus className="h-4 w-4" />
71
+ </button>
72
+
73
+ {Array.from({ length: columnCount }, (_, index) => (
74
+ <div key={`column-${index}`} className="flex items-center gap-1">
75
+ {/* カラム表示と削除ボタン */}
76
+ <div className="relative flex h-8 w-12 items-center justify-center rounded border border-slate-300 bg-slate-100">
77
+ <span className="font-medium font-mono text-slate-700 text-xs">{index + 1}</span>
78
+ {canDeleteColumns && !isResizing && (
79
+ <button
80
+ type="button"
81
+ onClick={() => onDeleteColumn(index)}
82
+ className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full border border-red-300 bg-red-100 text-red-600 hover:bg-red-200"
83
+ title={`カラム ${index + 1} を削除`}>
84
+ <X className="h-2.5 w-2.5" />
85
+ </button>
86
+ )}
87
+ </div>
88
+
89
+ {/* カラムの後に挿入ボタン */}
90
+ <button
91
+ type="button"
92
+ onClick={() => canInsertColumns && onInsertColumn(index + 1)}
93
+ disabled={!canInsertColumns || isResizing}
94
+ className={`flex h-8 w-8 items-center justify-center rounded border-2 border-dashed text-xs transition-colors ${
95
+ canInsertColumns && !isResizing ? "border-blue-300 text-blue-600 hover:border-blue-500 hover:bg-blue-50" : "cursor-not-allowed border-slate-200 text-slate-300"
96
+ }`}
97
+ title="この位置に新しいカラムを挿入">
98
+ <Plus className="h-4 w-4" />
99
+ </button>
100
+ </div>
101
+ ))}
102
+ </>
103
+ )}
104
+ </div>
105
+ )
106
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * @fileoverview Defines debug components and helper functions for the drag-and-drop layout.
3
+ * These components provide visual feedback and logging for development and troubleshooting purposes.
4
+ *
5
+ * ドラッグ&ドロップレイアウト用のデバッグコンポーネントとヘルパー関数を定義します。
6
+ * これらのコンポーネントは、開発およびトラブルシューティング目的で、視覚的なフィードバックとロギングを提供します。
7
+ */
8
+ import type { CSSProperties, MouseEvent } from "react"
9
+ import type { CustomDragState, DebugConfig, DragOverPosition, DragState, MousePosition, OriginalPanelPosition, TouchDragState } from "./types"
10
+ import { Logger } from "./utils/simple-logger"
11
+
12
+ /**
13
+ * Props for the DebugPanel component.
14
+ *
15
+ * DebugPanel コンポーネントのプロパティ。
16
+ */
17
+ interface DebugPanelProps {
18
+ /** Debug configuration object. / デバッグ設定オブジェクト。 */
19
+ config: DebugConfig
20
+ /** The current state of the drag operation. / ドラッグ操作の現在の状態。 */
21
+ dragState: DragState
22
+ /** The position where the dragged panel is hovering over. / ドラッグ中のパネルがホバーしている位置。 */
23
+ dragOverPosition: DragOverPosition | null
24
+ /** The original position of the panel being dragged. / ドラッグされているパネルの元の位置。 */
25
+ originalPanelPosition: OriginalPanelPosition | null
26
+ /** The current mouse coordinates. / 現在のマウス座標。 */
27
+ mousePosition: MousePosition
28
+ /** The state related to touch-based dragging. / タッチベースのドラッグに関連する状態。 */
29
+ touchDragState: TouchDragState
30
+ /** The state for custom drag handling. / カスタムドラッグ処理の状態。 */
31
+ customDrag: CustomDragState
32
+ }
33
+
34
+ /**
35
+ * A panel that displays various debug information related to the drag-and-drop state.
36
+ *
37
+ * ドラッグ&ドロップの状態に関連する様々なデバッグ情報を表示するパネル。
38
+ * @param {DebugPanelProps} props - The props for the component.
39
+ * @returns {JSX.Element | null} The rendered debug panel or null if not enabled.
40
+ */
41
+ export const DebugPanel = ({ config, dragState, dragOverPosition, originalPanelPosition, mousePosition, touchDragState, customDrag }: DebugPanelProps) => {
42
+ // デバッグパネルが表示設定でない場合は何もレンダリングしない
43
+ if (!config.showDebugPanel) {
44
+ return null
45
+ }
46
+
47
+ return (
48
+ <div className="fixed top-4 right-4 z-40 max-w-xs rounded-lg bg-black/80 px-3 py-2 font-mono text-white text-xs leading-tight shadow-xl">
49
+ <div className="mb-2 font-bold text-sm text-yellow-300">🐛 Debug Panel</div>
50
+
51
+ <div className="space-y-1">
52
+ {/* ドラッグ状態 */}
53
+ <div className="text-blue-300">Drag State:</div>
54
+ <div className="pl-2">
55
+ <div>isDragging: {dragState.isDragging ? "✅" : "❌"}</div>
56
+ <div>draggedPanel: {dragState.draggedPanel || "none"}</div>
57
+ <div>dragOverColumn: {dragState.dragOverColumn ?? "null"}</div>
58
+ </div>
59
+
60
+ {/* 挿入位置 */}
61
+ <div className="mt-2 text-green-300">Insert Position:</div>
62
+ <div className="pl-2">
63
+ <div>column: {dragOverPosition?.columnIndex ?? "null"}</div>
64
+ <div>index: {dragOverPosition?.insertIndex ?? "null"}</div>
65
+ </div>
66
+
67
+ {/* 元の位置 */}
68
+ <div className="mt-2 text-purple-300">Original Position:</div>
69
+ <div className="pl-2">
70
+ <div>column: {originalPanelPosition?.columnIndex ?? "null"}</div>
71
+ <div>index: {originalPanelPosition?.panelIndex ?? "null"}</div>
72
+ </div>
73
+
74
+ {/* マウス位置 */}
75
+ <div className="mt-2 text-orange-300">Mouse Position:</div>
76
+ <div className="pl-2">
77
+ <div>
78
+ X: {mousePosition.x}, Y: {mousePosition.y}
79
+ </div>
80
+ <div>visible: {mousePosition.isVisible ? "👁️" : "🙈"}</div>
81
+ </div>
82
+
83
+ {/* タッチドラッグ状態 */}
84
+ <div className="mt-2 text-red-300">Touch Drag:</div>
85
+ <div className="pl-2">
86
+ <div>active: {touchDragState.isTouchDragging ? "✅" : "❌"}</div>
87
+ <div>element: {touchDragState.draggedElement ? "📱" : "none"}</div>
88
+ </div>
89
+
90
+ {/* カスタムドラッグ状態 */}
91
+ <div className="mt-2 text-cyan-300">Custom Drag:</div>
92
+ <div className="pl-2">
93
+ <div>active: {customDrag.isActive ? "✅" : "❌"}</div>
94
+ <div>panelId: {customDrag.panelId || "none"}</div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ )
99
+ }
100
+
101
+ /**
102
+ * Props for the MousePositionDisplay component.
103
+ *
104
+ * MousePositionDisplay コンポーネントのプロパティ。
105
+ */
106
+ interface MousePositionDisplayProps {
107
+ /** Debug configuration object. / デバッグ設定オブジェクト。 */
108
+ config: DebugConfig
109
+ /** The current mouse coordinates. / 現在のマウス座標。 */
110
+ mousePosition: MousePosition
111
+ /** The current state of the drag operation. / ドラッグ操作の現在の状態。 */
112
+ dragState: DragState
113
+ }
114
+
115
+ /**
116
+ * Displays the current mouse coordinates on the screen during a drag operation.
117
+ *
118
+ * ドラッグ操作中に画面上に現在のマウス座標を表示します。
119
+ * @param {MousePositionDisplayProps} props - The props for the component。
120
+ * @returns {JSX.Element | null} The rendered mouse position display or null if not enabled。
121
+ */
122
+ export const MousePositionDisplay = ({ config, mousePosition, dragState }: MousePositionDisplayProps) => {
123
+ // マウス位置表示が無効、またはドラッグ中でない場合は何も表示しない
124
+ if (!(config.showMousePosition && dragState.isDragging)) {
125
+ return null
126
+ }
127
+
128
+ return (
129
+ <div
130
+ className="pointer-events-none fixed z-50 rounded-md bg-yellow-500/90 px-2 py-1 font-mono text-black text-xs leading-tight shadow-lg"
131
+ style={
132
+ {
133
+ left: `${mousePosition.x + 10}px`,
134
+ top: `${mousePosition.y - 30}px`,
135
+ } as CSSProperties
136
+ }>
137
+ <div>
138
+ Mouse: {mousePosition.x}, {mousePosition.y}
139
+ </div>
140
+ <div className="text-[10px] opacity-80">Dragging: {dragState.draggedPanel || "none"}</div>
141
+ </div>
142
+ )
143
+ }
144
+
145
+ /**
146
+ * Props for the PanelSizeDisplay component。
147
+ *
148
+ * PanelSizeDisplay コンポーネントのプロパティ。
149
+ */
150
+ interface PanelSizeDisplayProps {
151
+ /** Debug configuration object. / デバッグ設定オブジェクト。 */
152
+ config: DebugConfig
153
+ panelId: string
154
+ /** The size and position information of the panel. / パネルのサイズと位置情報。 */
155
+ sizeInfo:
156
+ | {
157
+ width: number
158
+ height: number
159
+ x: number
160
+ y: number
161
+ minHeight: string
162
+ maxHeight: string
163
+ }
164
+ | undefined
165
+ /** Callback function for click events on the size info display. / サイズ情報表示のクリックイベントのコールバック関数。 */
166
+ onSizeInfoClick: (e: MouseEvent | TouchEvent) => void
167
+ }
168
+
169
+ /**
170
+ * Displays the size and position of a panel.
171
+ *
172
+ * パネルのサイズと位置を表示します。
173
+ * @param {PanelSizeDisplayProps} props - The props for the component。
174
+ * @returns {JSX.Element | null} The rendered panel size display or null if not enabled。
175
+ */
176
+ export const PanelSizeDisplay = ({ config, panelId, sizeInfo, onSizeInfoClick }: PanelSizeDisplayProps) => {
177
+ // パネルサイズ表示が無効、またはサイズ情報がない場合は何も表示しない
178
+ if (!(config.showPanelSizes && sizeInfo)) {
179
+ return null
180
+ }
181
+
182
+ return (
183
+ // biome-ignore lint/a11y/noStaticElementInteractions: クリックイベントを持つ静的要素。デバッグ目的のため、インタラクティブ性は限定的。
184
+ // biome-ignore lint/a11y/useKeyWithClickEvents: キーボード操作はデバッグ機能では優先度が低い。
185
+ <div
186
+ className="absolute top-1 right-24 z-10 cursor-default rounded-md bg-red-500/90 px-2 py-1 font-mono text-white text-xs leading-tight shadow-lg"
187
+ onClick={onSizeInfoClick}
188
+ onMouseDown={onSizeInfoClick}
189
+ onTouchStart={(e) => e instanceof TouchEvent && onSizeInfoClick(e)}
190
+ onDragStart={onSizeInfoClick}>
191
+ {/* パネル識別子 */}
192
+ <div className="text-[10px] opacity-80">ID:{panelId}</div>
193
+ {/* 幅と高さ */}
194
+ <div>
195
+ {sizeInfo.width}×{sizeInfo.height}
196
+ </div>
197
+ {/* X, Y座標 */}
198
+ <div>
199
+ X:{sizeInfo.x} Y:{sizeInfo.y}
200
+ </div>
201
+ {/* 最小・最大高さ */}
202
+ <div className="text-[10px] opacity-80">
203
+ min:{sizeInfo.minHeight} max:{sizeInfo.maxHeight}
204
+ </div>
205
+ </div>
206
+ )
207
+ }
208
+
209
+ /**
210
+ * A helper function for logging debug messages to the console based on the configured log level.
211
+ *
212
+ * 設定されたログレベルに基づいてデバッグメッセージをコンソールに出力するためのヘルパー関数。
213
+ * @param {DebugConfig} config - The debug configuration object。
214
+ * @param {1 | 2} level - The log level of the message (1 for basic, 2 for verbose)。
215
+ * @param {...any[]} args - The arguments to log to the console。
216
+ */
217
+ export const debugLog = (config: DebugConfig, level: 1 | 2, ...args: unknown[]) => {
218
+ // 設定されたログレベルがメッセージのレベル以上の場合にのみログ出力
219
+ if ((config.logLevel ?? 0) >= level) {
220
+ Logger.info(...args)
221
+ }
222
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * @fileoverview Defines the DebugControlPanel component.
3
+ * This component provides a UI for toggling various debug visualizations and setting the log level
4
+ * for the drag-and-drop layout system.
5
+ *
6
+ * DebugControlPanel コンポーネントを定義します。
7
+ * このコンポーネントは、ドラッグ&ドロップレイアウトシステムの様々なデバッグ表示を切り替えたり、
8
+ * ログレベルを設定したりするためのUIを提供します。
9
+ */
10
+ import type { DebugConfig } from "./types"
11
+
12
+ /**
13
+ * Props for the DebugControlPanel component.
14
+ *
15
+ * DebugControlPanel コンポーネントのプロパティ。
16
+ */
17
+ type DebugControlPanelProps = {
18
+ /** The current debug configuration object. / 現在のデバッグ設定オブジェクト。 */
19
+ config: DebugConfig
20
+ /** Callback function to update the debug configuration. / デバッグ設定を更新するためのコールバック関数。 */
21
+ onConfigChange: (newConfig: DebugConfig) => void
22
+ /** A flag indicating if the control panel is currently visible. / コントロールパネルが現在表示されているかどうかを示すフラグ。 */
23
+ isVisible: boolean
24
+ /** Callback function to toggle the visibility of the control panel. / コントロールパネルの表示を切り替えるためのコールバック関数。 */
25
+ onToggleVisibility: () => void
26
+ }
27
+
28
+ /**
29
+ * A floating panel with controls to manage debug settings.
30
+ * It can be toggled to show/hide and provides checkboxes and a slider
31
+ * to dynamically change the debug configuration.
32
+ *
33
+ * デバッグ設定を管理するためのコントロールを持つフローティングパネル。
34
+ * 表示・非表示を切り替えることができ、チェックボックスとスライダーで
35
+ * デバッグ設定を動的に変更できます。
36
+ * @param {DebugControlPanelProps} props - The props for the component.
37
+ * @returns {JSX.Element} The rendered debug control panel.
38
+ */
39
+ export const DebugControlPanel = ({ config, onConfigChange, isVisible, onToggleVisibility }: DebugControlPanelProps) => {
40
+ // パネルが非表示の場合、表示を切り替えるための小さなボタンのみをレンダリング
41
+ if (!isVisible) {
42
+ return (
43
+ <button type="button" onClick={onToggleVisibility} className="fixed right-4 bottom-4 z-50 rounded-lg bg-gray-800 p-2 text-white shadow-lg transition-colors hover:bg-gray-700" title="デバッグ設定を開く">
44
+ 🛠️
45
+ </button>
46
+ )
47
+ }
48
+
49
+ // パネルが表示されている場合、すべての設定コントロールをレンダリング
50
+ return (
51
+ <div className="fixed right-4 bottom-4 z-50 max-w-sm rounded-lg border border-gray-300 bg-white p-4 shadow-xl">
52
+ {/* パネルヘッダー */}
53
+ <div className="mb-3 flex items-center justify-between">
54
+ <h3 className="font-bold text-gray-800 text-sm">🛠️ デバッグ設定</h3>
55
+ <button type="button" onClick={onToggleVisibility} className="text-gray-500 text-lg hover:text-gray-700" title="閉じる">
56
+
57
+ </button>
58
+ </div>
59
+
60
+ <div className="space-y-2">
61
+ {/* 各種デバッグ表示のトグルスイッチ */}
62
+ <label className="flex items-center space-x-2 text-sm">
63
+ <input
64
+ type="checkbox"
65
+ checked={config.showDebugPanel ?? false}
66
+ onChange={(e) =>
67
+ onConfigChange({
68
+ ...config,
69
+ showDebugPanel: e.target.checked,
70
+ })
71
+ }
72
+ className="rounded"
73
+ />
74
+ <span>デバッグパネル表示</span>
75
+ </label>
76
+
77
+ <label className="flex items-center space-x-2 text-sm">
78
+ <input
79
+ type="checkbox"
80
+ checked={config.showMousePosition ?? false}
81
+ onChange={(e) =>
82
+ onConfigChange({
83
+ ...config,
84
+ showMousePosition: e.target.checked,
85
+ })
86
+ }
87
+ className="rounded"
88
+ />
89
+ <span>マウス位置表示</span>
90
+ </label>
91
+
92
+ <label className="flex items-center space-x-2 text-sm">
93
+ <input
94
+ type="checkbox"
95
+ checked={config.showPanelSizes ?? false}
96
+ onChange={(e) =>
97
+ onConfigChange({
98
+ ...config,
99
+ showPanelSizes: e.target.checked,
100
+ })
101
+ }
102
+ className="rounded"
103
+ />
104
+ <span>パネルサイズ情報</span>
105
+ </label>
106
+
107
+ <label className="flex items-center space-x-2 text-sm">
108
+ <input
109
+ type="checkbox"
110
+ checked={config.showPlaceholderInfo ?? false}
111
+ onChange={(e) =>
112
+ onConfigChange({
113
+ ...config,
114
+ showPlaceholderInfo: e.target.checked,
115
+ })
116
+ }
117
+ className="rounded"
118
+ />
119
+ <span>プレースホルダー詳細</span>
120
+ </label>
121
+
122
+ <label className="flex items-center space-x-2 text-sm">
123
+ <input
124
+ type="checkbox"
125
+ checked={config.showDropZoneOutlines ?? false}
126
+ onChange={(e) =>
127
+ onConfigChange({
128
+ ...config,
129
+ showDropZoneOutlines: e.target.checked,
130
+ })
131
+ }
132
+ className="rounded"
133
+ />
134
+ <span>ドロップゾーン境界</span>
135
+ </label>
136
+
137
+ {/* ログレベル設定スライダー */}
138
+ <div className="border-gray-200 border-t pt-2">
139
+ <label htmlFor="log-level-slider" className="mb-1 block text-sm">
140
+ ログレベル: {config.logLevel ?? 0}
141
+ </label>
142
+ <input
143
+ id="log-level-slider"
144
+ type="range"
145
+ min="0"
146
+ max="2"
147
+ value={config.logLevel ?? 0}
148
+ onChange={(e) =>
149
+ onConfigChange({
150
+ ...config,
151
+ logLevel: parseInt(e.target.value, 10) as 0 | 1 | 2,
152
+ })
153
+ }
154
+ className="w-full"
155
+ aria-label="ログレベル設定"
156
+ />
157
+ <div className="mt-1 flex justify-between text-gray-500 text-xs">
158
+ <span>無効</span>
159
+ <span>基本</span>
160
+ <span>詳細</span>
161
+ </div>
162
+ </div>
163
+
164
+ {/* 一括設定ボタン */}
165
+ <div className="space-y-2 pt-2">
166
+ <button
167
+ type="button"
168
+ onClick={() =>
169
+ onConfigChange({
170
+ showDebugPanel: true,
171
+ showMousePosition: true,
172
+ showPanelSizes: true,
173
+ showPlaceholderInfo: true,
174
+ showDropZoneOutlines: true,
175
+ logLevel: 2,
176
+ })
177
+ }
178
+ className="w-full rounded bg-blue-100 px-2 py-1 text-blue-700 text-sm transition-colors hover:bg-blue-200">
179
+ すべて有効
180
+ </button>
181
+ <button
182
+ type="button"
183
+ onClick={() =>
184
+ onConfigChange({
185
+ showDebugPanel: false,
186
+ showMousePosition: false,
187
+ showPanelSizes: false,
188
+ showPlaceholderInfo: false,
189
+ showDropZoneOutlines: false,
190
+ logLevel: 0,
191
+ })
192
+ }
193
+ className="w-full rounded bg-gray-100 px-2 py-1 text-gray-700 text-sm transition-colors hover:bg-gray-200">
194
+ すべて無効
195
+ </button>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ )
200
+ }