@mintplayer/ng-bootstrap 20.4.0 → 20.5.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) {
|
|
@@ -1266,6 +1290,12 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1266
1290
|
// We defer its removal to ensure the browser has captured it.
|
|
1267
1291
|
setTimeout(() => ghost.remove(), 0);
|
|
1268
1292
|
const { path: sourcePath, floatingIndex, pointerOffsetX, pointerOffsetY, } = this.preparePaneDragSource(path, pane, stackEl, event);
|
|
1293
|
+
// Capture header bounds for detecting when to convert to floating
|
|
1294
|
+
const headerEl = stackEl?.querySelector('.dock-stack__header') ?? null;
|
|
1295
|
+
const headerRect = headerEl ? headerEl.getBoundingClientRect() : null;
|
|
1296
|
+
const headerBounds = headerRect
|
|
1297
|
+
? { left: headerRect.left, top: headerRect.top, right: headerRect.right, bottom: headerRect.bottom }
|
|
1298
|
+
: null;
|
|
1269
1299
|
this.dragState = {
|
|
1270
1300
|
pane,
|
|
1271
1301
|
sourcePath: this.clonePath(sourcePath),
|
|
@@ -1273,7 +1303,18 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1273
1303
|
pointerOffsetX,
|
|
1274
1304
|
pointerOffsetY,
|
|
1275
1305
|
dropHandled: false,
|
|
1306
|
+
sourceStackEl: stackEl,
|
|
1307
|
+
sourceHeaderBounds: headerBounds,
|
|
1308
|
+
startClientX: Number.isFinite(event.clientX) ? event.clientX : undefined,
|
|
1309
|
+
startClientY: Number.isFinite(event.clientY) ? event.clientY : undefined,
|
|
1276
1310
|
};
|
|
1311
|
+
// Prefer the pointer offset relative to the dragged tab to avoid jumps on conversion
|
|
1312
|
+
if (Number.isFinite(event.offsetX)) {
|
|
1313
|
+
this.dragState.pointerOffsetX = event.offsetX;
|
|
1314
|
+
}
|
|
1315
|
+
if (Number.isFinite(event.offsetY)) {
|
|
1316
|
+
this.dragState.pointerOffsetY = event.offsetY;
|
|
1317
|
+
}
|
|
1277
1318
|
this.updateDraggedFloatingPosition(event);
|
|
1278
1319
|
this.startDragPointerTracking();
|
|
1279
1320
|
event.dataTransfer.effectAllowed = 'move';
|
|
@@ -1282,7 +1323,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1282
1323
|
preparePaneDragSource(path, pane, stackEl, event) {
|
|
1283
1324
|
const location = this.resolveStackLocation(path);
|
|
1284
1325
|
if (!location || !location.node.panes.includes(pane)) {
|
|
1285
|
-
this.clearPendingTabDragMetrics();
|
|
1286
1326
|
return {
|
|
1287
1327
|
path,
|
|
1288
1328
|
floatingIndex: null,
|
|
@@ -1291,7 +1331,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1291
1331
|
};
|
|
1292
1332
|
}
|
|
1293
1333
|
const metrics = this.pendingTabDragMetrics;
|
|
1294
|
-
this.pendingTabDragMetrics = null;
|
|
1295
1334
|
const domHasSibling = !!stackEl &&
|
|
1296
1335
|
Array.from(stackEl.querySelectorAll('.dock-tab')).some((button) => button.dataset['pane'] && button.dataset['pane'] !== pane);
|
|
1297
1336
|
const hasSiblingInStack = location.node.panes.some((existing) => existing !== pane);
|
|
@@ -1341,95 +1380,19 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1341
1380
|
pointerOffsetY: 0,
|
|
1342
1381
|
};
|
|
1343
1382
|
}
|
|
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)
|
|
1383
|
+
// Do not convert to floating yet; allow in-header reordering first.
|
|
1384
|
+
// We return placeholder values and will convert once the pointer leaves the tab header.
|
|
1385
|
+
const pointerOffsetXVal = metrics && Number.isFinite(metrics.pointerOffsetX)
|
|
1359
1386
|
? metrics.pointerOffsetX
|
|
1360
|
-
:
|
|
1361
|
-
|
|
1362
|
-
: width / 2;
|
|
1363
|
-
const pointerOffsetY = metrics && Number.isFinite(metrics.pointerOffsetY)
|
|
1387
|
+
: 0;
|
|
1388
|
+
const pointerOffsetYVal = metrics && Number.isFinite(metrics.pointerOffsetY)
|
|
1364
1389
|
? 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);
|
|
1390
|
+
: 0;
|
|
1428
1391
|
return {
|
|
1429
|
-
path
|
|
1392
|
+
path,
|
|
1430
1393
|
floatingIndex: -1,
|
|
1431
|
-
pointerOffsetX,
|
|
1432
|
-
pointerOffsetY,
|
|
1394
|
+
pointerOffsetX: pointerOffsetXVal,
|
|
1395
|
+
pointerOffsetY: pointerOffsetYVal,
|
|
1433
1396
|
};
|
|
1434
1397
|
}
|
|
1435
1398
|
endPaneDrag() {
|
|
@@ -1448,17 +1411,39 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1448
1411
|
return;
|
|
1449
1412
|
}
|
|
1450
1413
|
event.preventDefault();
|
|
1414
|
+
// Keep internal pointer tracking up-to-date.
|
|
1451
1415
|
this.updateDraggedFloatingPosition(event);
|
|
1452
1416
|
if (event.dataTransfer) {
|
|
1453
1417
|
event.dataTransfer.dropEffect = 'move';
|
|
1454
1418
|
}
|
|
1455
|
-
|
|
1419
|
+
// Some browsers intermittently report (0,0) for dragover coordinates.
|
|
1420
|
+
// Mirror the robust logic used in onDrop: prefer actual event coordinates
|
|
1421
|
+
// when valid, otherwise fall back to the last tracked pointer position.
|
|
1422
|
+
const pointFromEvent = Number.isFinite(event.clientX) && Number.isFinite(event.clientY)
|
|
1423
|
+
? { clientX: event.clientX, clientY: event.clientY }
|
|
1424
|
+
: null;
|
|
1425
|
+
const point = pointFromEvent ??
|
|
1426
|
+
(this.lastDragPointerPosition
|
|
1427
|
+
? { clientX: this.lastDragPointerPosition.x, clientY: this.lastDragPointerPosition.y }
|
|
1428
|
+
: null);
|
|
1429
|
+
const stack = this.findStackElement(event) ??
|
|
1430
|
+
(point ? this.findStackAtPoint(point.clientX, point.clientY) : null);
|
|
1456
1431
|
if (!stack) {
|
|
1457
|
-
this.
|
|
1432
|
+
if (this.dropJoystick.dataset['visible'] !== 'true') {
|
|
1433
|
+
this.hideDropIndicator();
|
|
1434
|
+
}
|
|
1458
1435
|
return;
|
|
1459
1436
|
}
|
|
1460
1437
|
const path = this.parsePath(stack.dataset['path']);
|
|
1461
|
-
|
|
1438
|
+
// If the hovered stack changed, clear any sticky zone from the previous
|
|
1439
|
+
// target before computing the new zone.
|
|
1440
|
+
if (this.dropJoystickTarget && this.dropJoystickTarget !== stack) {
|
|
1441
|
+
delete this.dropJoystick.dataset['zone'];
|
|
1442
|
+
this.updateDropJoystickActiveZone(null);
|
|
1443
|
+
}
|
|
1444
|
+
const eventZoneHint = this.extractDropZoneFromEvent(event);
|
|
1445
|
+
const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
|
|
1446
|
+
const zone = this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint);
|
|
1462
1447
|
this.showDropIndicator(stack, zone);
|
|
1463
1448
|
}
|
|
1464
1449
|
updateDraggedFloatingPosition(event) {
|
|
@@ -1507,6 +1492,22 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1507
1492
|
if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
|
|
1508
1493
|
return;
|
|
1509
1494
|
}
|
|
1495
|
+
// If we are still dragging a tab inside its header, only convert to floating once we leave the header bounds.
|
|
1496
|
+
if (this.dragState.floatingIndex !== null && this.dragState.floatingIndex < 0) {
|
|
1497
|
+
const b = this.dragState.sourceHeaderBounds;
|
|
1498
|
+
const sx = this.dragState.startClientX ?? clientX;
|
|
1499
|
+
const sy = this.dragState.startClientY ?? clientY;
|
|
1500
|
+
const dist = Math.hypot(clientX - sx, clientY - sy);
|
|
1501
|
+
const threshold = 4; // pixels to move before converting
|
|
1502
|
+
let insideHeader = false;
|
|
1503
|
+
if (b) {
|
|
1504
|
+
insideHeader = clientX >= b.left && clientX <= b.right && clientY >= b.top && clientY <= b.bottom;
|
|
1505
|
+
}
|
|
1506
|
+
if (!insideHeader && dist > threshold) {
|
|
1507
|
+
// Convert to floating now using current pointer position
|
|
1508
|
+
this.convertPendingTabDragToFloating(clientX, clientY);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1510
1511
|
this.updatePaneDragDropTargetFromPoint(clientX, clientY);
|
|
1511
1512
|
const { floatingIndex, pointerOffsetX, pointerOffsetY } = this.dragState;
|
|
1512
1513
|
if (floatingIndex === null || floatingIndex < 0) {
|
|
@@ -1532,14 +1533,20 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1532
1533
|
return;
|
|
1533
1534
|
}
|
|
1534
1535
|
const stack = this.findStackAtPoint(clientX, clientY);
|
|
1535
|
-
|
|
1536
|
-
|
|
1536
|
+
const path = stack ? this.parsePath(stack.dataset['path']) : null;
|
|
1537
|
+
if (!stack || !path) {
|
|
1538
|
+
// While actively dragging, avoid hiding the indicator if it is visible.
|
|
1539
|
+
// Transient misses from hit-testing are common near the joystick.
|
|
1540
|
+
if (this.dropJoystick.dataset['visible'] !== 'true') {
|
|
1541
|
+
this.hideDropIndicator();
|
|
1542
|
+
}
|
|
1537
1543
|
return;
|
|
1538
1544
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1545
|
+
// If we moved to a different target stack, reset any sticky zone so
|
|
1546
|
+
// the new drop area doesn't inherit the previous side selection.
|
|
1547
|
+
if (this.dropJoystickTarget && this.dropJoystickTarget !== stack) {
|
|
1548
|
+
delete this.dropJoystick.dataset['zone'];
|
|
1549
|
+
this.updateDropJoystickActiveZone(null);
|
|
1543
1550
|
}
|
|
1544
1551
|
const zoneHint = this.findDropZoneByPoint(clientX, clientY);
|
|
1545
1552
|
const zone = this.computeDropZone(stack, { clientX, clientY }, zoneHint);
|
|
@@ -1599,18 +1606,122 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1599
1606
|
this.updateDraggedFloatingPositionFromPoint(touch.clientX, touch.clientY);
|
|
1600
1607
|
}
|
|
1601
1608
|
onDragMouseUp() {
|
|
1609
|
+
this.handleDragPointerUpCommon();
|
|
1610
|
+
}
|
|
1611
|
+
// Convert a currently in-header tab drag into a floating window
|
|
1612
|
+
convertPendingTabDragToFloating(clientX, clientY) {
|
|
1602
1613
|
if (!this.dragState) {
|
|
1603
|
-
this.stopDragPointerTracking();
|
|
1604
1614
|
return;
|
|
1605
1615
|
}
|
|
1606
|
-
this.
|
|
1616
|
+
const state = this.dragState;
|
|
1617
|
+
if (state.floatingIndex !== null && state.floatingIndex >= 0) {
|
|
1618
|
+
return; // already floating
|
|
1619
|
+
}
|
|
1620
|
+
const location = this.resolveStackLocation(state.sourcePath);
|
|
1621
|
+
if (!location) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const pane = state.pane;
|
|
1625
|
+
const stackEl = state.sourceStackEl ?? null;
|
|
1626
|
+
const hostRect = this.getBoundingClientRect();
|
|
1627
|
+
const stackRect = stackEl?.getBoundingClientRect() ?? null;
|
|
1628
|
+
const metrics = this.pendingTabDragMetrics;
|
|
1629
|
+
const fallbackWidth = 320;
|
|
1630
|
+
const fallbackHeight = 240;
|
|
1631
|
+
const width = metrics && Number.isFinite(metrics.width) && metrics.width > 0
|
|
1632
|
+
? metrics.width
|
|
1633
|
+
: stackRect && Number.isFinite(stackRect.width)
|
|
1634
|
+
? stackRect.width
|
|
1635
|
+
: fallbackWidth;
|
|
1636
|
+
const height = metrics && Number.isFinite(metrics.height) && metrics.height > 0
|
|
1637
|
+
? metrics.height
|
|
1638
|
+
: stackRect && Number.isFinite(stackRect.height)
|
|
1639
|
+
? stackRect.height
|
|
1640
|
+
: fallbackHeight;
|
|
1641
|
+
const pointerOffsetX = Number.isFinite(this.dragState?.pointerOffsetX)
|
|
1642
|
+
? this.dragState.pointerOffsetX
|
|
1643
|
+
: metrics && Number.isFinite(metrics.pointerOffsetX)
|
|
1644
|
+
? metrics.pointerOffsetX
|
|
1645
|
+
: width / 2;
|
|
1646
|
+
const pointerOffsetY = Number.isFinite(this.dragState?.pointerOffsetY)
|
|
1647
|
+
? this.dragState.pointerOffsetY
|
|
1648
|
+
: metrics && Number.isFinite(metrics.pointerOffsetY)
|
|
1649
|
+
? metrics.pointerOffsetY
|
|
1650
|
+
: height / 2;
|
|
1651
|
+
const pointerLeft = Number.isFinite(clientX)
|
|
1652
|
+
? clientX - hostRect.left - pointerOffsetX
|
|
1653
|
+
: 0;
|
|
1654
|
+
const pointerTop = Number.isFinite(clientY)
|
|
1655
|
+
? clientY - hostRect.top - pointerOffsetY
|
|
1656
|
+
: 0;
|
|
1657
|
+
// Remove pane from its current stack and create a new floating entry
|
|
1658
|
+
this.removePaneFromLocation(location, pane);
|
|
1659
|
+
const floatingStack = {
|
|
1660
|
+
kind: 'stack',
|
|
1661
|
+
panes: [pane],
|
|
1662
|
+
activePane: pane,
|
|
1663
|
+
};
|
|
1664
|
+
const floatingLayout = {
|
|
1665
|
+
bounds: {
|
|
1666
|
+
left: pointerLeft,
|
|
1667
|
+
top: pointerTop,
|
|
1668
|
+
width,
|
|
1669
|
+
height,
|
|
1670
|
+
},
|
|
1671
|
+
root: floatingStack,
|
|
1672
|
+
activePane: pane,
|
|
1673
|
+
};
|
|
1674
|
+
this.floatingLayouts.push(floatingLayout);
|
|
1675
|
+
const newIndex = this.floatingLayouts.length - 1;
|
|
1676
|
+
this.render();
|
|
1677
|
+
const wrapper = this.getFloatingWrapper(newIndex);
|
|
1678
|
+
if (wrapper) {
|
|
1679
|
+
this.promoteFloatingPane(newIndex, wrapper);
|
|
1680
|
+
}
|
|
1681
|
+
// Update drag state so subsequent moves reposition the floating window
|
|
1682
|
+
state.sourcePath = { type: 'floating', index: newIndex, segments: [] };
|
|
1683
|
+
state.floatingIndex = newIndex;
|
|
1684
|
+
state.pointerOffsetX = pointerOffsetX;
|
|
1685
|
+
state.pointerOffsetY = pointerOffsetY;
|
|
1686
|
+
this.dispatchLayoutChanged();
|
|
1607
1687
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1688
|
+
// Compute the intended tab insert index within a header based on pointer X
|
|
1689
|
+
computeHeaderInsertIndex(header, clientX) {
|
|
1690
|
+
const tabs = Array.from(header.querySelectorAll('.dock-tab'));
|
|
1691
|
+
if (tabs.length === 0) {
|
|
1692
|
+
return 0;
|
|
1693
|
+
}
|
|
1694
|
+
for (let i = 0; i < tabs.length; i += 1) {
|
|
1695
|
+
const rect = tabs[i].getBoundingClientRect();
|
|
1696
|
+
const mid = rect.left + rect.width / 2;
|
|
1697
|
+
if (clientX < mid) {
|
|
1698
|
+
return i;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
return tabs.length; // insert at end
|
|
1702
|
+
}
|
|
1703
|
+
reorderPaneInLocationAtIndex(location, pane, targetIndex) {
|
|
1704
|
+
const panes = location.node.panes;
|
|
1705
|
+
const currentIndex = panes.indexOf(pane);
|
|
1706
|
+
if (currentIndex === -1) {
|
|
1611
1707
|
return;
|
|
1612
1708
|
}
|
|
1613
|
-
|
|
1709
|
+
const clampedTarget = Math.max(0, Math.min(targetIndex, panes.length - 1));
|
|
1710
|
+
if (clampedTarget === currentIndex) {
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
panes.splice(currentIndex, 1);
|
|
1714
|
+
panes.splice(clampedTarget, 0, pane);
|
|
1715
|
+
location.node.activePane = pane;
|
|
1716
|
+
if (location.context === 'floating') {
|
|
1717
|
+
const floating = this.floatingLayouts[location.index];
|
|
1718
|
+
if (floating) {
|
|
1719
|
+
floating.activePane = pane;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
onDragTouchEnd() {
|
|
1724
|
+
this.handleDragPointerUpCommon();
|
|
1614
1725
|
}
|
|
1615
1726
|
clearPendingDragEndTimeout() {
|
|
1616
1727
|
if (this.pendingDragEndTimeout !== null) {
|
|
@@ -1666,6 +1777,29 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1666
1777
|
return;
|
|
1667
1778
|
}
|
|
1668
1779
|
const path = this.parsePath(stack.dataset['path']);
|
|
1780
|
+
// Allow reordering within the same stack header without selecting a zone
|
|
1781
|
+
if (this.dragState &&
|
|
1782
|
+
this.dragState.floatingIndex !== null &&
|
|
1783
|
+
this.dragState.floatingIndex < 0 &&
|
|
1784
|
+
path &&
|
|
1785
|
+
this.pathsEqual(path, this.dragState.sourcePath)) {
|
|
1786
|
+
const header = stack.querySelector('.dock-stack__header');
|
|
1787
|
+
if (header) {
|
|
1788
|
+
const x = (point ? point.clientX : event.clientX);
|
|
1789
|
+
if (Number.isFinite(x)) {
|
|
1790
|
+
const location = this.resolveStackLocation(path);
|
|
1791
|
+
if (location) {
|
|
1792
|
+
const idx = this.computeHeaderInsertIndex(header, x);
|
|
1793
|
+
this.reorderPaneInLocationAtIndex(location, this.dragState.pane, idx);
|
|
1794
|
+
this.render();
|
|
1795
|
+
this.dispatchLayoutChanged();
|
|
1796
|
+
this.dragState.dropHandled = true;
|
|
1797
|
+
this.endPaneDrag();
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1669
1803
|
const eventZoneHint = this.extractDropZoneFromEvent(event);
|
|
1670
1804
|
const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
|
|
1671
1805
|
const zone = this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint);
|
|
@@ -1679,6 +1813,21 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1679
1813
|
}
|
|
1680
1814
|
onDragLeave(event) {
|
|
1681
1815
|
const related = event.relatedTarget;
|
|
1816
|
+
// During active drags, browsers can emit spurious dragleave with null
|
|
1817
|
+
// relatedTarget while the pointer is still over the joystick/buttons.
|
|
1818
|
+
// Be conservative: if we can resolve a stack/joystick at the last known
|
|
1819
|
+
// pointer position, don’t hide (prevents flicker of active state).
|
|
1820
|
+
if (this.dragState) {
|
|
1821
|
+
const pos = (Number.isFinite(event.clientX) && Number.isFinite(event.clientY))
|
|
1822
|
+
? { x: event.clientX, y: event.clientY }
|
|
1823
|
+
: this.lastDragPointerPosition;
|
|
1824
|
+
if (pos) {
|
|
1825
|
+
const stackAtPoint = this.findStackAtPoint(pos.x, pos.y);
|
|
1826
|
+
if (stackAtPoint) {
|
|
1827
|
+
return; // still inside our drop area; ignore this dragleave
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1682
1831
|
if (!related) {
|
|
1683
1832
|
this.hideDropIndicator();
|
|
1684
1833
|
return;
|
|
@@ -1696,6 +1845,28 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1696
1845
|
const { pane, sourcePath } = this.dragState;
|
|
1697
1846
|
const source = this.resolveStackLocation(sourcePath);
|
|
1698
1847
|
const target = this.resolveStackLocation(targetPath);
|
|
1848
|
+
// Special case: allow dropping onto an empty main dock area (no root).
|
|
1849
|
+
if (!target && targetPath.type === 'docked' && !this.rootLayout) {
|
|
1850
|
+
if (!source) {
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
const stackEmptied = this.removePaneFromLocation(source, pane, true);
|
|
1854
|
+
const newRoot = {
|
|
1855
|
+
kind: 'stack',
|
|
1856
|
+
panes: [pane],
|
|
1857
|
+
activePane: pane,
|
|
1858
|
+
};
|
|
1859
|
+
this.rootLayout = newRoot;
|
|
1860
|
+
if (stackEmptied) {
|
|
1861
|
+
this.cleanupLocation(source);
|
|
1862
|
+
}
|
|
1863
|
+
this.render();
|
|
1864
|
+
this.dispatchLayoutChanged();
|
|
1865
|
+
if (this.dragState) {
|
|
1866
|
+
this.dragState.dropHandled = true;
|
|
1867
|
+
}
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1699
1870
|
if (!source || !target) {
|
|
1700
1871
|
return;
|
|
1701
1872
|
}
|
|
@@ -1711,15 +1882,10 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1711
1882
|
}
|
|
1712
1883
|
return;
|
|
1713
1884
|
}
|
|
1714
|
-
const paneTitle = source.node.titles?.[pane];
|
|
1715
1885
|
const stackEmptied = this.removePaneFromLocation(source, pane, true);
|
|
1716
1886
|
if (zone === 'center') {
|
|
1717
1887
|
this.addPaneToLocation(target, pane);
|
|
1718
1888
|
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
1889
|
if (stackEmptied) {
|
|
1724
1890
|
this.cleanupLocation(source);
|
|
1725
1891
|
}
|
|
@@ -1735,9 +1901,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1735
1901
|
panes: [pane],
|
|
1736
1902
|
activePane: pane,
|
|
1737
1903
|
};
|
|
1738
|
-
if (paneTitle) {
|
|
1739
|
-
newStack.titles = { [pane]: paneTitle };
|
|
1740
|
-
}
|
|
1741
1904
|
if (target.context === 'docked') {
|
|
1742
1905
|
this.rootLayout = this.dockNodeBeside(this.rootLayout, target.node, newStack, zone);
|
|
1743
1906
|
}
|
|
@@ -1769,6 +1932,15 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1769
1932
|
return false;
|
|
1770
1933
|
}
|
|
1771
1934
|
const target = this.resolveStackLocation(targetPath);
|
|
1935
|
+
// Allow dropping an entire floating stack onto an empty main dock area
|
|
1936
|
+
// (no existing root).
|
|
1937
|
+
if (!target && targetPath.type === 'docked' && !this.rootLayout) {
|
|
1938
|
+
this.rootLayout = this.cloneLayoutNode(source.root);
|
|
1939
|
+
this.removeFloatingAt(sourceIndex);
|
|
1940
|
+
this.render();
|
|
1941
|
+
this.dispatchLayoutChanged();
|
|
1942
|
+
return true;
|
|
1943
|
+
}
|
|
1772
1944
|
if (!target) {
|
|
1773
1945
|
return false;
|
|
1774
1946
|
}
|
|
@@ -1776,17 +1948,12 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1776
1948
|
return false;
|
|
1777
1949
|
}
|
|
1778
1950
|
if (zone === 'center') {
|
|
1779
|
-
const
|
|
1951
|
+
const panes = this.collectPaneNames(source.root);
|
|
1780
1952
|
if (panes.length === 0) {
|
|
1781
1953
|
return false;
|
|
1782
1954
|
}
|
|
1783
1955
|
panes.forEach((pane) => {
|
|
1784
1956
|
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
1957
|
});
|
|
1791
1958
|
const activePane = source.activePane && panes.includes(source.activePane)
|
|
1792
1959
|
? source.activePane
|
|
@@ -1873,6 +2040,8 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1873
2040
|
return null;
|
|
1874
2041
|
}
|
|
1875
2042
|
computeDropZone(stack, point, zoneHint) {
|
|
2043
|
+
// Do not force a zone: even when the main area is empty we only
|
|
2044
|
+
// activate a zone when the pointer is actually over a joystick button.
|
|
1876
2045
|
const hintedZone = this.isDropZone(zoneHint) ? zoneHint : null;
|
|
1877
2046
|
if (hintedZone) {
|
|
1878
2047
|
this.updateDropJoystickActiveZone(hintedZone);
|
|
@@ -1888,6 +2057,14 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1888
2057
|
return pointZone;
|
|
1889
2058
|
}
|
|
1890
2059
|
if (this.dropJoystickTarget === stack) {
|
|
2060
|
+
// Be sticky while hovering the joystick: if we recently had a zone
|
|
2061
|
+
// selected, prefer keeping it instead of briefly clearing to null
|
|
2062
|
+
// when the browser reports transient coordinates/targets during drag.
|
|
2063
|
+
const sticky = this.dropJoystick.dataset['zone'];
|
|
2064
|
+
if (this.isDropZone(sticky)) {
|
|
2065
|
+
this.updateDropJoystickActiveZone(sticky);
|
|
2066
|
+
return sticky;
|
|
2067
|
+
}
|
|
1891
2068
|
this.updateDropJoystickActiveZone(null);
|
|
1892
2069
|
}
|
|
1893
2070
|
return null;
|
|
@@ -1898,14 +2075,9 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1898
2075
|
}
|
|
1899
2076
|
if (typeof event.composedPath === 'function') {
|
|
1900
2077
|
const path = event.composedPath();
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
const zone = target.dataset?.['zone'];
|
|
1905
|
-
if (this.isDropZone(zone)) {
|
|
1906
|
-
return zone;
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
2078
|
+
const zone = this.findDropZoneInTargets(path);
|
|
2079
|
+
if (zone) {
|
|
2080
|
+
return zone;
|
|
1909
2081
|
}
|
|
1910
2082
|
}
|
|
1911
2083
|
if ('clientX' in event && 'clientY' in event) {
|
|
@@ -1917,6 +2089,25 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1917
2089
|
}
|
|
1918
2090
|
return null;
|
|
1919
2091
|
}
|
|
2092
|
+
handleDragPointerUpCommon() {
|
|
2093
|
+
if (!this.dragState) {
|
|
2094
|
+
this.stopDragPointerTracking();
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
this.scheduleDeferredDragEnd();
|
|
2098
|
+
}
|
|
2099
|
+
findDropZoneInTargets(targets) {
|
|
2100
|
+
for (const target of targets) {
|
|
2101
|
+
if (target instanceof HTMLElement &&
|
|
2102
|
+
target.classList.contains('dock-drop-joystick__button')) {
|
|
2103
|
+
const zone = target.dataset?.['zone'];
|
|
2104
|
+
if (this.isDropZone(zone)) {
|
|
2105
|
+
return zone;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
return null;
|
|
2110
|
+
}
|
|
1920
2111
|
findDropZoneByPoint(clientX, clientY) {
|
|
1921
2112
|
if (!this.dropJoystickButtons ||
|
|
1922
2113
|
this.dropJoystick.dataset['visible'] !== 'true' ||
|
|
@@ -1924,6 +2115,10 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1924
2115
|
return null;
|
|
1925
2116
|
}
|
|
1926
2117
|
for (const button of this.dropJoystickButtons) {
|
|
2118
|
+
// Skip hidden/inactive buttons (used in center-only mode)
|
|
2119
|
+
if ((button.dataset['hidden'] === 'true') || button.style.visibility === 'hidden' || button.style.display === 'none') {
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
1927
2122
|
const rect = button.getBoundingClientRect();
|
|
1928
2123
|
if (clientX >= rect.left &&
|
|
1929
2124
|
clientX <= rect.right &&
|
|
@@ -1938,8 +2133,14 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1938
2133
|
return null;
|
|
1939
2134
|
}
|
|
1940
2135
|
updateDropJoystickActiveZone(zone) {
|
|
2136
|
+
// If no zone is computed but the joystick is visible, keep the last
|
|
2137
|
+
// known active zone to avoid visual jitter while dragging across
|
|
2138
|
+
// small gaps where hit‑testing momentarily fails.
|
|
2139
|
+
const visible = this.dropJoystick.dataset['visible'] === 'true';
|
|
2140
|
+
const sticky = visible ? this.dropJoystick.dataset['zone'] : undefined;
|
|
2141
|
+
const effectiveZone = zone ?? (this.isDropZone(sticky) ? sticky : null);
|
|
1941
2142
|
this.dropJoystickButtons.forEach((button) => {
|
|
1942
|
-
const isActive =
|
|
2143
|
+
const isActive = effectiveZone !== null && button.dataset['zone'] === effectiveZone;
|
|
1943
2144
|
if (isActive) {
|
|
1944
2145
|
button.dataset['active'] = 'true';
|
|
1945
2146
|
}
|
|
@@ -1947,8 +2148,8 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1947
2148
|
delete button.dataset['active'];
|
|
1948
2149
|
}
|
|
1949
2150
|
});
|
|
1950
|
-
if (
|
|
1951
|
-
this.dropJoystick.dataset['zone'] =
|
|
2151
|
+
if (effectiveZone) {
|
|
2152
|
+
this.dropJoystick.dataset['zone'] = effectiveZone;
|
|
1952
2153
|
}
|
|
1953
2154
|
else {
|
|
1954
2155
|
delete this.dropJoystick.dataset['zone'];
|
|
@@ -2014,7 +2215,52 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2014
2215
|
joystick.dataset['visible'] = 'true';
|
|
2015
2216
|
this.dropJoystick.style.display = 'grid';
|
|
2016
2217
|
joystick.dataset['path'] = stack.dataset['path'] ?? '';
|
|
2218
|
+
const changedTarget = this.dropJoystickTarget && this.dropJoystickTarget !== stack;
|
|
2017
2219
|
this.dropJoystickTarget = stack;
|
|
2220
|
+
if (changedTarget) {
|
|
2221
|
+
// New target stack: forget any previously sticky zone.
|
|
2222
|
+
delete this.dropJoystick.dataset['zone'];
|
|
2223
|
+
}
|
|
2224
|
+
// If main dock area is empty, show only the center button and collapse the grid
|
|
2225
|
+
const isEmptyMainArea = !this.rootLayout && (stack === this.dockedEl || (targetPath && targetPath.type === 'docked' && targetPath.segments.length === 0));
|
|
2226
|
+
const spacers = Array.from(this.dropJoystick.querySelectorAll('.dock-drop-joystick__spacer'));
|
|
2227
|
+
if (isEmptyMainArea) {
|
|
2228
|
+
// Keep spacers visible so the joystick keeps its circular footprint.
|
|
2229
|
+
spacers.forEach((s) => {
|
|
2230
|
+
s.style.display = '';
|
|
2231
|
+
});
|
|
2232
|
+
this.dropJoystickButtons.forEach((btn) => {
|
|
2233
|
+
const isCenter = btn.dataset['zone'] === 'center';
|
|
2234
|
+
if (isCenter) {
|
|
2235
|
+
btn.style.visibility = '';
|
|
2236
|
+
btn.style.pointerEvents = '';
|
|
2237
|
+
delete btn.dataset['hidden'];
|
|
2238
|
+
btn.style.display = '';
|
|
2239
|
+
}
|
|
2240
|
+
else {
|
|
2241
|
+
// Hide visually but keep layout space; also prevent interaction.
|
|
2242
|
+
btn.style.visibility = 'hidden';
|
|
2243
|
+
btn.style.pointerEvents = 'none';
|
|
2244
|
+
btn.dataset['hidden'] = 'true';
|
|
2245
|
+
btn.style.display = '';
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
// Keep default 3x3 grid so the circular background size stays the same.
|
|
2249
|
+
this.dropJoystick.style.gridTemplateColumns = '';
|
|
2250
|
+
this.dropJoystick.style.gridTemplateRows = '';
|
|
2251
|
+
// Do not set an active zone automatically; users must hover the button.
|
|
2252
|
+
}
|
|
2253
|
+
else {
|
|
2254
|
+
this.dropJoystickButtons.forEach((btn) => {
|
|
2255
|
+
btn.style.visibility = '';
|
|
2256
|
+
btn.style.pointerEvents = '';
|
|
2257
|
+
delete btn.dataset['hidden'];
|
|
2258
|
+
btn.style.display = '';
|
|
2259
|
+
});
|
|
2260
|
+
spacers.forEach((s) => (s.style.display = ''));
|
|
2261
|
+
this.dropJoystick.style.gridTemplateColumns = '';
|
|
2262
|
+
this.dropJoystick.style.gridTemplateRows = '';
|
|
2263
|
+
}
|
|
2018
2264
|
this.updateDropJoystickActiveZone(zone);
|
|
2019
2265
|
}
|
|
2020
2266
|
hideDropIndicator() {
|
|
@@ -2024,6 +2270,16 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2024
2270
|
delete this.dropJoystick.dataset['path'];
|
|
2025
2271
|
this.dropJoystickTarget = null;
|
|
2026
2272
|
this.updateDropJoystickActiveZone(null);
|
|
2273
|
+
// Restore joystick structure to default.
|
|
2274
|
+
this.dropJoystickButtons.forEach((btn) => {
|
|
2275
|
+
btn.style.display = '';
|
|
2276
|
+
btn.style.visibility = '';
|
|
2277
|
+
btn.style.pointerEvents = '';
|
|
2278
|
+
delete btn.dataset['hidden'];
|
|
2279
|
+
});
|
|
2280
|
+
Array.from(this.dropJoystick.querySelectorAll('.dock-drop-joystick__spacer')).forEach((s) => (s.style.display = ''));
|
|
2281
|
+
this.dropJoystick.style.gridTemplateColumns = '';
|
|
2282
|
+
this.dropJoystick.style.gridTemplateRows = '';
|
|
2027
2283
|
}
|
|
2028
2284
|
findStackAtPoint(clientX, clientY) {
|
|
2029
2285
|
const shadow = this.shadowRoot;
|
|
@@ -2031,35 +2287,53 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2031
2287
|
return null;
|
|
2032
2288
|
}
|
|
2033
2289
|
const elements = shadow.elementsFromPoint(clientX, clientY);
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2290
|
+
const stack = this.findStackInTargets(elements);
|
|
2291
|
+
if (stack) {
|
|
2292
|
+
return stack;
|
|
2293
|
+
}
|
|
2294
|
+
// If there are no docked stacks (all panes are floating), allow the
|
|
2295
|
+
// docked surface itself to serve as a drop target for the main zone.
|
|
2296
|
+
if (!this.rootLayout) {
|
|
2297
|
+
const dockRect = this.dockedEl.getBoundingClientRect();
|
|
2298
|
+
if (clientX >= dockRect.left &&
|
|
2299
|
+
clientX <= dockRect.right &&
|
|
2300
|
+
clientY >= dockRect.top &&
|
|
2301
|
+
clientY <= dockRect.bottom) {
|
|
2302
|
+
return this.dockedEl;
|
|
2046
2303
|
}
|
|
2047
2304
|
}
|
|
2048
2305
|
return null;
|
|
2049
2306
|
}
|
|
2050
2307
|
findStackElement(event) {
|
|
2051
2308
|
const path = event.composedPath();
|
|
2052
|
-
|
|
2053
|
-
|
|
2309
|
+
const stack = this.findStackInTargets(path);
|
|
2310
|
+
if (stack) {
|
|
2311
|
+
return stack;
|
|
2312
|
+
}
|
|
2313
|
+
// If the root dock area is empty, treat the docked surface as a valid
|
|
2314
|
+
// target when it appears in the composed path.
|
|
2315
|
+
if (!this.rootLayout) {
|
|
2316
|
+
for (const target of path) {
|
|
2317
|
+
if (target instanceof HTMLElement &&
|
|
2318
|
+
(target === this.dockedEl || target.classList.contains('dock-docked'))) {
|
|
2319
|
+
return this.dockedEl;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
return null;
|
|
2324
|
+
}
|
|
2325
|
+
findStackInTargets(targets) {
|
|
2326
|
+
for (const element of targets) {
|
|
2327
|
+
if (!(element instanceof HTMLElement)) {
|
|
2054
2328
|
continue;
|
|
2055
2329
|
}
|
|
2056
|
-
if (
|
|
2057
|
-
return
|
|
2330
|
+
if (element.classList.contains('dock-stack')) {
|
|
2331
|
+
return element;
|
|
2058
2332
|
}
|
|
2059
2333
|
if (this.dropJoystickTarget &&
|
|
2060
|
-
(
|
|
2061
|
-
|
|
2062
|
-
|
|
2334
|
+
(element.classList.contains('dock-drop-joystick') ||
|
|
2335
|
+
element.classList.contains('dock-drop-joystick__button') ||
|
|
2336
|
+
element.classList.contains('dock-drop-joystick__spacer'))) {
|
|
2063
2337
|
return this.dropJoystickTarget;
|
|
2064
2338
|
}
|
|
2065
2339
|
}
|
|
@@ -2234,19 +2508,25 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2234
2508
|
return found;
|
|
2235
2509
|
}
|
|
2236
2510
|
collectFloatingPaneMetadata(node) {
|
|
2237
|
-
|
|
2511
|
+
// Deprecated method retained temporarily for signature compatibility.
|
|
2512
|
+
// Use collectPaneNames instead.
|
|
2513
|
+
const panes = this.collectPaneNames(node);
|
|
2238
2514
|
const titles = {};
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
titles[pane] = title;
|
|
2245
|
-
}
|
|
2246
|
-
});
|
|
2515
|
+
panes.forEach((p) => {
|
|
2516
|
+
const t = this.titles[p];
|
|
2517
|
+
if (t) {
|
|
2518
|
+
titles[p] = t;
|
|
2519
|
+
}
|
|
2247
2520
|
});
|
|
2248
2521
|
return { panes, titles };
|
|
2249
2522
|
}
|
|
2523
|
+
collectPaneNames(node) {
|
|
2524
|
+
const panes = [];
|
|
2525
|
+
this.forEachStack(node, (stack) => {
|
|
2526
|
+
stack.panes.forEach((pane) => panes.push(pane));
|
|
2527
|
+
});
|
|
2528
|
+
return panes;
|
|
2529
|
+
}
|
|
2250
2530
|
normalizeFloatingLayout(layout) {
|
|
2251
2531
|
const bounds = layout.bounds ?? { left: 0, top: 0, width: 320, height: 200 };
|
|
2252
2532
|
const normalizedBounds = {
|
|
@@ -2255,21 +2535,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2255
2535
|
width: Number.isFinite(bounds.width) ? Math.max(bounds.width, 160) : 320,
|
|
2256
2536
|
height: Number.isFinite(bounds.height) ? Math.max(bounds.height, 120) : 200,
|
|
2257
2537
|
};
|
|
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
|
-
}
|
|
2538
|
+
const root = layout.root ? this.cloneLayoutNode(layout.root) : null;
|
|
2273
2539
|
return {
|
|
2274
2540
|
id: layout.id,
|
|
2275
2541
|
bounds: normalizedBounds,
|
|
@@ -2574,14 +2840,15 @@ class BsDockManagerComponent {
|
|
|
2574
2840
|
}
|
|
2575
2841
|
ensureSnapshot(value) {
|
|
2576
2842
|
if (!value) {
|
|
2577
|
-
return { root: null, floating: [] };
|
|
2843
|
+
return { root: null, floating: [], titles: {} };
|
|
2578
2844
|
}
|
|
2579
2845
|
if ('kind' in value) {
|
|
2580
|
-
return { root: value, floating: [] };
|
|
2846
|
+
return { root: value, floating: [], titles: {} };
|
|
2581
2847
|
}
|
|
2582
2848
|
return {
|
|
2583
2849
|
root: value.root ?? null,
|
|
2584
2850
|
floating: Array.isArray(value.floating) ? [...value.floating] : [],
|
|
2851
|
+
titles: value.titles ? { ...value.titles } : {},
|
|
2585
2852
|
};
|
|
2586
2853
|
}
|
|
2587
2854
|
stringifyLayout(layout) {
|