@mintplayer/ng-bootstrap 20.5.0 → 20.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dock/index.d.ts
CHANGED
|
@@ -150,6 +150,11 @@ declare class MintDockManagerElement extends HTMLElement {
|
|
|
150
150
|
private onGlobalDragEnd;
|
|
151
151
|
private updateDraggedFloatingPositionFromPoint;
|
|
152
152
|
private updatePaneDragDropTargetFromPoint;
|
|
153
|
+
private isPointerOverSourceHeader;
|
|
154
|
+
private isPointWithinBounds;
|
|
155
|
+
private ensureHeaderDragPlaceholder;
|
|
156
|
+
private updateHeaderDragPlaceholderPosition;
|
|
157
|
+
private clearHeaderDragPlaceholder;
|
|
153
158
|
private startDragPointerTracking;
|
|
154
159
|
private stopDragPointerTracking;
|
|
155
160
|
private onDragMouseMove;
|
|
@@ -159,6 +164,7 @@ declare class MintDockManagerElement extends HTMLElement {
|
|
|
159
164
|
private computeHeaderInsertIndex;
|
|
160
165
|
private reorderPaneInLocationAtIndex;
|
|
161
166
|
private onDragTouchEnd;
|
|
167
|
+
private finalizeDropFromPoint;
|
|
162
168
|
private clearPendingDragEndTimeout;
|
|
163
169
|
private scheduleDeferredDragEnd;
|
|
164
170
|
private onDrop;
|
|
@@ -1222,7 +1222,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1222
1222
|
this.handleFloatingResizeMove(event);
|
|
1223
1223
|
}
|
|
1224
1224
|
if (this.floatingDragState && event.pointerId === this.floatingDragState.pointerId) {
|
|
1225
|
-
console.warn('state', this.floatingDragState);
|
|
1226
1225
|
this.handleFloatingDragMove(event);
|
|
1227
1226
|
}
|
|
1228
1227
|
}
|
|
@@ -1265,6 +1264,8 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1265
1264
|
top,
|
|
1266
1265
|
width,
|
|
1267
1266
|
height,
|
|
1267
|
+
startClientX: event.clientX,
|
|
1268
|
+
startClientY: event.clientY,
|
|
1268
1269
|
};
|
|
1269
1270
|
}
|
|
1270
1271
|
clearPendingTabDragMetrics() {
|
|
@@ -1285,7 +1286,9 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1285
1286
|
this.shadowRoot?.appendChild(ghost);
|
|
1286
1287
|
// Use the ghost element as the drag image.
|
|
1287
1288
|
// The offset is set to where the user's cursor is on the original element.
|
|
1288
|
-
|
|
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);
|
|
1289
1292
|
// The ghost element is no longer needed after the drag image is set.
|
|
1290
1293
|
// We defer its removal to ensure the browser has captured it.
|
|
1291
1294
|
setTimeout(() => ghost.remove(), 0);
|
|
@@ -1296,6 +1299,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1296
1299
|
const headerBounds = headerRect
|
|
1297
1300
|
? { left: headerRect.left, top: headerRect.top, right: headerRect.right, bottom: headerRect.bottom }
|
|
1298
1301
|
: null;
|
|
1302
|
+
const metrics = this.pendingTabDragMetrics;
|
|
1299
1303
|
this.dragState = {
|
|
1300
1304
|
pane,
|
|
1301
1305
|
sourcePath: this.clonePath(sourcePath),
|
|
@@ -1305,9 +1309,27 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1305
1309
|
dropHandled: false,
|
|
1306
1310
|
sourceStackEl: stackEl,
|
|
1307
1311
|
sourceHeaderBounds: headerBounds,
|
|
1308
|
-
startClientX: Number.isFinite(
|
|
1309
|
-
|
|
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,
|
|
1310
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
|
+
}
|
|
1311
1333
|
// Prefer the pointer offset relative to the dragged tab to avoid jumps on conversion
|
|
1312
1334
|
if (Number.isFinite(event.offsetX)) {
|
|
1313
1335
|
this.dragState.pointerOffsetX = event.offsetX;
|
|
@@ -1319,6 +1341,28 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1319
1341
|
this.startDragPointerTracking();
|
|
1320
1342
|
event.dataTransfer.effectAllowed = 'move';
|
|
1321
1343
|
event.dataTransfer.setData('text/plain', pane);
|
|
1344
|
+
// Preferred UX: if the dragged tab is the only one in its stack,
|
|
1345
|
+
// immediately convert to a floating window unless it is already the
|
|
1346
|
+
// only pane in a floating window (this case is handled by reuse logic).
|
|
1347
|
+
if (this.dragState && this.dragState.floatingIndex !== null && this.dragState.floatingIndex < 0) {
|
|
1348
|
+
const loc = this.resolveStackLocation(this.dragState.sourcePath);
|
|
1349
|
+
if (loc && Array.isArray(loc.node.panes) && loc.node.panes.length === 1) {
|
|
1350
|
+
let shouldConvert = false;
|
|
1351
|
+
if (loc.context === "docked") {
|
|
1352
|
+
shouldConvert = true;
|
|
1353
|
+
}
|
|
1354
|
+
else if (loc.context === "floating") {
|
|
1355
|
+
const floating = this.floatingLayouts[loc.index];
|
|
1356
|
+
const totalPanes = floating && floating.root ? this.countPanesInTree(floating.root) : 0;
|
|
1357
|
+
shouldConvert = totalPanes > 1; // not the only pane in this floating window
|
|
1358
|
+
}
|
|
1359
|
+
if (shouldConvert) {
|
|
1360
|
+
const startX = Number.isFinite(event.clientX) ? event.clientX : (this.dragState.startClientX ?? 0);
|
|
1361
|
+
const startY = Number.isFinite(event.clientY) ? event.clientY : (this.dragState.startClientY ?? 0);
|
|
1362
|
+
this.convertPendingTabDragToFloating(startX, startY);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1322
1366
|
}
|
|
1323
1367
|
preparePaneDragSource(path, pane, stackEl, event) {
|
|
1324
1368
|
const location = this.resolveStackLocation(path);
|
|
@@ -1400,6 +1444,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1400
1444
|
const state = this.dragState;
|
|
1401
1445
|
this.dragState = null;
|
|
1402
1446
|
this.hideDropIndicator();
|
|
1447
|
+
this.clearHeaderDragPlaceholder();
|
|
1403
1448
|
this.stopDragPointerTracking();
|
|
1404
1449
|
this.lastDragPointerPosition = null;
|
|
1405
1450
|
if (state && state.floatingIndex !== null && !state.dropHandled) {
|
|
@@ -1435,6 +1480,21 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1435
1480
|
return;
|
|
1436
1481
|
}
|
|
1437
1482
|
const path = this.parsePath(stack.dataset['path']);
|
|
1483
|
+
// While reordering within the same header, suppress the joystick/indicator entirely
|
|
1484
|
+
if (this.dragState &&
|
|
1485
|
+
this.dragState.floatingIndex !== null &&
|
|
1486
|
+
this.dragState.floatingIndex < 0 &&
|
|
1487
|
+
path &&
|
|
1488
|
+
this.pathsEqual(path, this.dragState.sourcePath)) {
|
|
1489
|
+
const px = (point ? point.clientX : event.clientX);
|
|
1490
|
+
const py = (point ? point.clientY : event.clientY);
|
|
1491
|
+
if (Number.isFinite(px) && Number.isFinite(py) && this.isPointerOverSourceHeader(px, py)) {
|
|
1492
|
+
// Drive live reorder using the unified path so we update instantly.
|
|
1493
|
+
this.updatePaneDragDropTargetFromPoint(px, py);
|
|
1494
|
+
this.hideDropIndicator();
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1438
1498
|
// If the hovered stack changed, clear any sticky zone from the previous
|
|
1439
1499
|
// target before computing the new zone.
|
|
1440
1500
|
if (this.dropJoystickTarget && this.dropJoystickTarget !== stack) {
|
|
@@ -1450,10 +1510,10 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1450
1510
|
if (!this.dragState) {
|
|
1451
1511
|
return;
|
|
1452
1512
|
}
|
|
1453
|
-
const { clientX, clientY
|
|
1513
|
+
const { clientX, clientY } = event;
|
|
1454
1514
|
const hasValidCoordinates = Number.isFinite(clientX) &&
|
|
1455
1515
|
Number.isFinite(clientY) &&
|
|
1456
|
-
!(clientX === 0 && clientY === 0
|
|
1516
|
+
!(clientX === 0 && clientY === 0);
|
|
1457
1517
|
if (hasValidCoordinates) {
|
|
1458
1518
|
this.lastDragPointerPosition = { x: clientX, y: clientY };
|
|
1459
1519
|
this.updateDraggedFloatingPositionFromPoint(clientX, clientY);
|
|
@@ -1477,7 +1537,31 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1477
1537
|
this.updateDraggedFloatingPosition(event);
|
|
1478
1538
|
}
|
|
1479
1539
|
onGlobalDragEnd() {
|
|
1480
|
-
|
|
1540
|
+
// Attempt to finalize a drop even if the drop event doesn't reach us (Firefox/edge cases)
|
|
1541
|
+
const state = this.dragState;
|
|
1542
|
+
const pos = this.lastDragPointerPosition;
|
|
1543
|
+
if (state && pos) {
|
|
1544
|
+
const stack = this.findStackAtPoint(pos.x, pos.y);
|
|
1545
|
+
const joystickVisible = this.dropJoystick.dataset['visible'] === 'true';
|
|
1546
|
+
const joystickPath = this.parsePath(this.dropJoystick.dataset['path']);
|
|
1547
|
+
const joystickTarget = this.dropJoystickTarget;
|
|
1548
|
+
const joystickTargetPath = joystickTarget ? this.parsePath(joystickTarget.dataset['path']) : null;
|
|
1549
|
+
const path = stack ? this.parsePath(stack.dataset['path']) : (joystickPath ?? joystickTargetPath);
|
|
1550
|
+
const joystickZone = this.dropJoystick.dataset['zone'];
|
|
1551
|
+
const zone = this.isDropZone(joystickZone)
|
|
1552
|
+
? joystickZone
|
|
1553
|
+
: (stack ? this.computeDropZone(stack, { clientX: pos.x, clientY: pos.y }, null) : null);
|
|
1554
|
+
if (path && this.isDropZone(zone)) {
|
|
1555
|
+
this.handleDrop(path, zone);
|
|
1556
|
+
this.hideDropIndicator();
|
|
1557
|
+
if (this.dragState) {
|
|
1558
|
+
this.dragState.dropHandled = true;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
else {
|
|
1563
|
+
this.hideDropIndicator();
|
|
1564
|
+
}
|
|
1481
1565
|
if (!this.dragState) {
|
|
1482
1566
|
this.clearPendingTabDragMetrics();
|
|
1483
1567
|
return;
|
|
@@ -1492,17 +1576,22 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1492
1576
|
if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
|
|
1493
1577
|
return;
|
|
1494
1578
|
}
|
|
1495
|
-
//
|
|
1579
|
+
// Ignore obviously bogus coordinates sometimes seen during HTML5 drag
|
|
1580
|
+
if (clientX === 0 && clientY === 0) {
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
// If still dragging a tab inside its header, only convert to floating once we leave the header.
|
|
1496
1584
|
if (this.dragState.floatingIndex !== null && this.dragState.floatingIndex < 0) {
|
|
1497
1585
|
const b = this.dragState.sourceHeaderBounds;
|
|
1498
1586
|
const sx = this.dragState.startClientX ?? clientX;
|
|
1499
1587
|
const sy = this.dragState.startClientY ?? clientY;
|
|
1500
1588
|
const dist = Math.hypot(clientX - sx, clientY - sy);
|
|
1501
|
-
const threshold =
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1589
|
+
const threshold = 8; // pixels to move before converting (tuned up)
|
|
1590
|
+
// Default to inside while bounds are unknown to avoid premature floating
|
|
1591
|
+
let insideHeader = true;
|
|
1592
|
+
const insideByBounds = b ? this.isPointWithinBounds(b, clientX, clientY) : true;
|
|
1593
|
+
const insideByHitTest = this.isPointerOverSourceHeader(clientX, clientY);
|
|
1594
|
+
insideHeader = insideByBounds || insideByHitTest;
|
|
1506
1595
|
if (!insideHeader && dist > threshold) {
|
|
1507
1596
|
// Convert to floating now using current pointer position
|
|
1508
1597
|
this.convertPendingTabDragToFloating(clientX, clientY);
|
|
@@ -1540,6 +1629,8 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1540
1629
|
if (this.dropJoystick.dataset['visible'] !== 'true') {
|
|
1541
1630
|
this.hideDropIndicator();
|
|
1542
1631
|
}
|
|
1632
|
+
// Also ensure any in-header placeholder is cleared when not over a stack
|
|
1633
|
+
this.clearHeaderDragPlaceholder();
|
|
1543
1634
|
return;
|
|
1544
1635
|
}
|
|
1545
1636
|
// If we moved to a different target stack, reset any sticky zone so
|
|
@@ -1548,10 +1639,122 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1548
1639
|
delete this.dropJoystick.dataset['zone'];
|
|
1549
1640
|
this.updateDropJoystickActiveZone(null);
|
|
1550
1641
|
}
|
|
1642
|
+
// Previous behavior hid the indicator and returned early here; instead,
|
|
1643
|
+
// allow the live-reorder branch below to handle in-header drags.
|
|
1644
|
+
// While dragging within the same header, show a placeholder and suppress joystick/indicator
|
|
1645
|
+
if (this.dragState &&
|
|
1646
|
+
this.dragState.floatingIndex !== null &&
|
|
1647
|
+
this.dragState.floatingIndex < 0 &&
|
|
1648
|
+
path &&
|
|
1649
|
+
this.pathsEqual(path, this.dragState.sourcePath)) {
|
|
1650
|
+
const inHeaderByBounds = !!this.dragState.sourceHeaderBounds && this.isPointWithinBounds(this.dragState.sourceHeaderBounds, clientX, clientY);
|
|
1651
|
+
const inHeaderByHitTest = this.isPointerOverSourceHeader(clientX, clientY);
|
|
1652
|
+
if (inHeaderByBounds || inHeaderByHitTest) {
|
|
1653
|
+
const header = stack.querySelector('.dock-stack__header');
|
|
1654
|
+
if (header) {
|
|
1655
|
+
// Ensure placeholder exists and move it as the pointer moves
|
|
1656
|
+
this.ensureHeaderDragPlaceholder(header, this.dragState.pane);
|
|
1657
|
+
const idx = this.computeHeaderInsertIndex(header, clientX);
|
|
1658
|
+
if (this.dragState.liveReorderIndex !== idx) {
|
|
1659
|
+
this.updateHeaderDragPlaceholderPosition(header, idx);
|
|
1660
|
+
// Keep model reordering until drop; only move the placeholder now
|
|
1661
|
+
this.dragState.liveReorderIndex = idx;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
this.hideDropIndicator();
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
// Leaving the header: ensure any placeholder is removed immediately
|
|
1669
|
+
this.clearHeaderDragPlaceholder();
|
|
1551
1670
|
const zoneHint = this.findDropZoneByPoint(clientX, clientY);
|
|
1552
1671
|
const zone = this.computeDropZone(stack, { clientX, clientY }, zoneHint);
|
|
1553
1672
|
this.showDropIndicator(stack, zone);
|
|
1554
1673
|
}
|
|
1674
|
+
// Returns true when the pointer is currently over the source stack's header (tab strip)
|
|
1675
|
+
isPointerOverSourceHeader(clientX, clientY) {
|
|
1676
|
+
const state = this.dragState;
|
|
1677
|
+
if (!state) {
|
|
1678
|
+
return false;
|
|
1679
|
+
}
|
|
1680
|
+
const stackEl = state.sourceStackEl ?? null;
|
|
1681
|
+
const header = stackEl?.querySelector('.dock-stack__header');
|
|
1682
|
+
if (!header) {
|
|
1683
|
+
// Be conservative: if we cannot resolve the header, treat as inside
|
|
1684
|
+
return true;
|
|
1685
|
+
}
|
|
1686
|
+
const sr = this.shadowRoot;
|
|
1687
|
+
const elements = sr ? sr.elementsFromPoint(clientX, clientY) : [];
|
|
1688
|
+
for (const el of elements) {
|
|
1689
|
+
if (el instanceof HTMLElement && header.contains(el)) {
|
|
1690
|
+
return true;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return false;
|
|
1694
|
+
}
|
|
1695
|
+
isPointWithinBounds(bounds, x, y) {
|
|
1696
|
+
return x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom;
|
|
1697
|
+
}
|
|
1698
|
+
// Ensure a placeholder tab exists during in-header drag and hide the real dragged tab visually
|
|
1699
|
+
ensureHeaderDragPlaceholder(header, pane) {
|
|
1700
|
+
if (this.dragState?.placeholderHeader === header && this.dragState.placeholderEl) {
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
const dragged = Array.from(header.querySelectorAll('.dock-tab')).find((t) => t.dataset['pane'] === pane) ?? null;
|
|
1704
|
+
if (!dragged) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
// Create placeholder
|
|
1708
|
+
const placeholder = this.documentRef.createElement('button');
|
|
1709
|
+
placeholder.type = 'button';
|
|
1710
|
+
placeholder.classList.add('dock-tab');
|
|
1711
|
+
placeholder.dataset['placeholder'] = 'true';
|
|
1712
|
+
// Keep the placeholder visually empty but reserving the same width
|
|
1713
|
+
placeholder.textContent = '';
|
|
1714
|
+
placeholder.setAttribute('aria-hidden', 'true');
|
|
1715
|
+
placeholder.style.width = `${dragged.offsetWidth}px`;
|
|
1716
|
+
// Hide the original dragged tab so it doesn't duplicate visually and free up its slot
|
|
1717
|
+
dragged.style.display = 'none';
|
|
1718
|
+
// Insert placeholder in the original position of the dragged tab
|
|
1719
|
+
header.insertBefore(placeholder, dragged);
|
|
1720
|
+
if (this.dragState) {
|
|
1721
|
+
this.dragState.placeholderHeader = header;
|
|
1722
|
+
this.dragState.placeholderEl = placeholder;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
// Move the placeholder to the computed target index within the header
|
|
1726
|
+
updateHeaderDragPlaceholderPosition(header, targetIndex) {
|
|
1727
|
+
const placeholder = this.dragState?.placeholderEl ?? null;
|
|
1728
|
+
if (!placeholder) {
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
const draggedPane = this.dragState?.pane ?? null;
|
|
1732
|
+
const tabs = Array.from(header.querySelectorAll('.dock-tab'))
|
|
1733
|
+
.filter((t) => t !== placeholder && (!draggedPane || t.dataset['pane'] !== draggedPane));
|
|
1734
|
+
const clampedTarget = Math.max(0, Math.min(targetIndex, tabs.length));
|
|
1735
|
+
const ref = tabs[clampedTarget] ?? null;
|
|
1736
|
+
header.insertBefore(placeholder, ref);
|
|
1737
|
+
}
|
|
1738
|
+
// Remove placeholder and restore original tab visibility
|
|
1739
|
+
clearHeaderDragPlaceholder() {
|
|
1740
|
+
const ph = this.dragState?.placeholderEl ?? null;
|
|
1741
|
+
const header = this.dragState?.placeholderHeader ?? null;
|
|
1742
|
+
if (header) {
|
|
1743
|
+
const dragged = this.dragState?.pane
|
|
1744
|
+
? (Array.from(header.querySelectorAll('.dock-tab')).find((t) => t.dataset['pane'] === this.dragState?.pane) ?? null)
|
|
1745
|
+
: null;
|
|
1746
|
+
if (dragged) {
|
|
1747
|
+
dragged.style.display = '';
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (ph && ph.parentElement) {
|
|
1751
|
+
ph.parentElement.removeChild(ph);
|
|
1752
|
+
}
|
|
1753
|
+
if (this.dragState) {
|
|
1754
|
+
this.dragState.placeholderEl = null;
|
|
1755
|
+
this.dragState.placeholderHeader = null;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1555
1758
|
startDragPointerTracking() {
|
|
1556
1759
|
if (this.dragPointerTrackingActive) {
|
|
1557
1760
|
return;
|
|
@@ -1606,6 +1809,13 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1606
1809
|
this.updateDraggedFloatingPositionFromPoint(touch.clientX, touch.clientY);
|
|
1607
1810
|
}
|
|
1608
1811
|
onDragMouseUp() {
|
|
1812
|
+
// Prefer committing a drop from pointer-up since some browsers suppress drop events
|
|
1813
|
+
if (this.dragState) {
|
|
1814
|
+
const pos = this.lastDragPointerPosition;
|
|
1815
|
+
if (pos) {
|
|
1816
|
+
this.finalizeDropFromPoint(pos.x, pos.y);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1609
1819
|
this.handleDragPointerUpCommon();
|
|
1610
1820
|
}
|
|
1611
1821
|
// Convert a currently in-header tab drag into a floating window
|
|
@@ -1617,6 +1827,8 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1617
1827
|
if (state.floatingIndex !== null && state.floatingIndex >= 0) {
|
|
1618
1828
|
return; // already floating
|
|
1619
1829
|
}
|
|
1830
|
+
// Clean up any placeholder before converting
|
|
1831
|
+
this.clearHeaderDragPlaceholder();
|
|
1620
1832
|
const location = this.resolveStackLocation(state.sourcePath);
|
|
1621
1833
|
if (!location) {
|
|
1622
1834
|
return;
|
|
@@ -1686,19 +1898,41 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1686
1898
|
this.dispatchLayoutChanged();
|
|
1687
1899
|
}
|
|
1688
1900
|
// Compute the intended tab insert index within a header based on pointer X
|
|
1901
|
+
// Adds a slight rightward bias and uses the placeholder rect (if present)
|
|
1902
|
+
// to ensure offsets are correct even when the dragged tab is display:none.
|
|
1689
1903
|
computeHeaderInsertIndex(header, clientX) {
|
|
1690
|
-
const
|
|
1691
|
-
if (
|
|
1904
|
+
const allTabs = Array.from(header.querySelectorAll('.dock-tab'));
|
|
1905
|
+
if (allTabs.length === 0) {
|
|
1906
|
+
return 0;
|
|
1907
|
+
}
|
|
1908
|
+
const draggedPane = this.dragState?.pane ?? null;
|
|
1909
|
+
const draggedEl = draggedPane
|
|
1910
|
+
? (allTabs.find((t) => t.dataset['pane'] === draggedPane) ?? null)
|
|
1911
|
+
: null;
|
|
1912
|
+
const placeholderEl = header.querySelector('.dock-tab[data-placeholder="true"]');
|
|
1913
|
+
const targets = allTabs.filter((t) => t !== draggedEl && t !== placeholderEl);
|
|
1914
|
+
if (targets.length === 0) {
|
|
1692
1915
|
return 0;
|
|
1693
1916
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1917
|
+
const rightBias = 12;
|
|
1918
|
+
const leftBias = 0;
|
|
1919
|
+
const baseRect = placeholderEl
|
|
1920
|
+
? placeholderEl.getBoundingClientRect()
|
|
1921
|
+
: draggedEl
|
|
1922
|
+
? draggedEl.getBoundingClientRect()
|
|
1923
|
+
: null;
|
|
1924
|
+
const rectValid = !!baseRect && Number.isFinite(baseRect.width) && baseRect.width > 0;
|
|
1925
|
+
const draggedCenter = rectValid && baseRect ? baseRect.left + baseRect.width / 2 : null;
|
|
1926
|
+
for (let i = 0; i < targets.length; i += 1) {
|
|
1927
|
+
const rect = targets[i].getBoundingClientRect();
|
|
1928
|
+
const baseMid = rect.left + rect.width / 2;
|
|
1929
|
+
const isRightOfDragged = draggedCenter !== null ? baseMid >= draggedCenter : false;
|
|
1930
|
+
const mid = isRightOfDragged ? baseMid + rightBias : baseMid - leftBias;
|
|
1697
1931
|
if (clientX < mid) {
|
|
1698
1932
|
return i;
|
|
1699
1933
|
}
|
|
1700
1934
|
}
|
|
1701
|
-
return
|
|
1935
|
+
return targets.length;
|
|
1702
1936
|
}
|
|
1703
1937
|
reorderPaneInLocationAtIndex(location, pane, targetIndex) {
|
|
1704
1938
|
const panes = location.node.panes;
|
|
@@ -1723,6 +1957,49 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1723
1957
|
onDragTouchEnd() {
|
|
1724
1958
|
this.handleDragPointerUpCommon();
|
|
1725
1959
|
}
|
|
1960
|
+
// Commit a drop using current pointer coordinates and joystick state
|
|
1961
|
+
finalizeDropFromPoint(clientX, clientY) {
|
|
1962
|
+
if (!this.dragState) {
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
const stack = this.findStackAtPoint(clientX, clientY);
|
|
1966
|
+
const stackPath = stack ? this.parsePath(stack.dataset['path']) : null;
|
|
1967
|
+
const joystickVisible = this.dropJoystick.dataset['visible'] === 'true';
|
|
1968
|
+
const joystickStoredPath = this.parsePath(this.dropJoystick.dataset['path']);
|
|
1969
|
+
const joystickTarget = this.dropJoystickTarget;
|
|
1970
|
+
const joystickTargetPath = joystickTarget ? this.parsePath(joystickTarget.dataset['path']) : null;
|
|
1971
|
+
const path = (joystickVisible ? (joystickStoredPath ?? joystickTargetPath) : null) ?? stackPath;
|
|
1972
|
+
const joystickZone = this.dropJoystick.dataset['zone'];
|
|
1973
|
+
const zone = this.isDropZone(joystickZone)
|
|
1974
|
+
? joystickZone
|
|
1975
|
+
: (stack ? this.computeDropZone(stack, { clientX, clientY }, null) : null);
|
|
1976
|
+
// Same-header reorder case when no side zone is chosen
|
|
1977
|
+
if (this.dragState &&
|
|
1978
|
+
this.dragState.floatingIndex !== null &&
|
|
1979
|
+
this.dragState.floatingIndex < 0 &&
|
|
1980
|
+
stack &&
|
|
1981
|
+
path &&
|
|
1982
|
+
stackPath &&
|
|
1983
|
+
this.pathsEqual(stackPath, this.dragState.sourcePath) &&
|
|
1984
|
+
(!zone || zone === 'center')) {
|
|
1985
|
+
const header = stack.querySelector('.dock-stack__header');
|
|
1986
|
+
if (header) {
|
|
1987
|
+
const location = this.resolveStackLocation(path);
|
|
1988
|
+
if (location) {
|
|
1989
|
+
const idx = this.computeHeaderInsertIndex(header, clientX);
|
|
1990
|
+
this.reorderPaneInLocationAtIndex(location, this.dragState.pane, idx);
|
|
1991
|
+
this.render();
|
|
1992
|
+
this.dispatchLayoutChanged();
|
|
1993
|
+
this.dragState.dropHandled = true;
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
if (path && this.isDropZone(zone)) {
|
|
1999
|
+
this.handleDrop(path, zone);
|
|
2000
|
+
this.dragState.dropHandled = true;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
1726
2003
|
clearPendingDragEndTimeout() {
|
|
1727
2004
|
if (this.pendingDragEndTimeout !== null) {
|
|
1728
2005
|
const win = this.windowRef;
|
|
@@ -1771,18 +2048,37 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1771
2048
|
: null);
|
|
1772
2049
|
const stack = this.findStackElement(event) ??
|
|
1773
2050
|
(point ? this.findStackAtPoint(point.clientX, point.clientY) : null);
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
2051
|
+
// Prefer joystick's stored target path when the joystick is visible (drop over buttons)
|
|
2052
|
+
const joystickVisible = this.dropJoystick.dataset['visible'] === 'true';
|
|
2053
|
+
const joystickPath = this.parsePath(this.dropJoystick.dataset['path']);
|
|
2054
|
+
const joystickTarget = this.dropJoystickTarget;
|
|
2055
|
+
const joystickTargetPath = joystickTarget ? this.parsePath(joystickTarget.dataset['path']) : null;
|
|
2056
|
+
let path = stack
|
|
2057
|
+
? this.parsePath(stack.dataset['path'])
|
|
2058
|
+
: (joystickPath ?? joystickTargetPath);
|
|
2059
|
+
if (!path && joystickVisible) {
|
|
2060
|
+
// As a last resort, target the main dock surface only when empty
|
|
2061
|
+
const dockPath = this.parsePath(this.dockedEl.dataset['path']);
|
|
2062
|
+
path = (!this.rootLayout ? dockPath : null);
|
|
2063
|
+
}
|
|
2064
|
+
// Defer same-header reorder decision until after zone resolution below
|
|
2065
|
+
// Prefer joystick's active zone if available, else infer from event/point
|
|
2066
|
+
const joystickZone = this.dropJoystick.dataset['zone'];
|
|
2067
|
+
const eventZoneHint = this.extractDropZoneFromEvent(event);
|
|
2068
|
+
const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
|
|
2069
|
+
const zone = this.isDropZone(joystickZone)
|
|
2070
|
+
? joystickZone
|
|
2071
|
+
: stack
|
|
2072
|
+
? this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint)
|
|
2073
|
+
: (this.isDropZone(pointZoneHint ?? eventZoneHint) ? (pointZoneHint ?? eventZoneHint) : null);
|
|
2074
|
+
// If still in same header and no side zone chosen, treat as in-header reorder
|
|
1781
2075
|
if (this.dragState &&
|
|
1782
2076
|
this.dragState.floatingIndex !== null &&
|
|
1783
2077
|
this.dragState.floatingIndex < 0 &&
|
|
2078
|
+
stack &&
|
|
1784
2079
|
path &&
|
|
1785
|
-
this.pathsEqual(path, this.dragState.sourcePath)
|
|
2080
|
+
this.pathsEqual(path, this.dragState.sourcePath) &&
|
|
2081
|
+
(!zone || zone === 'center')) {
|
|
1786
2082
|
const header = stack.querySelector('.dock-stack__header');
|
|
1787
2083
|
if (header) {
|
|
1788
2084
|
const x = (point ? point.clientX : event.clientX);
|
|
@@ -1800,9 +2096,12 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1800
2096
|
}
|
|
1801
2097
|
}
|
|
1802
2098
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
2099
|
+
// If joystick is visible and both path and zone are resolved, force using joystick as authoritative
|
|
2100
|
+
if (joystickVisible && path && this.isDropZone(joystickZone)) {
|
|
2101
|
+
this.handleDrop(path, joystickZone);
|
|
2102
|
+
this.endPaneDrag();
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
1806
2105
|
if (!zone) {
|
|
1807
2106
|
this.hideDropIndicator();
|
|
1808
2107
|
this.endPaneDrag();
|