@mintplayer/ng-bootstrap 20.4.0 → 20.6.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.
|
@@ -484,6 +484,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
484
484
|
this.dropJoystickTarget = null;
|
|
485
485
|
this.rootLayout = null;
|
|
486
486
|
this.floatingLayouts = [];
|
|
487
|
+
this.titles = {};
|
|
487
488
|
this.pendingTabDragMetrics = null;
|
|
488
489
|
this.resizeState = null;
|
|
489
490
|
this.dragState = null;
|
|
@@ -547,6 +548,9 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
547
548
|
if (!this.hasAttribute('role')) {
|
|
548
549
|
this.setAttribute('role', 'application');
|
|
549
550
|
}
|
|
551
|
+
// Tag the docked surface with a root path so it can act as
|
|
552
|
+
// a drop target when the main layout is empty.
|
|
553
|
+
this.dockedEl.dataset['path'] = this.formatPath({ type: 'docked', segments: [] });
|
|
550
554
|
this.render();
|
|
551
555
|
this.rootEl.addEventListener('dragover', this.onDragOver);
|
|
552
556
|
this.rootEl.addEventListener('drop', this.onDrop);
|
|
@@ -554,6 +558,21 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
554
558
|
this.dropJoystick.addEventListener('dragover', this.onDragOver);
|
|
555
559
|
this.dropJoystick.addEventListener('drop', this.onDrop);
|
|
556
560
|
this.dropJoystick.addEventListener('dragleave', this.onDragLeave);
|
|
561
|
+
// Strengthen zone tracking by reacting to dragenter/dragover directly on the buttons.
|
|
562
|
+
// This avoids relying solely on hit-testing each frame which can be jittery during HTML5 drag.
|
|
563
|
+
this.dropJoystickButtons.forEach((btn) => {
|
|
564
|
+
const handler = (e) => {
|
|
565
|
+
if (!this.dragState)
|
|
566
|
+
return;
|
|
567
|
+
const z = btn.dataset['zone'];
|
|
568
|
+
if (this.isDropZone(z)) {
|
|
569
|
+
this.updateDropJoystickActiveZone(z);
|
|
570
|
+
e.preventDefault();
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
btn.addEventListener('dragenter', handler);
|
|
574
|
+
btn.addEventListener('dragover', handler);
|
|
575
|
+
});
|
|
557
576
|
const win = this.windowRef;
|
|
558
577
|
win?.addEventListener('dragover', this.onGlobalDragOver);
|
|
559
578
|
win?.addEventListener('drag', this.onDrag);
|
|
@@ -584,12 +603,14 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
584
603
|
return {
|
|
585
604
|
root: this.cloneLayoutNode(this.rootLayout),
|
|
586
605
|
floating: this.cloneFloatingArray(this.floatingLayouts),
|
|
606
|
+
titles: { ...this.titles },
|
|
587
607
|
};
|
|
588
608
|
}
|
|
589
609
|
set layout(value) {
|
|
590
610
|
const snapshot = this.ensureSnapshot(value);
|
|
591
611
|
this.rootLayout = this.cloneLayoutNode(snapshot.root);
|
|
592
612
|
this.floatingLayouts = this.cloneFloatingArray(snapshot.floating);
|
|
613
|
+
this.titles = snapshot.titles ? { ...snapshot.titles } : {};
|
|
593
614
|
this.render();
|
|
594
615
|
}
|
|
595
616
|
get snapshot() {
|
|
@@ -629,10 +650,10 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
629
650
|
}
|
|
630
651
|
ensureSnapshot(value) {
|
|
631
652
|
if (!value) {
|
|
632
|
-
return { root: null, floating: [] };
|
|
653
|
+
return { root: null, floating: [], titles: {} };
|
|
633
654
|
}
|
|
634
655
|
if (value.kind) {
|
|
635
|
-
return { root: value, floating: [] };
|
|
656
|
+
return { root: value, floating: [], titles: {} };
|
|
636
657
|
}
|
|
637
658
|
const layout = value;
|
|
638
659
|
return {
|
|
@@ -640,6 +661,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
640
661
|
floating: Array.isArray(layout.floating)
|
|
641
662
|
? layout.floating.map((floating) => this.normalizeFloatingLayout(floating))
|
|
642
663
|
: [],
|
|
664
|
+
titles: layout.titles ? { ...layout.titles } : {},
|
|
643
665
|
};
|
|
644
666
|
}
|
|
645
667
|
render() {
|
|
@@ -860,6 +882,12 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
860
882
|
}
|
|
861
883
|
return;
|
|
862
884
|
}
|
|
885
|
+
// Reset sticky zone when moving to another stack while dragging the
|
|
886
|
+
// floating window so the side doesn't carry over.
|
|
887
|
+
if (this.dropJoystickTarget && this.dropJoystickTarget !== stack) {
|
|
888
|
+
delete this.dropJoystick.dataset['zone'];
|
|
889
|
+
this.updateDropJoystickActiveZone(null);
|
|
890
|
+
}
|
|
863
891
|
const zone = this.computeDropZone(stack, event, this.extractDropZoneFromEvent(event));
|
|
864
892
|
if (zone) {
|
|
865
893
|
state.dropTarget = { path, zone };
|
|
@@ -985,11 +1013,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
985
1013
|
if (!preferred) {
|
|
986
1014
|
return fallback;
|
|
987
1015
|
}
|
|
988
|
-
|
|
989
|
-
if (!owningStack) {
|
|
990
|
-
return preferred ?? fallback;
|
|
991
|
-
}
|
|
992
|
-
return owningStack.titles?.[preferred] ?? preferred ?? fallback;
|
|
1016
|
+
return this.titles[preferred] ?? preferred ?? fallback;
|
|
993
1017
|
}
|
|
994
1018
|
updateFloatingWindowTitle(index) {
|
|
995
1019
|
const floating = this.floatingLayouts[index];
|
|
@@ -1079,7 +1103,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1079
1103
|
button.classList.add('dock-tab');
|
|
1080
1104
|
button.dataset['pane'] = paneName;
|
|
1081
1105
|
button.id = tabId;
|
|
1082
|
-
button.textContent =
|
|
1106
|
+
button.textContent = this.titles[paneName] ?? paneName;
|
|
1083
1107
|
button.setAttribute('role', 'tab');
|
|
1084
1108
|
button.setAttribute('aria-controls', panelId);
|
|
1085
1109
|
if (paneName === activePane) {
|
|
@@ -1198,7 +1222,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1198
1222
|
this.handleFloatingResizeMove(event);
|
|
1199
1223
|
}
|
|
1200
1224
|
if (this.floatingDragState && event.pointerId === this.floatingDragState.pointerId) {
|
|
1201
|
-
console.warn('state', this.floatingDragState);
|
|
1202
1225
|
this.handleFloatingDragMove(event);
|
|
1203
1226
|
}
|
|
1204
1227
|
}
|
|
@@ -1241,6 +1264,8 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1241
1264
|
top,
|
|
1242
1265
|
width,
|
|
1243
1266
|
height,
|
|
1267
|
+
startClientX: event.clientX,
|
|
1268
|
+
startClientY: event.clientY,
|
|
1244
1269
|
};
|
|
1245
1270
|
}
|
|
1246
1271
|
clearPendingTabDragMetrics() {
|
|
@@ -1261,11 +1286,20 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1261
1286
|
this.shadowRoot?.appendChild(ghost);
|
|
1262
1287
|
// Use the ghost element as the drag image.
|
|
1263
1288
|
// The offset is set to where the user's cursor is on the original element.
|
|
1264
|
-
|
|
1289
|
+
const dragImgOffsetX = Number.isFinite(event.offsetX) ? event.offsetX : 0;
|
|
1290
|
+
const dragImgOffsetY = Number.isFinite(event.offsetY) ? event.offsetY : 0;
|
|
1291
|
+
event.dataTransfer.setDragImage(ghost, dragImgOffsetX, dragImgOffsetY);
|
|
1265
1292
|
// The ghost element is no longer needed after the drag image is set.
|
|
1266
1293
|
// We defer its removal to ensure the browser has captured it.
|
|
1267
1294
|
setTimeout(() => ghost.remove(), 0);
|
|
1268
1295
|
const { path: sourcePath, floatingIndex, pointerOffsetX, pointerOffsetY, } = this.preparePaneDragSource(path, pane, stackEl, event);
|
|
1296
|
+
// Capture header bounds for detecting when to convert to floating
|
|
1297
|
+
const headerEl = stackEl?.querySelector('.dock-stack__header') ?? null;
|
|
1298
|
+
const headerRect = headerEl ? headerEl.getBoundingClientRect() : null;
|
|
1299
|
+
const headerBounds = headerRect
|
|
1300
|
+
? { left: headerRect.left, top: headerRect.top, right: headerRect.right, bottom: headerRect.bottom }
|
|
1301
|
+
: null;
|
|
1302
|
+
const metrics = this.pendingTabDragMetrics;
|
|
1269
1303
|
this.dragState = {
|
|
1270
1304
|
pane,
|
|
1271
1305
|
sourcePath: this.clonePath(sourcePath),
|
|
@@ -1273,7 +1307,36 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1273
1307
|
pointerOffsetX,
|
|
1274
1308
|
pointerOffsetY,
|
|
1275
1309
|
dropHandled: false,
|
|
1310
|
+
sourceStackEl: stackEl,
|
|
1311
|
+
sourceHeaderBounds: headerBounds,
|
|
1312
|
+
startClientX: metrics && Number.isFinite(metrics.startClientX)
|
|
1313
|
+
? metrics.startClientX
|
|
1314
|
+
: Number.isFinite(event.clientX)
|
|
1315
|
+
? event.clientX
|
|
1316
|
+
: undefined,
|
|
1317
|
+
startClientY: metrics && Number.isFinite(metrics.startClientY)
|
|
1318
|
+
? metrics.startClientY
|
|
1319
|
+
: Number.isFinite(event.clientY)
|
|
1320
|
+
? event.clientY
|
|
1321
|
+
: undefined,
|
|
1276
1322
|
};
|
|
1323
|
+
// Seed last known pointer position from pointerdown metrics to avoid (0,0) glitches in Firefox
|
|
1324
|
+
if (this.dragState.startClientX !== undefined &&
|
|
1325
|
+
this.dragState.startClientY !== undefined &&
|
|
1326
|
+
Number.isFinite(this.dragState.startClientX) &&
|
|
1327
|
+
Number.isFinite(this.dragState.startClientY)) {
|
|
1328
|
+
this.lastDragPointerPosition = {
|
|
1329
|
+
x: this.dragState.startClientX,
|
|
1330
|
+
y: this.dragState.startClientY,
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
// Prefer the pointer offset relative to the dragged tab to avoid jumps on conversion
|
|
1334
|
+
if (Number.isFinite(event.offsetX)) {
|
|
1335
|
+
this.dragState.pointerOffsetX = event.offsetX;
|
|
1336
|
+
}
|
|
1337
|
+
if (Number.isFinite(event.offsetY)) {
|
|
1338
|
+
this.dragState.pointerOffsetY = event.offsetY;
|
|
1339
|
+
}
|
|
1277
1340
|
this.updateDraggedFloatingPosition(event);
|
|
1278
1341
|
this.startDragPointerTracking();
|
|
1279
1342
|
event.dataTransfer.effectAllowed = 'move';
|
|
@@ -1282,7 +1345,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1282
1345
|
preparePaneDragSource(path, pane, stackEl, event) {
|
|
1283
1346
|
const location = this.resolveStackLocation(path);
|
|
1284
1347
|
if (!location || !location.node.panes.includes(pane)) {
|
|
1285
|
-
this.clearPendingTabDragMetrics();
|
|
1286
1348
|
return {
|
|
1287
1349
|
path,
|
|
1288
1350
|
floatingIndex: null,
|
|
@@ -1291,7 +1353,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1291
1353
|
};
|
|
1292
1354
|
}
|
|
1293
1355
|
const metrics = this.pendingTabDragMetrics;
|
|
1294
|
-
this.pendingTabDragMetrics = null;
|
|
1295
1356
|
const domHasSibling = !!stackEl &&
|
|
1296
1357
|
Array.from(stackEl.querySelectorAll('.dock-tab')).some((button) => button.dataset['pane'] && button.dataset['pane'] !== pane);
|
|
1297
1358
|
const hasSiblingInStack = location.node.panes.some((existing) => existing !== pane);
|
|
@@ -1341,95 +1402,19 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1341
1402
|
pointerOffsetY: 0,
|
|
1342
1403
|
};
|
|
1343
1404
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
const
|
|
1347
|
-
const fallbackHeight = 240;
|
|
1348
|
-
const width = metrics && Number.isFinite(metrics.width) && metrics.width > 0
|
|
1349
|
-
? metrics.width
|
|
1350
|
-
: stackRect && Number.isFinite(stackRect.width)
|
|
1351
|
-
? stackRect.width
|
|
1352
|
-
: fallbackWidth;
|
|
1353
|
-
const height = metrics && Number.isFinite(metrics.height) && metrics.height > 0
|
|
1354
|
-
? metrics.height
|
|
1355
|
-
: stackRect && Number.isFinite(stackRect.height)
|
|
1356
|
-
? stackRect.height
|
|
1357
|
-
: fallbackHeight;
|
|
1358
|
-
const pointerOffsetX = metrics && Number.isFinite(metrics.pointerOffsetX)
|
|
1405
|
+
// Do not convert to floating yet; allow in-header reordering first.
|
|
1406
|
+
// We return placeholder values and will convert once the pointer leaves the tab header.
|
|
1407
|
+
const pointerOffsetXVal = metrics && Number.isFinite(metrics.pointerOffsetX)
|
|
1359
1408
|
? metrics.pointerOffsetX
|
|
1360
|
-
:
|
|
1361
|
-
|
|
1362
|
-
: width / 2;
|
|
1363
|
-
const pointerOffsetY = metrics && Number.isFinite(metrics.pointerOffsetY)
|
|
1409
|
+
: 0;
|
|
1410
|
+
const pointerOffsetYVal = metrics && Number.isFinite(metrics.pointerOffsetY)
|
|
1364
1411
|
? metrics.pointerOffsetY
|
|
1365
|
-
:
|
|
1366
|
-
? event.clientY - stackRect.top
|
|
1367
|
-
: height / 2;
|
|
1368
|
-
const pointerLeft = metrics && Number.isFinite(metrics.left)
|
|
1369
|
-
? metrics.left
|
|
1370
|
-
: Number.isFinite(event.clientX)
|
|
1371
|
-
? event.clientX - hostRect.left - pointerOffsetX
|
|
1372
|
-
: null;
|
|
1373
|
-
const pointerTop = metrics && Number.isFinite(metrics.top)
|
|
1374
|
-
? metrics.top
|
|
1375
|
-
: Number.isFinite(event.clientY)
|
|
1376
|
-
? event.clientY - hostRect.top - pointerOffsetY
|
|
1377
|
-
: null;
|
|
1378
|
-
const left = pointerLeft !== null
|
|
1379
|
-
? pointerLeft
|
|
1380
|
-
: stackRect
|
|
1381
|
-
? stackRect.left - hostRect.left
|
|
1382
|
-
: (hostRect.width - width) / 2;
|
|
1383
|
-
const top = pointerTop !== null
|
|
1384
|
-
? pointerTop
|
|
1385
|
-
: stackRect
|
|
1386
|
-
? stackRect.top - hostRect.top
|
|
1387
|
-
: (hostRect.height - height) / 2;
|
|
1388
|
-
// Defer the DOM modification to prevent the browser from cancelling the drag operation.
|
|
1389
|
-
setTimeout(() => {
|
|
1390
|
-
const paneTitle = location.node.titles?.[pane];
|
|
1391
|
-
this.removePaneFromLocation(location, pane);
|
|
1392
|
-
const floatingStack = {
|
|
1393
|
-
kind: 'stack',
|
|
1394
|
-
panes: [pane],
|
|
1395
|
-
activePane: pane,
|
|
1396
|
-
};
|
|
1397
|
-
if (paneTitle) {
|
|
1398
|
-
floatingStack.titles = { [pane]: paneTitle };
|
|
1399
|
-
}
|
|
1400
|
-
const floatingLayout = {
|
|
1401
|
-
bounds: {
|
|
1402
|
-
left,
|
|
1403
|
-
top,
|
|
1404
|
-
width,
|
|
1405
|
-
height,
|
|
1406
|
-
},
|
|
1407
|
-
root: floatingStack,
|
|
1408
|
-
activePane: pane,
|
|
1409
|
-
};
|
|
1410
|
-
if (paneTitle) {
|
|
1411
|
-
floatingLayout.titles = { [pane]: paneTitle };
|
|
1412
|
-
}
|
|
1413
|
-
this.floatingLayouts.push(floatingLayout);
|
|
1414
|
-
const floatingIndex = this.floatingLayouts.length - 1;
|
|
1415
|
-
// Defer rendering to avoid interrupting the drag-and-drop initialization in the browser.
|
|
1416
|
-
// Synchronously re-rendering can cause the browser to lose track of the drag operation.
|
|
1417
|
-
this.render();
|
|
1418
|
-
const wrapper = this.getFloatingWrapper(floatingIndex);
|
|
1419
|
-
if (wrapper) {
|
|
1420
|
-
this.promoteFloatingPane(floatingIndex, wrapper);
|
|
1421
|
-
}
|
|
1422
|
-
this.dispatchLayoutChanged();
|
|
1423
|
-
if (this.dragState) {
|
|
1424
|
-
this.dragState.sourcePath = { type: 'floating', index: floatingIndex, segments: [] };
|
|
1425
|
-
this.dragState.floatingIndex = floatingIndex;
|
|
1426
|
-
}
|
|
1427
|
-
}, 0);
|
|
1412
|
+
: 0;
|
|
1428
1413
|
return {
|
|
1429
|
-
path
|
|
1414
|
+
path,
|
|
1430
1415
|
floatingIndex: -1,
|
|
1431
|
-
pointerOffsetX,
|
|
1432
|
-
pointerOffsetY,
|
|
1416
|
+
pointerOffsetX: pointerOffsetXVal,
|
|
1417
|
+
pointerOffsetY: pointerOffsetYVal,
|
|
1433
1418
|
};
|
|
1434
1419
|
}
|
|
1435
1420
|
endPaneDrag() {
|
|
@@ -1437,6 +1422,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1437
1422
|
const state = this.dragState;
|
|
1438
1423
|
this.dragState = null;
|
|
1439
1424
|
this.hideDropIndicator();
|
|
1425
|
+
this.clearHeaderDragPlaceholder();
|
|
1440
1426
|
this.stopDragPointerTracking();
|
|
1441
1427
|
this.lastDragPointerPosition = null;
|
|
1442
1428
|
if (state && state.floatingIndex !== null && !state.dropHandled) {
|
|
@@ -1448,27 +1434,64 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1448
1434
|
return;
|
|
1449
1435
|
}
|
|
1450
1436
|
event.preventDefault();
|
|
1437
|
+
// Keep internal pointer tracking up-to-date.
|
|
1451
1438
|
this.updateDraggedFloatingPosition(event);
|
|
1452
1439
|
if (event.dataTransfer) {
|
|
1453
1440
|
event.dataTransfer.dropEffect = 'move';
|
|
1454
1441
|
}
|
|
1455
|
-
|
|
1442
|
+
// Some browsers intermittently report (0,0) for dragover coordinates.
|
|
1443
|
+
// Mirror the robust logic used in onDrop: prefer actual event coordinates
|
|
1444
|
+
// when valid, otherwise fall back to the last tracked pointer position.
|
|
1445
|
+
const pointFromEvent = Number.isFinite(event.clientX) && Number.isFinite(event.clientY)
|
|
1446
|
+
? { clientX: event.clientX, clientY: event.clientY }
|
|
1447
|
+
: null;
|
|
1448
|
+
const point = pointFromEvent ??
|
|
1449
|
+
(this.lastDragPointerPosition
|
|
1450
|
+
? { clientX: this.lastDragPointerPosition.x, clientY: this.lastDragPointerPosition.y }
|
|
1451
|
+
: null);
|
|
1452
|
+
const stack = this.findStackElement(event) ??
|
|
1453
|
+
(point ? this.findStackAtPoint(point.clientX, point.clientY) : null);
|
|
1456
1454
|
if (!stack) {
|
|
1457
|
-
this.
|
|
1455
|
+
if (this.dropJoystick.dataset['visible'] !== 'true') {
|
|
1456
|
+
this.hideDropIndicator();
|
|
1457
|
+
}
|
|
1458
1458
|
return;
|
|
1459
1459
|
}
|
|
1460
1460
|
const path = this.parsePath(stack.dataset['path']);
|
|
1461
|
-
|
|
1461
|
+
// While reordering within the same header, suppress the joystick/indicator entirely
|
|
1462
|
+
if (this.dragState &&
|
|
1463
|
+
this.dragState.floatingIndex !== null &&
|
|
1464
|
+
this.dragState.floatingIndex < 0 &&
|
|
1465
|
+
path &&
|
|
1466
|
+
this.pathsEqual(path, this.dragState.sourcePath)) {
|
|
1467
|
+
const px = (point ? point.clientX : event.clientX);
|
|
1468
|
+
const py = (point ? point.clientY : event.clientY);
|
|
1469
|
+
if (Number.isFinite(px) && Number.isFinite(py) && this.isPointerOverSourceHeader(px, py)) {
|
|
1470
|
+
// Drive live reorder using the unified path so we update instantly.
|
|
1471
|
+
this.updatePaneDragDropTargetFromPoint(px, py);
|
|
1472
|
+
this.hideDropIndicator();
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
// If the hovered stack changed, clear any sticky zone from the previous
|
|
1477
|
+
// target before computing the new zone.
|
|
1478
|
+
if (this.dropJoystickTarget && this.dropJoystickTarget !== stack) {
|
|
1479
|
+
delete this.dropJoystick.dataset['zone'];
|
|
1480
|
+
this.updateDropJoystickActiveZone(null);
|
|
1481
|
+
}
|
|
1482
|
+
const eventZoneHint = this.extractDropZoneFromEvent(event);
|
|
1483
|
+
const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
|
|
1484
|
+
const zone = this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint);
|
|
1462
1485
|
this.showDropIndicator(stack, zone);
|
|
1463
1486
|
}
|
|
1464
1487
|
updateDraggedFloatingPosition(event) {
|
|
1465
1488
|
if (!this.dragState) {
|
|
1466
1489
|
return;
|
|
1467
1490
|
}
|
|
1468
|
-
const { clientX, clientY
|
|
1491
|
+
const { clientX, clientY } = event;
|
|
1469
1492
|
const hasValidCoordinates = Number.isFinite(clientX) &&
|
|
1470
1493
|
Number.isFinite(clientY) &&
|
|
1471
|
-
!(clientX === 0 && clientY === 0
|
|
1494
|
+
!(clientX === 0 && clientY === 0);
|
|
1472
1495
|
if (hasValidCoordinates) {
|
|
1473
1496
|
this.lastDragPointerPosition = { x: clientX, y: clientY };
|
|
1474
1497
|
this.updateDraggedFloatingPositionFromPoint(clientX, clientY);
|
|
@@ -1492,7 +1515,31 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1492
1515
|
this.updateDraggedFloatingPosition(event);
|
|
1493
1516
|
}
|
|
1494
1517
|
onGlobalDragEnd() {
|
|
1495
|
-
|
|
1518
|
+
// Attempt to finalize a drop even if the drop event doesn't reach us (Firefox/edge cases)
|
|
1519
|
+
const state = this.dragState;
|
|
1520
|
+
const pos = this.lastDragPointerPosition;
|
|
1521
|
+
if (state && pos) {
|
|
1522
|
+
const stack = this.findStackAtPoint(pos.x, pos.y);
|
|
1523
|
+
const joystickVisible = this.dropJoystick.dataset['visible'] === 'true';
|
|
1524
|
+
const joystickPath = this.parsePath(this.dropJoystick.dataset['path']);
|
|
1525
|
+
const joystickTarget = this.dropJoystickTarget;
|
|
1526
|
+
const joystickTargetPath = joystickTarget ? this.parsePath(joystickTarget.dataset['path']) : null;
|
|
1527
|
+
const path = stack ? this.parsePath(stack.dataset['path']) : (joystickPath ?? joystickTargetPath);
|
|
1528
|
+
const joystickZone = this.dropJoystick.dataset['zone'];
|
|
1529
|
+
const zone = this.isDropZone(joystickZone)
|
|
1530
|
+
? joystickZone
|
|
1531
|
+
: (stack ? this.computeDropZone(stack, { clientX: pos.x, clientY: pos.y }, null) : null);
|
|
1532
|
+
if (path && this.isDropZone(zone)) {
|
|
1533
|
+
this.handleDrop(path, zone);
|
|
1534
|
+
this.hideDropIndicator();
|
|
1535
|
+
if (this.dragState) {
|
|
1536
|
+
this.dragState.dropHandled = true;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
else {
|
|
1541
|
+
this.hideDropIndicator();
|
|
1542
|
+
}
|
|
1496
1543
|
if (!this.dragState) {
|
|
1497
1544
|
this.clearPendingTabDragMetrics();
|
|
1498
1545
|
return;
|
|
@@ -1507,6 +1554,27 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1507
1554
|
if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
|
|
1508
1555
|
return;
|
|
1509
1556
|
}
|
|
1557
|
+
// Ignore obviously bogus coordinates sometimes seen during HTML5 drag
|
|
1558
|
+
if (clientX === 0 && clientY === 0) {
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
// If still dragging a tab inside its header, only convert to floating once we leave the header.
|
|
1562
|
+
if (this.dragState.floatingIndex !== null && this.dragState.floatingIndex < 0) {
|
|
1563
|
+
const b = this.dragState.sourceHeaderBounds;
|
|
1564
|
+
const sx = this.dragState.startClientX ?? clientX;
|
|
1565
|
+
const sy = this.dragState.startClientY ?? clientY;
|
|
1566
|
+
const dist = Math.hypot(clientX - sx, clientY - sy);
|
|
1567
|
+
const threshold = 8; // pixels to move before converting (tuned up)
|
|
1568
|
+
// Default to inside while bounds are unknown to avoid premature floating
|
|
1569
|
+
let insideHeader = true;
|
|
1570
|
+
const insideByBounds = b ? this.isPointWithinBounds(b, clientX, clientY) : true;
|
|
1571
|
+
const insideByHitTest = this.isPointerOverSourceHeader(clientX, clientY);
|
|
1572
|
+
insideHeader = insideByBounds || insideByHitTest;
|
|
1573
|
+
if (!insideHeader && dist > threshold) {
|
|
1574
|
+
// Convert to floating now using current pointer position
|
|
1575
|
+
this.convertPendingTabDragToFloating(clientX, clientY);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1510
1578
|
this.updatePaneDragDropTargetFromPoint(clientX, clientY);
|
|
1511
1579
|
const { floatingIndex, pointerOffsetX, pointerOffsetY } = this.dragState;
|
|
1512
1580
|
if (floatingIndex === null || floatingIndex < 0) {
|
|
@@ -1532,19 +1600,135 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1532
1600
|
return;
|
|
1533
1601
|
}
|
|
1534
1602
|
const stack = this.findStackAtPoint(clientX, clientY);
|
|
1535
|
-
|
|
1536
|
-
|
|
1603
|
+
const path = stack ? this.parsePath(stack.dataset['path']) : null;
|
|
1604
|
+
if (!stack || !path) {
|
|
1605
|
+
// While actively dragging, avoid hiding the indicator if it is visible.
|
|
1606
|
+
// Transient misses from hit-testing are common near the joystick.
|
|
1607
|
+
if (this.dropJoystick.dataset['visible'] !== 'true') {
|
|
1608
|
+
this.hideDropIndicator();
|
|
1609
|
+
}
|
|
1537
1610
|
return;
|
|
1538
1611
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1612
|
+
// If we moved to a different target stack, reset any sticky zone so
|
|
1613
|
+
// the new drop area doesn't inherit the previous side selection.
|
|
1614
|
+
if (this.dropJoystickTarget && this.dropJoystickTarget !== stack) {
|
|
1615
|
+
delete this.dropJoystick.dataset['zone'];
|
|
1616
|
+
this.updateDropJoystickActiveZone(null);
|
|
1617
|
+
}
|
|
1618
|
+
// Previous behavior hid the indicator and returned early here; instead,
|
|
1619
|
+
// allow the live-reorder branch below to handle in-header drags.
|
|
1620
|
+
// While reordering within the same header, update order live and suppress joystick/indicator
|
|
1621
|
+
if (this.dragState &&
|
|
1622
|
+
this.dragState.floatingIndex !== null &&
|
|
1623
|
+
this.dragState.floatingIndex < 0 &&
|
|
1624
|
+
path &&
|
|
1625
|
+
this.pathsEqual(path, this.dragState.sourcePath)) {
|
|
1626
|
+
const inHeaderByBounds = !!this.dragState.sourceHeaderBounds && this.isPointWithinBounds(this.dragState.sourceHeaderBounds, clientX, clientY);
|
|
1627
|
+
const inHeaderByHitTest = this.isPointerOverSourceHeader(clientX, clientY);
|
|
1628
|
+
if (inHeaderByBounds || inHeaderByHitTest) {
|
|
1629
|
+
const header = stack.querySelector('.dock-stack__header');
|
|
1630
|
+
if (header) {
|
|
1631
|
+
const idx = this.computeHeaderInsertIndex(header, clientX);
|
|
1632
|
+
if (this.dragState.liveReorderIndex !== idx) {
|
|
1633
|
+
this.ensureHeaderDragPlaceholder(header, this.dragState.pane);
|
|
1634
|
+
this.updateHeaderDragPlaceholderPosition(header, idx);
|
|
1635
|
+
const location = this.resolveStackLocation(path);
|
|
1636
|
+
if (location) {
|
|
1637
|
+
this.reorderPaneInLocationAtIndex(location, this.dragState.pane, idx);
|
|
1638
|
+
this.render();
|
|
1639
|
+
this.dispatchLayoutChanged();
|
|
1640
|
+
}
|
|
1641
|
+
this.dragState.liveReorderIndex = idx;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
this.hideDropIndicator();
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1543
1647
|
}
|
|
1544
1648
|
const zoneHint = this.findDropZoneByPoint(clientX, clientY);
|
|
1545
1649
|
const zone = this.computeDropZone(stack, { clientX, clientY }, zoneHint);
|
|
1546
1650
|
this.showDropIndicator(stack, zone);
|
|
1547
1651
|
}
|
|
1652
|
+
// Returns true when the pointer is currently over the source stack's header (tab strip)
|
|
1653
|
+
isPointerOverSourceHeader(clientX, clientY) {
|
|
1654
|
+
const state = this.dragState;
|
|
1655
|
+
if (!state) {
|
|
1656
|
+
return false;
|
|
1657
|
+
}
|
|
1658
|
+
const stackEl = state.sourceStackEl ?? null;
|
|
1659
|
+
const header = stackEl?.querySelector('.dock-stack__header');
|
|
1660
|
+
if (!header) {
|
|
1661
|
+
// Be conservative: if we cannot resolve the header, treat as inside
|
|
1662
|
+
return true;
|
|
1663
|
+
}
|
|
1664
|
+
const sr = this.shadowRoot;
|
|
1665
|
+
const elements = sr ? sr.elementsFromPoint(clientX, clientY) : [];
|
|
1666
|
+
for (const el of elements) {
|
|
1667
|
+
if (el instanceof HTMLElement && header.contains(el)) {
|
|
1668
|
+
return true;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
return false;
|
|
1672
|
+
}
|
|
1673
|
+
isPointWithinBounds(bounds, x, y) {
|
|
1674
|
+
return x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom;
|
|
1675
|
+
}
|
|
1676
|
+
// Ensure a placeholder tab exists during in-header drag and hide the real dragged tab visually
|
|
1677
|
+
ensureHeaderDragPlaceholder(header, pane) {
|
|
1678
|
+
if (this.dragState?.placeholderHeader === header && this.dragState.placeholderEl) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const dragged = Array.from(header.querySelectorAll('.dock-tab')).find((t) => t.dataset['pane'] === pane) ?? null;
|
|
1682
|
+
if (!dragged) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
// Create placeholder
|
|
1686
|
+
const placeholder = this.documentRef.createElement('button');
|
|
1687
|
+
placeholder.type = 'button';
|
|
1688
|
+
placeholder.classList.add('dock-tab');
|
|
1689
|
+
placeholder.dataset['placeholder'] = 'true';
|
|
1690
|
+
placeholder.textContent = dragged.textContent ?? '';
|
|
1691
|
+
// Hide the original dragged tab so it doesn't duplicate visually
|
|
1692
|
+
dragged.style.visibility = 'hidden';
|
|
1693
|
+
// Insert placeholder next to dragged initially
|
|
1694
|
+
header.insertBefore(placeholder, dragged.nextSibling);
|
|
1695
|
+
if (this.dragState) {
|
|
1696
|
+
this.dragState.placeholderHeader = header;
|
|
1697
|
+
this.dragState.placeholderEl = placeholder;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
// Move the placeholder to the computed target index within the header
|
|
1701
|
+
updateHeaderDragPlaceholderPosition(header, targetIndex) {
|
|
1702
|
+
const placeholder = this.dragState?.placeholderEl ?? null;
|
|
1703
|
+
if (!placeholder) {
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
const tabs = Array.from(header.querySelectorAll('.dock-tab'))
|
|
1707
|
+
.filter((t) => t !== placeholder);
|
|
1708
|
+
const clampedTarget = Math.max(0, Math.min(targetIndex, tabs.length));
|
|
1709
|
+
const ref = tabs[clampedTarget] ?? null;
|
|
1710
|
+
header.insertBefore(placeholder, ref);
|
|
1711
|
+
}
|
|
1712
|
+
// Remove placeholder and restore original tab visibility
|
|
1713
|
+
clearHeaderDragPlaceholder() {
|
|
1714
|
+
const ph = this.dragState?.placeholderEl ?? null;
|
|
1715
|
+
const header = this.dragState?.placeholderHeader ?? null;
|
|
1716
|
+
if (header) {
|
|
1717
|
+
const dragged = this.dragState?.pane
|
|
1718
|
+
? (Array.from(header.querySelectorAll('.dock-tab')).find((t) => t.dataset['pane'] === this.dragState?.pane) ?? null)
|
|
1719
|
+
: null;
|
|
1720
|
+
if (dragged) {
|
|
1721
|
+
dragged.style.visibility = '';
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
if (ph && ph.parentElement) {
|
|
1725
|
+
ph.parentElement.removeChild(ph);
|
|
1726
|
+
}
|
|
1727
|
+
if (this.dragState) {
|
|
1728
|
+
this.dragState.placeholderEl = null;
|
|
1729
|
+
this.dragState.placeholderHeader = null;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1548
1732
|
startDragPointerTracking() {
|
|
1549
1733
|
if (this.dragPointerTrackingActive) {
|
|
1550
1734
|
return;
|
|
@@ -1599,18 +1783,175 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1599
1783
|
this.updateDraggedFloatingPositionFromPoint(touch.clientX, touch.clientY);
|
|
1600
1784
|
}
|
|
1601
1785
|
onDragMouseUp() {
|
|
1786
|
+
// Prefer committing a drop from pointer-up since some browsers suppress drop events
|
|
1787
|
+
if (this.dragState) {
|
|
1788
|
+
const pos = this.lastDragPointerPosition;
|
|
1789
|
+
if (pos) {
|
|
1790
|
+
this.finalizeDropFromPoint(pos.x, pos.y);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
this.handleDragPointerUpCommon();
|
|
1794
|
+
}
|
|
1795
|
+
// Convert a currently in-header tab drag into a floating window
|
|
1796
|
+
convertPendingTabDragToFloating(clientX, clientY) {
|
|
1602
1797
|
if (!this.dragState) {
|
|
1603
|
-
this.stopDragPointerTracking();
|
|
1604
1798
|
return;
|
|
1605
1799
|
}
|
|
1606
|
-
this.
|
|
1800
|
+
const state = this.dragState;
|
|
1801
|
+
if (state.floatingIndex !== null && state.floatingIndex >= 0) {
|
|
1802
|
+
return; // already floating
|
|
1803
|
+
}
|
|
1804
|
+
// Clean up any placeholder before converting
|
|
1805
|
+
this.clearHeaderDragPlaceholder();
|
|
1806
|
+
const location = this.resolveStackLocation(state.sourcePath);
|
|
1807
|
+
if (!location) {
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
const pane = state.pane;
|
|
1811
|
+
const stackEl = state.sourceStackEl ?? null;
|
|
1812
|
+
const hostRect = this.getBoundingClientRect();
|
|
1813
|
+
const stackRect = stackEl?.getBoundingClientRect() ?? null;
|
|
1814
|
+
const metrics = this.pendingTabDragMetrics;
|
|
1815
|
+
const fallbackWidth = 320;
|
|
1816
|
+
const fallbackHeight = 240;
|
|
1817
|
+
const width = metrics && Number.isFinite(metrics.width) && metrics.width > 0
|
|
1818
|
+
? metrics.width
|
|
1819
|
+
: stackRect && Number.isFinite(stackRect.width)
|
|
1820
|
+
? stackRect.width
|
|
1821
|
+
: fallbackWidth;
|
|
1822
|
+
const height = metrics && Number.isFinite(metrics.height) && metrics.height > 0
|
|
1823
|
+
? metrics.height
|
|
1824
|
+
: stackRect && Number.isFinite(stackRect.height)
|
|
1825
|
+
? stackRect.height
|
|
1826
|
+
: fallbackHeight;
|
|
1827
|
+
const pointerOffsetX = Number.isFinite(this.dragState?.pointerOffsetX)
|
|
1828
|
+
? this.dragState.pointerOffsetX
|
|
1829
|
+
: metrics && Number.isFinite(metrics.pointerOffsetX)
|
|
1830
|
+
? metrics.pointerOffsetX
|
|
1831
|
+
: width / 2;
|
|
1832
|
+
const pointerOffsetY = Number.isFinite(this.dragState?.pointerOffsetY)
|
|
1833
|
+
? this.dragState.pointerOffsetY
|
|
1834
|
+
: metrics && Number.isFinite(metrics.pointerOffsetY)
|
|
1835
|
+
? metrics.pointerOffsetY
|
|
1836
|
+
: height / 2;
|
|
1837
|
+
const pointerLeft = Number.isFinite(clientX)
|
|
1838
|
+
? clientX - hostRect.left - pointerOffsetX
|
|
1839
|
+
: 0;
|
|
1840
|
+
const pointerTop = Number.isFinite(clientY)
|
|
1841
|
+
? clientY - hostRect.top - pointerOffsetY
|
|
1842
|
+
: 0;
|
|
1843
|
+
// Remove pane from its current stack and create a new floating entry
|
|
1844
|
+
this.removePaneFromLocation(location, pane);
|
|
1845
|
+
const floatingStack = {
|
|
1846
|
+
kind: 'stack',
|
|
1847
|
+
panes: [pane],
|
|
1848
|
+
activePane: pane,
|
|
1849
|
+
};
|
|
1850
|
+
const floatingLayout = {
|
|
1851
|
+
bounds: {
|
|
1852
|
+
left: pointerLeft,
|
|
1853
|
+
top: pointerTop,
|
|
1854
|
+
width,
|
|
1855
|
+
height,
|
|
1856
|
+
},
|
|
1857
|
+
root: floatingStack,
|
|
1858
|
+
activePane: pane,
|
|
1859
|
+
};
|
|
1860
|
+
this.floatingLayouts.push(floatingLayout);
|
|
1861
|
+
const newIndex = this.floatingLayouts.length - 1;
|
|
1862
|
+
this.render();
|
|
1863
|
+
const wrapper = this.getFloatingWrapper(newIndex);
|
|
1864
|
+
if (wrapper) {
|
|
1865
|
+
this.promoteFloatingPane(newIndex, wrapper);
|
|
1866
|
+
}
|
|
1867
|
+
// Update drag state so subsequent moves reposition the floating window
|
|
1868
|
+
state.sourcePath = { type: 'floating', index: newIndex, segments: [] };
|
|
1869
|
+
state.floatingIndex = newIndex;
|
|
1870
|
+
state.pointerOffsetX = pointerOffsetX;
|
|
1871
|
+
state.pointerOffsetY = pointerOffsetY;
|
|
1872
|
+
this.dispatchLayoutChanged();
|
|
1873
|
+
}
|
|
1874
|
+
// Compute the intended tab insert index within a header based on pointer X
|
|
1875
|
+
computeHeaderInsertIndex(header, clientX) {
|
|
1876
|
+
const tabs = Array.from(header.querySelectorAll('.dock-tab'))
|
|
1877
|
+
.filter((t) => t.dataset['placeholder'] !== 'true');
|
|
1878
|
+
if (tabs.length === 0) {
|
|
1879
|
+
return 0;
|
|
1880
|
+
}
|
|
1881
|
+
for (let i = 0; i < tabs.length; i += 1) {
|
|
1882
|
+
const rect = tabs[i].getBoundingClientRect();
|
|
1883
|
+
const mid = rect.left + rect.width / 2;
|
|
1884
|
+
if (clientX < mid) {
|
|
1885
|
+
return i;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
return tabs.length; // insert at end
|
|
1889
|
+
}
|
|
1890
|
+
reorderPaneInLocationAtIndex(location, pane, targetIndex) {
|
|
1891
|
+
const panes = location.node.panes;
|
|
1892
|
+
const currentIndex = panes.indexOf(pane);
|
|
1893
|
+
if (currentIndex === -1) {
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
const clampedTarget = Math.max(0, Math.min(targetIndex, panes.length - 1));
|
|
1897
|
+
if (clampedTarget === currentIndex) {
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
panes.splice(currentIndex, 1);
|
|
1901
|
+
panes.splice(clampedTarget, 0, pane);
|
|
1902
|
+
location.node.activePane = pane;
|
|
1903
|
+
if (location.context === 'floating') {
|
|
1904
|
+
const floating = this.floatingLayouts[location.index];
|
|
1905
|
+
if (floating) {
|
|
1906
|
+
floating.activePane = pane;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1607
1909
|
}
|
|
1608
1910
|
onDragTouchEnd() {
|
|
1911
|
+
this.handleDragPointerUpCommon();
|
|
1912
|
+
}
|
|
1913
|
+
// Commit a drop using current pointer coordinates and joystick state
|
|
1914
|
+
finalizeDropFromPoint(clientX, clientY) {
|
|
1609
1915
|
if (!this.dragState) {
|
|
1610
|
-
this.stopDragPointerTracking();
|
|
1611
1916
|
return;
|
|
1612
1917
|
}
|
|
1613
|
-
this.
|
|
1918
|
+
const stack = this.findStackAtPoint(clientX, clientY);
|
|
1919
|
+
const stackPath = stack ? this.parsePath(stack.dataset['path']) : null;
|
|
1920
|
+
const joystickVisible = this.dropJoystick.dataset['visible'] === 'true';
|
|
1921
|
+
const joystickStoredPath = this.parsePath(this.dropJoystick.dataset['path']);
|
|
1922
|
+
const joystickTarget = this.dropJoystickTarget;
|
|
1923
|
+
const joystickTargetPath = joystickTarget ? this.parsePath(joystickTarget.dataset['path']) : null;
|
|
1924
|
+
const path = (joystickVisible ? (joystickStoredPath ?? joystickTargetPath) : null) ?? stackPath;
|
|
1925
|
+
const joystickZone = this.dropJoystick.dataset['zone'];
|
|
1926
|
+
const zone = this.isDropZone(joystickZone)
|
|
1927
|
+
? joystickZone
|
|
1928
|
+
: (stack ? this.computeDropZone(stack, { clientX, clientY }, null) : null);
|
|
1929
|
+
// Same-header reorder case when no side zone is chosen
|
|
1930
|
+
if (this.dragState &&
|
|
1931
|
+
this.dragState.floatingIndex !== null &&
|
|
1932
|
+
this.dragState.floatingIndex < 0 &&
|
|
1933
|
+
stack &&
|
|
1934
|
+
path &&
|
|
1935
|
+
stackPath &&
|
|
1936
|
+
this.pathsEqual(stackPath, this.dragState.sourcePath) &&
|
|
1937
|
+
(!zone || zone === 'center')) {
|
|
1938
|
+
const header = stack.querySelector('.dock-stack__header');
|
|
1939
|
+
if (header) {
|
|
1940
|
+
const location = this.resolveStackLocation(path);
|
|
1941
|
+
if (location) {
|
|
1942
|
+
const idx = this.computeHeaderInsertIndex(header, clientX);
|
|
1943
|
+
this.reorderPaneInLocationAtIndex(location, this.dragState.pane, idx);
|
|
1944
|
+
this.render();
|
|
1945
|
+
this.dispatchLayoutChanged();
|
|
1946
|
+
this.dragState.dropHandled = true;
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
if (path && this.isDropZone(zone)) {
|
|
1952
|
+
this.handleDrop(path, zone);
|
|
1953
|
+
this.dragState.dropHandled = true;
|
|
1954
|
+
}
|
|
1614
1955
|
}
|
|
1615
1956
|
clearPendingDragEndTimeout() {
|
|
1616
1957
|
if (this.pendingDragEndTimeout !== null) {
|
|
@@ -1660,15 +2001,60 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1660
2001
|
: null);
|
|
1661
2002
|
const stack = this.findStackElement(event) ??
|
|
1662
2003
|
(point ? this.findStackAtPoint(point.clientX, point.clientY) : null);
|
|
1663
|
-
|
|
1664
|
-
|
|
2004
|
+
// Prefer joystick's stored target path when the joystick is visible (drop over buttons)
|
|
2005
|
+
const joystickVisible = this.dropJoystick.dataset['visible'] === 'true';
|
|
2006
|
+
const joystickPath = this.parsePath(this.dropJoystick.dataset['path']);
|
|
2007
|
+
const joystickTarget = this.dropJoystickTarget;
|
|
2008
|
+
const joystickTargetPath = joystickTarget ? this.parsePath(joystickTarget.dataset['path']) : null;
|
|
2009
|
+
let path = stack
|
|
2010
|
+
? this.parsePath(stack.dataset['path'])
|
|
2011
|
+
: (joystickPath ?? joystickTargetPath);
|
|
2012
|
+
if (!path && joystickVisible) {
|
|
2013
|
+
// As a last resort, target the main dock surface only when empty
|
|
2014
|
+
const dockPath = this.parsePath(this.dockedEl.dataset['path']);
|
|
2015
|
+
path = (!this.rootLayout ? dockPath : null);
|
|
2016
|
+
}
|
|
2017
|
+
// Defer same-header reorder decision until after zone resolution below
|
|
2018
|
+
// Prefer joystick's active zone if available, else infer from event/point
|
|
2019
|
+
const joystickZone = this.dropJoystick.dataset['zone'];
|
|
2020
|
+
const eventZoneHint = this.extractDropZoneFromEvent(event);
|
|
2021
|
+
const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
|
|
2022
|
+
const zone = this.isDropZone(joystickZone)
|
|
2023
|
+
? joystickZone
|
|
2024
|
+
: stack
|
|
2025
|
+
? this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint)
|
|
2026
|
+
: (this.isDropZone(pointZoneHint ?? eventZoneHint) ? (pointZoneHint ?? eventZoneHint) : null);
|
|
2027
|
+
// If still in same header and no side zone chosen, treat as in-header reorder
|
|
2028
|
+
if (this.dragState &&
|
|
2029
|
+
this.dragState.floatingIndex !== null &&
|
|
2030
|
+
this.dragState.floatingIndex < 0 &&
|
|
2031
|
+
stack &&
|
|
2032
|
+
path &&
|
|
2033
|
+
this.pathsEqual(path, this.dragState.sourcePath) &&
|
|
2034
|
+
(!zone || zone === 'center')) {
|
|
2035
|
+
const header = stack.querySelector('.dock-stack__header');
|
|
2036
|
+
if (header) {
|
|
2037
|
+
const x = (point ? point.clientX : event.clientX);
|
|
2038
|
+
if (Number.isFinite(x)) {
|
|
2039
|
+
const location = this.resolveStackLocation(path);
|
|
2040
|
+
if (location) {
|
|
2041
|
+
const idx = this.computeHeaderInsertIndex(header, x);
|
|
2042
|
+
this.reorderPaneInLocationAtIndex(location, this.dragState.pane, idx);
|
|
2043
|
+
this.render();
|
|
2044
|
+
this.dispatchLayoutChanged();
|
|
2045
|
+
this.dragState.dropHandled = true;
|
|
2046
|
+
this.endPaneDrag();
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
// If joystick is visible and both path and zone are resolved, force using joystick as authoritative
|
|
2053
|
+
if (joystickVisible && path && this.isDropZone(joystickZone)) {
|
|
2054
|
+
this.handleDrop(path, joystickZone);
|
|
1665
2055
|
this.endPaneDrag();
|
|
1666
2056
|
return;
|
|
1667
2057
|
}
|
|
1668
|
-
const path = this.parsePath(stack.dataset['path']);
|
|
1669
|
-
const eventZoneHint = this.extractDropZoneFromEvent(event);
|
|
1670
|
-
const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
|
|
1671
|
-
const zone = this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint);
|
|
1672
2058
|
if (!zone) {
|
|
1673
2059
|
this.hideDropIndicator();
|
|
1674
2060
|
this.endPaneDrag();
|
|
@@ -1679,6 +2065,21 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1679
2065
|
}
|
|
1680
2066
|
onDragLeave(event) {
|
|
1681
2067
|
const related = event.relatedTarget;
|
|
2068
|
+
// During active drags, browsers can emit spurious dragleave with null
|
|
2069
|
+
// relatedTarget while the pointer is still over the joystick/buttons.
|
|
2070
|
+
// Be conservative: if we can resolve a stack/joystick at the last known
|
|
2071
|
+
// pointer position, don’t hide (prevents flicker of active state).
|
|
2072
|
+
if (this.dragState) {
|
|
2073
|
+
const pos = (Number.isFinite(event.clientX) && Number.isFinite(event.clientY))
|
|
2074
|
+
? { x: event.clientX, y: event.clientY }
|
|
2075
|
+
: this.lastDragPointerPosition;
|
|
2076
|
+
if (pos) {
|
|
2077
|
+
const stackAtPoint = this.findStackAtPoint(pos.x, pos.y);
|
|
2078
|
+
if (stackAtPoint) {
|
|
2079
|
+
return; // still inside our drop area; ignore this dragleave
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
1682
2083
|
if (!related) {
|
|
1683
2084
|
this.hideDropIndicator();
|
|
1684
2085
|
return;
|
|
@@ -1696,6 +2097,28 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1696
2097
|
const { pane, sourcePath } = this.dragState;
|
|
1697
2098
|
const source = this.resolveStackLocation(sourcePath);
|
|
1698
2099
|
const target = this.resolveStackLocation(targetPath);
|
|
2100
|
+
// Special case: allow dropping onto an empty main dock area (no root).
|
|
2101
|
+
if (!target && targetPath.type === 'docked' && !this.rootLayout) {
|
|
2102
|
+
if (!source) {
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
const stackEmptied = this.removePaneFromLocation(source, pane, true);
|
|
2106
|
+
const newRoot = {
|
|
2107
|
+
kind: 'stack',
|
|
2108
|
+
panes: [pane],
|
|
2109
|
+
activePane: pane,
|
|
2110
|
+
};
|
|
2111
|
+
this.rootLayout = newRoot;
|
|
2112
|
+
if (stackEmptied) {
|
|
2113
|
+
this.cleanupLocation(source);
|
|
2114
|
+
}
|
|
2115
|
+
this.render();
|
|
2116
|
+
this.dispatchLayoutChanged();
|
|
2117
|
+
if (this.dragState) {
|
|
2118
|
+
this.dragState.dropHandled = true;
|
|
2119
|
+
}
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
1699
2122
|
if (!source || !target) {
|
|
1700
2123
|
return;
|
|
1701
2124
|
}
|
|
@@ -1711,15 +2134,10 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1711
2134
|
}
|
|
1712
2135
|
return;
|
|
1713
2136
|
}
|
|
1714
|
-
const paneTitle = source.node.titles?.[pane];
|
|
1715
2137
|
const stackEmptied = this.removePaneFromLocation(source, pane, true);
|
|
1716
2138
|
if (zone === 'center') {
|
|
1717
2139
|
this.addPaneToLocation(target, pane);
|
|
1718
2140
|
this.setActivePaneForLocation(target, pane);
|
|
1719
|
-
if (paneTitle) {
|
|
1720
|
-
target.node.titles = target.node.titles ? { ...target.node.titles } : {};
|
|
1721
|
-
target.node.titles[pane] = paneTitle;
|
|
1722
|
-
}
|
|
1723
2141
|
if (stackEmptied) {
|
|
1724
2142
|
this.cleanupLocation(source);
|
|
1725
2143
|
}
|
|
@@ -1735,9 +2153,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1735
2153
|
panes: [pane],
|
|
1736
2154
|
activePane: pane,
|
|
1737
2155
|
};
|
|
1738
|
-
if (paneTitle) {
|
|
1739
|
-
newStack.titles = { [pane]: paneTitle };
|
|
1740
|
-
}
|
|
1741
2156
|
if (target.context === 'docked') {
|
|
1742
2157
|
this.rootLayout = this.dockNodeBeside(this.rootLayout, target.node, newStack, zone);
|
|
1743
2158
|
}
|
|
@@ -1769,6 +2184,15 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1769
2184
|
return false;
|
|
1770
2185
|
}
|
|
1771
2186
|
const target = this.resolveStackLocation(targetPath);
|
|
2187
|
+
// Allow dropping an entire floating stack onto an empty main dock area
|
|
2188
|
+
// (no existing root).
|
|
2189
|
+
if (!target && targetPath.type === 'docked' && !this.rootLayout) {
|
|
2190
|
+
this.rootLayout = this.cloneLayoutNode(source.root);
|
|
2191
|
+
this.removeFloatingAt(sourceIndex);
|
|
2192
|
+
this.render();
|
|
2193
|
+
this.dispatchLayoutChanged();
|
|
2194
|
+
return true;
|
|
2195
|
+
}
|
|
1772
2196
|
if (!target) {
|
|
1773
2197
|
return false;
|
|
1774
2198
|
}
|
|
@@ -1776,17 +2200,12 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1776
2200
|
return false;
|
|
1777
2201
|
}
|
|
1778
2202
|
if (zone === 'center') {
|
|
1779
|
-
const
|
|
2203
|
+
const panes = this.collectPaneNames(source.root);
|
|
1780
2204
|
if (panes.length === 0) {
|
|
1781
2205
|
return false;
|
|
1782
2206
|
}
|
|
1783
2207
|
panes.forEach((pane) => {
|
|
1784
2208
|
this.addPaneToLocation(target, pane);
|
|
1785
|
-
const title = titles[pane];
|
|
1786
|
-
if (title) {
|
|
1787
|
-
target.node.titles = target.node.titles ? { ...target.node.titles } : {};
|
|
1788
|
-
target.node.titles[pane] = title;
|
|
1789
|
-
}
|
|
1790
2209
|
});
|
|
1791
2210
|
const activePane = source.activePane && panes.includes(source.activePane)
|
|
1792
2211
|
? source.activePane
|
|
@@ -1873,6 +2292,8 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1873
2292
|
return null;
|
|
1874
2293
|
}
|
|
1875
2294
|
computeDropZone(stack, point, zoneHint) {
|
|
2295
|
+
// Do not force a zone: even when the main area is empty we only
|
|
2296
|
+
// activate a zone when the pointer is actually over a joystick button.
|
|
1876
2297
|
const hintedZone = this.isDropZone(zoneHint) ? zoneHint : null;
|
|
1877
2298
|
if (hintedZone) {
|
|
1878
2299
|
this.updateDropJoystickActiveZone(hintedZone);
|
|
@@ -1888,6 +2309,14 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1888
2309
|
return pointZone;
|
|
1889
2310
|
}
|
|
1890
2311
|
if (this.dropJoystickTarget === stack) {
|
|
2312
|
+
// Be sticky while hovering the joystick: if we recently had a zone
|
|
2313
|
+
// selected, prefer keeping it instead of briefly clearing to null
|
|
2314
|
+
// when the browser reports transient coordinates/targets during drag.
|
|
2315
|
+
const sticky = this.dropJoystick.dataset['zone'];
|
|
2316
|
+
if (this.isDropZone(sticky)) {
|
|
2317
|
+
this.updateDropJoystickActiveZone(sticky);
|
|
2318
|
+
return sticky;
|
|
2319
|
+
}
|
|
1891
2320
|
this.updateDropJoystickActiveZone(null);
|
|
1892
2321
|
}
|
|
1893
2322
|
return null;
|
|
@@ -1898,14 +2327,9 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1898
2327
|
}
|
|
1899
2328
|
if (typeof event.composedPath === 'function') {
|
|
1900
2329
|
const path = event.composedPath();
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
const zone = target.dataset?.['zone'];
|
|
1905
|
-
if (this.isDropZone(zone)) {
|
|
1906
|
-
return zone;
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
2330
|
+
const zone = this.findDropZoneInTargets(path);
|
|
2331
|
+
if (zone) {
|
|
2332
|
+
return zone;
|
|
1909
2333
|
}
|
|
1910
2334
|
}
|
|
1911
2335
|
if ('clientX' in event && 'clientY' in event) {
|
|
@@ -1917,6 +2341,25 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1917
2341
|
}
|
|
1918
2342
|
return null;
|
|
1919
2343
|
}
|
|
2344
|
+
handleDragPointerUpCommon() {
|
|
2345
|
+
if (!this.dragState) {
|
|
2346
|
+
this.stopDragPointerTracking();
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
this.scheduleDeferredDragEnd();
|
|
2350
|
+
}
|
|
2351
|
+
findDropZoneInTargets(targets) {
|
|
2352
|
+
for (const target of targets) {
|
|
2353
|
+
if (target instanceof HTMLElement &&
|
|
2354
|
+
target.classList.contains('dock-drop-joystick__button')) {
|
|
2355
|
+
const zone = target.dataset?.['zone'];
|
|
2356
|
+
if (this.isDropZone(zone)) {
|
|
2357
|
+
return zone;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
return null;
|
|
2362
|
+
}
|
|
1920
2363
|
findDropZoneByPoint(clientX, clientY) {
|
|
1921
2364
|
if (!this.dropJoystickButtons ||
|
|
1922
2365
|
this.dropJoystick.dataset['visible'] !== 'true' ||
|
|
@@ -1924,6 +2367,10 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1924
2367
|
return null;
|
|
1925
2368
|
}
|
|
1926
2369
|
for (const button of this.dropJoystickButtons) {
|
|
2370
|
+
// Skip hidden/inactive buttons (used in center-only mode)
|
|
2371
|
+
if ((button.dataset['hidden'] === 'true') || button.style.visibility === 'hidden' || button.style.display === 'none') {
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
1927
2374
|
const rect = button.getBoundingClientRect();
|
|
1928
2375
|
if (clientX >= rect.left &&
|
|
1929
2376
|
clientX <= rect.right &&
|
|
@@ -1938,8 +2385,14 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1938
2385
|
return null;
|
|
1939
2386
|
}
|
|
1940
2387
|
updateDropJoystickActiveZone(zone) {
|
|
2388
|
+
// If no zone is computed but the joystick is visible, keep the last
|
|
2389
|
+
// known active zone to avoid visual jitter while dragging across
|
|
2390
|
+
// small gaps where hit‑testing momentarily fails.
|
|
2391
|
+
const visible = this.dropJoystick.dataset['visible'] === 'true';
|
|
2392
|
+
const sticky = visible ? this.dropJoystick.dataset['zone'] : undefined;
|
|
2393
|
+
const effectiveZone = zone ?? (this.isDropZone(sticky) ? sticky : null);
|
|
1941
2394
|
this.dropJoystickButtons.forEach((button) => {
|
|
1942
|
-
const isActive =
|
|
2395
|
+
const isActive = effectiveZone !== null && button.dataset['zone'] === effectiveZone;
|
|
1943
2396
|
if (isActive) {
|
|
1944
2397
|
button.dataset['active'] = 'true';
|
|
1945
2398
|
}
|
|
@@ -1947,8 +2400,8 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1947
2400
|
delete button.dataset['active'];
|
|
1948
2401
|
}
|
|
1949
2402
|
});
|
|
1950
|
-
if (
|
|
1951
|
-
this.dropJoystick.dataset['zone'] =
|
|
2403
|
+
if (effectiveZone) {
|
|
2404
|
+
this.dropJoystick.dataset['zone'] = effectiveZone;
|
|
1952
2405
|
}
|
|
1953
2406
|
else {
|
|
1954
2407
|
delete this.dropJoystick.dataset['zone'];
|
|
@@ -2014,7 +2467,52 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2014
2467
|
joystick.dataset['visible'] = 'true';
|
|
2015
2468
|
this.dropJoystick.style.display = 'grid';
|
|
2016
2469
|
joystick.dataset['path'] = stack.dataset['path'] ?? '';
|
|
2470
|
+
const changedTarget = this.dropJoystickTarget && this.dropJoystickTarget !== stack;
|
|
2017
2471
|
this.dropJoystickTarget = stack;
|
|
2472
|
+
if (changedTarget) {
|
|
2473
|
+
// New target stack: forget any previously sticky zone.
|
|
2474
|
+
delete this.dropJoystick.dataset['zone'];
|
|
2475
|
+
}
|
|
2476
|
+
// If main dock area is empty, show only the center button and collapse the grid
|
|
2477
|
+
const isEmptyMainArea = !this.rootLayout && (stack === this.dockedEl || (targetPath && targetPath.type === 'docked' && targetPath.segments.length === 0));
|
|
2478
|
+
const spacers = Array.from(this.dropJoystick.querySelectorAll('.dock-drop-joystick__spacer'));
|
|
2479
|
+
if (isEmptyMainArea) {
|
|
2480
|
+
// Keep spacers visible so the joystick keeps its circular footprint.
|
|
2481
|
+
spacers.forEach((s) => {
|
|
2482
|
+
s.style.display = '';
|
|
2483
|
+
});
|
|
2484
|
+
this.dropJoystickButtons.forEach((btn) => {
|
|
2485
|
+
const isCenter = btn.dataset['zone'] === 'center';
|
|
2486
|
+
if (isCenter) {
|
|
2487
|
+
btn.style.visibility = '';
|
|
2488
|
+
btn.style.pointerEvents = '';
|
|
2489
|
+
delete btn.dataset['hidden'];
|
|
2490
|
+
btn.style.display = '';
|
|
2491
|
+
}
|
|
2492
|
+
else {
|
|
2493
|
+
// Hide visually but keep layout space; also prevent interaction.
|
|
2494
|
+
btn.style.visibility = 'hidden';
|
|
2495
|
+
btn.style.pointerEvents = 'none';
|
|
2496
|
+
btn.dataset['hidden'] = 'true';
|
|
2497
|
+
btn.style.display = '';
|
|
2498
|
+
}
|
|
2499
|
+
});
|
|
2500
|
+
// Keep default 3x3 grid so the circular background size stays the same.
|
|
2501
|
+
this.dropJoystick.style.gridTemplateColumns = '';
|
|
2502
|
+
this.dropJoystick.style.gridTemplateRows = '';
|
|
2503
|
+
// Do not set an active zone automatically; users must hover the button.
|
|
2504
|
+
}
|
|
2505
|
+
else {
|
|
2506
|
+
this.dropJoystickButtons.forEach((btn) => {
|
|
2507
|
+
btn.style.visibility = '';
|
|
2508
|
+
btn.style.pointerEvents = '';
|
|
2509
|
+
delete btn.dataset['hidden'];
|
|
2510
|
+
btn.style.display = '';
|
|
2511
|
+
});
|
|
2512
|
+
spacers.forEach((s) => (s.style.display = ''));
|
|
2513
|
+
this.dropJoystick.style.gridTemplateColumns = '';
|
|
2514
|
+
this.dropJoystick.style.gridTemplateRows = '';
|
|
2515
|
+
}
|
|
2018
2516
|
this.updateDropJoystickActiveZone(zone);
|
|
2019
2517
|
}
|
|
2020
2518
|
hideDropIndicator() {
|
|
@@ -2024,6 +2522,16 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2024
2522
|
delete this.dropJoystick.dataset['path'];
|
|
2025
2523
|
this.dropJoystickTarget = null;
|
|
2026
2524
|
this.updateDropJoystickActiveZone(null);
|
|
2525
|
+
// Restore joystick structure to default.
|
|
2526
|
+
this.dropJoystickButtons.forEach((btn) => {
|
|
2527
|
+
btn.style.display = '';
|
|
2528
|
+
btn.style.visibility = '';
|
|
2529
|
+
btn.style.pointerEvents = '';
|
|
2530
|
+
delete btn.dataset['hidden'];
|
|
2531
|
+
});
|
|
2532
|
+
Array.from(this.dropJoystick.querySelectorAll('.dock-drop-joystick__spacer')).forEach((s) => (s.style.display = ''));
|
|
2533
|
+
this.dropJoystick.style.gridTemplateColumns = '';
|
|
2534
|
+
this.dropJoystick.style.gridTemplateRows = '';
|
|
2027
2535
|
}
|
|
2028
2536
|
findStackAtPoint(clientX, clientY) {
|
|
2029
2537
|
const shadow = this.shadowRoot;
|
|
@@ -2031,35 +2539,53 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2031
2539
|
return null;
|
|
2032
2540
|
}
|
|
2033
2541
|
const elements = shadow.elementsFromPoint(clientX, clientY);
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2542
|
+
const stack = this.findStackInTargets(elements);
|
|
2543
|
+
if (stack) {
|
|
2544
|
+
return stack;
|
|
2545
|
+
}
|
|
2546
|
+
// If there are no docked stacks (all panes are floating), allow the
|
|
2547
|
+
// docked surface itself to serve as a drop target for the main zone.
|
|
2548
|
+
if (!this.rootLayout) {
|
|
2549
|
+
const dockRect = this.dockedEl.getBoundingClientRect();
|
|
2550
|
+
if (clientX >= dockRect.left &&
|
|
2551
|
+
clientX <= dockRect.right &&
|
|
2552
|
+
clientY >= dockRect.top &&
|
|
2553
|
+
clientY <= dockRect.bottom) {
|
|
2554
|
+
return this.dockedEl;
|
|
2046
2555
|
}
|
|
2047
2556
|
}
|
|
2048
2557
|
return null;
|
|
2049
2558
|
}
|
|
2050
2559
|
findStackElement(event) {
|
|
2051
2560
|
const path = event.composedPath();
|
|
2052
|
-
|
|
2053
|
-
|
|
2561
|
+
const stack = this.findStackInTargets(path);
|
|
2562
|
+
if (stack) {
|
|
2563
|
+
return stack;
|
|
2564
|
+
}
|
|
2565
|
+
// If the root dock area is empty, treat the docked surface as a valid
|
|
2566
|
+
// target when it appears in the composed path.
|
|
2567
|
+
if (!this.rootLayout) {
|
|
2568
|
+
for (const target of path) {
|
|
2569
|
+
if (target instanceof HTMLElement &&
|
|
2570
|
+
(target === this.dockedEl || target.classList.contains('dock-docked'))) {
|
|
2571
|
+
return this.dockedEl;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
return null;
|
|
2576
|
+
}
|
|
2577
|
+
findStackInTargets(targets) {
|
|
2578
|
+
for (const element of targets) {
|
|
2579
|
+
if (!(element instanceof HTMLElement)) {
|
|
2054
2580
|
continue;
|
|
2055
2581
|
}
|
|
2056
|
-
if (
|
|
2057
|
-
return
|
|
2582
|
+
if (element.classList.contains('dock-stack')) {
|
|
2583
|
+
return element;
|
|
2058
2584
|
}
|
|
2059
2585
|
if (this.dropJoystickTarget &&
|
|
2060
|
-
(
|
|
2061
|
-
|
|
2062
|
-
|
|
2586
|
+
(element.classList.contains('dock-drop-joystick') ||
|
|
2587
|
+
element.classList.contains('dock-drop-joystick__button') ||
|
|
2588
|
+
element.classList.contains('dock-drop-joystick__spacer'))) {
|
|
2063
2589
|
return this.dropJoystickTarget;
|
|
2064
2590
|
}
|
|
2065
2591
|
}
|
|
@@ -2234,19 +2760,25 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2234
2760
|
return found;
|
|
2235
2761
|
}
|
|
2236
2762
|
collectFloatingPaneMetadata(node) {
|
|
2237
|
-
|
|
2763
|
+
// Deprecated method retained temporarily for signature compatibility.
|
|
2764
|
+
// Use collectPaneNames instead.
|
|
2765
|
+
const panes = this.collectPaneNames(node);
|
|
2238
2766
|
const titles = {};
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
titles[pane] = title;
|
|
2245
|
-
}
|
|
2246
|
-
});
|
|
2767
|
+
panes.forEach((p) => {
|
|
2768
|
+
const t = this.titles[p];
|
|
2769
|
+
if (t) {
|
|
2770
|
+
titles[p] = t;
|
|
2771
|
+
}
|
|
2247
2772
|
});
|
|
2248
2773
|
return { panes, titles };
|
|
2249
2774
|
}
|
|
2775
|
+
collectPaneNames(node) {
|
|
2776
|
+
const panes = [];
|
|
2777
|
+
this.forEachStack(node, (stack) => {
|
|
2778
|
+
stack.panes.forEach((pane) => panes.push(pane));
|
|
2779
|
+
});
|
|
2780
|
+
return panes;
|
|
2781
|
+
}
|
|
2250
2782
|
normalizeFloatingLayout(layout) {
|
|
2251
2783
|
const bounds = layout.bounds ?? { left: 0, top: 0, width: 320, height: 200 };
|
|
2252
2784
|
const normalizedBounds = {
|
|
@@ -2255,21 +2787,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2255
2787
|
width: Number.isFinite(bounds.width) ? Math.max(bounds.width, 160) : 320,
|
|
2256
2788
|
height: Number.isFinite(bounds.height) ? Math.max(bounds.height, 120) : 200,
|
|
2257
2789
|
};
|
|
2258
|
-
|
|
2259
|
-
if (!root) {
|
|
2260
|
-
const panes = Array.isArray(layout.panes) ? [...layout.panes] : [];
|
|
2261
|
-
if (panes.length > 0) {
|
|
2262
|
-
const active = layout.activePane && panes.includes(layout.activePane)
|
|
2263
|
-
? layout.activePane
|
|
2264
|
-
: panes[0];
|
|
2265
|
-
root = {
|
|
2266
|
-
kind: 'stack',
|
|
2267
|
-
panes,
|
|
2268
|
-
titles: layout.titles ? { ...layout.titles } : undefined,
|
|
2269
|
-
activePane: active,
|
|
2270
|
-
};
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2790
|
+
const root = layout.root ? this.cloneLayoutNode(layout.root) : null;
|
|
2273
2791
|
return {
|
|
2274
2792
|
id: layout.id,
|
|
2275
2793
|
bounds: normalizedBounds,
|
|
@@ -2574,14 +3092,15 @@ class BsDockManagerComponent {
|
|
|
2574
3092
|
}
|
|
2575
3093
|
ensureSnapshot(value) {
|
|
2576
3094
|
if (!value) {
|
|
2577
|
-
return { root: null, floating: [] };
|
|
3095
|
+
return { root: null, floating: [], titles: {} };
|
|
2578
3096
|
}
|
|
2579
3097
|
if ('kind' in value) {
|
|
2580
|
-
return { root: value, floating: [] };
|
|
3098
|
+
return { root: value, floating: [], titles: {} };
|
|
2581
3099
|
}
|
|
2582
3100
|
return {
|
|
2583
3101
|
root: value.root ?? null,
|
|
2584
3102
|
floating: Array.isArray(value.floating) ? [...value.floating] : [],
|
|
3103
|
+
titles: value.titles ? { ...value.titles } : {},
|
|
2585
3104
|
};
|
|
2586
3105
|
}
|
|
2587
3106
|
stringifyLayout(layout) {
|