@mintplayer/ng-bootstrap 20.5.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.
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
- event.dataTransfer.setDragImage(ghost, event.offsetX, event.offsetY);
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(event.clientX) ? event.clientX : undefined,
1309
- startClientY: Number.isFinite(event.clientY) ? event.clientY : undefined,
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;
@@ -1400,6 +1422,7 @@ class MintDockManagerElement extends HTMLElement {
1400
1422
  const state = this.dragState;
1401
1423
  this.dragState = null;
1402
1424
  this.hideDropIndicator();
1425
+ this.clearHeaderDragPlaceholder();
1403
1426
  this.stopDragPointerTracking();
1404
1427
  this.lastDragPointerPosition = null;
1405
1428
  if (state && state.floatingIndex !== null && !state.dropHandled) {
@@ -1435,6 +1458,21 @@ class MintDockManagerElement extends HTMLElement {
1435
1458
  return;
1436
1459
  }
1437
1460
  const path = this.parsePath(stack.dataset['path']);
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
+ }
1438
1476
  // If the hovered stack changed, clear any sticky zone from the previous
1439
1477
  // target before computing the new zone.
1440
1478
  if (this.dropJoystickTarget && this.dropJoystickTarget !== stack) {
@@ -1450,10 +1488,10 @@ class MintDockManagerElement extends HTMLElement {
1450
1488
  if (!this.dragState) {
1451
1489
  return;
1452
1490
  }
1453
- const { clientX, clientY, screenX, screenY } = event;
1491
+ const { clientX, clientY } = event;
1454
1492
  const hasValidCoordinates = Number.isFinite(clientX) &&
1455
1493
  Number.isFinite(clientY) &&
1456
- !(clientX === 0 && clientY === 0 && screenX === 0 && screenY === 0);
1494
+ !(clientX === 0 && clientY === 0);
1457
1495
  if (hasValidCoordinates) {
1458
1496
  this.lastDragPointerPosition = { x: clientX, y: clientY };
1459
1497
  this.updateDraggedFloatingPositionFromPoint(clientX, clientY);
@@ -1477,7 +1515,31 @@ class MintDockManagerElement extends HTMLElement {
1477
1515
  this.updateDraggedFloatingPosition(event);
1478
1516
  }
1479
1517
  onGlobalDragEnd() {
1480
- this.hideDropIndicator();
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
+ }
1481
1543
  if (!this.dragState) {
1482
1544
  this.clearPendingTabDragMetrics();
1483
1545
  return;
@@ -1492,17 +1554,22 @@ class MintDockManagerElement extends HTMLElement {
1492
1554
  if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
1493
1555
  return;
1494
1556
  }
1495
- // If we are still dragging a tab inside its header, only convert to floating once we leave the header bounds.
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.
1496
1562
  if (this.dragState.floatingIndex !== null && this.dragState.floatingIndex < 0) {
1497
1563
  const b = this.dragState.sourceHeaderBounds;
1498
1564
  const sx = this.dragState.startClientX ?? clientX;
1499
1565
  const sy = this.dragState.startClientY ?? clientY;
1500
1566
  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
- }
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;
1506
1573
  if (!insideHeader && dist > threshold) {
1507
1574
  // Convert to floating now using current pointer position
1508
1575
  this.convertPendingTabDragToFloating(clientX, clientY);
@@ -1548,10 +1615,120 @@ class MintDockManagerElement extends HTMLElement {
1548
1615
  delete this.dropJoystick.dataset['zone'];
1549
1616
  this.updateDropJoystickActiveZone(null);
1550
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
+ }
1647
+ }
1551
1648
  const zoneHint = this.findDropZoneByPoint(clientX, clientY);
1552
1649
  const zone = this.computeDropZone(stack, { clientX, clientY }, zoneHint);
1553
1650
  this.showDropIndicator(stack, zone);
1554
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
+ }
1555
1732
  startDragPointerTracking() {
1556
1733
  if (this.dragPointerTrackingActive) {
1557
1734
  return;
@@ -1606,6 +1783,13 @@ class MintDockManagerElement extends HTMLElement {
1606
1783
  this.updateDraggedFloatingPositionFromPoint(touch.clientX, touch.clientY);
1607
1784
  }
1608
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
+ }
1609
1793
  this.handleDragPointerUpCommon();
1610
1794
  }
1611
1795
  // Convert a currently in-header tab drag into a floating window
@@ -1617,6 +1801,8 @@ class MintDockManagerElement extends HTMLElement {
1617
1801
  if (state.floatingIndex !== null && state.floatingIndex >= 0) {
1618
1802
  return; // already floating
1619
1803
  }
1804
+ // Clean up any placeholder before converting
1805
+ this.clearHeaderDragPlaceholder();
1620
1806
  const location = this.resolveStackLocation(state.sourcePath);
1621
1807
  if (!location) {
1622
1808
  return;
@@ -1687,7 +1873,8 @@ class MintDockManagerElement extends HTMLElement {
1687
1873
  }
1688
1874
  // Compute the intended tab insert index within a header based on pointer X
1689
1875
  computeHeaderInsertIndex(header, clientX) {
1690
- const tabs = Array.from(header.querySelectorAll('.dock-tab'));
1876
+ const tabs = Array.from(header.querySelectorAll('.dock-tab'))
1877
+ .filter((t) => t.dataset['placeholder'] !== 'true');
1691
1878
  if (tabs.length === 0) {
1692
1879
  return 0;
1693
1880
  }
@@ -1723,6 +1910,49 @@ class MintDockManagerElement extends HTMLElement {
1723
1910
  onDragTouchEnd() {
1724
1911
  this.handleDragPointerUpCommon();
1725
1912
  }
1913
+ // Commit a drop using current pointer coordinates and joystick state
1914
+ finalizeDropFromPoint(clientX, clientY) {
1915
+ if (!this.dragState) {
1916
+ return;
1917
+ }
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
+ }
1955
+ }
1726
1956
  clearPendingDragEndTimeout() {
1727
1957
  if (this.pendingDragEndTimeout !== null) {
1728
1958
  const win = this.windowRef;
@@ -1771,18 +2001,37 @@ class MintDockManagerElement extends HTMLElement {
1771
2001
  : null);
1772
2002
  const stack = this.findStackElement(event) ??
1773
2003
  (point ? this.findStackAtPoint(point.clientX, point.clientY) : null);
1774
- if (!stack) {
1775
- this.hideDropIndicator();
1776
- this.endPaneDrag();
1777
- return;
1778
- }
1779
- const path = this.parsePath(stack.dataset['path']);
1780
- // Allow reordering within the same stack header without selecting a zone
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
1781
2028
  if (this.dragState &&
1782
2029
  this.dragState.floatingIndex !== null &&
1783
2030
  this.dragState.floatingIndex < 0 &&
2031
+ stack &&
1784
2032
  path &&
1785
- this.pathsEqual(path, this.dragState.sourcePath)) {
2033
+ this.pathsEqual(path, this.dragState.sourcePath) &&
2034
+ (!zone || zone === 'center')) {
1786
2035
  const header = stack.querySelector('.dock-stack__header');
1787
2036
  if (header) {
1788
2037
  const x = (point ? point.clientX : event.clientX);
@@ -1800,9 +2049,12 @@ class MintDockManagerElement extends HTMLElement {
1800
2049
  }
1801
2050
  }
1802
2051
  }
1803
- const eventZoneHint = this.extractDropZoneFromEvent(event);
1804
- const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
1805
- const zone = this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint);
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);
2055
+ this.endPaneDrag();
2056
+ return;
2057
+ }
1806
2058
  if (!zone) {
1807
2059
  this.hideDropIndicator();
1808
2060
  this.endPaneDrag();