split-dock 1.0.0 → 1.0.1
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/README.md +0 -0
- package/package.json +1 -1
- package/src/dock.js +125 -76
- package/src/frame.js +118 -130
- package/src/handles/drag-drop-handler.js +145 -0
- package/src/handles/frame-adjust-handler.js +5 -17
- package/src/index.js +19 -65
- package/src/panel.js +10 -17
- package/src/handles/dock-drop-handler.js +0 -187
- package/src/handles/drop-handler.js +0 -348
- package/src/handles/tab-bar-drop-handler.js +0 -173
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { CONFIG } from '../index.js';
|
|
2
|
+
|
|
3
|
+
export class DragDropHandler {
|
|
4
|
+
constructor(dock) {
|
|
5
|
+
this.draggedPanel = null;
|
|
6
|
+
this.draggedFromDock = null;
|
|
7
|
+
this.currentHoverDock = null;
|
|
8
|
+
this.dragGhost = null;
|
|
9
|
+
|
|
10
|
+
// Reuse single handler across all docks
|
|
11
|
+
if (dock?.splitDock?.dragDropHandler) {
|
|
12
|
+
return dock.splitDock.dragDropHandler;
|
|
13
|
+
}
|
|
14
|
+
if (dock?.splitDock) {
|
|
15
|
+
dock.splitDock.dragDropHandler = this;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
startDragging(panel, dock) {
|
|
20
|
+
this.draggedPanel = panel;
|
|
21
|
+
this.draggedFromDock = dock;
|
|
22
|
+
|
|
23
|
+
this.dragGhost = document.createElement('div');
|
|
24
|
+
this.dragGhost.className = 'drag-ghost';
|
|
25
|
+
this.dragGhost.textContent = panel.title;
|
|
26
|
+
document.body.appendChild(this.dragGhost);
|
|
27
|
+
|
|
28
|
+
document.addEventListener('dragover', this.updateDragGhost);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
stopDragging() {
|
|
32
|
+
this.dragGhost?.remove();
|
|
33
|
+
this.dragGhost = null;
|
|
34
|
+
document.removeEventListener('dragover', this.updateDragGhost);
|
|
35
|
+
this.currentHoverDock?.clearDropIndicators();
|
|
36
|
+
this.draggedPanel = null;
|
|
37
|
+
this.draggedFromDock = null;
|
|
38
|
+
this.currentHoverDock = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
updateDragGhost = (e) => {
|
|
42
|
+
if (this.dragGhost) {
|
|
43
|
+
this.dragGhost.style.left = `${e.clientX + CONFIG.layout.dragGhostOffset}px`;
|
|
44
|
+
this.dragGhost.style.top = `${e.clientY + CONFIG.layout.dragGhostOffset}px`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
onDragOver(e, dock) {
|
|
49
|
+
if (!this.draggedPanel) return;
|
|
50
|
+
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
e.stopPropagation();
|
|
53
|
+
e.dataTransfer.dropEffect = 'move';
|
|
54
|
+
|
|
55
|
+
if (this.currentHoverDock && this.currentHoverDock !== dock) {
|
|
56
|
+
this.currentHoverDock.clearDropIndicators();
|
|
57
|
+
}
|
|
58
|
+
this.currentHoverDock = dock;
|
|
59
|
+
dock.clearDropIndicators();
|
|
60
|
+
|
|
61
|
+
const zone = this.getDropZone(e, dock);
|
|
62
|
+
if (zone?.type === 'navbar') {
|
|
63
|
+
dock.showNavbarDropIndicator(e);
|
|
64
|
+
} else if (zone) {
|
|
65
|
+
dock.element.classList.add(`drop-${zone.type}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onDragLeave(e, dock) {
|
|
70
|
+
const rect = dock.element.getBoundingClientRect();
|
|
71
|
+
if (e.clientX < rect.left || e.clientX >= rect.right ||
|
|
72
|
+
e.clientY < rect.top || e.clientY >= rect.bottom) {
|
|
73
|
+
dock.clearDropIndicators();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onDrop(e, dock) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
e.stopPropagation();
|
|
80
|
+
|
|
81
|
+
if (!this.draggedPanel || !this.draggedFromDock || !dock.splitDock) return;
|
|
82
|
+
|
|
83
|
+
dock.clearDropIndicators();
|
|
84
|
+
const zone = this.getDropZone(e, dock);
|
|
85
|
+
if (!zone) return;
|
|
86
|
+
|
|
87
|
+
if (zone.type === 'navbar') {
|
|
88
|
+
this.handleNavbarDrop(dock, this.draggedPanel, this.draggedFromDock);
|
|
89
|
+
} else if (zone.type === 'center') {
|
|
90
|
+
if (this.draggedFromDock !== dock) {
|
|
91
|
+
dock.acceptPanel(this.draggedPanel, this.draggedFromDock);
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
// Don't allow splitting if dragging the only panel from the same dock
|
|
95
|
+
if (this.draggedFromDock === dock && this.draggedFromDock.panels.length === 1) return;
|
|
96
|
+
dock.parentFrame?.splitWithPanel(dock, zone.type, this.draggedPanel, this.draggedFromDock);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getDropZone(e, dock) {
|
|
101
|
+
const navbarRect = dock.navbar.getBoundingClientRect();
|
|
102
|
+
if (e.clientX >= navbarRect.left && e.clientX <= navbarRect.right &&
|
|
103
|
+
e.clientY >= navbarRect.top && e.clientY <= navbarRect.bottom) {
|
|
104
|
+
return { type: 'navbar', element: dock.navbar };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const rect = dock.element.getBoundingClientRect();
|
|
108
|
+
const x = e.clientX - rect.left;
|
|
109
|
+
const y = e.clientY - rect.top;
|
|
110
|
+
const edgeSize = Math.min(rect.width, rect.height) * CONFIG.layout.dropZoneRatio;
|
|
111
|
+
|
|
112
|
+
if (y < edgeSize) return { type: 'top' };
|
|
113
|
+
if (y > rect.height - edgeSize) return { type: 'bottom' };
|
|
114
|
+
if (x < edgeSize) return { type: 'left' };
|
|
115
|
+
if (x > rect.width - edgeSize) return { type: 'right' };
|
|
116
|
+
|
|
117
|
+
return { type: 'center' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
handleNavbarDrop(dock, panel, fromDock) {
|
|
121
|
+
const targetInfo = dock.getDropTargetPanelInfo(event);
|
|
122
|
+
|
|
123
|
+
if (!targetInfo) {
|
|
124
|
+
if (fromDock !== dock) {
|
|
125
|
+
dock.acceptPanel(panel, fromDock);
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (fromDock === dock) {
|
|
131
|
+
dock.reorderPanel(panel, targetInfo.panel, targetInfo.position);
|
|
132
|
+
} else {
|
|
133
|
+
dock.acceptPanelAt(panel, fromDock, targetInfo.panel, targetInfo.position);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
destroy() {
|
|
138
|
+
this.dragGhost?.remove();
|
|
139
|
+
this.dragGhost = null;
|
|
140
|
+
document.removeEventListener('dragover', this.updateDragGhost);
|
|
141
|
+
this.draggedPanel = null;
|
|
142
|
+
this.draggedFromDock = null;
|
|
143
|
+
this.currentHoverDock = null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { CONFIG } from '../index.js';
|
|
2
|
+
|
|
1
3
|
export class FrameAdjustHandler {
|
|
2
4
|
constructor(frame) {
|
|
3
5
|
this.frame = frame;
|
|
@@ -6,41 +8,31 @@ export class FrameAdjustHandler {
|
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
destroy() {
|
|
9
|
-
// Remove all resize handles from DOM
|
|
10
11
|
this.handles.forEach(handle => {
|
|
11
|
-
if (handle && handle.parentNode)
|
|
12
|
-
|
|
13
|
-
}
|
|
12
|
+
if (handle && handle.parentNode) handle.remove();
|
|
13
|
+
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
// Clear arrays
|
|
17
16
|
this.handles = [];
|
|
18
17
|
this.handleListeners = [];
|
|
19
|
-
|
|
20
|
-
// Clear frame reference
|
|
21
18
|
this.frame = null;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
setupResizeHandles() {
|
|
25
|
-
// Clear existing handles
|
|
26
22
|
this.handles.forEach(handle => handle.remove());
|
|
27
23
|
this.handles = [];
|
|
28
24
|
|
|
29
|
-
// Only add resize handles if we have a split direction and multiple children
|
|
30
25
|
if (!this.frame.splitDirection || this.frame.children.length < 2) {
|
|
31
26
|
return;
|
|
32
27
|
}
|
|
33
28
|
|
|
34
|
-
// Insert resize handles between children
|
|
35
29
|
for (let i = 0; i < this.frame.children.length - 1; i++) {
|
|
36
30
|
const handle = document.createElement('div');
|
|
37
31
|
handle.className = `sd-resize-handle ${this.frame.splitDirection}`;
|
|
38
32
|
|
|
39
|
-
// Store indices for resize
|
|
40
33
|
handle.dataset.leftIndex = i;
|
|
41
34
|
handle.dataset.rightIndex = i + 1;
|
|
42
35
|
|
|
43
|
-
// Insert handle after the current child
|
|
44
36
|
const currentChild = this.frame.children[i].element;
|
|
45
37
|
currentChild.parentNode.insertBefore(handle, currentChild.nextSibling);
|
|
46
38
|
|
|
@@ -57,7 +49,7 @@ export class FrameAdjustHandler {
|
|
|
57
49
|
let rightChild = null;
|
|
58
50
|
let containerSize = 0;
|
|
59
51
|
|
|
60
|
-
const minSize =
|
|
52
|
+
const minSize = CONFIG.layout.minPaneSize;
|
|
61
53
|
|
|
62
54
|
const onMouseDown = (e) => {
|
|
63
55
|
e.preventDefault();
|
|
@@ -80,7 +72,6 @@ export class FrameAdjustHandler {
|
|
|
80
72
|
document.addEventListener('mousemove', onMouseMove);
|
|
81
73
|
document.addEventListener('mouseup', onMouseUp);
|
|
82
74
|
|
|
83
|
-
// Add visual feedback
|
|
84
75
|
handle.style.background = '#1e88e5';
|
|
85
76
|
document.body.style.cursor = this.frame.splitDirection === 'horizontal' ? 'col-resize' : 'row-resize';
|
|
86
77
|
};
|
|
@@ -94,9 +85,7 @@ export class FrameAdjustHandler {
|
|
|
94
85
|
const newLeftSize = leftStartSize + delta;
|
|
95
86
|
const newRightSize = rightStartSize - delta;
|
|
96
87
|
|
|
97
|
-
// Enforce minimum sizes
|
|
98
88
|
if (newLeftSize >= minSize && newRightSize >= minSize) {
|
|
99
|
-
// Calculate flex values relative to the entire container
|
|
100
89
|
const leftFlex = newLeftSize / containerSize;
|
|
101
90
|
const rightFlex = newRightSize / containerSize;
|
|
102
91
|
|
|
@@ -109,7 +98,6 @@ export class FrameAdjustHandler {
|
|
|
109
98
|
document.removeEventListener('mousemove', onMouseMove);
|
|
110
99
|
document.removeEventListener('mouseup', onMouseUp);
|
|
111
100
|
|
|
112
|
-
// Remove visual feedback
|
|
113
101
|
handle.style.background = '';
|
|
114
102
|
document.body.style.cursor = '';
|
|
115
103
|
};
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,23 @@ import { Panel } from './panel.js';
|
|
|
2
2
|
import { Dock } from './dock.js';
|
|
3
3
|
import { Frame } from './frame.js';
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// Configuration constants
|
|
6
|
+
export const CONFIG = {
|
|
7
|
+
// Layout constants
|
|
8
|
+
layout: {
|
|
9
|
+
minPaneSize: 100, // Minimum size in pixels for a pane
|
|
10
|
+
dropZoneRatio: 0.33, // 33% of edge is drop zone
|
|
11
|
+
dragGhostOffset: 10, // Offset in pixels for drag ghost
|
|
12
|
+
defaultFlexBasis: '1 1 0px' // Default flex basis for splits
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
// UI constants
|
|
16
|
+
ui: {
|
|
17
|
+
closeButtonSymbol: '×', // Close button character
|
|
18
|
+
defaultPanelTitle: 'New Panel'
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
6
22
|
(function loadCSS() {
|
|
7
23
|
const existingLink = document.querySelector('link[href*="style.css"]');
|
|
8
24
|
if (!existingLink) {
|
|
@@ -13,23 +29,17 @@ import { Frame } from './frame.js';
|
|
|
13
29
|
}
|
|
14
30
|
})();
|
|
15
31
|
|
|
16
|
-
// ID counter for generating unique IDs
|
|
17
32
|
let idCounter = 0;
|
|
18
33
|
export function generateId() {
|
|
19
34
|
return `sd-${Date.now()}-${++idCounter}`;
|
|
20
35
|
}
|
|
21
36
|
|
|
22
|
-
// Main framework
|
|
23
37
|
export class SplitDock {
|
|
24
38
|
constructor() {
|
|
25
39
|
this.rootFrames = [];
|
|
26
|
-
this.
|
|
27
|
-
this.draggedFromDock = null;
|
|
28
|
-
this.dragGhost = null;
|
|
29
|
-
this.currentHoverDock = null;
|
|
40
|
+
this.dragDropHandler = null;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
|
-
// Initialize framework by loading frame elements from HTML
|
|
33
43
|
initialize() {
|
|
34
44
|
const rootElement = document.getElementById('app');
|
|
35
45
|
if (rootElement && rootElement.classList.contains('sd-frame')) {
|
|
@@ -40,74 +50,18 @@ export class SplitDock {
|
|
|
40
50
|
return this;
|
|
41
51
|
}
|
|
42
52
|
|
|
43
|
-
startDragging(panel, dock) {
|
|
44
|
-
this.draggedPanel = panel;
|
|
45
|
-
this.draggedFromDock = dock;
|
|
46
|
-
|
|
47
|
-
this.dragGhost = document.createElement('div');
|
|
48
|
-
this.dragGhost.className = 'drag-ghost';
|
|
49
|
-
this.dragGhost.textContent = panel.title;
|
|
50
|
-
document.body.appendChild(this.dragGhost);
|
|
51
|
-
|
|
52
|
-
document.addEventListener('dragover', this.updateDragGhost);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
stopDragging() {
|
|
56
|
-
if (this.dragGhost) {
|
|
57
|
-
this.dragGhost.remove();
|
|
58
|
-
this.dragGhost = null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
document.removeEventListener('dragover', this.updateDragGhost);
|
|
62
|
-
|
|
63
|
-
// Clear drop indicators from the currently hovered dock only
|
|
64
|
-
if (this.currentHoverDock) {
|
|
65
|
-
this.currentHoverDock.element.classList.remove('drop-center', 'drop-top', 'drop-bottom', 'drop-left', 'drop-right', 'drop-navbar');
|
|
66
|
-
// Also clear tab reordering indicators
|
|
67
|
-
const titleElements = Array.from(this.currentHoverDock.navbar.querySelectorAll('.sd-panel-title'));
|
|
68
|
-
titleElements.forEach(el => el.classList.remove('drop-before', 'drop-after'));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.draggedPanel = null;
|
|
72
|
-
this.draggedFromDock = null;
|
|
73
|
-
this.currentHoverDock = null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
updateDragGhost = (e) => {
|
|
77
|
-
if (this.dragGhost) {
|
|
78
|
-
this.dragGhost.style.left = (e.clientX + 10) + 'px';
|
|
79
|
-
this.dragGhost.style.top = (e.clientY + 10) + 'px';
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
53
|
destroy() {
|
|
84
|
-
// Cleanup drag ghost if exists
|
|
85
|
-
if (this.dragGhost) {
|
|
86
|
-
this.dragGhost.remove();
|
|
87
|
-
this.dragGhost = null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Remove event listeners
|
|
91
|
-
document.removeEventListener('dragover', this.updateDragGhost);
|
|
92
|
-
|
|
93
|
-
// Destroy all root frames
|
|
94
54
|
this.rootFrames.forEach(frame => frame.destroy());
|
|
95
55
|
this.rootFrames = [];
|
|
96
|
-
|
|
97
|
-
// Clear references
|
|
98
|
-
this.draggedPanel = null;
|
|
99
|
-
this.draggedFromDock = null;
|
|
100
|
-
this.currentHoverDock = null;
|
|
56
|
+
this.dragDropHandler = null;
|
|
101
57
|
}
|
|
102
58
|
}
|
|
103
59
|
|
|
104
|
-
// Export classes to window object for backward compatibility
|
|
105
60
|
window.SplitDock = SplitDock;
|
|
106
61
|
window.SdPanel = Panel;
|
|
107
62
|
window.SdDock = Dock;
|
|
108
63
|
window.SdFrame = Frame;
|
|
109
64
|
|
|
110
|
-
// Auto-initialize when DOM is ready
|
|
111
65
|
document.addEventListener('DOMContentLoaded', () => {
|
|
112
66
|
const framework = new SplitDock();
|
|
113
67
|
framework.initialize();
|
package/src/panel.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { generateId } from './index.js';
|
|
1
|
+
import { generateId, CONFIG } from './index.js';
|
|
2
2
|
|
|
3
3
|
// Panel class - represents a single panel with title and content
|
|
4
4
|
export class Panel {
|
|
@@ -7,15 +7,13 @@ export class Panel {
|
|
|
7
7
|
this.dock = null;
|
|
8
8
|
this.eventListeners = [];
|
|
9
9
|
|
|
10
|
-
// Use existing elements from HTML or create new ones
|
|
11
10
|
if (titleElement && contentElement) {
|
|
12
11
|
this.titleElement = titleElement;
|
|
13
12
|
this.contentElement = contentElement;
|
|
14
13
|
this.title = titleElement.textContent.trim();
|
|
15
14
|
this.content = contentElement.innerHTML;
|
|
16
15
|
} else {
|
|
17
|
-
|
|
18
|
-
this.title = titleElement || 'New Panel';
|
|
16
|
+
this.title = titleElement || CONFIG.ui.defaultPanelTitle;
|
|
19
17
|
this.content = contentElement || '';
|
|
20
18
|
this.createElements();
|
|
21
19
|
}
|
|
@@ -28,7 +26,7 @@ export class Panel {
|
|
|
28
26
|
this.titleElement.className = 'sd-panel-title';
|
|
29
27
|
this.titleElement.innerHTML = `
|
|
30
28
|
<span class="sd-panel-title-text">${this.title}</span>
|
|
31
|
-
<span class="sd-panel-close"
|
|
29
|
+
<span class="sd-panel-close">${CONFIG.ui.closeButtonSymbol}</span>
|
|
32
30
|
`;
|
|
33
31
|
|
|
34
32
|
this.contentElement = document.createElement('div');
|
|
@@ -37,14 +35,13 @@ export class Panel {
|
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
setupElements() {
|
|
40
|
-
// Make title draggable and ensure it has close button
|
|
41
38
|
this.titleElement.setAttribute('draggable', 'true');
|
|
42
39
|
this.titleElement.draggable = true;
|
|
43
40
|
|
|
44
41
|
if (!this.titleElement.querySelector('.sd-panel-close')) {
|
|
45
42
|
const closeBtn = document.createElement('span');
|
|
46
43
|
closeBtn.className = 'sd-panel-close';
|
|
47
|
-
closeBtn.textContent =
|
|
44
|
+
closeBtn.textContent = CONFIG.ui.closeButtonSymbol;
|
|
48
45
|
this.titleElement.appendChild(closeBtn);
|
|
49
46
|
}
|
|
50
47
|
|
|
@@ -53,18 +50,15 @@ export class Panel {
|
|
|
53
50
|
|
|
54
51
|
setupEventListeners() {
|
|
55
52
|
const clickHandler = (e) => {
|
|
56
|
-
if (!e.target.classList.contains('sd-panel-close') && this.dock)
|
|
53
|
+
if (!e.target.classList.contains('sd-panel-close') && this.dock)
|
|
57
54
|
this.dock.setActivePanel(this);
|
|
58
|
-
}
|
|
59
55
|
};
|
|
60
56
|
this.titleElement.addEventListener('click', clickHandler);
|
|
61
57
|
this.eventListeners.push({ element: this.titleElement, event: 'click', handler: clickHandler });
|
|
62
58
|
|
|
63
59
|
const closeHandler = (e) => {
|
|
64
60
|
e.stopPropagation();
|
|
65
|
-
if (this.dock)
|
|
66
|
-
this.dock.removePanel(this);
|
|
67
|
-
}
|
|
61
|
+
if (this.dock) this.dock.removePanel(this);
|
|
68
62
|
};
|
|
69
63
|
const closeBtn = this.titleElement.querySelector('.sd-panel-close');
|
|
70
64
|
closeBtn.addEventListener('click', closeHandler);
|
|
@@ -80,8 +74,8 @@ export class Panel {
|
|
|
80
74
|
}
|
|
81
75
|
|
|
82
76
|
onDragStart(e) {
|
|
83
|
-
if (this.dock && this.dock.splitDock) {
|
|
84
|
-
this.dock.splitDock.startDragging(this, this.dock);
|
|
77
|
+
if (this.dock && this.dock.splitDock && this.dock.splitDock.dragDropHandler) {
|
|
78
|
+
this.dock.splitDock.dragDropHandler.startDragging(this, this.dock);
|
|
85
79
|
}
|
|
86
80
|
this.titleElement.classList.add('dragging');
|
|
87
81
|
e.dataTransfer.effectAllowed = 'move';
|
|
@@ -89,8 +83,8 @@ export class Panel {
|
|
|
89
83
|
}
|
|
90
84
|
|
|
91
85
|
onDragEnd(e) {
|
|
92
|
-
if (this.dock && this.dock.splitDock) {
|
|
93
|
-
this.dock.splitDock.stopDragging();
|
|
86
|
+
if (this.dock && this.dock.splitDock && this.dock.splitDock.dragDropHandler) {
|
|
87
|
+
this.dock.splitDock.dragDropHandler.stopDragging();
|
|
94
88
|
}
|
|
95
89
|
this.titleElement.classList.remove('dragging');
|
|
96
90
|
}
|
|
@@ -112,7 +106,6 @@ export class Panel {
|
|
|
112
106
|
}
|
|
113
107
|
|
|
114
108
|
destroy() {
|
|
115
|
-
// Remove all event listeners
|
|
116
109
|
this.eventListeners.forEach(({ element, event, handler }) => {
|
|
117
110
|
element.removeEventListener(event, handler);
|
|
118
111
|
});
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { Frame } from '../frame.js';
|
|
2
|
-
import { Dock } from '../dock.js';
|
|
3
|
-
|
|
4
|
-
export class DockDropHandler {
|
|
5
|
-
constructor(dock) {
|
|
6
|
-
this.dock = dock;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
onDragOver(e) {
|
|
10
|
-
if (!this.dock.splitDock?.draggedPanel) return;
|
|
11
|
-
|
|
12
|
-
// If over navbar, let the tab handler deal with it exclusively
|
|
13
|
-
if (this.isOverNavbar(e)) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
e.preventDefault();
|
|
18
|
-
e.stopPropagation();
|
|
19
|
-
e.dataTransfer.dropEffect = 'move';
|
|
20
|
-
|
|
21
|
-
// Clear previous dock's indicators if switching to a new dock
|
|
22
|
-
if (this.dock.splitDock.currentHoverDock && this.dock.splitDock.currentHoverDock !== this.dock) {
|
|
23
|
-
this.dock.splitDock.currentHoverDock.element.classList.remove('drop-center', 'drop-top', 'drop-bottom', 'drop-left', 'drop-right', 'drop-navbar');
|
|
24
|
-
}
|
|
25
|
-
this.dock.splitDock.currentHoverDock = this.dock;
|
|
26
|
-
|
|
27
|
-
// Remove all zone classes
|
|
28
|
-
this.dock.element.classList.remove('drop-center', 'drop-top', 'drop-bottom', 'drop-left', 'drop-right', 'drop-navbar');
|
|
29
|
-
|
|
30
|
-
const zone = this.getDropZoneFromEvent(e);
|
|
31
|
-
if (zone) {
|
|
32
|
-
this.dock.element.classList.add(`drop-${zone}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
onDragLeave(e) {
|
|
37
|
-
const rect = this.dock.element.getBoundingClientRect();
|
|
38
|
-
if (e.clientX < rect.left || e.clientX >= rect.right ||
|
|
39
|
-
e.clientY < rect.top || e.clientY >= rect.bottom) {
|
|
40
|
-
this.dock.element.classList.remove('drop-center', 'drop-top', 'drop-bottom', 'drop-left', 'drop-right', 'drop-navbar');
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
onDrop(e) {
|
|
45
|
-
// If over navbar, let the tab handler deal with it
|
|
46
|
-
if (this.isOverNavbar(e)) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
e.preventDefault();
|
|
51
|
-
e.stopPropagation();
|
|
52
|
-
|
|
53
|
-
if (!this.dock.splitDock) return;
|
|
54
|
-
|
|
55
|
-
const panel = this.dock.splitDock.draggedPanel;
|
|
56
|
-
const fromDock = this.dock.splitDock.draggedFromDock;
|
|
57
|
-
|
|
58
|
-
if (!panel || !fromDock) return;
|
|
59
|
-
|
|
60
|
-
this.dock.element.classList.remove('drop-center', 'drop-top', 'drop-bottom', 'drop-left', 'drop-right', 'drop-navbar');
|
|
61
|
-
|
|
62
|
-
const zone = this.getDropZoneFromEvent(e);
|
|
63
|
-
|
|
64
|
-
if (zone === 'center') {
|
|
65
|
-
if (fromDock !== this.dock) {
|
|
66
|
-
this.dock.acceptPanel(panel, fromDock);
|
|
67
|
-
}
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Don't allow splitting if dragging the only panel from the same dock
|
|
72
|
-
if (fromDock === this.dock && fromDock.panels.length === 1) return;
|
|
73
|
-
|
|
74
|
-
this.createSplit(zone, panel, fromDock);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
isOverNavbar(e) {
|
|
78
|
-
// Check if mouse is over ANY navbar, not just this dock's navbar
|
|
79
|
-
const allNavbars = document.querySelectorAll('.sd-dock-navbar');
|
|
80
|
-
for (const navbar of allNavbars) {
|
|
81
|
-
const rect = navbar.getBoundingClientRect();
|
|
82
|
-
if (e.clientX >= rect.left &&
|
|
83
|
-
e.clientX <= rect.right &&
|
|
84
|
-
e.clientY >= rect.top &&
|
|
85
|
-
e.clientY <= rect.bottom) {
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
getDropZoneFromEvent(e) {
|
|
93
|
-
const rect = this.dock.element.getBoundingClientRect();
|
|
94
|
-
const x = e.clientX - rect.left;
|
|
95
|
-
const y = e.clientY - rect.top;
|
|
96
|
-
return this.getDropZone(x, y, rect.width, rect.height);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
getDropZone(x, y, width, height) {
|
|
100
|
-
const topThreshold = height * 0.3;
|
|
101
|
-
const bottomThreshold = height * 0.7;
|
|
102
|
-
const leftThreshold = width * 0.3;
|
|
103
|
-
const rightThreshold = width * 0.7;
|
|
104
|
-
|
|
105
|
-
if (y < topThreshold) return 'top';
|
|
106
|
-
if (y > bottomThreshold) return 'bottom';
|
|
107
|
-
if (x < leftThreshold) return 'left';
|
|
108
|
-
if (x > rightThreshold) return 'right';
|
|
109
|
-
return 'center';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
createSplit(direction, panel, fromDock) {
|
|
113
|
-
if (!this.dock.splitDock) return;
|
|
114
|
-
if (fromDock === this.dock && fromDock.panels.length === 1) return;
|
|
115
|
-
if (!this.dock.parentFrame) return;
|
|
116
|
-
|
|
117
|
-
fromDock.removePanel(panel, true);
|
|
118
|
-
|
|
119
|
-
const newDock = new Dock(null, null);
|
|
120
|
-
newDock.addPanel(panel);
|
|
121
|
-
|
|
122
|
-
const splitDir = (direction === 'left' || direction === 'right') ? 'horizontal' : 'vertical';
|
|
123
|
-
const insertBefore = (direction === 'top' || direction === 'left');
|
|
124
|
-
|
|
125
|
-
this.applySplit(newDock, splitDir, insertBefore);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
applySplit(newDock, splitDir, insertBefore) {
|
|
129
|
-
const parent = this.dock.parentFrame;
|
|
130
|
-
const isOnlyChild = parent.children.length === 1 && parent.children[0] === this.dock;
|
|
131
|
-
const hasSameSplitDir = parent.splitDirection === splitDir;
|
|
132
|
-
|
|
133
|
-
if (isOnlyChild) {
|
|
134
|
-
this.splitIntoParent(parent, newDock, splitDir, insertBefore);
|
|
135
|
-
} else if (hasSameSplitDir) {
|
|
136
|
-
this.insertIntoParent(parent, newDock, insertBefore);
|
|
137
|
-
} else {
|
|
138
|
-
this.createNestedSplit(parent, newDock, splitDir, insertBefore);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
splitIntoParent(parent, newDock, splitDir, insertBefore) {
|
|
143
|
-
parent.splitDirection = splitDir;
|
|
144
|
-
parent.children = [];
|
|
145
|
-
|
|
146
|
-
if (insertBefore) {
|
|
147
|
-
parent.addChild(newDock);
|
|
148
|
-
parent.addChild(this.dock);
|
|
149
|
-
} else {
|
|
150
|
-
parent.addChild(this.dock);
|
|
151
|
-
parent.addChild(newDock);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
insertIntoParent(parent, newDock, insertBefore) {
|
|
156
|
-
const index = parent.children.indexOf(this.dock);
|
|
157
|
-
const insertIndex = insertBefore ? index : index + 1;
|
|
158
|
-
|
|
159
|
-
parent.children.splice(insertIndex, 0, newDock);
|
|
160
|
-
newDock.parentFrame = parent;
|
|
161
|
-
|
|
162
|
-
const refElement = insertBefore ? this.dock.element : this.dock.element.nextSibling;
|
|
163
|
-
parent.element.insertBefore(newDock.element, refElement);
|
|
164
|
-
parent.updateStyles();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
createNestedSplit(parent, newDock, splitDir, insertBefore) {
|
|
168
|
-
const newFrame = new Frame(null, this.dock.splitDock);
|
|
169
|
-
newFrame.splitDirection = splitDir;
|
|
170
|
-
|
|
171
|
-
const index = parent.children.indexOf(this.dock);
|
|
172
|
-
parent.children[index] = newFrame;
|
|
173
|
-
|
|
174
|
-
parent.element.insertBefore(newFrame.element, this.dock.element);
|
|
175
|
-
this.dock.element.remove();
|
|
176
|
-
|
|
177
|
-
if (insertBefore) {
|
|
178
|
-
newFrame.addChild(newDock);
|
|
179
|
-
newFrame.addChild(this.dock);
|
|
180
|
-
} else {
|
|
181
|
-
newFrame.addChild(this.dock);
|
|
182
|
-
newFrame.addChild(newDock);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
parent.updateStyles();
|
|
186
|
-
}
|
|
187
|
-
}
|