split-dock 1.0.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.
@@ -0,0 +1,348 @@
1
+ import { Frame } from '../frame.js';
2
+ import { Dock } from '../dock.js';
3
+
4
+ export class DropHandler {
5
+ constructor(dock) {
6
+ this.dock = dock;
7
+ }
8
+
9
+ onDragOver(e) {
10
+ if (!this.dock.splitDock?.draggedPanel) return;
11
+
12
+ e.preventDefault();
13
+ e.stopPropagation();
14
+ e.dataTransfer.dropEffect = 'move';
15
+
16
+ // Clear previous dock's indicators if switching to a new dock
17
+ if (this.dock.splitDock.currentHoverDock && this.dock.splitDock.currentHoverDock !== this.dock) {
18
+ this.clearAllIndicators(this.dock.splitDock.currentHoverDock);
19
+ }
20
+ this.dock.splitDock.currentHoverDock = this.dock;
21
+
22
+ // Clear all indicators first
23
+ this.clearAllIndicators(this.dock);
24
+
25
+ const zone = this.getDropZone(e);
26
+ if (!zone) return;
27
+
28
+ // Apply the appropriate indicator based on zone
29
+ if (zone.type === 'navbar') {
30
+ this.showNavbarIndicator(zone, e);
31
+ } else {
32
+ this.dock.element.classList.add(`drop-${zone.type}`);
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.clearAllIndicators(this.dock);
41
+ }
42
+ }
43
+
44
+ onDrop(e) {
45
+ e.preventDefault();
46
+ e.stopPropagation();
47
+
48
+ if (!this.dock.splitDock) return;
49
+
50
+ const panel = this.dock.splitDock.draggedPanel;
51
+ const fromDock = this.dock.splitDock.draggedFromDock;
52
+ if (!panel || !fromDock) return;
53
+
54
+ this.clearAllIndicators(this.dock);
55
+
56
+ const zone = this.getDropZone(e);
57
+ if (!zone) return;
58
+
59
+ if (zone.type === 'navbar') {
60
+ this.handleNavbarDrop(zone, panel, fromDock);
61
+ } else if (zone.type === 'center') {
62
+ if (fromDock !== this.dock) {
63
+ this.dock.acceptPanel(panel, fromDock);
64
+ }
65
+ } else {
66
+ // Don't allow splitting if dragging the only panel from the same dock
67
+ if (fromDock === this.dock && fromDock.panels.length === 1) return;
68
+ this.createSplit(zone.type, panel, fromDock);
69
+ }
70
+ }
71
+
72
+ getDropZone(e) {
73
+ // First check if over THIS dock's navbar
74
+ const navbarRect = this.dock.navbar.getBoundingClientRect();
75
+ if (e.clientX >= navbarRect.left && e.clientX <= navbarRect.right &&
76
+ e.clientY >= navbarRect.top && e.clientY <= navbarRect.bottom) {
77
+ return { type: 'navbar', element: this.dock.navbar };
78
+ }
79
+
80
+ // Check dock split zones
81
+ const rect = this.dock.element.getBoundingClientRect();
82
+ const x = e.clientX - rect.left;
83
+ const y = e.clientY - rect.top;
84
+ const edgeSize = Math.min(rect.width, rect.height) * 0.33; // 33% of the smaller dimension
85
+
86
+ if (y < edgeSize) return { type: 'top' };
87
+ if (y > rect.height - edgeSize) return { type: 'bottom' };
88
+ if (x < edgeSize) return { type: 'left' };
89
+ if (x > rect.width - edgeSize) return { type: 'right' };
90
+
91
+ return { type: 'center' };
92
+ }
93
+
94
+ clearAllIndicators(dock) {
95
+ // Clear dock zone indicators
96
+ dock.element.classList.remove('drop-center', 'drop-top', 'drop-bottom', 'drop-left', 'drop-right', 'drop-navbar');
97
+
98
+ // Clear tab indicators
99
+ const titleElements = Array.from(dock.navbar.querySelectorAll('.sd-panel-title'));
100
+ titleElements.forEach(el => el.classList.remove('drop-before', 'drop-after'));
101
+ }
102
+
103
+ showNavbarIndicator(zone, e) {
104
+ const titleElements = Array.from(this.dock.navbar.querySelectorAll('.sd-panel-title:not(.dragging)'));
105
+
106
+ if (titleElements.length === 0) return;
107
+
108
+ const firstTab = titleElements[0];
109
+ const lastTab = titleElements[titleElements.length - 1];
110
+ const firstRect = firstTab.getBoundingClientRect();
111
+ const lastRect = lastTab.getBoundingClientRect();
112
+
113
+ if (e.clientX < firstRect.left) {
114
+ firstTab.classList.add('drop-before');
115
+ return;
116
+ }
117
+
118
+ if (e.clientX > lastRect.right) {
119
+ lastTab.classList.add('drop-after');
120
+ return;
121
+ }
122
+
123
+ const hoveredTitle = titleElements.find(el => {
124
+ const rect = el.getBoundingClientRect();
125
+ return e.clientX >= rect.left && e.clientX <= rect.right;
126
+ });
127
+
128
+ if (hoveredTitle) {
129
+ const rect = hoveredTitle.getBoundingClientRect();
130
+ const midpoint = rect.left + rect.width / 2;
131
+
132
+ if (e.clientX < midpoint) {
133
+ hoveredTitle.classList.add('drop-before');
134
+ } else {
135
+ hoveredTitle.classList.add('drop-after');
136
+ }
137
+ }
138
+ }
139
+
140
+ handleNavbarDrop(zone, panel, fromDock) {
141
+ const targetInfo = this.getTargetPanelInfo(event);
142
+
143
+ if (!targetInfo) {
144
+ // No specific target, add to end if from different dock
145
+ if (fromDock !== this.dock) {
146
+ this.dock.acceptPanel(panel, fromDock);
147
+ }
148
+ return;
149
+ }
150
+
151
+ if (fromDock === this.dock) {
152
+ // Reordering within same dock
153
+ this.reorderPanel(panel, targetInfo.panel, targetInfo.position);
154
+ } else {
155
+ // Moving from different dock
156
+ this.movePanel(panel, fromDock, targetInfo.panel, targetInfo.position);
157
+ }
158
+ }
159
+
160
+ getTargetPanelInfo(e) {
161
+ const titleElements = Array.from(this.dock.navbar.querySelectorAll('.sd-panel-title:not(.dragging)'));
162
+ if (titleElements.length === 0) return null;
163
+
164
+ const firstTab = titleElements[0];
165
+ const lastTab = titleElements[titleElements.length - 1];
166
+ const firstRect = firstTab.getBoundingClientRect();
167
+ const lastRect = lastTab.getBoundingClientRect();
168
+
169
+ // Before first tab
170
+ if (e.clientX < firstRect.left) {
171
+ const targetPanel = this.dock.panels.find(p => p.titleElement === firstTab);
172
+ return targetPanel ? { panel: targetPanel, position: 'before' } : null;
173
+ }
174
+
175
+ // After last tab
176
+ if (e.clientX > lastRect.right) {
177
+ const targetPanel = this.dock.panels.find(p => p.titleElement === lastTab);
178
+ return targetPanel ? { panel: targetPanel, position: 'after' } : null;
179
+ }
180
+
181
+ // Over a specific tab
182
+ const hoveredTitle = titleElements.find(el => {
183
+ const rect = el.getBoundingClientRect();
184
+ return e.clientX >= rect.left && e.clientX <= rect.right;
185
+ });
186
+
187
+ if (hoveredTitle) {
188
+ const rect = hoveredTitle.getBoundingClientRect();
189
+ const midpoint = rect.left + rect.width / 2;
190
+ const targetPanel = this.dock.panels.find(p => p.titleElement === hoveredTitle);
191
+
192
+ if (targetPanel) {
193
+ return {
194
+ panel: targetPanel,
195
+ position: e.clientX < midpoint ? 'before' : 'after'
196
+ };
197
+ }
198
+ }
199
+
200
+ return null;
201
+ }
202
+
203
+ reorderPanel(panel, targetPanel, position) {
204
+ const panelIndex = this.dock.panels.indexOf(panel);
205
+ const targetIndex = this.dock.panels.indexOf(targetPanel);
206
+
207
+ if (panelIndex === -1 || targetIndex === -1) return;
208
+
209
+ // Remove panel from current position
210
+ this.dock.panels.splice(panelIndex, 1);
211
+
212
+ // Calculate new index
213
+ let newIndex = targetIndex;
214
+ if (position === 'after') {
215
+ newIndex++;
216
+ }
217
+ // Adjust if we removed panel before target
218
+ if (panelIndex < targetIndex) {
219
+ newIndex--;
220
+ }
221
+
222
+ // Insert at new position
223
+ this.dock.panels.splice(newIndex, 0, panel);
224
+
225
+ // Update DOM
226
+ if (position === 'before') {
227
+ this.dock.navbar.insertBefore(panel.titleElement, targetPanel.titleElement);
228
+ } else {
229
+ this.dock.navbar.insertBefore(panel.titleElement, targetPanel.titleElement.nextSibling);
230
+ }
231
+ }
232
+
233
+ movePanel(panel, fromDock, targetPanel, position) {
234
+ // Remove from source dock
235
+ fromDock.removePanel(panel, true);
236
+
237
+ // Add to target dock at specific position
238
+ const targetIndex = this.dock.panels.indexOf(targetPanel);
239
+ if (targetIndex === -1) {
240
+ this.dock.addPanel(panel);
241
+ return;
242
+ }
243
+
244
+ const insertIndex = position === 'before' ? targetIndex : targetIndex + 1;
245
+
246
+ panel.dock = this.dock;
247
+ this.dock.panels.splice(insertIndex, 0, panel);
248
+
249
+ // Update DOM
250
+ if (position === 'before') {
251
+ this.dock.navbar.insertBefore(panel.titleElement, targetPanel.titleElement);
252
+ } else {
253
+ this.dock.navbar.insertBefore(panel.titleElement, targetPanel.titleElement.nextSibling);
254
+ }
255
+ this.dock.content.appendChild(panel.contentElement);
256
+
257
+ this.dock.setActivePanel(panel);
258
+ }
259
+
260
+ createSplit(direction, panel, fromDock) {
261
+ if (!this.dock.parentFrame) return;
262
+
263
+ const currentIndex = this.dock.parentFrame.children.indexOf(this.dock);
264
+ if (currentIndex === -1) return;
265
+
266
+ const newDock = new Dock(null, this.dock.parentFrame);
267
+ newDock.addPanel(panel);
268
+ fromDock.removePanel(panel, true);
269
+
270
+ this.applySplit(direction, newDock, currentIndex);
271
+ }
272
+
273
+ applySplit(direction, newDock, currentIndex) {
274
+ const parentFrame = this.dock.parentFrame;
275
+ const needsVerticalSplit = direction === 'top' || direction === 'bottom';
276
+ const needsHorizontalSplit = direction === 'left' || direction === 'right';
277
+
278
+ if ((needsVerticalSplit && parentFrame.splitDirection === 'vertical') ||
279
+ (needsHorizontalSplit && parentFrame.splitDirection === 'horizontal')) {
280
+ this.splitIntoParent(direction, newDock, currentIndex);
281
+ } else if (parentFrame.splitDirection === null && parentFrame.children.length === 1) {
282
+ this.insertIntoParent(direction, newDock, currentIndex);
283
+ } else {
284
+ this.createNestedSplit(direction, newDock);
285
+ }
286
+ }
287
+
288
+ splitIntoParent(direction, newDock, currentIndex) {
289
+ const insertBefore = direction === 'top' || direction === 'left';
290
+ const insertIndex = insertBefore ? currentIndex : currentIndex + 1;
291
+
292
+ this.dock.parentFrame.children.splice(insertIndex, 0, newDock);
293
+
294
+ if (insertBefore) {
295
+ this.dock.parentFrame.element.insertBefore(newDock.element, this.dock.element);
296
+ } else {
297
+ this.dock.parentFrame.element.insertBefore(newDock.element, this.dock.element.nextSibling);
298
+ }
299
+
300
+ this.dock.element.style.flex = '1 1 0px';
301
+ newDock.element.style.flex = '1 1 0px';
302
+
303
+ this.dock.parentFrame.updateStyles();
304
+ }
305
+
306
+ insertIntoParent(direction, newDock, currentIndex) {
307
+ const needsVerticalSplit = direction === 'top' || direction === 'bottom';
308
+ // Default to vertical split direction
309
+ this.dock.parentFrame.splitDirection = needsVerticalSplit ? 'vertical' : 'horizontal';
310
+
311
+ this.splitIntoParent(direction, newDock, currentIndex);
312
+ }
313
+
314
+ createNestedSplit(direction, newDock) {
315
+ const parentFrame = this.dock.parentFrame;
316
+ const currentIndex = parentFrame.children.indexOf(this.dock);
317
+
318
+ const needsVerticalSplit = direction === 'top' || direction === 'bottom';
319
+ const newSplitDirection = needsVerticalSplit ? 'vertical' : 'horizontal';
320
+
321
+ const wrapperFrame = new Frame(null, this.dock.splitDock);
322
+ wrapperFrame.splitDirection = newSplitDirection;
323
+ wrapperFrame.parentFrame = parentFrame;
324
+
325
+ parentFrame.children[currentIndex] = wrapperFrame;
326
+ parentFrame.element.insertBefore(wrapperFrame.element, this.dock.element);
327
+
328
+ this.dock.element.remove();
329
+ this.dock.parentFrame = wrapperFrame;
330
+ newDock.parentFrame = wrapperFrame;
331
+
332
+ const insertBefore = direction === 'top' || direction === 'left';
333
+ if (insertBefore) {
334
+ wrapperFrame.children = [newDock, this.dock];
335
+ wrapperFrame.element.appendChild(newDock.element);
336
+ wrapperFrame.element.appendChild(this.dock.element);
337
+ } else {
338
+ wrapperFrame.children = [this.dock, newDock];
339
+ wrapperFrame.element.appendChild(this.dock.element);
340
+ wrapperFrame.element.appendChild(newDock.element);
341
+ }
342
+
343
+ this.dock.element.style.flex = '1 1 0px';
344
+ newDock.element.style.flex = '1 1 0px';
345
+
346
+ wrapperFrame.updateStyles();
347
+ }
348
+ }
@@ -0,0 +1,119 @@
1
+ export class FrameAdjustHandler {
2
+ constructor(frame) {
3
+ this.frame = frame;
4
+ this.handles = [];
5
+ this.handleListeners = [];
6
+ }
7
+
8
+ destroy() {
9
+ // Remove all resize handles from DOM
10
+ this.handles.forEach(handle => {
11
+ if (handle && handle.parentNode) {
12
+ handle.remove();
13
+ }
14
+ });
15
+
16
+ // Clear arrays
17
+ this.handles = [];
18
+ this.handleListeners = [];
19
+
20
+ // Clear frame reference
21
+ this.frame = null;
22
+ }
23
+
24
+ setupResizeHandles() {
25
+ // Clear existing handles
26
+ this.handles.forEach(handle => handle.remove());
27
+ this.handles = [];
28
+
29
+ // Only add resize handles if we have a split direction and multiple children
30
+ if (!this.frame.splitDirection || this.frame.children.length < 2) {
31
+ return;
32
+ }
33
+
34
+ // Insert resize handles between children
35
+ for (let i = 0; i < this.frame.children.length - 1; i++) {
36
+ const handle = document.createElement('div');
37
+ handle.className = `sd-resize-handle ${this.frame.splitDirection}`;
38
+
39
+ // Store indices for resize
40
+ handle.dataset.leftIndex = i;
41
+ handle.dataset.rightIndex = i + 1;
42
+
43
+ // Insert handle after the current child
44
+ const currentChild = this.frame.children[i].element;
45
+ currentChild.parentNode.insertBefore(handle, currentChild.nextSibling);
46
+
47
+ this.handles.push(handle);
48
+ this.setupResizeListener(handle, i, i + 1);
49
+ }
50
+ }
51
+
52
+ setupResizeListener(handle, leftIndex, rightIndex) {
53
+ let startPos = 0;
54
+ let leftStartSize = 0;
55
+ let rightStartSize = 0;
56
+ let leftChild = null;
57
+ let rightChild = null;
58
+ let containerSize = 0;
59
+
60
+ const minSize = 100; // Minimum size in pixels
61
+
62
+ const onMouseDown = (e) => {
63
+ e.preventDefault();
64
+
65
+ leftChild = this.frame.children[leftIndex];
66
+ rightChild = this.frame.children[rightIndex];
67
+
68
+ if (!leftChild || !rightChild) return;
69
+
70
+ startPos = this.frame.splitDirection === 'horizontal' ? e.clientX : e.clientY;
71
+
72
+ const leftRect = leftChild.element.getBoundingClientRect();
73
+ const rightRect = rightChild.element.getBoundingClientRect();
74
+ const containerRect = this.frame.element.getBoundingClientRect();
75
+
76
+ leftStartSize = this.frame.splitDirection === 'horizontal' ? leftRect.width : leftRect.height;
77
+ rightStartSize = this.frame.splitDirection === 'horizontal' ? rightRect.width : rightRect.height;
78
+ containerSize = this.frame.splitDirection === 'horizontal' ? containerRect.width : containerRect.height;
79
+
80
+ document.addEventListener('mousemove', onMouseMove);
81
+ document.addEventListener('mouseup', onMouseUp);
82
+
83
+ // Add visual feedback
84
+ handle.style.background = '#1e88e5';
85
+ document.body.style.cursor = this.frame.splitDirection === 'horizontal' ? 'col-resize' : 'row-resize';
86
+ };
87
+
88
+ const onMouseMove = (e) => {
89
+ if (!leftChild || !rightChild) return;
90
+
91
+ const currentPos = this.frame.splitDirection === 'horizontal' ? e.clientX : e.clientY;
92
+ const delta = currentPos - startPos;
93
+
94
+ const newLeftSize = leftStartSize + delta;
95
+ const newRightSize = rightStartSize - delta;
96
+
97
+ // Enforce minimum sizes
98
+ if (newLeftSize >= minSize && newRightSize >= minSize) {
99
+ // Calculate flex values relative to the entire container
100
+ const leftFlex = newLeftSize / containerSize;
101
+ const rightFlex = newRightSize / containerSize;
102
+
103
+ leftChild.element.style.flex = `${leftFlex} 1 0px`;
104
+ rightChild.element.style.flex = `${rightFlex} 1 0px`;
105
+ }
106
+ };
107
+
108
+ const onMouseUp = () => {
109
+ document.removeEventListener('mousemove', onMouseMove);
110
+ document.removeEventListener('mouseup', onMouseUp);
111
+
112
+ // Remove visual feedback
113
+ handle.style.background = '';
114
+ document.body.style.cursor = '';
115
+ };
116
+
117
+ handle.addEventListener('mousedown', onMouseDown);
118
+ }
119
+ }
@@ -0,0 +1,173 @@
1
+ export class TabBarDropHandler {
2
+ constructor(dock) {
3
+ this.dock = dock;
4
+ }
5
+
6
+ onDragOver(e) {
7
+ if (!this.dock.splitDock?.draggedPanel) return;
8
+
9
+ e.preventDefault();
10
+ e.stopPropagation();
11
+ e.dataTransfer.dropEffect = 'move';
12
+
13
+ // Clear tab indicators from previous dock if switching
14
+ if (this.dock.splitDock.currentHoverDock && this.dock.splitDock.currentHoverDock !== this.dock) {
15
+ const prevTitleElements = Array.from(this.dock.splitDock.currentHoverDock.navbar.querySelectorAll('.sd-panel-title'));
16
+ prevTitleElements.forEach(el => el.classList.remove('drop-before', 'drop-after'));
17
+ this.dock.splitDock.currentHoverDock.element.classList.remove('drop-center', 'drop-top', 'drop-bottom', 'drop-left', 'drop-right', 'drop-navbar');
18
+ }
19
+ this.dock.splitDock.currentHoverDock = this.dock;
20
+
21
+ // Clear any dock zone indicators since we're over the navbar
22
+ this.dock.element.classList.remove('drop-center', 'drop-top', 'drop-bottom', 'drop-left', 'drop-right', 'drop-navbar');
23
+
24
+ const titleElements = Array.from(this.dock.navbar.querySelectorAll('.sd-panel-title:not(.dragging)'));
25
+
26
+ // Remove all drop markers
27
+ titleElements.forEach(el => el.classList.remove('drop-before', 'drop-after'));
28
+
29
+ if (titleElements.length === 0) return;
30
+
31
+ const firstTab = titleElements[0];
32
+ const lastTab = titleElements[titleElements.length - 1];
33
+ const firstRect = firstTab.getBoundingClientRect();
34
+ const lastRect = lastTab.getBoundingClientRect();
35
+
36
+ if (e.clientX < firstRect.left) {
37
+ firstTab.classList.add('drop-before');
38
+ return;
39
+ }
40
+
41
+ if (e.clientX > lastRect.right) {
42
+ lastTab.classList.add('drop-after');
43
+ return;
44
+ }
45
+
46
+ const hoveredTitle = titleElements.find(el => {
47
+ const rect = el.getBoundingClientRect();
48
+ return e.clientX >= rect.left && e.clientX <= rect.right;
49
+ });
50
+
51
+ if (hoveredTitle) {
52
+ const rect = hoveredTitle.getBoundingClientRect();
53
+ const midpoint = rect.left + rect.width / 2;
54
+
55
+ if (e.clientX < midpoint) {
56
+ hoveredTitle.classList.add('drop-before');
57
+ } else {
58
+ hoveredTitle.classList.add('drop-after');
59
+ }
60
+ }
61
+ }
62
+
63
+ onDragLeave(e) {
64
+ const navbarRect = this.dock.navbar.getBoundingClientRect();
65
+ if (e.clientX < navbarRect.left || e.clientX >= navbarRect.right ||
66
+ e.clientY < navbarRect.top || e.clientY >= navbarRect.bottom) {
67
+ const titleElements = Array.from(this.dock.navbar.querySelectorAll('.sd-panel-title'));
68
+ titleElements.forEach(el => el.classList.remove('drop-before', 'drop-after'));
69
+ }
70
+ }
71
+
72
+ onDrop(e) {
73
+ if (!this.dock.splitDock?.draggedPanel) return;
74
+
75
+ e.preventDefault();
76
+ e.stopPropagation();
77
+
78
+ const panel = this.dock.splitDock.draggedPanel;
79
+ const fromDock = this.dock.splitDock.draggedFromDock;
80
+ const titleElements = Array.from(this.dock.navbar.querySelectorAll('.sd-panel-title:not(.dragging)'));
81
+
82
+ titleElements.forEach(el => el.classList.remove('drop-before', 'drop-after'));
83
+
84
+ if (titleElements.length === 0) {
85
+ if (fromDock !== this.dock) {
86
+ this.dock.acceptPanel(panel, fromDock);
87
+ }
88
+ return;
89
+ }
90
+
91
+ const { targetPanel, insertBefore } = this.getTargetPanelInfo(e, titleElements);
92
+
93
+ if (!targetPanel) {
94
+ if (fromDock !== this.dock) {
95
+ this.dock.acceptPanel(panel, fromDock);
96
+ }
97
+ return;
98
+ }
99
+
100
+ if (fromDock === this.dock) {
101
+ this.reorderPanel(panel, targetPanel, insertBefore);
102
+ } else {
103
+ this.movePanel(panel, fromDock, targetPanel, insertBefore);
104
+ }
105
+ }
106
+
107
+ getTargetPanelInfo(e, titleElements) {
108
+ const firstTab = titleElements[0];
109
+ const lastTab = titleElements[titleElements.length - 1];
110
+ const firstRect = firstTab.getBoundingClientRect();
111
+ const lastRect = lastTab.getBoundingClientRect();
112
+
113
+ if (e.clientX < firstRect.left) {
114
+ return {
115
+ targetPanel: this.dock.panels.find(p => p.titleElement === firstTab),
116
+ insertBefore: true
117
+ };
118
+ }
119
+
120
+ if (e.clientX > lastRect.right) {
121
+ return {
122
+ targetPanel: this.dock.panels.find(p => p.titleElement === lastTab),
123
+ insertBefore: false
124
+ };
125
+ }
126
+
127
+ const hoveredTitle = titleElements.find(el => {
128
+ const rect = el.getBoundingClientRect();
129
+ return e.clientX >= rect.left && e.clientX <= rect.right;
130
+ });
131
+
132
+ if (!hoveredTitle) {
133
+ return { targetPanel: null, insertBefore: true };
134
+ }
135
+
136
+ const targetPanel = this.dock.panels.find(p => p.titleElement === hoveredTitle);
137
+ const rect = hoveredTitle.getBoundingClientRect();
138
+ const midpoint = rect.left + rect.width / 2;
139
+
140
+ return { targetPanel, insertBefore: e.clientX < midpoint };
141
+ }
142
+
143
+ reorderPanel(panel, targetPanel, insertBefore) {
144
+ const oldIndex = this.dock.panels.indexOf(panel);
145
+ const newIndex = this.dock.panels.indexOf(targetPanel);
146
+
147
+ if (oldIndex === newIndex) return;
148
+
149
+ this.dock.panels.splice(oldIndex, 1);
150
+
151
+ const adjustedIndex = oldIndex < newIndex ? newIndex : newIndex + (insertBefore ? 0 : 1);
152
+ this.dock.panels.splice(insertBefore ? newIndex : adjustedIndex, 0, panel);
153
+
154
+ const refElement = insertBefore ? targetPanel.titleElement : targetPanel.titleElement.nextSibling;
155
+ this.dock.navbar.insertBefore(panel.titleElement, refElement);
156
+ }
157
+
158
+ movePanel(panel, fromDock, targetPanel, insertBefore) {
159
+ fromDock.removePanel(panel, true);
160
+
161
+ const targetIndex = this.dock.panels.indexOf(targetPanel);
162
+ const insertIndex = insertBefore ? targetIndex : targetIndex + 1;
163
+
164
+ panel.dock = this.dock;
165
+ this.dock.panels.splice(insertIndex, 0, panel);
166
+ this.dock.content.appendChild(panel.contentElement);
167
+
168
+ const refElement = insertBefore ? targetPanel.titleElement : targetPanel.titleElement.nextSibling;
169
+ this.dock.navbar.insertBefore(panel.titleElement, refElement);
170
+
171
+ this.dock.setActivePanel(panel);
172
+ }
173
+ }