dockview-core 6.2.2 → 6.4.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.
Files changed (145) hide show
  1. package/README.md +1 -0
  2. package/dist/cjs/api/dockviewGroupPanelApi.d.ts +10 -1
  3. package/dist/cjs/api/dockviewGroupPanelApi.js +16 -0
  4. package/dist/cjs/dnd/backend.d.ts +70 -0
  5. package/dist/cjs/dnd/backend.js +171 -0
  6. package/dist/cjs/dnd/dropOverlay.d.ts +20 -0
  7. package/dist/cjs/dnd/dropOverlay.js +197 -0
  8. package/dist/cjs/dnd/droptarget.d.ts +20 -6
  9. package/dist/cjs/dnd/droptarget.js +14 -208
  10. package/dist/cjs/dnd/pointer/index.d.ts +11 -0
  11. package/dist/cjs/dnd/pointer/index.js +13 -0
  12. package/dist/cjs/dnd/pointer/longPress.d.ts +32 -0
  13. package/dist/cjs/dnd/pointer/longPress.js +151 -0
  14. package/dist/cjs/dnd/pointer/pointerDragController.d.ts +60 -0
  15. package/dist/cjs/dnd/pointer/pointerDragController.js +241 -0
  16. package/dist/cjs/dnd/pointer/pointerDragSource.d.ts +61 -0
  17. package/dist/cjs/dnd/pointer/pointerDragSource.js +195 -0
  18. package/dist/cjs/dnd/pointer/pointerDropTarget.d.ts +39 -0
  19. package/dist/cjs/dnd/pointer/pointerDropTarget.js +198 -0
  20. package/dist/cjs/dnd/pointer/pointerGhost.d.ts +30 -0
  21. package/dist/cjs/dnd/pointer/pointerGhost.js +44 -0
  22. package/dist/cjs/dnd/pointer/types.d.ts +16 -0
  23. package/dist/cjs/dnd/pointer/types.js +2 -0
  24. package/dist/cjs/dockview/components/panel/content.d.ts +3 -1
  25. package/dist/cjs/dockview/components/panel/content.js +33 -16
  26. package/dist/cjs/dockview/components/popupService.js +34 -0
  27. package/dist/cjs/dockview/components/tab/tab.d.ts +11 -3
  28. package/dist/cjs/dockview/components/tab/tab.js +151 -117
  29. package/dist/cjs/dockview/components/titlebar/tabGroupChip.d.ts +9 -2
  30. package/dist/cjs/dockview/components/titlebar/tabGroupChip.js +15 -6
  31. package/dist/cjs/dockview/components/titlebar/tabGroupIndicator.js +2 -2
  32. package/dist/cjs/dockview/components/titlebar/tabGroups.d.ts +33 -5
  33. package/dist/cjs/dockview/components/titlebar/tabGroups.js +231 -40
  34. package/dist/cjs/dockview/components/titlebar/tabs.d.ts +38 -1
  35. package/dist/cjs/dockview/components/titlebar/tabs.js +381 -253
  36. package/dist/cjs/dockview/components/titlebar/tabsContainer.d.ts +5 -3
  37. package/dist/cjs/dockview/components/titlebar/voidContainer.d.ts +6 -2
  38. package/dist/cjs/dockview/components/titlebar/voidContainer.js +190 -22
  39. package/dist/cjs/dockview/contextMenu.js +19 -4
  40. package/dist/cjs/dockview/dndCapabilities.d.ts +19 -0
  41. package/dist/cjs/dockview/dndCapabilities.js +39 -0
  42. package/dist/cjs/dockview/dockviewComponent.d.ts +2 -0
  43. package/dist/cjs/dockview/dockviewComponent.js +241 -158
  44. package/dist/cjs/dockview/dockviewGroupPanelModel.d.ts +10 -5
  45. package/dist/cjs/dockview/dockviewGroupPanelModel.js +34 -11
  46. package/dist/cjs/dockview/dockviewPanel.js +5 -0
  47. package/dist/cjs/dockview/dockviewPanelModel.d.ts +2 -0
  48. package/dist/cjs/dockview/dockviewPanelModel.js +8 -0
  49. package/dist/cjs/dockview/events.d.ts +2 -1
  50. package/dist/cjs/dockview/events.js +1 -0
  51. package/dist/cjs/dockview/framework.d.ts +8 -0
  52. package/dist/cjs/dockview/options.d.ts +31 -5
  53. package/dist/cjs/dockview/options.js +3 -0
  54. package/dist/cjs/dom.d.ts +5 -1
  55. package/dist/cjs/dom.js +21 -5
  56. package/dist/cjs/index.d.ts +1 -1
  57. package/dist/cjs/overlay/overlay.d.ts +12 -0
  58. package/dist/cjs/overlay/overlay.js +84 -16
  59. package/dist/cjs/paneview/draggablePaneviewPanel.d.ts +3 -1
  60. package/dist/cjs/paneview/draggablePaneviewPanel.js +27 -26
  61. package/dist/cjs/paneview/options.d.ts +4 -3
  62. package/dist/cjs/popoutWindow.d.ts +2 -0
  63. package/dist/cjs/popoutWindow.js +3 -1
  64. package/dist/dockview-core.js +2431 -937
  65. package/dist/dockview-core.min.js +2 -2
  66. package/dist/dockview-core.min.js.map +1 -1
  67. package/dist/dockview-core.min.noStyle.js +2 -2
  68. package/dist/dockview-core.min.noStyle.js.map +1 -1
  69. package/dist/dockview-core.noStyle.js +2430 -936
  70. package/dist/esm/api/dockviewGroupPanelApi.d.ts +10 -1
  71. package/dist/esm/api/dockviewGroupPanelApi.js +12 -0
  72. package/dist/esm/dnd/backend.d.ts +70 -0
  73. package/dist/esm/dnd/backend.js +148 -0
  74. package/dist/esm/dnd/dropOverlay.d.ts +20 -0
  75. package/dist/esm/dnd/dropOverlay.js +192 -0
  76. package/dist/esm/dnd/droptarget.d.ts +20 -6
  77. package/dist/esm/dnd/droptarget.js +16 -210
  78. package/dist/esm/dnd/pointer/index.d.ts +11 -0
  79. package/dist/esm/dnd/pointer/index.js +5 -0
  80. package/dist/esm/dnd/pointer/longPress.d.ts +32 -0
  81. package/dist/esm/dnd/pointer/longPress.js +127 -0
  82. package/dist/esm/dnd/pointer/pointerDragController.d.ts +60 -0
  83. package/dist/esm/dnd/pointer/pointerDragController.js +191 -0
  84. package/dist/esm/dnd/pointer/pointerDragSource.d.ts +61 -0
  85. package/dist/esm/dnd/pointer/pointerDragSource.js +171 -0
  86. package/dist/esm/dnd/pointer/pointerDropTarget.d.ts +39 -0
  87. package/dist/esm/dnd/pointer/pointerDropTarget.js +168 -0
  88. package/dist/esm/dnd/pointer/pointerGhost.d.ts +30 -0
  89. package/dist/esm/dnd/pointer/pointerGhost.js +39 -0
  90. package/dist/esm/dnd/pointer/types.d.ts +16 -0
  91. package/dist/esm/dnd/pointer/types.js +1 -0
  92. package/dist/esm/dockview/components/panel/content.d.ts +3 -1
  93. package/dist/esm/dockview/components/panel/content.js +33 -16
  94. package/dist/esm/dockview/components/popupService.js +34 -0
  95. package/dist/esm/dockview/components/tab/tab.d.ts +11 -3
  96. package/dist/esm/dockview/components/tab/tab.js +139 -114
  97. package/dist/esm/dockview/components/titlebar/tabGroupChip.d.ts +9 -2
  98. package/dist/esm/dockview/components/titlebar/tabGroupChip.js +15 -6
  99. package/dist/esm/dockview/components/titlebar/tabGroupIndicator.js +2 -2
  100. package/dist/esm/dockview/components/titlebar/tabGroups.d.ts +33 -5
  101. package/dist/esm/dockview/components/titlebar/tabGroups.js +177 -12
  102. package/dist/esm/dockview/components/titlebar/tabs.d.ts +38 -1
  103. package/dist/esm/dockview/components/titlebar/tabs.js +360 -229
  104. package/dist/esm/dockview/components/titlebar/tabsContainer.d.ts +5 -3
  105. package/dist/esm/dockview/components/titlebar/voidContainer.d.ts +6 -2
  106. package/dist/esm/dockview/components/titlebar/voidContainer.js +180 -26
  107. package/dist/esm/dockview/contextMenu.js +19 -4
  108. package/dist/esm/dockview/dndCapabilities.d.ts +19 -0
  109. package/dist/esm/dockview/dndCapabilities.js +36 -0
  110. package/dist/esm/dockview/dockviewComponent.d.ts +2 -0
  111. package/dist/esm/dockview/dockviewComponent.js +104 -41
  112. package/dist/esm/dockview/dockviewGroupPanelModel.d.ts +10 -5
  113. package/dist/esm/dockview/dockviewGroupPanelModel.js +33 -11
  114. package/dist/esm/dockview/dockviewPanel.js +5 -0
  115. package/dist/esm/dockview/dockviewPanelModel.d.ts +2 -0
  116. package/dist/esm/dockview/dockviewPanelModel.js +8 -0
  117. package/dist/esm/dockview/events.d.ts +2 -1
  118. package/dist/esm/dockview/events.js +1 -0
  119. package/dist/esm/dockview/framework.d.ts +8 -0
  120. package/dist/esm/dockview/options.d.ts +31 -5
  121. package/dist/esm/dockview/options.js +3 -0
  122. package/dist/esm/dom.d.ts +5 -1
  123. package/dist/esm/dom.js +20 -5
  124. package/dist/esm/index.d.ts +1 -1
  125. package/dist/esm/overlay/overlay.d.ts +12 -0
  126. package/dist/esm/overlay/overlay.js +85 -17
  127. package/dist/esm/paneview/draggablePaneviewPanel.d.ts +3 -1
  128. package/dist/esm/paneview/draggablePaneviewPanel.js +26 -20
  129. package/dist/esm/paneview/options.d.ts +4 -3
  130. package/dist/esm/popoutWindow.d.ts +2 -0
  131. package/dist/esm/popoutWindow.js +3 -1
  132. package/dist/package/main.cjs.js +2430 -936
  133. package/dist/package/main.cjs.min.js +2 -2
  134. package/dist/package/main.esm.min.mjs +2 -2
  135. package/dist/package/main.esm.mjs +2430 -936
  136. package/dist/styles/dockview.css +117 -1
  137. package/package.json +3 -1
  138. package/dist/cjs/dnd/abstractDragHandler.d.ts +0 -14
  139. package/dist/cjs/dnd/abstractDragHandler.js +0 -86
  140. package/dist/cjs/dnd/groupDragHandler.d.ts +0 -12
  141. package/dist/cjs/dnd/groupDragHandler.js +0 -82
  142. package/dist/esm/dnd/abstractDragHandler.d.ts +0 -14
  143. package/dist/esm/dnd/abstractDragHandler.js +0 -63
  144. package/dist/esm/dnd/groupDragHandler.d.ts +0 -12
  145. package/dist/esm/dnd/groupDragHandler.js +0 -59
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * dockview-core
3
- * @version 6.2.2
3
+ * @version 6.4.0
4
4
  * @link https://github.com/mathuo/dockview
5
5
  * @license MIT
6
6
  */
@@ -474,8 +474,10 @@ function quasiPreventDefault(event) {
474
474
  function quasiDefaultPrevented(event) {
475
475
  return event[QUASI_PREVENT_DEFAULT_KEY];
476
476
  }
477
- function addStyles(document, styleSheetList) {
477
+ function addStyles(document, styleSheetList, options = {}) {
478
478
  const styleSheets = Array.from(styleSheetList);
479
+ const { nonce } = options;
480
+ const resolvedNonce = typeof nonce === 'function' ? nonce(document) : nonce;
479
481
  for (const styleSheet of styleSheets) {
480
482
  if (styleSheet.href) {
481
483
  const link = document.createElement('link');
@@ -483,6 +485,10 @@ function addStyles(document, styleSheetList) {
483
485
  link.type = styleSheet.type;
484
486
  link.rel = 'stylesheet';
485
487
  document.head.appendChild(link);
488
+ // The <link> will load and apply its rules in the target
489
+ // document. Reading cssRules here would duplicate them
490
+ // (and throws for cross-origin sheets).
491
+ continue;
486
492
  }
487
493
  let cssTexts = [];
488
494
  try {
@@ -493,11 +499,16 @@ function addStyles(document, styleSheetList) {
493
499
  catch (err) {
494
500
  console.warn('dockview: failed to access stylesheet rules due to security restrictions', err);
495
501
  }
502
+ const fragment = document.createDocumentFragment();
496
503
  for (const rule of cssTexts) {
497
504
  const style = document.createElement('style');
505
+ if (resolvedNonce) {
506
+ style.setAttribute('nonce', resolvedNonce);
507
+ }
498
508
  style.appendChild(document.createTextNode(rule));
499
- document.head.appendChild(style);
509
+ fragment.appendChild(style);
500
510
  }
511
+ document.head.appendChild(fragment);
501
512
  }
502
513
  }
503
514
  function getDomNodePagePosition(domNode) {
@@ -536,7 +547,7 @@ function addTestId(element, id) {
536
547
  * Should be more efficient than element.querySelectorAll("*") since there
537
548
  * is no need to store every element in-memory using this approach
538
549
  */
539
- function allTagsNamesInclusiveOfShadowDoms(tagNames) {
550
+ function allTagsNamesInclusiveOfShadowDoms(tagNames, rootNode) {
540
551
  const iframes = [];
541
552
  function findIframesInNode(node) {
542
553
  if (node.nodeType === Node.ELEMENT_NODE) {
@@ -551,11 +562,15 @@ function allTagsNamesInclusiveOfShadowDoms(tagNames) {
551
562
  }
552
563
  }
553
564
  }
554
- findIframesInNode(document.documentElement);
565
+ // Document → walk from its root element. Element → walk from itself.
566
+ const startEl = rootNode instanceof Document
567
+ ? rootNode.documentElement
568
+ : rootNode;
569
+ findIframesInNode(startEl);
555
570
  return iframes;
556
571
  }
557
572
  function disableIframePointEvents(rootNode = document) {
558
- const iframes = allTagsNamesInclusiveOfShadowDoms(['IFRAME', 'WEBVIEW']);
573
+ const iframes = allTagsNamesInclusiveOfShadowDoms(['IFRAME', 'WEBVIEW'], rootNode);
559
574
  const original = new WeakMap(); // don't hold onto HTMLElement references longer than required
560
575
  for (const iframe of iframes) {
561
576
  original.set(iframe, iframe.style.pointerEvents);
@@ -3975,67 +3990,6 @@ class DockviewApi {
3975
3990
  }
3976
3991
  }
3977
3992
 
3978
- class DragHandler extends CompositeDisposable {
3979
- constructor(el, disabled) {
3980
- super();
3981
- this.el = el;
3982
- this.disabled = disabled;
3983
- this.dataDisposable = new MutableDisposable();
3984
- this.pointerEventsDisposable = new MutableDisposable();
3985
- this._onDragStart = new Emitter();
3986
- this.onDragStart = this._onDragStart.event;
3987
- this.addDisposables(this._onDragStart, this.dataDisposable, this.pointerEventsDisposable);
3988
- this.configure();
3989
- }
3990
- setDisabled(disabled) {
3991
- this.disabled = disabled;
3992
- }
3993
- isCancelled(_event) {
3994
- return false;
3995
- }
3996
- configure() {
3997
- this.addDisposables(this._onDragStart, addDisposableListener(this.el, 'dragstart', (event) => {
3998
- if (event.defaultPrevented ||
3999
- this.isCancelled(event) ||
4000
- this.disabled) {
4001
- event.preventDefault();
4002
- return;
4003
- }
4004
- const iframes = disableIframePointEvents();
4005
- this.pointerEventsDisposable.value = {
4006
- dispose: () => {
4007
- iframes.release();
4008
- },
4009
- };
4010
- this.el.classList.add('dv-dragged');
4011
- setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
4012
- this.dataDisposable.value = this.getData(event);
4013
- this._onDragStart.fire(event);
4014
- if (event.dataTransfer) {
4015
- event.dataTransfer.effectAllowed = 'move';
4016
- const hasData = event.dataTransfer.items.length > 0;
4017
- if (!hasData) {
4018
- /**
4019
- * Although this is not used by dockview many third party dnd libraries will check
4020
- * dataTransfer.types to determine valid drag events.
4021
- *
4022
- * For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled
4023
- * through .preventDefault(). Since this is applied globally to all drag events this would break dockviews
4024
- * dnd logic. You can see the code at
4025
- P * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
4026
- */
4027
- event.dataTransfer.setData('text/plain', '');
4028
- }
4029
- }
4030
- }), addDisposableListener(this.el, 'dragend', () => {
4031
- this.pointerEventsDisposable.dispose();
4032
- setTimeout(() => {
4033
- this.dataDisposable.dispose(); // allow the data to be read by other handlers before disposing
4034
- }, 0);
4035
- }));
4036
- }
4037
- }
4038
-
4039
3993
  class DragAndDropObserver extends CompositeDisposable {
4040
3994
  constructor(element, callbacks) {
4041
3995
  super();
@@ -4086,48 +4040,197 @@ class DragAndDropObserver extends CompositeDisposable {
4086
4040
  }
4087
4041
  }
4088
4042
 
4089
- function setGPUOptimizedBounds(element, bounds) {
4090
- const { top, left, width, height } = bounds;
4091
- const topPx = `${Math.round(top)}px`;
4092
- const leftPx = `${Math.round(left)}px`;
4093
- const widthPx = `${Math.round(width)}px`;
4094
- const heightPx = `${Math.round(height)}px`;
4095
- // Use traditional positioning but maintain GPU layer
4096
- element.style.top = topPx;
4097
- element.style.left = leftPx;
4098
- element.style.width = widthPx;
4099
- element.style.height = heightPx;
4100
- element.style.visibility = 'visible';
4101
- // Ensure GPU layer is maintained
4102
- if (!element.style.transform || element.style.transform === '') {
4103
- element.style.transform = 'translate3d(0, 0, 0)';
4104
- }
4105
- }
4106
- function setGPUOptimizedBoundsFromStrings(element, bounds) {
4107
- const { top, left, width, height } = bounds;
4108
- // Use traditional positioning but maintain GPU layer
4109
- element.style.top = top;
4110
- element.style.left = left;
4111
- element.style.width = width;
4112
- element.style.height = height;
4113
- element.style.visibility = 'visible';
4114
- // Ensure GPU layer is maintained
4115
- if (!element.style.transform || element.style.transform === '') {
4116
- element.style.transform = 'translate3d(0, 0, 0)';
4117
- }
4118
- }
4119
- function checkBoundsChanged(element, bounds) {
4120
- const { top, left, width, height } = bounds;
4121
- const topPx = `${Math.round(top)}px`;
4122
- const leftPx = `${Math.round(left)}px`;
4123
- const widthPx = `${Math.round(width)}px`;
4124
- const heightPx = `${Math.round(height)}px`;
4125
- // Check if position or size changed (back to traditional method)
4126
- return (element.style.top !== topPx ||
4127
- element.style.left !== leftPx ||
4128
- element.style.width !== widthPx ||
4129
- element.style.height !== heightPx);
4043
+ // Two render paths: in-place (dropzone appended to drop element) and
4044
+ // anchored (overlay rendered into an external anchor container).
4045
+ const DEFAULT_SIZE = { value: 50, type: 'percentage' };
4046
+ const SMALL_WIDTH_BOUNDARY = 100;
4047
+ const SMALL_HEIGHT_BOUNDARY = 100;
4048
+ function createOverlayElements() {
4049
+ const dropzone = document.createElement('div');
4050
+ dropzone.className = 'dv-drop-target-dropzone';
4051
+ const selection = document.createElement('div');
4052
+ selection.className = 'dv-drop-target-selection';
4053
+ dropzone.appendChild(selection);
4054
+ return { dropzone, selection };
4055
+ }
4056
+ function computeOverlayShape(quadrant, width, height, overlayModel) {
4057
+ var _a, _b, _c;
4058
+ const smallWidthBoundary = (_a = overlayModel === null || overlayModel === void 0 ? void 0 : overlayModel.smallWidthBoundary) !== null && _a !== void 0 ? _a : SMALL_WIDTH_BOUNDARY;
4059
+ const smallHeightBoundary = (_b = overlayModel === null || overlayModel === void 0 ? void 0 : overlayModel.smallHeightBoundary) !== null && _b !== void 0 ? _b : SMALL_HEIGHT_BOUNDARY;
4060
+ const isSmallX = width < smallWidthBoundary;
4061
+ const isSmallY = height < smallHeightBoundary;
4062
+ const isLeft = quadrant === 'left';
4063
+ const isRight = quadrant === 'right';
4064
+ const isTop = quadrant === 'top';
4065
+ const isBottom = quadrant === 'bottom';
4066
+ const rightClass = !isSmallX && isRight;
4067
+ const leftClass = !isSmallX && isLeft;
4068
+ const topClass = !isSmallY && isTop;
4069
+ const bottomClass = !isSmallY && isBottom;
4070
+ let size = 1;
4071
+ const sizeOptions = (_c = overlayModel === null || overlayModel === void 0 ? void 0 : overlayModel.size) !== null && _c !== void 0 ? _c : DEFAULT_SIZE;
4072
+ if (sizeOptions.type === 'percentage') {
4073
+ size = clamp(sizeOptions.value, 0, 100) / 100;
4074
+ }
4075
+ else {
4076
+ if (rightClass || leftClass) {
4077
+ size = clamp(0, sizeOptions.value, width) / width;
4078
+ }
4079
+ if (topClass || bottomClass) {
4080
+ size = clamp(0, sizeOptions.value, height) / height;
4081
+ }
4082
+ }
4083
+ return {
4084
+ isSmallX,
4085
+ isSmallY,
4086
+ isLeft,
4087
+ isRight,
4088
+ isTop,
4089
+ isBottom,
4090
+ rightClass,
4091
+ leftClass,
4092
+ topClass,
4093
+ bottomClass,
4094
+ size,
4095
+ };
4096
+ }
4097
+ function renderInPlaceOverlay(overlay, quadrant, width, height, overlayModel) {
4098
+ const shape = computeOverlayShape(quadrant, width, height, overlayModel);
4099
+ const { rightClass, leftClass, topClass, bottomClass, size } = shape;
4100
+ const box = { top: '0px', left: '0px', width: '100%', height: '100%' };
4101
+ if (rightClass) {
4102
+ box.left = `${100 * (1 - size)}%`;
4103
+ box.width = `${100 * size}%`;
4104
+ }
4105
+ else if (leftClass) {
4106
+ box.width = `${100 * size}%`;
4107
+ }
4108
+ else if (topClass) {
4109
+ box.height = `${100 * size}%`;
4110
+ }
4111
+ else if (bottomClass) {
4112
+ box.top = `${100 * (1 - size)}%`;
4113
+ box.height = `${100 * size}%`;
4114
+ }
4115
+ if (shape.isSmallX && shape.isLeft) {
4116
+ box.width = '4px';
4117
+ }
4118
+ if (shape.isSmallX && shape.isRight) {
4119
+ box.left = `${width - 4}px`;
4120
+ box.width = '4px';
4121
+ }
4122
+ if (shape.isSmallY && shape.isTop) {
4123
+ box.height = '4px';
4124
+ }
4125
+ if (shape.isSmallY && shape.isBottom) {
4126
+ box.top = `${height - 4}px`;
4127
+ box.height = '4px';
4128
+ }
4129
+ overlay.style.top = box.top;
4130
+ overlay.style.left = box.left;
4131
+ overlay.style.width = box.width;
4132
+ overlay.style.height = box.height;
4133
+ overlay.style.visibility = 'visible';
4134
+ if (!overlay.style.transform || overlay.style.transform === '') {
4135
+ overlay.style.transform = 'translate3d(0, 0, 0)';
4136
+ }
4137
+ const isLine = (shape.isSmallX && (shape.isLeft || shape.isRight)) ||
4138
+ (shape.isSmallY && (shape.isTop || shape.isBottom));
4139
+ toggleClass(overlay, 'dv-drop-target-small-vertical', shape.isSmallY);
4140
+ toggleClass(overlay, 'dv-drop-target-small-horizontal', shape.isSmallX);
4141
+ toggleClass(overlay, 'dv-drop-target-selection-line', isLine);
4142
+ toggleClass(overlay, 'dv-drop-target-left', shape.isLeft);
4143
+ toggleClass(overlay, 'dv-drop-target-right', shape.isRight);
4144
+ toggleClass(overlay, 'dv-drop-target-top', shape.isTop);
4145
+ toggleClass(overlay, 'dv-drop-target-bottom', shape.isBottom);
4146
+ toggleClass(overlay, 'dv-drop-target-center', quadrant === 'center');
4147
+ }
4148
+ function checkAnchoredBoundsChanged(overlay, bounds) {
4149
+ const topPx = `${Math.round(bounds.top)}px`;
4150
+ const leftPx = `${Math.round(bounds.left)}px`;
4151
+ const widthPx = `${Math.round(bounds.width)}px`;
4152
+ const heightPx = `${Math.round(bounds.height)}px`;
4153
+ return (overlay.style.top !== topPx ||
4154
+ overlay.style.left !== leftPx ||
4155
+ overlay.style.width !== widthPx ||
4156
+ overlay.style.height !== heightPx);
4157
+ }
4158
+ function applyAnchoredBounds(overlay, bounds) {
4159
+ overlay.style.top = `${Math.round(bounds.top)}px`;
4160
+ overlay.style.left = `${Math.round(bounds.left)}px`;
4161
+ overlay.style.width = `${Math.round(bounds.width)}px`;
4162
+ overlay.style.height = `${Math.round(bounds.height)}px`;
4163
+ overlay.style.visibility = 'visible';
4164
+ if (!overlay.style.transform || overlay.style.transform === '') {
4165
+ overlay.style.transform = 'translate3d(0, 0, 0)';
4166
+ }
4167
+ }
4168
+ /** `boundsChanged: false` lets callers skip redundant work on tight drag loops. */
4169
+ function renderAnchoredOverlay(args) {
4170
+ const shape = computeOverlayShape(args.quadrant, args.width, args.height, args.overlayModel);
4171
+ const { rightClass, leftClass, topClass, bottomClass, size } = shape;
4172
+ const elBox = args.outlineElement.getBoundingClientRect();
4173
+ const ta = args.targetModel.getElements(undefined, args.outlineElement);
4174
+ const el = ta.root;
4175
+ const overlay = ta.overlay;
4176
+ const bigbox = el.getBoundingClientRect();
4177
+ const rootTop = elBox.top - bigbox.top;
4178
+ const rootLeft = elBox.left - bigbox.left;
4179
+ const box = {
4180
+ top: rootTop,
4181
+ left: rootLeft,
4182
+ width: args.width,
4183
+ height: args.height,
4184
+ };
4185
+ if (rightClass) {
4186
+ box.left = rootLeft + args.width * (1 - size);
4187
+ box.width = args.width * size;
4188
+ }
4189
+ else if (leftClass) {
4190
+ box.width = args.width * size;
4191
+ }
4192
+ else if (topClass) {
4193
+ box.height = args.height * size;
4194
+ }
4195
+ else if (bottomClass) {
4196
+ box.top = rootTop + args.height * (1 - size);
4197
+ box.height = args.height * size;
4198
+ }
4199
+ if (shape.isSmallX && shape.isLeft) {
4200
+ box.width = 4;
4201
+ }
4202
+ if (shape.isSmallX && shape.isRight) {
4203
+ box.left = rootLeft + args.width - 4;
4204
+ box.width = 4;
4205
+ }
4206
+ if (shape.isSmallY && shape.isTop) {
4207
+ box.height = 4;
4208
+ }
4209
+ if (shape.isSmallY && shape.isBottom) {
4210
+ box.top = rootTop + args.height - 4;
4211
+ box.height = 4;
4212
+ }
4213
+ if (!checkAnchoredBoundsChanged(overlay, box)) {
4214
+ return { boundsChanged: false, targetChanged: ta.changed };
4215
+ }
4216
+ applyAnchoredBounds(overlay, box);
4217
+ overlay.className = `dv-drop-target-anchor${args.className ? ` ${args.className}` : ''}`;
4218
+ toggleClass(overlay, 'dv-drop-target-left', shape.isLeft);
4219
+ toggleClass(overlay, 'dv-drop-target-right', shape.isRight);
4220
+ toggleClass(overlay, 'dv-drop-target-top', shape.isTop);
4221
+ toggleClass(overlay, 'dv-drop-target-bottom', shape.isBottom);
4222
+ toggleClass(overlay, 'dv-drop-target-anchor-line', (shape.isSmallX && (shape.isLeft || shape.isRight)) ||
4223
+ (shape.isSmallY && (shape.isTop || shape.isBottom)));
4224
+ toggleClass(overlay, 'dv-drop-target-center', args.quadrant === 'center');
4225
+ if (ta.changed) {
4226
+ toggleClass(overlay, 'dv-drop-target-anchor-container-changed', true);
4227
+ setTimeout(() => {
4228
+ toggleClass(overlay, 'dv-drop-target-anchor-container-changed', false);
4229
+ }, 10);
4230
+ }
4231
+ return { boundsChanged: true, targetChanged: ta.changed };
4130
4232
  }
4233
+
4131
4234
  class WillShowOverlayEvent extends DockviewEvent {
4132
4235
  get nativeEvent() {
4133
4236
  return this.options.nativeEvent;
@@ -4172,16 +4275,10 @@ function positionToDirection(position) {
4172
4275
  throw new Error(`invalid position '${position}'`);
4173
4276
  }
4174
4277
  }
4175
- const DEFAULT_ACTIVATION_SIZE = {
4278
+ const DEFAULT_ACTIVATION_SIZE$1 = {
4176
4279
  value: 20,
4177
4280
  type: 'percentage',
4178
4281
  };
4179
- const DEFAULT_SIZE = {
4180
- value: 50,
4181
- type: 'percentage',
4182
- };
4183
- const SMALL_WIDTH_BOUNDARY = 100;
4184
- const SMALL_HEIGHT_BOUNDARY = 100;
4185
4282
  class Droptarget extends CompositeDisposable {
4186
4283
  get disabled() {
4187
4284
  return this._disabled;
@@ -4262,20 +4359,12 @@ class Droptarget extends CompositeDisposable {
4262
4359
  this.markAsUsed(e);
4263
4360
  if (overrideTarget) ;
4264
4361
  else if (!this.targetElement) {
4265
- this.targetElement = document.createElement('div');
4266
- this.targetElement.className = 'dv-drop-target-dropzone';
4267
- this.overlayElement = document.createElement('div');
4268
- this.overlayElement.className = 'dv-drop-target-selection';
4362
+ const els = createOverlayElements();
4363
+ this.targetElement = els.dropzone;
4364
+ this.overlayElement = els.selection;
4269
4365
  this._state = 'center';
4270
- this.targetElement.appendChild(this.overlayElement);
4271
4366
  target.classList.add('dv-drop-target');
4272
4367
  target.append(this.targetElement);
4273
- // this.overlayElement.style.opacity = '0';
4274
- // requestAnimationFrame(() => {
4275
- // if (this.overlayElement) {
4276
- // this.overlayElement.style.opacity = '';
4277
- // }
4278
- // });
4279
4368
  }
4280
4369
  this.toggleClasses(quadrant, width, height);
4281
4370
  this._state = quadrant;
@@ -4345,166 +4434,29 @@ class Droptarget extends CompositeDisposable {
4345
4434
  return typeof value === 'boolean' && value;
4346
4435
  }
4347
4436
  toggleClasses(quadrant, width, height) {
4348
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
4437
+ var _a, _b, _c, _d, _e;
4349
4438
  const target = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
4350
- if (!target && !this.overlayElement) {
4351
- return;
4352
- }
4353
- const smallWidthBoundary = (_d = (_c = this.options.overlayModel) === null || _c === void 0 ? void 0 : _c.smallWidthBoundary) !== null && _d !== void 0 ? _d : SMALL_WIDTH_BOUNDARY;
4354
- const smallHeightBoundary = (_f = (_e = this.options.overlayModel) === null || _e === void 0 ? void 0 : _e.smallHeightBoundary) !== null && _f !== void 0 ? _f : SMALL_HEIGHT_BOUNDARY;
4355
- const isSmallX = width < smallWidthBoundary;
4356
- const isSmallY = height < smallHeightBoundary;
4357
- const isLeft = quadrant === 'left';
4358
- const isRight = quadrant === 'right';
4359
- const isTop = quadrant === 'top';
4360
- const isBottom = quadrant === 'bottom';
4361
- const rightClass = !isSmallX && isRight;
4362
- const leftClass = !isSmallX && isLeft;
4363
- const topClass = !isSmallY && isTop;
4364
- const bottomClass = !isSmallY && isBottom;
4365
- let size = 1;
4366
- const sizeOptions = (_h = (_g = this.options.overlayModel) === null || _g === void 0 ? void 0 : _g.size) !== null && _h !== void 0 ? _h : DEFAULT_SIZE;
4367
- if (sizeOptions.type === 'percentage') {
4368
- size = clamp(sizeOptions.value, 0, 100) / 100;
4369
- }
4370
- else {
4371
- if (rightClass || leftClass) {
4372
- size = clamp(0, sizeOptions.value, width) / width;
4373
- }
4374
- if (topClass || bottomClass) {
4375
- size = clamp(0, sizeOptions.value, height) / height;
4376
- }
4377
- }
4378
4439
  if (target) {
4379
- const outlineEl = (_l = (_k = (_j = this.options).getOverlayOutline) === null || _k === void 0 ? void 0 : _k.call(_j)) !== null && _l !== void 0 ? _l : this.element;
4380
- const elBox = outlineEl.getBoundingClientRect();
4381
- const ta = target.getElements(undefined, outlineEl);
4382
- const el = ta.root;
4383
- const overlay = ta.overlay;
4384
- const bigbox = el.getBoundingClientRect();
4385
- const rootTop = elBox.top - bigbox.top;
4386
- const rootLeft = elBox.left - bigbox.left;
4387
- const box = {
4388
- top: rootTop,
4389
- left: rootLeft,
4390
- width: width,
4391
- height: height,
4392
- };
4393
- if (rightClass) {
4394
- box.left = rootLeft + width * (1 - size);
4395
- box.width = width * size;
4396
- }
4397
- else if (leftClass) {
4398
- box.width = width * size;
4399
- }
4400
- else if (topClass) {
4401
- box.height = height * size;
4402
- }
4403
- else if (bottomClass) {
4404
- box.top = rootTop + height * (1 - size);
4405
- box.height = height * size;
4406
- }
4407
- if (isSmallX && isLeft) {
4408
- box.width = 4;
4409
- }
4410
- if (isSmallX && isRight) {
4411
- box.left = rootLeft + width - 4;
4412
- box.width = 4;
4413
- }
4414
- if (isSmallY && isTop) {
4415
- box.height = 4;
4416
- }
4417
- if (isSmallY && isBottom) {
4418
- box.top = rootTop + height - 4;
4419
- box.height = 4;
4420
- }
4421
- // Use GPU-optimized bounds checking and setting
4422
- if (!checkBoundsChanged(overlay, box)) {
4423
- return;
4424
- }
4425
- setGPUOptimizedBounds(overlay, box);
4426
- overlay.className = `dv-drop-target-anchor${this.options.className ? ` ${this.options.className}` : ''}`;
4427
- toggleClass(overlay, 'dv-drop-target-left', isLeft);
4428
- toggleClass(overlay, 'dv-drop-target-right', isRight);
4429
- toggleClass(overlay, 'dv-drop-target-top', isTop);
4430
- toggleClass(overlay, 'dv-drop-target-bottom', isBottom);
4431
- toggleClass(overlay, 'dv-drop-target-anchor-line', (isSmallX && (isLeft || isRight)) ||
4432
- (isSmallY && (isTop || isBottom)));
4433
- toggleClass(overlay, 'dv-drop-target-center', quadrant === 'center');
4434
- if (ta.changed) {
4435
- toggleClass(overlay, 'dv-drop-target-anchor-container-changed', true);
4436
- setTimeout(() => {
4437
- toggleClass(overlay, 'dv-drop-target-anchor-container-changed', false);
4438
- }, 10);
4439
- }
4440
+ const outlineEl = (_e = (_d = (_c = this.options).getOverlayOutline) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : this.element;
4441
+ renderAnchoredOverlay({
4442
+ outlineElement: outlineEl,
4443
+ targetModel: target,
4444
+ quadrant,
4445
+ width,
4446
+ height,
4447
+ overlayModel: this.options.overlayModel,
4448
+ className: this.options.className,
4449
+ });
4440
4450
  return;
4441
4451
  }
4442
4452
  if (!this.overlayElement) {
4443
4453
  return;
4444
4454
  }
4445
- const box = { top: '0px', left: '0px', width: '100%', height: '100%' };
4446
- /**
4447
- * You can also achieve the overlay placement using the transform CSS property
4448
- * to translate and scale the element however this has the undesired effect of
4449
- * 'skewing' the element. Comment left here for anybody that ever revisits this.
4450
- *
4451
- * @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform
4452
- *
4453
- * right
4454
- * translateX(${100 * (1 - size) / 2}%) scaleX(${scale})
4455
- *
4456
- * left
4457
- * translateX(-${100 * (1 - size) / 2}%) scaleX(${scale})
4458
- *
4459
- * top
4460
- * translateY(-${100 * (1 - size) / 2}%) scaleY(${scale})
4461
- *
4462
- * bottom
4463
- * translateY(${100 * (1 - size) / 2}%) scaleY(${scale})
4464
- */
4465
- if (rightClass) {
4466
- box.left = `${100 * (1 - size)}%`;
4467
- box.width = `${100 * size}%`;
4468
- }
4469
- else if (leftClass) {
4470
- box.width = `${100 * size}%`;
4471
- }
4472
- else if (topClass) {
4473
- box.height = `${100 * size}%`;
4474
- }
4475
- else if (bottomClass) {
4476
- box.top = `${100 * (1 - size)}%`;
4477
- box.height = `${100 * size}%`;
4478
- }
4479
- if (isSmallX && isLeft) {
4480
- box.width = '4px';
4481
- }
4482
- if (isSmallX && isRight) {
4483
- box.left = `${width - 4}px`;
4484
- box.width = '4px';
4485
- }
4486
- if (isSmallY && isTop) {
4487
- box.height = '4px';
4488
- }
4489
- if (isSmallY && isBottom) {
4490
- box.top = `${height - 4}px`;
4491
- box.height = '4px';
4492
- }
4493
- setGPUOptimizedBoundsFromStrings(this.overlayElement, box);
4494
- const isLine = (isSmallX && (isLeft || isRight)) ||
4495
- (isSmallY && (isTop || isBottom));
4496
- toggleClass(this.overlayElement, 'dv-drop-target-small-vertical', isSmallY);
4497
- toggleClass(this.overlayElement, 'dv-drop-target-small-horizontal', isSmallX);
4498
- toggleClass(this.overlayElement, 'dv-drop-target-selection-line', isLine);
4499
- toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft);
4500
- toggleClass(this.overlayElement, 'dv-drop-target-right', isRight);
4501
- toggleClass(this.overlayElement, 'dv-drop-target-top', isTop);
4502
- toggleClass(this.overlayElement, 'dv-drop-target-bottom', isBottom);
4503
- toggleClass(this.overlayElement, 'dv-drop-target-center', quadrant === 'center');
4455
+ renderInPlaceOverlay(this.overlayElement, quadrant, width, height, this.options.overlayModel);
4504
4456
  }
4505
4457
  calculateQuadrant(overlayType, x, y, width, height) {
4506
4458
  var _a, _b;
4507
- const activationSizeOptions = (_b = (_a = this.options.overlayModel) === null || _a === void 0 ? void 0 : _a.activationSize) !== null && _b !== void 0 ? _b : DEFAULT_ACTIVATION_SIZE;
4459
+ const activationSizeOptions = (_b = (_a = this.options.overlayModel) === null || _a === void 0 ? void 0 : _a.activationSize) !== null && _b !== void 0 ? _b : DEFAULT_ACTIVATION_SIZE$1;
4508
4460
  const isPercentage = activationSizeOptions.type === 'percentage';
4509
4461
  if (isPercentage) {
4510
4462
  return calculateQuadrantAsPercentage(overlayType, x, y, width, height, activationSizeOptions.value);
@@ -4562,129 +4514,846 @@ function calculateQuadrantAsPixels(overlayType, x, y, width, height, threshold)
4562
4514
  return 'center';
4563
4515
  }
4564
4516
 
4565
- const PROPERTY_KEYS_PANEVIEW = (() => {
4566
- /**
4567
- * by readong the keys from an empty value object TypeScript will error
4568
- * when we add or remove new properties to `DockviewOptions`
4569
- */
4570
- const properties = {
4571
- disableAutoResizing: undefined,
4572
- disableDnd: undefined,
4573
- className: undefined,
4574
- };
4575
- return Object.keys(properties);
4576
- })();
4577
- class PaneviewUnhandledDragOverEvent extends AcceptableEvent {
4578
- constructor(nativeEvent, position, getData, panel) {
4579
- super();
4580
- this.nativeEvent = nativeEvent;
4581
- this.position = position;
4582
- this.getData = getData;
4583
- this.panel = panel;
4584
- }
4517
+ function addGhostImage(dataTransfer, ghostElement, options) {
4518
+ var _a, _b;
4519
+ // class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
4520
+ addClasses(ghostElement, 'dv-dragged');
4521
+ // move the element off-screen initially otherwise it may in some cases be rendered at (0,0) momentarily
4522
+ ghostElement.style.top = '-9999px';
4523
+ document.body.appendChild(ghostElement);
4524
+ dataTransfer.setDragImage(ghostElement, (_a = options === null || options === void 0 ? void 0 : options.x) !== null && _a !== void 0 ? _a : 0, (_b = options === null || options === void 0 ? void 0 : options.y) !== null && _b !== void 0 ? _b : 0);
4525
+ setTimeout(() => {
4526
+ removeClasses(ghostElement, 'dv-dragged');
4527
+ ghostElement.remove();
4528
+ }, 0);
4585
4529
  }
4586
4530
 
4587
- class WillFocusEvent extends DockviewEvent {
4588
- constructor() {
4589
- super();
4590
- }
4591
- }
4592
4531
  /**
4593
- * A core api implementation that should be used across all panel-like objects
4532
+ * Singleton only one pointer-driven drag active at a time.
4533
+ *
4534
+ * State is shared across every Dockview instance on the page. Targets
4535
+ * from instance B receive hit-tests from drags originating in instance A;
4536
+ * that's intentional for cross-instance drops since `LocalSelectionTransfer`
4537
+ * is also process-wide. The corollary is that every Tabs subscriber to
4538
+ * `onDragMove` fires for every pointer drag globally — each subscriber
4539
+ * hit-tests against its own DOM, so this is O(N) per pointermove where N
4540
+ * is the number of registered listeners across all instances.
4594
4541
  */
4595
- class PanelApiImpl extends CompositeDisposable {
4596
- get isFocused() {
4597
- return this._isFocused;
4598
- }
4599
- get isActive() {
4600
- return this._isActive;
4601
- }
4602
- get isVisible() {
4603
- return this._isVisible;
4604
- }
4605
- get width() {
4606
- return this._width;
4607
- }
4608
- get height() {
4609
- return this._height;
4542
+ class PointerDragController extends CompositeDisposable {
4543
+ static getInstance() {
4544
+ if (!PointerDragController._instance) {
4545
+ PointerDragController._instance = new PointerDragController();
4546
+ }
4547
+ return PointerDragController._instance;
4610
4548
  }
4611
- constructor(id, component) {
4549
+ constructor() {
4612
4550
  super();
4613
- this.id = id;
4614
- this.component = component;
4615
- this._isFocused = false;
4616
- this._isActive = false;
4617
- this._isVisible = true;
4618
- this._width = 0;
4619
- this._height = 0;
4620
- this._parameters = {};
4621
- this.panelUpdatesDisposable = new MutableDisposable();
4622
- this._onDidDimensionChange = new Emitter();
4623
- this.onDidDimensionsChange = this._onDidDimensionChange.event;
4624
- this._onDidChangeFocus = new Emitter();
4625
- this.onDidFocusChange = this._onDidChangeFocus.event;
4626
- //
4627
- this._onWillFocus = new Emitter();
4628
- this.onWillFocus = this._onWillFocus.event;
4629
- //
4630
- this._onDidVisibilityChange = new Emitter();
4631
- this.onDidVisibilityChange = this._onDidVisibilityChange.event;
4632
- this._onWillVisibilityChange = new Emitter();
4633
- this.onWillVisibilityChange = this._onWillVisibilityChange.event;
4634
- this._onDidActiveChange = new Emitter();
4635
- this.onDidActiveChange = this._onDidActiveChange.event;
4636
- this._onActiveChange = new Emitter();
4637
- this.onActiveChange = this._onActiveChange.event;
4638
- this._onDidParametersChange = new Emitter();
4639
- this.onDidParametersChange = this._onDidParametersChange.event;
4640
- this.addDisposables(this.onDidFocusChange((event) => {
4641
- this._isFocused = event.isFocused;
4642
- }), this.onDidActiveChange((event) => {
4643
- this._isActive = event.isActive;
4644
- }), this.onDidVisibilityChange((event) => {
4645
- this._isVisible = event.isVisible;
4646
- }), this.onDidDimensionsChange((event) => {
4647
- this._width = event.width;
4648
- this._height = event.height;
4649
- }), this.panelUpdatesDisposable, this._onDidDimensionChange, this._onDidChangeFocus, this._onDidVisibilityChange, this._onDidActiveChange, this._onWillFocus, this._onActiveChange, this._onWillFocus, this._onWillVisibilityChange, this._onDidParametersChange);
4551
+ this._targets = new Set();
4552
+ /** Kept in sync with `_targets` so hit-testing is allocation-free. */
4553
+ this._targetByElement = new Map();
4554
+ this._onDragStart = new Emitter();
4555
+ this.onDragStart = this._onDragStart.event;
4556
+ this._onDragMove = new Emitter();
4557
+ this.onDragMove = this._onDragMove.event;
4558
+ this._onDragEnd = new Emitter();
4559
+ this.onDragEnd = this._onDragEnd.event;
4560
+ this.addDisposables(this._onDragStart, this._onDragMove, this._onDragEnd);
4650
4561
  }
4651
- getParameters() {
4652
- return this._parameters;
4562
+ get active() {
4563
+ return this._active;
4653
4564
  }
4654
- initialize(panel) {
4655
- this.panelUpdatesDisposable.value = this._onDidParametersChange.event((parameters) => {
4656
- this._parameters = parameters;
4657
- panel.update({
4658
- params: parameters,
4659
- });
4565
+ registerTarget(target) {
4566
+ this._targets.add(target);
4567
+ this._targetByElement.set(target.element, target);
4568
+ return {
4569
+ dispose: () => {
4570
+ this._targets.delete(target);
4571
+ if (this._targetByElement.get(target.element) === target) {
4572
+ this._targetByElement.delete(target.element);
4573
+ }
4574
+ if (this._currentTarget === target) {
4575
+ this._currentTarget = undefined;
4576
+ }
4577
+ },
4578
+ };
4579
+ }
4580
+ beginDrag(args) {
4581
+ var _a, _b, _c;
4582
+ if (this._active) {
4583
+ this.cancel();
4584
+ }
4585
+ const { pointerEvent, source } = args;
4586
+ // Call `getData()` before mutating controller state — a throw
4587
+ // here would otherwise leave `_active` populated with no window
4588
+ // listeners installed, blocking every subsequent drag.
4589
+ const dataDisposable = args.getData();
4590
+ this._active = {
4591
+ pointerId: pointerEvent.pointerId,
4592
+ startX: pointerEvent.clientX,
4593
+ startY: pointerEvent.clientY,
4594
+ source,
4595
+ };
4596
+ this._onDragMoveCallback = args.onDragMove;
4597
+ this._onDragEndCallback = args.onDragEnd;
4598
+ this._dataDisposable = dataDisposable;
4599
+ this._ghost = args.ghost;
4600
+ // Iframes capture pointermove once the cursor crosses into them,
4601
+ // which would freeze the drag from the parent window's POV.
4602
+ this._iframeShield = disableIframePointEvents((_a = source.ownerDocument) !== null && _a !== void 0 ? _a : document);
4603
+ const startEvent = {
4604
+ clientX: pointerEvent.clientX,
4605
+ clientY: pointerEvent.clientY,
4606
+ pointerEvent,
4607
+ };
4608
+ this._onDragStart.fire(startEvent);
4609
+ // Source's owning window — popout drags fire on their own window,
4610
+ // not the main one.
4611
+ const targetWindow = (_c = (_b = source.ownerDocument) === null || _b === void 0 ? void 0 : _b.defaultView) !== null && _c !== void 0 ? _c : window;
4612
+ this._moveListener = addDisposableListener(targetWindow, 'pointermove', (e) => {
4613
+ if (!this._active || e.pointerId !== this._active.pointerId) {
4614
+ return;
4615
+ }
4616
+ this._handleMove(e);
4617
+ });
4618
+ this._upListener = addDisposableListener(targetWindow, 'pointerup', (e) => {
4619
+ if (!this._active || e.pointerId !== this._active.pointerId) {
4620
+ return;
4621
+ }
4622
+ this._handleEnd(e, true);
4623
+ });
4624
+ this._cancelListener = addDisposableListener(targetWindow, 'pointercancel', (e) => {
4625
+ if (!this._active || e.pointerId !== this._active.pointerId) {
4626
+ return;
4627
+ }
4628
+ this._handleEnd(e, false);
4660
4629
  });
4661
4630
  }
4662
- setVisible(isVisible) {
4663
- this._onWillVisibilityChange.fire({ isVisible });
4631
+ cancel() {
4632
+ var _a, _b;
4633
+ if (!this._active) {
4634
+ return;
4635
+ }
4636
+ (_a = this._currentTarget) === null || _a === void 0 ? void 0 : _a.handleDragLeave();
4637
+ this._teardown();
4638
+ (_b = this._dataDisposable) === null || _b === void 0 ? void 0 : _b.dispose();
4639
+ this._dataDisposable = undefined;
4664
4640
  }
4665
- setActive() {
4666
- this._onActiveChange.fire();
4641
+ _findTargetUnder(x, y) {
4642
+ var _a, _b;
4643
+ // `elementsFromPoint` is topmost-first; walk up to find the closest
4644
+ // registered ancestor (so a tab beats the layout-root that contains it).
4645
+ // Use the source's owning document so popout drags hit their own targets.
4646
+ const sourceDoc = (_b = (_a = this._active) === null || _a === void 0 ? void 0 : _a.source.ownerDocument) !== null && _b !== void 0 ? _b : document;
4647
+ const elements = sourceDoc.elementsFromPoint(x, y);
4648
+ for (const el of elements) {
4649
+ let current = el;
4650
+ while (current) {
4651
+ const target = this._targetByElement.get(current);
4652
+ if (target) {
4653
+ return target;
4654
+ }
4655
+ current = current.parentElement;
4656
+ }
4657
+ }
4658
+ return undefined;
4667
4659
  }
4668
- updateParameters(parameters) {
4669
- this._onDidParametersChange.fire(parameters);
4660
+ _handleMove(e) {
4661
+ var _a, _b, _c;
4662
+ (_a = this._ghost) === null || _a === void 0 ? void 0 : _a.update(e.clientX, e.clientY);
4663
+ const dragEvent = {
4664
+ clientX: e.clientX,
4665
+ clientY: e.clientY,
4666
+ pointerEvent: e,
4667
+ };
4668
+ const newTarget = this._findTargetUnder(e.clientX, e.clientY);
4669
+ if (newTarget !== this._currentTarget) {
4670
+ (_b = this._currentTarget) === null || _b === void 0 ? void 0 : _b.handleDragLeave();
4671
+ this._currentTarget = newTarget;
4672
+ }
4673
+ if (newTarget) {
4674
+ newTarget.handleDragOver(dragEvent);
4675
+ }
4676
+ (_c = this._onDragMoveCallback) === null || _c === void 0 ? void 0 : _c.call(this, dragEvent);
4677
+ this._onDragMove.fire(dragEvent);
4678
+ }
4679
+ _handleEnd(e, dropped) {
4680
+ var _a;
4681
+ const dragEvent = {
4682
+ clientX: e.clientX,
4683
+ clientY: e.clientY,
4684
+ pointerEvent: e,
4685
+ };
4686
+ if (dropped && this._currentTarget) {
4687
+ this._currentTarget.handleDrop(dragEvent);
4688
+ }
4689
+ else {
4690
+ (_a = this._currentTarget) === null || _a === void 0 ? void 0 : _a.handleDragLeave();
4691
+ }
4692
+ const onEnd = this._onDragEndCallback;
4693
+ const dataDisposable = this._dataDisposable;
4694
+ this._teardown();
4695
+ this._dataDisposable = undefined;
4696
+ // Defer disposal so drop handlers can still read the transfer data.
4697
+ setTimeout(() => dataDisposable === null || dataDisposable === void 0 ? void 0 : dataDisposable.dispose(), 0);
4698
+ onEnd === null || onEnd === void 0 ? void 0 : onEnd(dragEvent, dropped);
4699
+ this._onDragEnd.fire(dragEvent);
4700
+ }
4701
+ _teardown() {
4702
+ var _a, _b, _c, _d, _e;
4703
+ this._currentTarget = undefined;
4704
+ this._active = undefined;
4705
+ this._onDragMoveCallback = undefined;
4706
+ this._onDragEndCallback = undefined;
4707
+ (_a = this._ghost) === null || _a === void 0 ? void 0 : _a.dispose();
4708
+ this._ghost = undefined;
4709
+ (_b = this._iframeShield) === null || _b === void 0 ? void 0 : _b.release();
4710
+ this._iframeShield = undefined;
4711
+ (_c = this._moveListener) === null || _c === void 0 ? void 0 : _c.dispose();
4712
+ (_d = this._upListener) === null || _d === void 0 ? void 0 : _d.dispose();
4713
+ (_e = this._cancelListener) === null || _e === void 0 ? void 0 : _e.dispose();
4714
+ this._moveListener = undefined;
4715
+ this._upListener = undefined;
4716
+ this._cancelListener = undefined;
4670
4717
  }
4671
4718
  }
4672
4719
 
4673
- class SplitviewPanelApiImpl extends PanelApiImpl {
4674
- //
4675
- constructor(id, component) {
4676
- super(id, component);
4677
- this._onDidConstraintsChangeInternal = new Emitter();
4678
- this.onDidConstraintsChangeInternal = this._onDidConstraintsChangeInternal.event;
4679
- //
4680
- this._onDidConstraintsChange = new Emitter({
4681
- replay: true,
4682
- });
4683
- this.onDidConstraintsChange = this._onDidConstraintsChange.event;
4684
- //
4685
- this._onDidSizeChange = new Emitter();
4686
- this.onDidSizeChange = this._onDidSizeChange.event;
4687
- this.addDisposables(this._onDidConstraintsChangeInternal, this._onDidConstraintsChange, this._onDidSizeChange);
4720
+ const DEFAULT_ACTIVATION_SIZE = {
4721
+ value: 20,
4722
+ type: 'percentage',
4723
+ };
4724
+ /** Pointer-driven counterpart to `Droptarget` with identical visual output. */
4725
+ class PointerDropTarget extends CompositeDisposable {
4726
+ get disabled() {
4727
+ return this._disabled;
4728
+ }
4729
+ set disabled(value) {
4730
+ this._disabled = value;
4731
+ if (value) {
4732
+ this._removeOverlay();
4733
+ }
4734
+ }
4735
+ get state() {
4736
+ return this._state;
4737
+ }
4738
+ constructor(element, options) {
4739
+ super();
4740
+ this.element = element;
4741
+ this.options = options;
4742
+ this._onDrop = new Emitter();
4743
+ this.onDrop = this._onDrop.event;
4744
+ this._onWillShowOverlay = new Emitter();
4745
+ this.onWillShowOverlay = this._onWillShowOverlay.event;
4746
+ this._disabled = false;
4747
+ this._acceptedTargetZonesSet = new Set(options.acceptedTargetZones);
4748
+ const handle = {
4749
+ element: this.element,
4750
+ handleDragOver: (e) => this._onDragOver(e),
4751
+ handleDragLeave: () => this._onDragLeave(),
4752
+ handleDrop: (e) => this._onDropEvent(e),
4753
+ };
4754
+ this.addDisposables(this._onDrop, this._onWillShowOverlay, PointerDragController.getInstance().registerTarget(handle));
4755
+ }
4756
+ setTargetZones(zones) {
4757
+ this._acceptedTargetZonesSet = new Set(zones);
4758
+ }
4759
+ setOverlayModel(model) {
4760
+ this.options.overlayModel = model;
4761
+ }
4762
+ dispose() {
4763
+ this._removeOverlay();
4764
+ super.dispose();
4765
+ }
4766
+ _onDragOver(event) {
4767
+ var _a, _b, _c, _d, _e;
4768
+ if (this._disabled) {
4769
+ this._removeOverlay();
4770
+ return;
4771
+ }
4772
+ const overrideTarget = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
4773
+ if (this._acceptedTargetZonesSet.size === 0) {
4774
+ if (overrideTarget) {
4775
+ return;
4776
+ }
4777
+ this._removeOverlay();
4778
+ return;
4779
+ }
4780
+ const outlineEl = (_e = (_d = (_c = this.options).getOverlayOutline) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : this.element;
4781
+ const width = outlineEl.offsetWidth;
4782
+ const height = outlineEl.offsetHeight;
4783
+ if (width === 0 || height === 0) {
4784
+ return;
4785
+ }
4786
+ const rect = outlineEl.getBoundingClientRect();
4787
+ const x = event.clientX - rect.left;
4788
+ const y = event.clientY - rect.top;
4789
+ const quadrant = this._calculateQuadrant(x, y, width, height);
4790
+ if (quadrant === null) {
4791
+ this._removeOverlay();
4792
+ return;
4793
+ }
4794
+ if (!this.options.canDisplayOverlay(event.pointerEvent, quadrant)) {
4795
+ if (overrideTarget) {
4796
+ return;
4797
+ }
4798
+ this._removeOverlay();
4799
+ return;
4800
+ }
4801
+ const willShow = new WillShowOverlayEvent({
4802
+ nativeEvent: event.pointerEvent,
4803
+ position: quadrant,
4804
+ });
4805
+ this._onWillShowOverlay.fire(willShow);
4806
+ if (willShow.defaultPrevented) {
4807
+ this._removeOverlay();
4808
+ return;
4809
+ }
4810
+ if (overrideTarget) {
4811
+ renderAnchoredOverlay({
4812
+ outlineElement: outlineEl,
4813
+ targetModel: overrideTarget,
4814
+ quadrant,
4815
+ width,
4816
+ height,
4817
+ overlayModel: this.options.overlayModel,
4818
+ className: this.options.className,
4819
+ });
4820
+ this._state = quadrant;
4821
+ return;
4822
+ }
4823
+ if (!this._targetElement) {
4824
+ const els = createOverlayElements();
4825
+ this._targetElement = els.dropzone;
4826
+ this._overlayElement = els.selection;
4827
+ this._state = 'center';
4828
+ this.element.classList.add('dv-drop-target');
4829
+ this.element.append(this._targetElement);
4830
+ }
4831
+ if (this._overlayElement) {
4832
+ renderInPlaceOverlay(this._overlayElement, quadrant, width, height, this.options.overlayModel);
4833
+ }
4834
+ this._state = quadrant;
4835
+ }
4836
+ _onDragLeave() {
4837
+ var _a, _b;
4838
+ const overrideTarget = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
4839
+ // Anchor target owns its own lifecycle; just clear our latched
4840
+ // state so a subsequent pointerup doesn't fire a stale drop.
4841
+ if (overrideTarget) {
4842
+ this._state = undefined;
4843
+ overrideTarget.clear();
4844
+ return;
4845
+ }
4846
+ this._removeOverlay();
4847
+ }
4848
+ _onDropEvent(event) {
4849
+ var _a, _b;
4850
+ const state = this._state;
4851
+ const overrideTarget = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
4852
+ this._removeOverlay();
4853
+ overrideTarget === null || overrideTarget === void 0 ? void 0 : overrideTarget.clear();
4854
+ if (state) {
4855
+ this._onDrop.fire({
4856
+ position: state,
4857
+ nativeEvent: event.pointerEvent,
4858
+ });
4859
+ }
4860
+ }
4861
+ _calculateQuadrant(x, y, width, height) {
4862
+ var _a, _b;
4863
+ const activation = (_b = (_a = this.options.overlayModel) === null || _a === void 0 ? void 0 : _a.activationSize) !== null && _b !== void 0 ? _b : DEFAULT_ACTIVATION_SIZE;
4864
+ if (activation.type === 'percentage') {
4865
+ return calculateQuadrantAsPercentage(this._acceptedTargetZonesSet, x, y, width, height, activation.value);
4866
+ }
4867
+ return calculateQuadrantAsPixels(this._acceptedTargetZonesSet, x, y, width, height, activation.value);
4868
+ }
4869
+ _removeOverlay() {
4870
+ var _a;
4871
+ if (this._targetElement) {
4872
+ this._state = undefined;
4873
+ (_a = this._targetElement.parentElement) === null || _a === void 0 ? void 0 : _a.classList.remove('dv-drop-target');
4874
+ this._targetElement.remove();
4875
+ this._targetElement = undefined;
4876
+ this._overlayElement = undefined;
4877
+ }
4878
+ else {
4879
+ this._state = undefined;
4880
+ }
4881
+ }
4882
+ }
4883
+
4884
+ const DEFAULT_THRESHOLD = 5;
4885
+ const DEFAULT_TOUCH_INITIATION_DELAY = 250;
4886
+ const DEFAULT_PRESS_TOLERANCE = 8;
4887
+ /**
4888
+ * Pointer-event drag source. Waits for movement past `threshold` (and
4889
+ * touch-only `touchInitiationDelay`) before promoting to a drag so taps
4890
+ * pass through unaffected.
4891
+ */
4892
+ class PointerDragSource extends CompositeDisposable {
4893
+ constructor(element, options) {
4894
+ var _a;
4895
+ super();
4896
+ this.element = element;
4897
+ this.options = options;
4898
+ this._disabled = false;
4899
+ this._armed = false;
4900
+ this._startX = 0;
4901
+ this._startY = 0;
4902
+ this._touchOnly = (_a = options.touchOnly) !== null && _a !== void 0 ? _a : true;
4903
+ this.addDisposables(addDisposableListener(this.element, 'pointerdown', (e) => {
4904
+ this._onPointerDown(e);
4905
+ }));
4906
+ }
4907
+ setDisabled(value) {
4908
+ this._disabled = value;
4909
+ if (value) {
4910
+ this._cancelPending();
4911
+ }
4912
+ }
4913
+ /**
4914
+ * `false` lets the pointer source also handle mouse pointers; used when
4915
+ * `dndStrategy: 'pointer'` to drive every input type through this path.
4916
+ */
4917
+ setTouchOnly(value) {
4918
+ if (this._touchOnly === value) {
4919
+ return;
4920
+ }
4921
+ this._touchOnly = value;
4922
+ // A pending mouse-tracked drag should be abandoned if we re-enable
4923
+ // the touch-only filter mid-flight.
4924
+ if (value) {
4925
+ this._cancelPending();
4926
+ }
4927
+ }
4928
+ _shouldHandle(event) {
4929
+ var _a, _b;
4930
+ if (this._disabled) {
4931
+ return false;
4932
+ }
4933
+ // Pointer-type filter runs before isCancelled — consumer state read
4934
+ // by isCancelled may not be populated for events we'll never handle.
4935
+ if (this._touchOnly &&
4936
+ event.pointerType !== 'touch' &&
4937
+ event.pointerType !== 'pen') {
4938
+ return false;
4939
+ }
4940
+ if ((_b = (_a = this.options).isCancelled) === null || _b === void 0 ? void 0 : _b.call(_a, event)) {
4941
+ return false;
4942
+ }
4943
+ return true;
4944
+ }
4945
+ _onPointerDown(event) {
4946
+ var _a, _b, _c, _d, _e;
4947
+ if (!this._shouldHandle(event)) {
4948
+ return;
4949
+ }
4950
+ // Defensive: a fresh pointerdown supersedes any in-flight tracking.
4951
+ this._cancelPending();
4952
+ this._pendingPointerId = event.pointerId;
4953
+ this._startX = event.clientX;
4954
+ this._startY = event.clientY;
4955
+ this._startEvent = event;
4956
+ const isTouch = event.pointerType === 'touch' || event.pointerType === 'pen';
4957
+ // Touch waits a short window so a still finger can press-and-hold
4958
+ // before drifting; once the timer fires, any motion past `threshold`
4959
+ // begins the drag.
4960
+ const initiationDelayOpt = this.options.touchInitiationDelay;
4961
+ const initiationDelay = (_a = (typeof initiationDelayOpt === 'function'
4962
+ ? initiationDelayOpt()
4963
+ : initiationDelayOpt)) !== null && _a !== void 0 ? _a : DEFAULT_TOUCH_INITIATION_DELAY;
4964
+ this._armed = !isTouch || initiationDelay <= 0;
4965
+ if (isTouch && initiationDelay > 0 && isFinite(initiationDelay)) {
4966
+ this._armTimer = setTimeout(() => {
4967
+ this._armTimer = undefined;
4968
+ this._armed = true;
4969
+ }, initiationDelay);
4970
+ }
4971
+ const threshold = (_b = this.options.threshold) !== null && _b !== void 0 ? _b : DEFAULT_THRESHOLD;
4972
+ const pressToleranceOpt = this.options.pressTolerance;
4973
+ const pressTolerance = (_c = (typeof pressToleranceOpt === 'function'
4974
+ ? pressToleranceOpt()
4975
+ : pressToleranceOpt)) !== null && _c !== void 0 ? _c : DEFAULT_PRESS_TOLERANCE;
4976
+ // Source's owning window — popout drags fire on their own window.
4977
+ const targetWindow = (_e = (_d = this.element.ownerDocument) === null || _d === void 0 ? void 0 : _d.defaultView) !== null && _e !== void 0 ? _e : window;
4978
+ this._pendingMoveListener = addDisposableListener(targetWindow, 'pointermove', (moveEvent) => {
4979
+ if (moveEvent.pointerId !== this._pendingPointerId) {
4980
+ return;
4981
+ }
4982
+ const dx = moveEvent.clientX - this._startX;
4983
+ const dy = moveEvent.clientY - this._startY;
4984
+ const distance = Math.hypot(dx, dy);
4985
+ if (this._armed) {
4986
+ if (distance >= threshold) {
4987
+ this._beginDrag(moveEvent);
4988
+ }
4989
+ return;
4990
+ }
4991
+ // Pre-arm phase: a flick past `pressTolerance` in any
4992
+ // direction is treated as drag intent. The element opts out
4993
+ // of native scroll via `touch-action: none`; container-level
4994
+ // scrolling lives on the surrounding strip's empty space.
4995
+ if (distance > pressTolerance) {
4996
+ this._beginDrag(moveEvent);
4997
+ }
4998
+ });
4999
+ this._pendingUpListener = addDisposableListener(targetWindow, 'pointerup', (upEvent) => {
5000
+ if (upEvent.pointerId !== this._pendingPointerId) {
5001
+ return;
5002
+ }
5003
+ this._cancelPending();
5004
+ });
5005
+ this._pendingCancelListener = addDisposableListener(targetWindow, 'pointercancel', (cancelEvent) => {
5006
+ if (cancelEvent.pointerId !== this._pendingPointerId) {
5007
+ return;
5008
+ }
5009
+ this._cancelPending();
5010
+ });
5011
+ }
5012
+ /** For sibling gesture detectors (e.g. LongPressDetector) to dismiss a pending drag. */
5013
+ cancelPending() {
5014
+ this._cancelPending();
5015
+ }
5016
+ _cancelPending() {
5017
+ var _a, _b, _c;
5018
+ this._pendingPointerId = undefined;
5019
+ if (this._armTimer !== undefined) {
5020
+ clearTimeout(this._armTimer);
5021
+ this._armTimer = undefined;
5022
+ }
5023
+ this._armed = false;
5024
+ (_a = this._pendingMoveListener) === null || _a === void 0 ? void 0 : _a.dispose();
5025
+ (_b = this._pendingUpListener) === null || _b === void 0 ? void 0 : _b.dispose();
5026
+ (_c = this._pendingCancelListener) === null || _c === void 0 ? void 0 : _c.dispose();
5027
+ this._pendingMoveListener = undefined;
5028
+ this._pendingUpListener = undefined;
5029
+ this._pendingCancelListener = undefined;
5030
+ this._startEvent = undefined;
5031
+ }
5032
+ _beginDrag(triggerEvent) {
5033
+ var _a, _b, _c, _d, _e;
5034
+ const startEvent = (_a = this._startEvent) !== null && _a !== void 0 ? _a : triggerEvent;
5035
+ this._cancelPending();
5036
+ (_c = (_b = this.options).onDragStart) === null || _c === void 0 ? void 0 : _c.call(_b, startEvent);
5037
+ const ghost = (_e = (_d = this.options).createGhost) === null || _e === void 0 ? void 0 : _e.call(_d, startEvent);
5038
+ PointerDragController.getInstance().beginDrag({
5039
+ pointerEvent: triggerEvent,
5040
+ source: this.element,
5041
+ getData: () => this.options.getData(startEvent),
5042
+ ghost,
5043
+ onDragMove: this.options.onDragMove,
5044
+ onDragEnd: this.options.onDragEnd,
5045
+ });
5046
+ }
5047
+ dispose() {
5048
+ this._cancelPending();
5049
+ super.dispose();
5050
+ }
5051
+ }
5052
+
5053
+ /**
5054
+ * Floating clone that follows the pointer; appended to the owning
5055
+ * document's body with `pointer-events: none` so it doesn't intercept
5056
+ * hit-testing.
5057
+ */
5058
+ class PointerGhost {
5059
+ constructor(opts) {
5060
+ var _a, _b, _c, _d, _e;
5061
+ this._disposed = false;
5062
+ this.element = opts.element;
5063
+ this.offsetX = (_a = opts.offsetX) !== null && _a !== void 0 ? _a : 0;
5064
+ this.offsetY = (_b = opts.offsetY) !== null && _b !== void 0 ? _b : 0;
5065
+ // Animate via transform (see update); position:fixed for scroll-independence.
5066
+ this.element.style.position = 'fixed';
5067
+ this.element.style.left = '0px';
5068
+ this.element.style.top = '0px';
5069
+ this.element.style.pointerEvents = 'none';
5070
+ this.element.style.zIndex = '99999';
5071
+ this.element.style.opacity = String((_c = opts.opacity) !== null && _c !== void 0 ? _c : 0.8);
5072
+ this.element.style.willChange = 'transform';
5073
+ this.element.style.transform = `translate3d(${opts.initialX - this.offsetX}px, ${opts.initialY - this.offsetY}px, 0)`;
5074
+ const ownerDocument = (_e = (_d = opts.owner) === null || _d === void 0 ? void 0 : _d.ownerDocument) !== null && _e !== void 0 ? _e : document;
5075
+ ownerDocument.body.appendChild(this.element);
5076
+ }
5077
+ update(clientX, clientY) {
5078
+ if (this._disposed) {
5079
+ return;
5080
+ }
5081
+ // translate3d composites on the GPU — no layout per pointermove.
5082
+ this.element.style.transform = `translate3d(${clientX - this.offsetX}px, ${clientY - this.offsetY}px, 0)`;
5083
+ }
5084
+ dispose() {
5085
+ if (this._disposed) {
5086
+ return;
5087
+ }
5088
+ this._disposed = true;
5089
+ this.element.remove();
5090
+ }
5091
+ }
5092
+
5093
+ /**
5094
+ * HTML5 drag source. Listens for the native `dragstart` event, calls
5095
+ * `getData` to populate transfer, optionally renders the ghost via
5096
+ * `setDragImage`, fires `onDragStart` / `onDragEnd`, and tears down the
5097
+ * transfer disposer after `dragend`.
5098
+ */
5099
+ class Html5DragSource extends CompositeDisposable {
5100
+ constructor(el, opts) {
5101
+ super();
5102
+ this.el = el;
5103
+ this.opts = opts;
5104
+ this._dataDisposable = new MutableDisposable();
5105
+ this._pointerEventsDisposable = new MutableDisposable();
5106
+ this._disabled = !!opts.disabled;
5107
+ this.addDisposables(this._dataDisposable, this._pointerEventsDisposable, addDisposableListener(this.el, 'dragstart', (event) => {
5108
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
5109
+ if (event.defaultPrevented ||
5110
+ this._disabled ||
5111
+ ((_b = (_a = this.opts).isCancelled) === null || _b === void 0 ? void 0 : _b.call(_a, event))) {
5112
+ event.preventDefault();
5113
+ return;
5114
+ }
5115
+ // Iframes capture pointermove once the cursor enters them,
5116
+ // which freezes drag tracking from the parent window's
5117
+ // POV. Shield the source's owning document so popout-window
5118
+ // drags shield the popout, not the main window.
5119
+ const iframes = disableIframePointEvents((_c = this.el.ownerDocument) !== null && _c !== void 0 ? _c : document);
5120
+ this._pointerEventsDisposable.value = {
5121
+ dispose: () => iframes.release(),
5122
+ };
5123
+ this.el.classList.add('dv-dragged');
5124
+ setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
5125
+ this._dataDisposable.value = this.opts.getData(event);
5126
+ const ghost = (_e = (_d = this.opts).createGhost) === null || _e === void 0 ? void 0 : _e.call(_d, event);
5127
+ if (ghost && event.dataTransfer) {
5128
+ addGhostImage(event.dataTransfer, ghost.element, {
5129
+ x: (_f = ghost.offsetX) !== null && _f !== void 0 ? _f : 0,
5130
+ y: (_g = ghost.offsetY) !== null && _g !== void 0 ? _g : 0,
5131
+ });
5132
+ if (ghost.dispose) {
5133
+ // addGhostImage removes the element from the DOM on
5134
+ // the next tick; dispose the framework renderer on
5135
+ // the same schedule.
5136
+ const disposeGhost = ghost.dispose;
5137
+ setTimeout(() => disposeGhost(), 0);
5138
+ }
5139
+ }
5140
+ if (event.dataTransfer) {
5141
+ event.dataTransfer.effectAllowed = 'move';
5142
+ // Some third-party DnD libs (e.g. react-dnd) cancel the
5143
+ // dragstart when `dataTransfer.types` is empty.
5144
+ if (event.dataTransfer.items.length === 0) {
5145
+ event.dataTransfer.setData('text/plain', '');
5146
+ }
5147
+ }
5148
+ (_j = (_h = this.opts).onDragStart) === null || _j === void 0 ? void 0 : _j.call(_h, event);
5149
+ }), addDisposableListener(this.el, 'dragend', (event) => {
5150
+ var _a, _b;
5151
+ this._pointerEventsDisposable.dispose();
5152
+ // Defer disposal so drop handlers can still read the
5153
+ // transfer payload before it clears.
5154
+ setTimeout(() => this._dataDisposable.dispose(), 0);
5155
+ (_b = (_a = this.opts).onDragEnd) === null || _b === void 0 ? void 0 : _b.call(_a, event);
5156
+ }));
5157
+ }
5158
+ setDisabled(value) {
5159
+ this._disabled = value;
5160
+ }
5161
+ setTouchOnly(_) {
5162
+ // No-op — HTML5 path can't filter by pointer type.
5163
+ }
5164
+ cancelPending() {
5165
+ // No-op — HTML5 has no pre-arm phase to cancel.
5166
+ }
5167
+ }
5168
+ class Html5DragBackend {
5169
+ constructor() {
5170
+ this.kind = 'html5';
5171
+ }
5172
+ createDropTarget(element, options) {
5173
+ return new Droptarget(element, options);
5174
+ }
5175
+ createDragSource(element, options) {
5176
+ return new Html5DragSource(element, options);
5177
+ }
5178
+ }
5179
+ class PointerDragBackend {
5180
+ constructor() {
5181
+ this.kind = 'pointer';
5182
+ }
5183
+ createDropTarget(element, options) {
5184
+ return new PointerDropTarget(element, options);
5185
+ }
5186
+ createDragSource(element, options) {
5187
+ const pointerCreateGhost = options.createGhost
5188
+ ? (event) => {
5189
+ const spec = options.createGhost(event);
5190
+ if (!spec) {
5191
+ return undefined;
5192
+ }
5193
+ const ghost = new PointerGhost({
5194
+ element: spec.element,
5195
+ initialX: event.clientX,
5196
+ initialY: event.clientY,
5197
+ offsetX: spec.offsetX,
5198
+ offsetY: spec.offsetY,
5199
+ owner: element,
5200
+ });
5201
+ if (spec.dispose) {
5202
+ const baseDispose = ghost.dispose.bind(ghost);
5203
+ const disposeSpec = spec.dispose;
5204
+ ghost.dispose = () => {
5205
+ baseDispose();
5206
+ disposeSpec();
5207
+ };
5208
+ }
5209
+ return ghost;
5210
+ }
5211
+ : undefined;
5212
+ const source = new PointerDragSource(element, {
5213
+ getData: options.getData,
5214
+ isCancelled: options.isCancelled,
5215
+ onDragStart: options.onDragStart,
5216
+ onDragEnd: options.onDragEnd
5217
+ ? (event) => options.onDragEnd(event.pointerEvent)
5218
+ : undefined,
5219
+ createGhost: pointerCreateGhost,
5220
+ touchOnly: options.touchOnly,
5221
+ touchInitiationDelay: options.touchInitiationDelay,
5222
+ pressTolerance: options.pressTolerance,
5223
+ threshold: options.threshold,
5224
+ });
5225
+ if (options.disabled) {
5226
+ source.setDisabled(true);
5227
+ }
5228
+ return source;
5229
+ }
5230
+ }
5231
+ const html5Backend = new Html5DragBackend();
5232
+ const pointerBackend = new PointerDragBackend();
5233
+
5234
+ const PROPERTY_KEYS_PANEVIEW = (() => {
5235
+ /**
5236
+ * by readong the keys from an empty value object TypeScript will error
5237
+ * when we add or remove new properties to `DockviewOptions`
5238
+ */
5239
+ const properties = {
5240
+ disableAutoResizing: undefined,
5241
+ disableDnd: undefined,
5242
+ className: undefined,
5243
+ };
5244
+ return Object.keys(properties);
5245
+ })();
5246
+ class PaneviewUnhandledDragOverEvent extends AcceptableEvent {
5247
+ constructor(nativeEvent, position, getData, panel) {
5248
+ super();
5249
+ this.nativeEvent = nativeEvent;
5250
+ this.position = position;
5251
+ this.getData = getData;
5252
+ this.panel = panel;
5253
+ }
5254
+ }
5255
+
5256
+ class WillFocusEvent extends DockviewEvent {
5257
+ constructor() {
5258
+ super();
5259
+ }
5260
+ }
5261
+ /**
5262
+ * A core api implementation that should be used across all panel-like objects
5263
+ */
5264
+ class PanelApiImpl extends CompositeDisposable {
5265
+ get isFocused() {
5266
+ return this._isFocused;
5267
+ }
5268
+ get isActive() {
5269
+ return this._isActive;
5270
+ }
5271
+ get isVisible() {
5272
+ return this._isVisible;
5273
+ }
5274
+ get width() {
5275
+ return this._width;
5276
+ }
5277
+ get height() {
5278
+ return this._height;
5279
+ }
5280
+ constructor(id, component) {
5281
+ super();
5282
+ this.id = id;
5283
+ this.component = component;
5284
+ this._isFocused = false;
5285
+ this._isActive = false;
5286
+ this._isVisible = true;
5287
+ this._width = 0;
5288
+ this._height = 0;
5289
+ this._parameters = {};
5290
+ this.panelUpdatesDisposable = new MutableDisposable();
5291
+ this._onDidDimensionChange = new Emitter();
5292
+ this.onDidDimensionsChange = this._onDidDimensionChange.event;
5293
+ this._onDidChangeFocus = new Emitter();
5294
+ this.onDidFocusChange = this._onDidChangeFocus.event;
5295
+ //
5296
+ this._onWillFocus = new Emitter();
5297
+ this.onWillFocus = this._onWillFocus.event;
5298
+ //
5299
+ this._onDidVisibilityChange = new Emitter();
5300
+ this.onDidVisibilityChange = this._onDidVisibilityChange.event;
5301
+ this._onWillVisibilityChange = new Emitter();
5302
+ this.onWillVisibilityChange = this._onWillVisibilityChange.event;
5303
+ this._onDidActiveChange = new Emitter();
5304
+ this.onDidActiveChange = this._onDidActiveChange.event;
5305
+ this._onActiveChange = new Emitter();
5306
+ this.onActiveChange = this._onActiveChange.event;
5307
+ this._onDidParametersChange = new Emitter();
5308
+ this.onDidParametersChange = this._onDidParametersChange.event;
5309
+ this.addDisposables(this.onDidFocusChange((event) => {
5310
+ this._isFocused = event.isFocused;
5311
+ }), this.onDidActiveChange((event) => {
5312
+ this._isActive = event.isActive;
5313
+ }), this.onDidVisibilityChange((event) => {
5314
+ this._isVisible = event.isVisible;
5315
+ }), this.onDidDimensionsChange((event) => {
5316
+ this._width = event.width;
5317
+ this._height = event.height;
5318
+ }), this.panelUpdatesDisposable, this._onDidDimensionChange, this._onDidChangeFocus, this._onDidVisibilityChange, this._onDidActiveChange, this._onWillFocus, this._onActiveChange, this._onWillFocus, this._onWillVisibilityChange, this._onDidParametersChange);
5319
+ }
5320
+ getParameters() {
5321
+ return this._parameters;
5322
+ }
5323
+ initialize(panel) {
5324
+ this.panelUpdatesDisposable.value = this._onDidParametersChange.event((parameters) => {
5325
+ this._parameters = parameters;
5326
+ panel.update({
5327
+ params: parameters,
5328
+ });
5329
+ });
5330
+ }
5331
+ setVisible(isVisible) {
5332
+ this._onWillVisibilityChange.fire({ isVisible });
5333
+ }
5334
+ setActive() {
5335
+ this._onActiveChange.fire();
5336
+ }
5337
+ updateParameters(parameters) {
5338
+ this._onDidParametersChange.fire(parameters);
5339
+ }
5340
+ }
5341
+
5342
+ class SplitviewPanelApiImpl extends PanelApiImpl {
5343
+ //
5344
+ constructor(id, component) {
5345
+ super(id, component);
5346
+ this._onDidConstraintsChangeInternal = new Emitter();
5347
+ this.onDidConstraintsChangeInternal = this._onDidConstraintsChangeInternal.event;
5348
+ //
5349
+ this._onDidConstraintsChange = new Emitter({
5350
+ replay: true,
5351
+ });
5352
+ this.onDidConstraintsChange = this._onDidConstraintsChange.event;
5353
+ //
5354
+ this._onDidSizeChange = new Emitter();
5355
+ this.onDidSizeChange = this._onDidSizeChange.event;
5356
+ this.addDisposables(this._onDidConstraintsChangeInternal, this._onDidConstraintsChange, this._onDidSizeChange);
4688
5357
  }
4689
5358
  setConstraints(value) {
4690
5359
  this._onDidConstraintsChangeInternal.fire(value);
@@ -5027,35 +5696,42 @@ class DraggablePaneviewPanel extends PaneviewPanel {
5027
5696
  const id = this.id;
5028
5697
  const accessorId = this.accessor.id;
5029
5698
  this.header.draggable = true;
5030
- this.handler = new (class PaneDragHandler extends DragHandler {
5031
- getData() {
5699
+ const sharedDragOptions = {
5700
+ getData: () => {
5032
5701
  LocalSelectionTransfer.getInstance().setData([new PaneTransfer(accessorId, id)], PaneTransfer.prototype);
5033
5702
  return {
5034
5703
  dispose: () => {
5035
5704
  LocalSelectionTransfer.getInstance().clearData(PaneTransfer.prototype);
5036
5705
  },
5037
5706
  };
5707
+ },
5708
+ };
5709
+ this.html5DragSource = html5Backend.createDragSource(this.header, sharedDragOptions);
5710
+ this.pointerDragSource = pointerBackend.createDragSource(this.header, sharedDragOptions);
5711
+ const canDisplayOverlay = (event, position) => {
5712
+ const data = getPaneData();
5713
+ if (data) {
5714
+ if (data.paneId !== this.id &&
5715
+ data.viewId === this.accessor.id) {
5716
+ return true;
5717
+ }
5038
5718
  }
5039
- })(this.header);
5040
- this.target = new Droptarget(this.element, {
5719
+ const firedEvent = new PaneviewUnhandledDragOverEvent(event, position, getPaneData, this);
5720
+ this._onUnhandledDragOverEvent.fire(firedEvent);
5721
+ return firedEvent.isAccepted;
5722
+ };
5723
+ const dropTargetOptions = {
5041
5724
  acceptedTargetZones: ['top', 'bottom'],
5042
5725
  overlayModel: {
5043
5726
  activationSize: { type: 'percentage', value: 50 },
5044
5727
  },
5045
- canDisplayOverlay: (event, position) => {
5046
- const data = getPaneData();
5047
- if (data) {
5048
- if (data.paneId !== this.id &&
5049
- data.viewId === this.accessor.id) {
5050
- return true;
5051
- }
5052
- }
5053
- const firedEvent = new PaneviewUnhandledDragOverEvent(event, position, getPaneData, this);
5054
- this._onUnhandledDragOverEvent.fire(firedEvent);
5055
- return firedEvent.isAccepted;
5056
- },
5057
- });
5058
- this.addDisposables(this._onDidDrop, this.handler, this.target, this.target.onDrop((event) => {
5728
+ canDisplayOverlay,
5729
+ };
5730
+ this.target = html5Backend.createDropTarget(this.element, dropTargetOptions);
5731
+ this.pointerTarget = pointerBackend.createDropTarget(this.element, dropTargetOptions);
5732
+ this.addDisposables(this._onDidDrop, this.html5DragSource, this.pointerDragSource, this.target, this.pointerTarget, this.target.onDrop((event) => {
5733
+ this.onDrop(event);
5734
+ }), this.pointerTarget.onDrop((event) => {
5059
5735
  this.onDrop(event);
5060
5736
  }));
5061
5737
  }
@@ -5110,6 +5786,25 @@ class ContentContainer extends CompositeDisposable {
5110
5786
  this._element.tabIndex = -1;
5111
5787
  this.addDisposables(this._onDidFocus, this._onDidBlur);
5112
5788
  const target = group.dropTargetContainer;
5789
+ const canDisplayOverlay = (event, position) => {
5790
+ if (this.group.locked === 'no-drop-target' ||
5791
+ (this.group.locked && position === 'center')) {
5792
+ return false;
5793
+ }
5794
+ const data = getPanelData();
5795
+ if (!data &&
5796
+ event.shiftKey &&
5797
+ this.group.location.type !== 'floating') {
5798
+ return false;
5799
+ }
5800
+ if (data && data.viewId === this.accessor.id) {
5801
+ return true;
5802
+ }
5803
+ return this.group.canDisplayOverlay(event, position, 'content');
5804
+ };
5805
+ // `dropTarget` stays the concrete `Droptarget` (not via the backend
5806
+ // factory) because overlayRenderContainer forwards HTML5 drag events
5807
+ // through `dropTarget.dnd` — that field is not part of `IDropTarget`.
5113
5808
  this.dropTarget = new Droptarget(this.element, {
5114
5809
  getOverlayOutline: () => {
5115
5810
  var _a;
@@ -5119,25 +5814,22 @@ class ContentContainer extends CompositeDisposable {
5119
5814
  },
5120
5815
  className: 'dv-drop-target-content',
5121
5816
  acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
5122
- canDisplayOverlay: (event, position) => {
5123
- if (this.group.locked === 'no-drop-target' ||
5124
- (this.group.locked && position === 'center')) {
5125
- return false;
5126
- }
5127
- const data = getPanelData();
5128
- if (!data &&
5129
- event.shiftKey &&
5130
- this.group.location.type !== 'floating') {
5131
- return false;
5132
- }
5133
- if (data && data.viewId === this.accessor.id) {
5134
- return true;
5135
- }
5136
- return this.group.canDisplayOverlay(event, position, 'content');
5817
+ canDisplayOverlay,
5818
+ getOverrideTarget: target ? () => target.model : undefined,
5819
+ });
5820
+ this.pointerDropTarget = pointerBackend.createDropTarget(this.element, {
5821
+ acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
5822
+ canDisplayOverlay,
5823
+ getOverlayOutline: () => {
5824
+ var _a;
5825
+ return ((_a = accessor.options.theme) === null || _a === void 0 ? void 0 : _a.dndPanelOverlay) === 'group'
5826
+ ? this.element.parentElement
5827
+ : null;
5137
5828
  },
5829
+ className: 'dv-drop-target-content',
5138
5830
  getOverrideTarget: target ? () => target.model : undefined,
5139
5831
  });
5140
- this.addDisposables(this.dropTarget);
5832
+ this.addDisposables(this.dropTarget, this.pointerDropTarget);
5141
5833
  }
5142
5834
  show() {
5143
5835
  this.element.style.display = '';
@@ -5225,37 +5917,169 @@ class ContentContainer extends CompositeDisposable {
5225
5917
  }
5226
5918
  }
5227
5919
 
5228
- function addGhostImage(dataTransfer, ghostElement, options) {
5229
- var _a, _b;
5230
- // class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
5231
- addClasses(ghostElement, 'dv-dragged');
5232
- // move the element off-screen initially otherwise it may in some cases be rendered at (0,0) momentarily
5233
- ghostElement.style.top = '-9999px';
5234
- document.body.appendChild(ghostElement);
5235
- dataTransfer.setDragImage(ghostElement, (_a = options === null || options === void 0 ? void 0 : options.x) !== null && _a !== void 0 ? _a : 0, (_b = options === null || options === void 0 ? void 0 : options.y) !== null && _b !== void 0 ? _b : 0);
5236
- setTimeout(() => {
5237
- removeClasses(ghostElement, 'dv-dragged');
5238
- ghostElement.remove();
5239
- }, 0);
5240
- }
5241
-
5242
- class TabDragHandler extends DragHandler {
5243
- constructor(element, accessor, group, panel, disabled) {
5244
- super(element, disabled);
5245
- this.accessor = accessor;
5246
- this.group = group;
5247
- this.panel = panel;
5248
- this.panelTransfer = LocalSelectionTransfer.getInstance();
5920
+ const DEFAULT_DELAY = 500;
5921
+ const DEFAULT_TOLERANCE = 8;
5922
+ /**
5923
+ * Passive — does not consume the pointer; movement past `tolerance`
5924
+ * cancels silently so a sibling `PointerDragSource` can take over.
5925
+ */
5926
+ class LongPressDetector extends CompositeDisposable {
5927
+ constructor(element, options) {
5928
+ super();
5929
+ this.element = element;
5930
+ this.options = options;
5931
+ this._startX = 0;
5932
+ this._startY = 0;
5933
+ this.addDisposables(addDisposableListener(this.element, 'pointerdown', (e) => {
5934
+ this._onPointerDown(e);
5935
+ }));
5936
+ }
5937
+ _onPointerDown(event) {
5938
+ var _a, _b, _c, _d, _e;
5939
+ const touchOnly = (_a = this.options.touchOnly) !== null && _a !== void 0 ? _a : true;
5940
+ if (touchOnly &&
5941
+ event.pointerType !== 'touch' &&
5942
+ event.pointerType !== 'pen') {
5943
+ return;
5944
+ }
5945
+ // Defensive — supersede any in-flight press.
5946
+ this._cancelPending();
5947
+ this._pointerId = event.pointerId;
5948
+ this._startX = event.clientX;
5949
+ this._startY = event.clientY;
5950
+ const delay = (_b = this.options.delay) !== null && _b !== void 0 ? _b : DEFAULT_DELAY;
5951
+ const tolerance = (_c = this.options.tolerance) !== null && _c !== void 0 ? _c : DEFAULT_TOLERANCE;
5952
+ // Source's owning window — popout drags fire on their own window.
5953
+ const targetWindow = (_e = (_d = this.element.ownerDocument) === null || _d === void 0 ? void 0 : _d.defaultView) !== null && _e !== void 0 ? _e : window;
5954
+ this._timer = setTimeout(() => {
5955
+ this._timer = undefined;
5956
+ this._cancelPending();
5957
+ // Touch browsers synthesize a compatibility `contextmenu` event
5958
+ // for long-press. preventDefault on the original pointerdown is
5959
+ // too late (already dispatched), so install a one-shot
5960
+ // capture-phase guard for the next contextmenu. Without this,
5961
+ // consumers that don't preventDefault inside their onLongPress
5962
+ // (or that early-return before doing so) leak the browser's
5963
+ // native menu on top of theirs.
5964
+ this._installContextMenuGuard(targetWindow);
5965
+ // Same idea for `click`: when the user releases their finger
5966
+ // after the long-press, touch browsers dispatch a `click` to
5967
+ // the element the touch ended on (the source). Consumers
5968
+ // typically wire click to a primary action (e.g. tab activate,
5969
+ // tab-group chip collapse-toggle). Without this guard, the
5970
+ // long-press immediately fires both the context menu AND the
5971
+ // primary action — and the action's side effects (e.g. a chip
5972
+ // collapse animation) read as a screen wobble while the menu
5973
+ // is supposed to be open. Scoped to the source element so
5974
+ // clicks on menu items elsewhere remain effective.
5975
+ this._installClickGuard(targetWindow);
5976
+ this.options.onLongPress(event);
5977
+ }, delay);
5978
+ this._moveListener = addDisposableListener(targetWindow, 'pointermove', (moveEvent) => {
5979
+ if (moveEvent.pointerId !== this._pointerId) {
5980
+ return;
5981
+ }
5982
+ const dx = moveEvent.clientX - this._startX;
5983
+ const dy = moveEvent.clientY - this._startY;
5984
+ if (Math.hypot(dx, dy) > tolerance) {
5985
+ this._cancelPending();
5986
+ }
5987
+ });
5988
+ this._upListener = addDisposableListener(targetWindow, 'pointerup', (upEvent) => {
5989
+ if (upEvent.pointerId !== this._pointerId) {
5990
+ return;
5991
+ }
5992
+ this._cancelPending();
5993
+ });
5994
+ this._cancelListener = addDisposableListener(targetWindow, 'pointercancel', (cancelEvent) => {
5995
+ if (cancelEvent.pointerId !== this._pointerId) {
5996
+ return;
5997
+ }
5998
+ this._cancelPending();
5999
+ });
6000
+ }
6001
+ _installContextMenuGuard(targetWindow) {
6002
+ let guard;
6003
+ const timeout = setTimeout(() => guard === null || guard === void 0 ? void 0 : guard.dispose(), 500);
6004
+ guard = addDisposableListener(targetWindow, 'contextmenu', (event) => {
6005
+ event.preventDefault();
6006
+ clearTimeout(timeout);
6007
+ guard === null || guard === void 0 ? void 0 : guard.dispose();
6008
+ }, { capture: true });
6009
+ }
6010
+ _installClickGuard(targetWindow) {
6011
+ let guard;
6012
+ const timeout = setTimeout(() => guard === null || guard === void 0 ? void 0 : guard.dispose(), 500);
6013
+ guard = addDisposableListener(targetWindow, 'click', (event) => {
6014
+ // Only suppress clicks targeted at the long-pressed element
6015
+ // or its descendants. A user tap on a context menu item (or
6016
+ // anywhere else) still gets through unchanged.
6017
+ const target = event.target;
6018
+ if (target && this.element.contains(target)) {
6019
+ event.preventDefault();
6020
+ event.stopPropagation();
6021
+ }
6022
+ clearTimeout(timeout);
6023
+ guard === null || guard === void 0 ? void 0 : guard.dispose();
6024
+ }, { capture: true });
6025
+ }
6026
+ _cancelPending() {
6027
+ var _a, _b, _c;
6028
+ if (this._timer !== undefined) {
6029
+ clearTimeout(this._timer);
6030
+ this._timer = undefined;
6031
+ }
6032
+ this._pointerId = undefined;
6033
+ (_a = this._moveListener) === null || _a === void 0 ? void 0 : _a.dispose();
6034
+ (_b = this._upListener) === null || _b === void 0 ? void 0 : _b.dispose();
6035
+ (_c = this._cancelListener) === null || _c === void 0 ? void 0 : _c.dispose();
6036
+ this._moveListener = undefined;
6037
+ this._upListener = undefined;
6038
+ this._cancelListener = undefined;
6039
+ }
6040
+ dispose() {
6041
+ this._cancelPending();
6042
+ super.dispose();
6043
+ }
6044
+ }
6045
+
6046
+ function resolveDndCapabilities(options) {
6047
+ if (options.disableDnd) {
6048
+ return { html5: false, pointer: false, pointerHandlesMouse: false };
6049
+ }
6050
+ switch (options.dndStrategy) {
6051
+ case 'pointer':
6052
+ return { html5: false, pointer: true, pointerHandlesMouse: true };
6053
+ case 'html5':
6054
+ return { html5: true, pointer: false, pointerHandlesMouse: false };
6055
+ case 'auto':
6056
+ case undefined:
6057
+ default:
6058
+ // On touch-primary devices (phones / basic tablets) HTML5 DnD's
6059
+ // native long-press intercepts the gesture before our pointer
6060
+ // backend can react — Android Chrome launches a system drag with
6061
+ // its half-transparent thumbnail, and the long-press context menu
6062
+ // never opens. Disable HTML5 there so the pointer backend owns
6063
+ // every gesture. Hybrid devices (touchscreen laptops, Surface,
6064
+ // iPad with mouse) keep both backends — mouse uses HTML5, touch
6065
+ // falls back to whichever backend the underlying element wired.
6066
+ return isCoarsePrimaryInput$2()
6067
+ ? { html5: false, pointer: true, pointerHandlesMouse: true }
6068
+ : { html5: true, pointer: true, pointerHandlesMouse: false };
5249
6069
  }
5250
- getData(event) {
5251
- this.panelTransfer.setData([new PanelTransfer(this.accessor.id, this.group.id, this.panel.id)], PanelTransfer.prototype);
5252
- return {
5253
- dispose: () => {
5254
- this.panelTransfer.clearData(PanelTransfer.prototype);
5255
- },
5256
- };
6070
+ }
6071
+ function isCoarsePrimaryInput$2() {
6072
+ if (typeof window === 'undefined' || !window.matchMedia) {
6073
+ return false;
5257
6074
  }
6075
+ // Coarse pointer without any fine pointer = phone-class device. A laptop
6076
+ // touchscreen reports both, and we want HTML5 to remain available there
6077
+ // because a real mouse is also plugged in.
6078
+ const coarse = window.matchMedia('(pointer: coarse)').matches;
6079
+ const fine = window.matchMedia('(pointer: fine)').matches;
6080
+ return coarse && !fine;
5258
6081
  }
6082
+
5259
6083
  class Tab extends CompositeDisposable {
5260
6084
  get element() {
5261
6085
  return this._element;
@@ -5266,6 +6090,7 @@ class Tab extends CompositeDisposable {
5266
6090
  this.accessor = accessor;
5267
6091
  this.group = group;
5268
6092
  this.content = undefined;
6093
+ this.panelTransfer = LocalSelectionTransfer.getInstance();
5269
6094
  this._direction = 'horizontal';
5270
6095
  this._onPointDown = new Emitter();
5271
6096
  this.onPointerDown = this._onPointDown.event;
@@ -5277,114 +6102,106 @@ class Tab extends CompositeDisposable {
5277
6102
  this.onDragStart = this._onDragStart.event;
5278
6103
  this._onDragEnd = new Emitter();
5279
6104
  this.onDragEnd = this._onDragEnd.event;
6105
+ const caps = resolveDndCapabilities(this.accessor.options);
5280
6106
  this._element = document.createElement('div');
5281
6107
  this._element.className = 'dv-tab';
5282
6108
  this._element.tabIndex = 0;
5283
- this._element.draggable = !this.accessor.options.disableDnd;
6109
+ this._element.draggable = caps.html5;
5284
6110
  toggleClass(this.element, 'dv-inactive-tab', true);
5285
- this.dragHandler = new TabDragHandler(this._element, this.accessor, this.group, this.panel, !!this.accessor.options.disableDnd);
5286
- this.dropTarget = new Droptarget(this._element, {
6111
+ const canDisplayOverlay = (event, position) => {
6112
+ var _a;
6113
+ if (this.group.locked) {
6114
+ return false;
6115
+ }
6116
+ const data = getPanelData();
6117
+ if (data && this.accessor.id === data.viewId) {
6118
+ // Smooth-reorder takes over the in-flight visual when active,
6119
+ // so individual tab overlays are suppressed for internal drags.
6120
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
6121
+ return false;
6122
+ }
6123
+ return true;
6124
+ }
6125
+ return this.group.model.canDisplayOverlay(event, position, 'tab');
6126
+ };
6127
+ this.dropTarget = html5Backend.createDropTarget(this._element, {
5287
6128
  acceptedTargetZones: ['left', 'right'],
5288
6129
  overlayModel: this._buildOverlayModel(),
5289
- canDisplayOverlay: (event, position) => {
6130
+ canDisplayOverlay,
6131
+ getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
6132
+ });
6133
+ this.pointerDropTarget = pointerBackend.createDropTarget(this._element, {
6134
+ acceptedTargetZones: ['left', 'right'],
6135
+ overlayModel: this._buildOverlayModel(),
6136
+ canDisplayOverlay,
6137
+ getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
6138
+ });
6139
+ const sharedDragOptions = {
6140
+ getData: () => {
6141
+ this.panelTransfer.setData([
6142
+ new PanelTransfer(this.accessor.id, this.group.id, this.panel.id),
6143
+ ], PanelTransfer.prototype);
6144
+ return {
6145
+ dispose: () => {
6146
+ this.panelTransfer.clearData(PanelTransfer.prototype);
6147
+ },
6148
+ };
6149
+ },
6150
+ // 30/-10 matches the HTML5 setDragImage offset that has been
6151
+ // shipped for years; pointer backend wraps in PointerGhost,
6152
+ // HTML5 backend feeds into setDragImage.
6153
+ createGhost: () => ({
6154
+ element: this._buildGhostElement(),
6155
+ offsetX: 30,
6156
+ offsetY: -10,
6157
+ }),
6158
+ onDragStart: (event) => {
5290
6159
  var _a;
5291
- if (this.group.locked) {
5292
- return false;
5293
- }
5294
- const data = getPanelData();
5295
- if (data && this.accessor.id === data.viewId) {
5296
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
5297
- // When smooth reorder is enabled, the Tabs
5298
- // container handles all intra-accessor drops
5299
- // (both same-group and cross-group) via
5300
- // animation. Suppress the per-tab overlay so
5301
- // the tab is dropped *beside* rather than *on*.
5302
- return false;
5303
- }
5304
- return true;
6160
+ this._onDragStart.fire(event);
6161
+ if (!(event instanceof PointerEvent) &&
6162
+ ((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
6163
+ // Delay collapse to next frame so the browser
6164
+ // captures the full drag image first.
6165
+ requestAnimationFrame(() => {
6166
+ toggleClass(this.element, 'dv-tab--dragging', true);
6167
+ });
5305
6168
  }
5306
- return this.group.model.canDisplayOverlay(event, position, 'tab');
5307
6169
  },
5308
- getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
5309
- });
5310
- this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
6170
+ onDragEnd: (event) => {
6171
+ this._onDragEnd.fire(event);
6172
+ },
6173
+ };
6174
+ this.html5DragSource = html5Backend.createDragSource(this._element, Object.assign(Object.assign({}, sharedDragOptions), { disabled: !caps.html5 }));
6175
+ this.pointerDragSource = pointerBackend.createDragSource(this._element, Object.assign(Object.assign({}, sharedDragOptions), { disabled: !caps.pointer, touchOnly: !caps.pointerHandlesMouse, isCancelled: () => !resolveDndCapabilities(this.accessor.options).pointer }));
6176
+ // Both droptargets feed the same downstream stream; consumers don't
6177
+ // need to know which path produced the overlay.
6178
+ this.onWillShowOverlay = exports.DockviewEvent.any(this.dropTarget.onWillShowOverlay, this.pointerDropTarget.onWillShowOverlay);
5311
6179
  this.addDisposables(this._onPointDown, this._onTabClick, this._onDropped, this._onDragStart, this._onDragEnd, this.accessor.onDidOptionsChange(() => {
5312
- this.dropTarget.setOverlayModel(this._buildOverlayModel());
5313
- }), this.dragHandler.onDragStart((event) => {
5314
- var _a;
5315
- if (event.dataTransfer) {
5316
- const style = getComputedStyle(this.element);
5317
- const newNode = this.element.cloneNode(true);
5318
- const isVertical = this._direction === 'vertical';
5319
- /**
5320
- * Properties to skip when copying computed styles for a
5321
- * vertical tab ghost. `writing-mode` is excluded so we
5322
- * can force `horizontal-tb`. Size and margin logical
5323
- * properties are excluded because their physical meaning
5324
- * flips when writing-mode changes, which would produce
5325
- * incorrect dimensions.
5326
- */
5327
- const verticalSkip = new Set([
5328
- 'writing-mode',
5329
- 'inline-size',
5330
- 'block-size',
5331
- 'min-inline-size',
5332
- 'min-block-size',
5333
- 'max-inline-size',
5334
- 'max-block-size',
5335
- 'margin-inline',
5336
- 'margin-inline-start',
5337
- 'margin-inline-end',
5338
- 'margin-block',
5339
- 'margin-block-start',
5340
- 'margin-block-end',
5341
- 'padding-inline',
5342
- 'padding-inline-start',
5343
- 'padding-inline-end',
5344
- 'padding-block',
5345
- 'padding-block-start',
5346
- 'padding-block-end',
5347
- ]);
5348
- Array.from(style).forEach((key) => {
5349
- if (isVertical && verticalSkip.has(key)) {
5350
- return;
5351
- }
5352
- newNode.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key));
5353
- });
5354
- if (isVertical) {
5355
- // Force horizontal text flow and swap the physical
5356
- // dimensions so the ghost appears as a horizontal tab.
5357
- newNode.style.setProperty('writing-mode', 'horizontal-tb');
5358
- newNode.style.setProperty('width', style.height);
5359
- newNode.style.setProperty('height', style.width);
5360
- }
5361
- newNode.style.position = 'absolute';
5362
- newNode.classList.add('dv-tab-ghost-drag');
5363
- addGhostImage(event.dataTransfer, newNode, {
5364
- y: -10,
5365
- x: 30,
5366
- });
5367
- }
5368
- this._onDragStart.fire(event);
5369
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
5370
- // Delay collapse to next frame so the browser
5371
- // captures the full drag image first
5372
- requestAnimationFrame(() => {
5373
- toggleClass(this.element, 'dv-tab--dragging', true);
5374
- });
5375
- }
5376
- }), addDisposableListener(this._element, 'dragend', (event) => {
6180
+ const model = this._buildOverlayModel();
6181
+ this.dropTarget.setOverlayModel(model);
6182
+ this.pointerDropTarget.setOverlayModel(model);
6183
+ }), addDisposableListener(this._element, 'dragend', () => {
6184
+ // The shared onDragEnd handler already fires _onDragEnd via
6185
+ // the HTML5 backend; just strip the dragging class here.
5377
6186
  toggleClass(this.element, 'dv-tab--dragging', false);
5378
- this._onDragEnd.fire(event);
5379
- }), this.dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => {
6187
+ }), this.html5DragSource, addDisposableListener(this._element, 'pointerdown', (event) => {
5380
6188
  this._onPointDown.fire(event);
5381
6189
  }), addDisposableListener(this._element, 'click', (event) => {
5382
6190
  this._onTabClick.fire(event);
5383
6191
  }), addDisposableListener(this._element, 'contextmenu', (event) => {
5384
6192
  this.accessor.contextMenuController.show(this.panel, this.group, event);
6193
+ }), new LongPressDetector(this._element, {
6194
+ onLongPress: (event) => {
6195
+ // Don't let a subsequent finger move arm a drag on top
6196
+ // of the just-opened menu.
6197
+ this.pointerDragSource.cancelPending();
6198
+ this.accessor.contextMenuController.show(this.panel, this.group, event);
6199
+ },
5385
6200
  }), this.dropTarget.onDrop((event) => {
5386
6201
  this._onDropped.fire(event);
5387
- }), this.dropTarget);
6202
+ }), this.pointerDropTarget.onDrop((event) => {
6203
+ this._onDropped.fire(event);
6204
+ }), this.dropTarget, this.pointerDropTarget, this.pointerDragSource);
5388
6205
  }
5389
6206
  setActive(isActive) {
5390
6207
  toggleClass(this.element, 'dv-active-tab', isActive);
@@ -5414,11 +6231,60 @@ class Tab extends CompositeDisposable {
5414
6231
  }
5415
6232
  setDirection(direction) {
5416
6233
  this._direction = direction;
5417
- this.dropTarget.setTargetZones(direction === 'vertical' ? ['top', 'bottom'] : ['left', 'right']);
6234
+ const zones = direction === 'vertical' ? ['top', 'bottom'] : ['left', 'right'];
6235
+ this.dropTarget.setTargetZones(zones);
6236
+ this.pointerDropTarget.setTargetZones(zones);
5418
6237
  }
5419
6238
  updateDragAndDropState() {
5420
- this._element.draggable = !this.accessor.options.disableDnd;
5421
- this.dragHandler.setDisabled(!!this.accessor.options.disableDnd);
6239
+ const caps = resolveDndCapabilities(this.accessor.options);
6240
+ this._element.draggable = caps.html5;
6241
+ this.html5DragSource.setDisabled(!caps.html5);
6242
+ this.pointerDragSource.setDisabled(!caps.pointer);
6243
+ this.pointerDragSource.setTouchOnly(!caps.pointerHandlesMouse);
6244
+ }
6245
+ /**
6246
+ * Vertical tabs are flipped to horizontal so the ghost stays readable
6247
+ * during the drag rather than appearing sideways-rotated.
6248
+ */
6249
+ _buildGhostElement() {
6250
+ const style = getComputedStyle(this.element);
6251
+ const newNode = this.element.cloneNode(true);
6252
+ const isVertical = this._direction === 'vertical';
6253
+ const verticalSkip = new Set([
6254
+ 'writing-mode',
6255
+ 'inline-size',
6256
+ 'block-size',
6257
+ 'min-inline-size',
6258
+ 'min-block-size',
6259
+ 'max-inline-size',
6260
+ 'max-block-size',
6261
+ 'margin-inline',
6262
+ 'margin-inline-start',
6263
+ 'margin-inline-end',
6264
+ 'margin-block',
6265
+ 'margin-block-start',
6266
+ 'margin-block-end',
6267
+ 'padding-inline',
6268
+ 'padding-inline-start',
6269
+ 'padding-inline-end',
6270
+ 'padding-block',
6271
+ 'padding-block-start',
6272
+ 'padding-block-end',
6273
+ ]);
6274
+ Array.from(style).forEach((key) => {
6275
+ if (isVertical && verticalSkip.has(key)) {
6276
+ return;
6277
+ }
6278
+ newNode.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key));
6279
+ });
6280
+ if (isVertical) {
6281
+ newNode.style.setProperty('writing-mode', 'horizontal-tb');
6282
+ newNode.style.setProperty('width', style.height);
6283
+ newNode.style.setProperty('height', style.width);
6284
+ }
6285
+ newNode.style.position = 'absolute';
6286
+ newNode.classList.add('dv-tab-ghost-drag');
6287
+ return newNode;
5422
6288
  }
5423
6289
  }
5424
6290
 
@@ -5426,6 +6292,7 @@ class DockviewWillShowOverlayLocationEvent {
5426
6292
  get kind() {
5427
6293
  return this.options.kind;
5428
6294
  }
6295
+ /** Narrow with `instanceof DragEvent` before reading `dataTransfer`. */
5429
6296
  get nativeEvent() {
5430
6297
  return this.event.nativeEvent;
5431
6298
  }
@@ -5456,103 +6323,202 @@ class DockviewWillShowOverlayLocationEvent {
5456
6323
  }
5457
6324
  }
5458
6325
 
5459
- class GroupDragHandler extends DragHandler {
5460
- constructor(element, accessor, group, disabled) {
5461
- super(element, disabled);
5462
- this.accessor = accessor;
5463
- this.group = group;
5464
- this.panelTransfer = LocalSelectionTransfer.getInstance();
5465
- this.addDisposables(addDisposableListener(element, 'pointerdown', (e) => {
5466
- if (e.shiftKey) {
5467
- /**
5468
- * You cannot call e.preventDefault() because that will prevent drag events from firing
5469
- * but we also need to stop any group overlay drag events from occuring
5470
- * Use a custom event marker that can be checked by the overlay drag events
5471
- */
5472
- quasiPreventDefault(e);
5473
- }
5474
- }, true));
5475
- }
5476
- isCancelled(_event) {
5477
- if (this.group.api.location.type === 'floating' && !_event.shiftKey) {
5478
- return true;
5479
- }
5480
- if (this.group.api.location.type === 'edge' && this.group.size === 0) {
5481
- return true;
5482
- }
5483
- return false;
5484
- }
5485
- getData(dragEvent) {
5486
- const dataTransfer = dragEvent.dataTransfer;
5487
- this.panelTransfer.setData([new PanelTransfer(this.accessor.id, this.group.id, null)], PanelTransfer.prototype);
5488
- const style = window.getComputedStyle(this.el);
5489
- const bgColor = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-background-color');
5490
- const color = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-color');
5491
- if (dataTransfer) {
5492
- const ghostElement = document.createElement('div');
5493
- ghostElement.style.backgroundColor = bgColor;
5494
- ghostElement.style.color = color;
5495
- ghostElement.style.padding = '2px 8px';
5496
- ghostElement.style.height = '24px';
5497
- ghostElement.style.fontSize = '11px';
5498
- ghostElement.style.lineHeight = '20px';
5499
- ghostElement.style.borderRadius = '12px';
5500
- ghostElement.style.position = 'absolute';
5501
- ghostElement.style.pointerEvents = 'none';
5502
- ghostElement.style.top = '-9999px';
5503
- ghostElement.textContent = `Multiple Panels (${this.group.size})`;
5504
- addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 });
5505
- }
5506
- return {
5507
- dispose: () => {
5508
- this.panelTransfer.clearData(PanelTransfer.prototype);
5509
- },
5510
- };
5511
- }
5512
- }
5513
-
6326
+ // Floating-group redock via touch: require a deliberate long press so the
6327
+ // "move the float around" gesture doesn't double-trigger the redock ghost.
6328
+ // Infinity pressTolerance disables the pre-arm flick override; any motion
6329
+ // during the wait is treated as drag-the-float, not redock intent.
6330
+ const FLOATING_REDOCK_INITIATION_DELAY_MS = 500;
5514
6331
  class VoidContainer extends CompositeDisposable {
5515
6332
  get element() {
5516
6333
  return this._element;
5517
6334
  }
5518
6335
  constructor(accessor, group) {
6336
+ var _a, _b;
5519
6337
  super();
5520
6338
  this.accessor = accessor;
5521
6339
  this.group = group;
6340
+ this.panelTransfer = LocalSelectionTransfer.getInstance();
5522
6341
  this._onDrop = new Emitter();
5523
6342
  this.onDrop = this._onDrop.event;
5524
6343
  this._onDragStart = new Emitter();
5525
6344
  this.onDragStart = this._onDragStart.event;
6345
+ const caps = resolveDndCapabilities(this.accessor.options);
5526
6346
  this._element = document.createElement('div');
5527
6347
  this._element.className = 'dv-void-container';
5528
- this._element.draggable = !this.accessor.options.disableDnd;
5529
- toggleClass(this._element, 'dv-draggable', !this.accessor.options.disableDnd);
6348
+ this._element.draggable = caps.html5;
6349
+ toggleClass(this._element, 'dv-draggable', caps.html5 || caps.pointer);
5530
6350
  this.addDisposables(this._onDrop, this._onDragStart, addDisposableListener(this._element, 'pointerdown', () => {
5531
6351
  this.accessor.doSetGroupActive(this.group);
5532
- }));
5533
- this.handler = new GroupDragHandler(this._element, accessor, group, !!this.accessor.options.disableDnd);
5534
- this.dropTarget = new Droptarget(this._element, {
6352
+ }),
6353
+ // Shift+pointerdown marks the event so the group's overlay
6354
+ // drag (move-by-floating) sees it was consumed and doesn't
6355
+ // fire alongside the HTML5 drag. quasiPreventDefault sets the
6356
+ // marker without calling preventDefault — that would also
6357
+ // block dragstart, which we need to fire.
6358
+ addDisposableListener(this._element, 'pointerdown', (e) => {
6359
+ if (e.shiftKey) {
6360
+ quasiPreventDefault(e);
6361
+ }
6362
+ }, true));
6363
+ const canDisplayOverlay = (event, position) => {
6364
+ if (this.group.api.locked) {
6365
+ // Dropping on the void/header space adds the panel
6366
+ // to this group, which `locked` is meant to prevent
6367
+ // (both `true` and `'no-drop-target'`).
6368
+ return false;
6369
+ }
6370
+ const data = getPanelData();
6371
+ if (data && this.accessor.id === data.viewId) {
6372
+ return true;
6373
+ }
6374
+ return group.model.canDisplayOverlay(event, position, 'header_space');
6375
+ };
6376
+ this.dropTarget = html5Backend.createDropTarget(this._element, {
5535
6377
  acceptedTargetZones: ['center'],
5536
- canDisplayOverlay: (event, position) => {
5537
- const data = getPanelData();
5538
- if (data && this.accessor.id === data.viewId) {
5539
- return true;
5540
- }
5541
- return group.model.canDisplayOverlay(event, position, 'header_space');
5542
- },
6378
+ canDisplayOverlay,
5543
6379
  getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
5544
6380
  });
5545
- this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
5546
- this.addDisposables(this.handler, this.handler.onDragStart((event) => {
5547
- this._onDragStart.fire(event);
5548
- }), this.dropTarget.onDrop((event) => {
6381
+ this.pointerDropTarget = pointerBackend.createDropTarget(this._element, {
6382
+ acceptedTargetZones: ['center'],
6383
+ canDisplayOverlay,
6384
+ getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
6385
+ });
6386
+ const buildMultiPanelsGhost = () => {
6387
+ const ghostEl = document.createElement('div');
6388
+ const style = window.getComputedStyle(this._element);
6389
+ const bgColor = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-background-color');
6390
+ const color = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-color');
6391
+ ghostEl.style.backgroundColor = bgColor;
6392
+ ghostEl.style.color = color;
6393
+ ghostEl.style.padding = '2px 8px';
6394
+ ghostEl.style.height = '24px';
6395
+ ghostEl.style.fontSize = '11px';
6396
+ ghostEl.style.lineHeight = '20px';
6397
+ ghostEl.style.borderRadius = '12px';
6398
+ ghostEl.style.whiteSpace = 'nowrap';
6399
+ ghostEl.style.boxSizing = 'border-box';
6400
+ // HTML5 setDragImage snapshots the element as appended to the
6401
+ // document; a default block-level div would stretch to the
6402
+ // body's width and render as a viewport-wide bar.
6403
+ ghostEl.style.display = 'inline-block';
6404
+ ghostEl.textContent = `Multiple Panels (${this.group.size})`;
6405
+ return ghostEl;
6406
+ };
6407
+ const buildGhostSpec = () => {
6408
+ const createGhost = this.accessor.options.createGroupDragGhostComponent;
6409
+ if (createGhost) {
6410
+ const renderer = createGhost(this.group);
6411
+ renderer.init({
6412
+ group: this.group,
6413
+ api: this.accessor.api,
6414
+ });
6415
+ return {
6416
+ element: renderer.element,
6417
+ offsetX: 30,
6418
+ offsetY: -10,
6419
+ dispose: renderer.dispose
6420
+ ? () => { var _a; return (_a = renderer.dispose) === null || _a === void 0 ? void 0 : _a.call(renderer); }
6421
+ : undefined,
6422
+ };
6423
+ }
6424
+ return {
6425
+ element: buildMultiPanelsGhost(),
6426
+ offsetX: 30,
6427
+ offsetY: -10,
6428
+ };
6429
+ };
6430
+ const sharedDragOptions = {
6431
+ getData: () => {
6432
+ this.panelTransfer.setData([new PanelTransfer(this.accessor.id, this.group.id, null)], PanelTransfer.prototype);
6433
+ return {
6434
+ dispose: () => {
6435
+ this.panelTransfer.clearData(PanelTransfer.prototype);
6436
+ },
6437
+ };
6438
+ },
6439
+ createGhost: buildGhostSpec,
6440
+ onDragStart: (event) => {
6441
+ this._onDragStart.fire(event);
6442
+ },
6443
+ };
6444
+ this.html5DragSource = html5Backend.createDragSource(this._element, Object.assign(Object.assign({}, sharedDragOptions), { disabled: !caps.html5, isCancelled: (event) => {
6445
+ // HTML5: floating groups need shift+drag as the explicit
6446
+ // detach gesture (otherwise click-and-drag conflicts with
6447
+ // moving the floating group itself).
6448
+ if (this.group.api.location.type === 'floating' &&
6449
+ !event.shiftKey) {
6450
+ return true;
6451
+ }
6452
+ if (this.group.api.location.type === 'edge' &&
6453
+ this.group.size === 0) {
6454
+ return true;
6455
+ }
6456
+ return false;
6457
+ } }));
6458
+ const isFloating = () => { var _a, _b, _c; return ((_c = (_b = (_a = this.group) === null || _a === void 0 ? void 0 : _a.api) === null || _b === void 0 ? void 0 : _b.location) === null || _c === void 0 ? void 0 : _c.type) === 'floating'; };
6459
+ this.pointerDragSource = pointerBackend.createDragSource(this._element, Object.assign(Object.assign({}, sharedDragOptions), { disabled: !caps.pointer, touchOnly: !caps.pointerHandlesMouse,
6460
+ // Floating groups share this element with the overlay's
6461
+ // move-the-float drag. Without a longer hold + tolerance
6462
+ // override, both gestures commit simultaneously and the
6463
+ // user sees the float follow their finger *and* a ghost.
6464
+ touchInitiationDelay: () => isFloating() ? FLOATING_REDOCK_INITIATION_DELAY_MS : 250, pressTolerance: () => (isFloating() ? Infinity : 8), isCancelled: () => {
6465
+ if (!resolveDndCapabilities(this.accessor.options).pointer) {
6466
+ return true;
6467
+ }
6468
+ // Pointer: long-press IS the deliberate gesture, so
6469
+ // floating groups don't need the shift gate.
6470
+ if (this.group.api.location.type === 'edge' &&
6471
+ this.group.size === 0) {
6472
+ return true;
6473
+ }
6474
+ return false;
6475
+ }, onDragStart: (event) => {
6476
+ var _a;
6477
+ // Redock just committed — abort any in-flight overlay
6478
+ // move so the float stops following the finger while
6479
+ // the ghost takes over.
6480
+ (_a = this.getFloatingOverlay()) === null || _a === void 0 ? void 0 : _a.cancelPendingDrag();
6481
+ this._onDragStart.fire(event);
6482
+ } }));
6483
+ // Mirror direction: once the overlay's move-the-float gesture has
6484
+ // actually moved something, cancel the pending redock arm so the
6485
+ // ghost doesn't appear mid-drag if the user holds past 500ms.
6486
+ const overlayMoveSub = new MutableDisposable();
6487
+ const refreshOverlayMoveSub = () => {
6488
+ const overlay = this.getFloatingOverlay();
6489
+ overlayMoveSub.value = overlay
6490
+ ? overlay.onDidStartMoving(() => {
6491
+ this.pointerDragSource.cancelPending();
6492
+ })
6493
+ : exports.DockviewDisposable.NONE;
6494
+ };
6495
+ refreshOverlayMoveSub();
6496
+ this.addDisposables(overlayMoveSub);
6497
+ const locationChange = (_b = (_a = this.group) === null || _a === void 0 ? void 0 : _a.api) === null || _b === void 0 ? void 0 : _b.onDidLocationChange;
6498
+ if (locationChange) {
6499
+ this.addDisposables(locationChange(refreshOverlayMoveSub));
6500
+ }
6501
+ this.onWillShowOverlay = exports.DockviewEvent.any(this.dropTarget.onWillShowOverlay, this.pointerDropTarget.onWillShowOverlay);
6502
+ this.addDisposables(this.html5DragSource, this.dropTarget.onDrop((event) => {
6503
+ this._onDrop.fire(event);
6504
+ }), this.pointerDropTarget.onDrop((event) => {
5549
6505
  this._onDrop.fire(event);
5550
- }), this.dropTarget);
6506
+ }), this.dropTarget, this.pointerDropTarget, this.pointerDragSource);
5551
6507
  }
5552
6508
  updateDragAndDropState() {
5553
- this._element.draggable = !this.accessor.options.disableDnd;
5554
- toggleClass(this._element, 'dv-draggable', !this.accessor.options.disableDnd);
5555
- this.handler.setDisabled(!!this.accessor.options.disableDnd);
6509
+ const caps = resolveDndCapabilities(this.accessor.options);
6510
+ this._element.draggable = caps.html5;
6511
+ toggleClass(this._element, 'dv-draggable', caps.html5 || caps.pointer);
6512
+ this.html5DragSource.setDisabled(!caps.html5);
6513
+ this.pointerDragSource.setDisabled(!caps.pointer);
6514
+ this.pointerDragSource.setTouchOnly(!caps.pointerHandlesMouse);
6515
+ }
6516
+ getFloatingOverlay() {
6517
+ var _a, _b;
6518
+ if (!this.group) {
6519
+ return undefined;
6520
+ }
6521
+ return (_b = (_a = this.accessor.floatingGroups) === null || _a === void 0 ? void 0 : _a.find((fg) => fg.group === this.group)) === null || _b === void 0 ? void 0 : _b.overlay;
5556
6522
  }
5557
6523
  }
5558
6524
 
@@ -5808,6 +6774,14 @@ function resolveTabGroupAccent(color, palette) {
5808
6774
  return (palette !== null && palette !== void 0 ? palette : getFallbackPalette()).resolveValue(color);
5809
6775
  }
5810
6776
 
6777
+ /**
6778
+ * Visual chip for a tab group. Owns the DOM element, label, click /
6779
+ * context-menu interactions, and exposes a long-press gesture as a
6780
+ * second `onContextMenu` source. Drag-and-drop wiring lives in
6781
+ * `TabGroupManager` — the manager constructs the drag sources on this
6782
+ * chip's element so it can include tabs-list context (custom group
6783
+ * drag image, tab-group transfer payload).
6784
+ */
5811
6785
  class TabGroupChip extends CompositeDisposable {
5812
6786
  get element() {
5813
6787
  return this._element;
@@ -5818,22 +6792,22 @@ class TabGroupChip extends CompositeDisposable {
5818
6792
  this._onClick = new Emitter();
5819
6793
  this.onClick = this._onClick.event;
5820
6794
  this._onContextMenu = new Emitter();
6795
+ /** Fires on right-click and on touch long-press. */
5821
6796
  this.onContextMenu = this._onContextMenu.event;
5822
- this._onDragStart = new Emitter();
5823
- this.onDragStart = this._onDragStart.event;
5824
6797
  this._element = document.createElement('div');
5825
6798
  this._element.className = 'dv-tab-group-chip';
5826
6799
  this._element.tabIndex = 0;
5827
- this._element.draggable = true;
5828
6800
  this._label = document.createElement('span');
5829
6801
  this._label.className = 'dv-tab-group-chip-label';
5830
6802
  this._element.appendChild(this._label);
5831
- this.addDisposables(this._onClick, this._onContextMenu, this._onDragStart, addDisposableListener(this._element, 'click', (event) => {
6803
+ this.addDisposables(this._onClick, this._onContextMenu, new LongPressDetector(this._element, {
6804
+ onLongPress: (event) => {
6805
+ this._onContextMenu.fire(event);
6806
+ },
6807
+ }), addDisposableListener(this._element, 'click', (event) => {
5832
6808
  this._onClick.fire(event);
5833
6809
  }), addDisposableListener(this._element, 'contextmenu', (event) => {
5834
6810
  this._onContextMenu.fire(event);
5835
- }), addDisposableListener(this._element, 'dragstart', (event) => {
5836
- this._onDragStart.fire(event);
5837
6811
  }));
5838
6812
  }
5839
6813
  init(params) {
@@ -6115,7 +7089,7 @@ class WrapTabGroupIndicator extends BaseTabGroupIndicator {
6115
7089
  let svg = underline.firstElementChild;
6116
7090
  let path;
6117
7091
  if (!svg || svg.tagName !== 'svg') {
6118
- underline.innerHTML = '';
7092
+ underline.replaceChildren();
6119
7093
  svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
6120
7094
  svg.style.display = 'block';
6121
7095
  path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
@@ -6213,7 +7187,7 @@ class NoneTabGroupIndicator extends BaseTabGroupIndicator {
6213
7187
  underline.style.display = '';
6214
7188
  // Clear any SVG content left over from a mode switch
6215
7189
  if (underline.firstElementChild) {
6216
- underline.innerHTML = '';
7190
+ underline.replaceChildren();
6217
7191
  }
6218
7192
  underline.style.backgroundColor = color;
6219
7193
  if (isVertical) {
@@ -6297,6 +7271,12 @@ class TabGroupManager {
6297
7271
  this._positionChipForGroup(tabGroup);
6298
7272
  }
6299
7273
  }
7274
+ updateDirection() {
7275
+ const isVertical = this._ctx.getDirection() === 'vertical';
7276
+ for (const [, entry] of this._chipRenderers) {
7277
+ entry.dropTarget.setTargetZones(isVertical ? ['top'] : ['left']);
7278
+ }
7279
+ }
6300
7280
  snapshotChipWidths() {
6301
7281
  const widths = new Map();
6302
7282
  for (const [groupId, entry] of this._chipRenderers) {
@@ -6390,6 +7370,45 @@ class TabGroupManager {
6390
7370
  (_a = this._pendingTransitionCleanups.get(panelId)) === null || _a === void 0 ? void 0 : _a();
6391
7371
  this._pendingTransitionCleanups.delete(panelId);
6392
7372
  }
7373
+ updateDragAndDropState() {
7374
+ const caps = resolveDndCapabilities(this._ctx.accessor.options);
7375
+ for (const entry of this._chipRenderers.values()) {
7376
+ entry.chip.element.draggable = caps.html5;
7377
+ entry.html5DragSource.setDisabled(!caps.html5);
7378
+ entry.pointerDragSource.setDisabled(!caps.pointer);
7379
+ entry.pointerDragSource.setTouchOnly(!caps.pointerHandlesMouse);
7380
+ }
7381
+ }
7382
+ /**
7383
+ * Synchronously dispose the chip drag sources for an in-flight chip
7384
+ * drag. Called from `_commitGroupMove` so the transfer payload +
7385
+ * iframe shield are released BEFORE the cross-group move detaches
7386
+ * the chip (chip dispose is scheduled on a microtask via
7387
+ * `_scheduleTabGroupUpdate`, which is too late for callers that read
7388
+ * `getPanelData()` synchronously after the move). Idempotent — the
7389
+ * subsequent `update()` will also dispose the sources.
7390
+ */
7391
+ disposeChipDrag(tabGroupId) {
7392
+ var _a, _b;
7393
+ const entry = this._chipRenderers.get(tabGroupId);
7394
+ if (!entry) {
7395
+ return;
7396
+ }
7397
+ // Optional-chained because tests may inject minimal entries
7398
+ // that skip the manager's normal `_ensureChipForGroup` flow.
7399
+ (_a = entry.html5DragSource) === null || _a === void 0 ? void 0 : _a.dispose();
7400
+ (_b = entry.pointerDragSource) === null || _b === void 0 ? void 0 : _b.dispose();
7401
+ }
7402
+ /** Cloned chip rect used as the pointer follow-finger ghost. */
7403
+ _buildChipGhostElement(chipEl) {
7404
+ const style = getComputedStyle(chipEl);
7405
+ const clone = chipEl.cloneNode(true);
7406
+ Array.from(style).forEach((key) => {
7407
+ clone.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key));
7408
+ });
7409
+ clone.style.position = 'absolute';
7410
+ return clone;
7411
+ }
6393
7412
  disposeAll() {
6394
7413
  var _a;
6395
7414
  (_a = this._indicator) === null || _a === void 0 ? void 0 : _a.dispose();
@@ -6435,6 +7454,74 @@ class TabGroupManager {
6435
7454
  ? createChip(tabGroup)
6436
7455
  : new TabGroupChip(this._ctx.accessor.tabGroupColorPalette);
6437
7456
  chip.init({ tabGroup, api: this._ctx.accessor.api });
7457
+ const caps = resolveDndCapabilities(this._ctx.accessor.options);
7458
+ chip.element.draggable = caps.html5;
7459
+ const panelTransfer = LocalSelectionTransfer.getInstance();
7460
+ // Shared `getData` for both backends. Sets a group-level
7461
+ // PanelTransfer (panelId=null, tabGroupId identifies the group).
7462
+ // The returned disposer clears it on drag end.
7463
+ const getData = () => {
7464
+ panelTransfer.setData([
7465
+ new PanelTransfer(this._ctx.accessor.id, this._ctx.group.id, null, tabGroup.id),
7466
+ ], PanelTransfer.prototype);
7467
+ return {
7468
+ dispose: () => {
7469
+ panelTransfer.clearData(PanelTransfer.prototype);
7470
+ },
7471
+ };
7472
+ };
7473
+ // The chip's HTML5 drag image is the cloned tabs list (chip only),
7474
+ // mounted inside the dockview root for CSS-variable inheritance and
7475
+ // positioned against the chip's in-place rect. Layout-dependent
7476
+ // offset means we set the drag image directly in `onDragStart`
7477
+ // (inside the dragstart handler) rather than via the generic
7478
+ // `createGhost` factory, which only knows about ghost specs that
7479
+ // can be appended to `document.body`.
7480
+ const html5DragSource = html5Backend.createDragSource(chip.element, {
7481
+ getData,
7482
+ disabled: !caps.html5,
7483
+ isCancelled: () => !resolveDndCapabilities(this._ctx.accessor.options).html5,
7484
+ onDragStart: (event) => {
7485
+ // Type guard via `dataTransfer` — `instanceof DragEvent`
7486
+ // would throw in jsdom which doesn't ship a DragEvent
7487
+ // constructor.
7488
+ if ('dataTransfer' in event && event.dataTransfer) {
7489
+ this.setGroupDragImage(event, tabGroup, chip.element);
7490
+ }
7491
+ this._callbacks.onChipDragStart(tabGroup, chip, event);
7492
+ },
7493
+ onDragEnd: (event) => {
7494
+ var _a, _b;
7495
+ (_b = (_a = this._callbacks).onChipDragEnd) === null || _b === void 0 ? void 0 : _b.call(_a, tabGroup, chip, event);
7496
+ },
7497
+ });
7498
+ // Synchronous panelTransfer cleanup directly on the chip element.
7499
+ // `Html5DragSource`'s dragend defers data disposal via `setTimeout(0)`
7500
+ // so drop handlers can read the payload — but a chip drag that
7501
+ // ends via `moveGroupOrPanel` (no actual drop event) needs the
7502
+ // singleton cleared immediately, otherwise a synchronous
7503
+ // `getPanelData()` after the move still sees the stale chip
7504
+ // payload. Attached directly (not via `addDisposableListener`) so
7505
+ // the listener survives chip disposal in the detach-then-dragend
7506
+ // cross-group path; `once: true` auto-removes after the single
7507
+ // dragend that we care about. (#1254)
7508
+ chip.element.addEventListener('dragend', () => {
7509
+ panelTransfer.clearData(PanelTransfer.prototype);
7510
+ }, { once: true });
7511
+ const pointerDragSource = pointerBackend.createDragSource(chip.element, {
7512
+ getData,
7513
+ disabled: !caps.pointer,
7514
+ touchOnly: !caps.pointerHandlesMouse,
7515
+ isCancelled: () => !resolveDndCapabilities(this._ctx.accessor.options).pointer,
7516
+ createGhost: () => ({
7517
+ element: this._buildChipGhostElement(chip.element),
7518
+ offsetX: 8,
7519
+ offsetY: 8,
7520
+ }),
7521
+ onDragStart: (event) => {
7522
+ this._callbacks.onChipDragStart(tabGroup, chip, event);
7523
+ },
7524
+ });
6438
7525
  const disposables = [
6439
7526
  tabGroup.onDidChange(() => {
6440
7527
  var _a;
@@ -6448,24 +7535,70 @@ class TabGroupManager {
6448
7535
  tabGroup.onDidCollapseChange(() => {
6449
7536
  this._updateTabGroupClasses();
6450
7537
  }),
7538
+ html5DragSource,
7539
+ pointerDragSource,
6451
7540
  ];
6452
- // Wire chip context menu and drag for all chip renderers
7541
+ // Context menu: built-in TabGroupChip already aggregates right-click
7542
+ // + touch long-press into `onContextMenu`. Custom chip renderers
7543
+ // don't, so attach a long-press detector and contextmenu listener
7544
+ // directly on their element.
7545
+ const onContextMenu = (event) => {
7546
+ // A long-press on a chip should preempt the in-flight pointer
7547
+ // drag and open the menu instead.
7548
+ pointerDragSource.cancelPending();
7549
+ this._callbacks.onChipContextMenu(tabGroup, event);
7550
+ };
6453
7551
  if (chip instanceof TabGroupChip) {
6454
- disposables.push(chip.onContextMenu((event) => {
6455
- this._callbacks.onChipContextMenu(tabGroup, event);
6456
- }), chip.onDragStart((event) => {
6457
- this._callbacks.onChipDragStart(tabGroup, chip, event);
6458
- }));
7552
+ disposables.push(chip.onContextMenu(onContextMenu));
6459
7553
  }
6460
7554
  else {
6461
- disposables.push(addDisposableListener(chip.element, 'contextmenu', (event) => {
6462
- this._callbacks.onChipContextMenu(tabGroup, event);
6463
- }), addDisposableListener(chip.element, 'dragstart', (event) => {
6464
- this._callbacks.onChipDragStart(tabGroup, chip, event);
6465
- }));
6466
- }
7555
+ disposables.push(new LongPressDetector(chip.element, {
7556
+ onLongPress: onContextMenu,
7557
+ }), addDisposableListener(chip.element, 'contextmenu', onContextMenu));
7558
+ }
7559
+ // The chip sits before its group's first tab in the DOM, so it
7560
+ // covers the "drop before the group" position. Without a drop
7561
+ // target here, dropping a tab over the chip is a dead zone —
7562
+ // particularly visible when the group is first in the tabs list
7563
+ // and there's no preceding tab whose right zone covers position 0.
7564
+ // The smooth animation path already shifts the chip's margin to
7565
+ // open a gap, so suppress the overlay in that mode.
7566
+ const isVertical = this._ctx.getDirection() === 'vertical';
7567
+ const dropTarget = new Droptarget(chip.element, {
7568
+ acceptedTargetZones: isVertical ? ['top'] : ['left'],
7569
+ overlayModel: {
7570
+ activationSize: { value: 100, type: 'percentage' },
7571
+ },
7572
+ canDisplayOverlay: (event, position) => {
7573
+ var _a;
7574
+ if (this._ctx.group.locked) {
7575
+ return false;
7576
+ }
7577
+ if (this._ctx.accessor.options.disableDnd) {
7578
+ return false;
7579
+ }
7580
+ const data = getPanelData();
7581
+ if (data && this._ctx.accessor.id === data.viewId) {
7582
+ if (((_a = this._ctx.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) ===
7583
+ 'smooth') {
7584
+ return false;
7585
+ }
7586
+ return true;
7587
+ }
7588
+ return this._ctx.group.model.canDisplayOverlay(event, position, 'tab');
7589
+ },
7590
+ });
7591
+ disposables.push(dropTarget, dropTarget.onDrop((event) => {
7592
+ this._callbacks.onChipDrop(tabGroup, event);
7593
+ }));
6467
7594
  const disposable = new CompositeDisposable(...disposables);
6468
- this._chipRenderers.set(tabGroup.id, { chip, disposable });
7595
+ this._chipRenderers.set(tabGroup.id, {
7596
+ chip,
7597
+ html5DragSource,
7598
+ pointerDragSource,
7599
+ disposable,
7600
+ dropTarget,
7601
+ });
6469
7602
  // Group is born collapsed (cross-group drop, layout restore, etc.):
6470
7603
  // its tabs are about to be added without the collapsed class. Skip
6471
7604
  // the animation in the upcoming _updateTabGroupClasses call so they
@@ -6724,6 +7857,7 @@ class Tabs extends CompositeDisposable {
6724
7857
  for (const tab of this._tabs) {
6725
7858
  tab.value.setDirection(value);
6726
7859
  }
7860
+ this._tabGroupManager.updateDirection();
6727
7861
  }
6728
7862
  constructor(group, accessor, options) {
6729
7863
  super();
@@ -6743,7 +7877,7 @@ class Tabs extends CompositeDisposable {
6743
7877
  this._voidContainer = null;
6744
7878
  this._voidContainerListeners = null;
6745
7879
  this._extendedDropZone = null;
6746
- this._chipDragCleanup = null;
7880
+ this._pointerInsideTabsList = false;
6747
7881
  this._onTabDragStart = new Emitter();
6748
7882
  this.onTabDragStart = this._onTabDragStart.event;
6749
7883
  this._onDrop = new Emitter();
@@ -6778,13 +7912,40 @@ class Tabs extends CompositeDisposable {
6778
7912
  onChipDragStart: (tabGroup, chip, event) => {
6779
7913
  this._handleChipDragStart(tabGroup, chip, event);
6780
7914
  },
7915
+ onChipDragEnd: () => {
7916
+ // HTML5 chip dragend (incl. cancels). The Html5DragSource
7917
+ // owns the listener on the chip element, so this fires
7918
+ // even if the chip was detached cross-group — the
7919
+ // element keeps its listeners until the source is
7920
+ // disposed. resetDragAnimation is a no-op after a
7921
+ // successful drop (anim state already null) thanks to
7922
+ // the gating inside it.
7923
+ this.resetDragAnimation();
7924
+ },
7925
+ onChipDrop: (tabGroup, event) => {
7926
+ this._handleChipDrop(tabGroup, event);
7927
+ },
6781
7928
  });
6782
7929
  this.addDisposables(this._onOverflowTabsChange, this._observerDisposable, this._onWillShowOverlay, this._onDrop, this._onTabDragStart, {
6783
7930
  dispose: () => {
6784
7931
  var _a;
6785
7932
  (_a = this._flipTransitionCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
6786
7933
  },
6787
- }, addDisposableListener(this.element, 'pointerdown', (event) => {
7934
+ },
7935
+ // Pointer-side cleanup: when any pointer drag ends, tear
7936
+ // down smooth-reorder anim state the dragover bridge may
7937
+ // have installed. The chip's pointer drag source handles
7938
+ // its own transfer payload + iframe-shield cleanup.
7939
+ PointerDragController.getInstance().onDragEnd(() => {
7940
+ this._pointerInsideTabsList = false;
7941
+ this.resetDragAnimation();
7942
+ }),
7943
+ // Pointer-event mirror of the HTML5 dragover / dragleave handlers
7944
+ // below. Drives smooth-reorder for `dndStrategy: 'pointer'` and
7945
+ // for touch drags in `'auto'`.
7946
+ PointerDragController.getInstance().onDragMove((e) => {
7947
+ this._handlePointerDragMove(e.clientX, e.clientY);
7948
+ }), addDisposableListener(this.element, 'pointerdown', (event) => {
6788
7949
  if (event.defaultPrevented) {
6789
7950
  return;
6790
7951
  }
@@ -6792,135 +7953,60 @@ class Tabs extends CompositeDisposable {
6792
7953
  if (isLeftClick) {
6793
7954
  this.accessor.doSetGroupActive(this.group);
6794
7955
  }
6795
- }), addDisposableListener(this._tabsList, 'dragover', (event) => {
6796
- var _a, _b, _c, _d;
6797
- if (this.accessor.options.disableDnd) {
6798
- return;
6799
- }
6800
- // If _animState exists but belongs to a different
6801
- // drag (stale from a previous operation), replace it
6802
- // so the current drag is handled correctly.
6803
- if (this._animState) {
6804
- const data = getPanelData();
6805
- if ((data === null || data === void 0 ? void 0 : data.tabGroupId) &&
6806
- data.groupId !== this.group.id &&
6807
- this._animState.sourceTabGroupId !== data.tabGroupId) {
6808
- this._animState = null;
6809
- }
6810
- }
6811
- if (!this._animState) {
6812
- const data = getPanelData();
6813
- // In default animation mode, individual tab drops
6814
- // are handled by per-tab Droptargets. But tab group
6815
- // chip drags still need tab-list-level handling so
6816
- // that drops on gaps / void space work.
6817
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) ===
6818
- 'default' &&
6819
- !(data === null || data === void 0 ? void 0 : data.tabGroupId)) {
6820
- return;
6821
- }
6822
- if (data &&
6823
- (data.panelId || data.tabGroupId) &&
6824
- data.groupId !== this.group.id) {
6825
- const avgWidth = this.getAverageTabWidth();
6826
- if (data.tabGroupId) {
6827
- // External group drag — look up the
6828
- // source tab group to size the gap
6829
- const sourceGroup = this.accessor.getPanel(data.groupId);
6830
- const sourceTg = sourceGroup === null || sourceGroup === void 0 ? void 0 : sourceGroup.model.getTabGroups().find((tg) => tg.id === data.tabGroupId);
6831
- const panelCount = (_b = sourceTg === null || sourceTg === void 0 ? void 0 : sourceTg.panelIds.length) !== null && _b !== void 0 ? _b : 1;
6832
- const groupGapWidth = avgWidth * panelCount + avgWidth;
6833
- this._animState = {
6834
- sourceTabId: '',
6835
- sourceIndex: -1,
6836
- tabPositions: this.snapshotTabPositions(),
6837
- chipPositions: this._tabGroupManager.snapshotChipWidths(),
6838
- currentInsertionIndex: null,
6839
- targetTabGroupId: null,
6840
- sourceTabGroupId: data.tabGroupId,
6841
- sourceGroupPanelIds: sourceTg
6842
- ? new Set(sourceTg.panelIds)
6843
- : new Set(),
6844
- sourceChipWidth: avgWidth,
6845
- cursorOffsetFromDragLeft: groupGapWidth / 2,
6846
- sourceGapWidth: groupGapWidth,
6847
- containerLeft: this._tabsList.getBoundingClientRect()
6848
- .left,
6849
- };
6850
- }
6851
- else {
6852
- this._animState = {
6853
- sourceTabId: data.panelId,
6854
- sourceIndex: -1,
6855
- tabPositions: this.snapshotTabPositions(),
6856
- chipPositions: this._tabGroupManager.snapshotChipWidths(),
6857
- currentInsertionIndex: null,
6858
- targetTabGroupId: null,
6859
- sourceTabGroupId: null,
6860
- sourceGroupPanelIds: null,
6861
- sourceChipWidth: 0,
6862
- cursorOffsetFromDragLeft: avgWidth / 2,
6863
- sourceGapWidth: avgWidth,
6864
- containerLeft: this._tabsList.getBoundingClientRect()
6865
- .left,
6866
- };
6867
- }
6868
- }
6869
- else {
6870
- return;
6871
- }
6872
- }
6873
- event.preventDefault(); // allow drop to fire on the container
6874
- // For intra-group drag (sourceIndex >= 0) the gap
6875
- // animation is the sole visual indicator — clear any
6876
- // stale anchor overlay that may have been set while the
6877
- // cursor was over the panel content area or another zone.
6878
- // External drags (sourceIndex === -1) leave the overlay
6879
- // to the individual tab Droptargets so cross-group
6880
- // animation is not disrupted.
6881
- if (this._animState.sourceIndex !== -1) {
6882
- (_d = (_c = this.group.model.dropTargetContainer) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.clear();
6883
- }
6884
- this.handleDragOver(event);
6885
- }, true), addDisposableListener(this._tabsList, 'dragleave', (event) => {
6886
- var _a, _b, _c;
6887
- if (!this._animState) {
7956
+ }),
7957
+ // Trackpad / wheel forwarding. The strip scrolls along its own
7958
+ // axis (x for horizontal headers, y for vertical), so deltaY
7959
+ // from a plain mouse wheel maps onto the strip's axis too —
7960
+ // this gives the VS Code-style "scroll over tab bar to page
7961
+ // through tabs" feel. We only consume the event when the strip
7962
+ // is actually overflowing in the direction the user wheeled in,
7963
+ // so a wheel at the edge of a non-overflowing strip still
7964
+ // bubbles up and scrolls the page. `{ passive: false }` is
7965
+ // required because we call preventDefault().
7966
+ addDisposableListener(this._tabsList, 'wheel', (event) => {
7967
+ const isVertical = this._direction === 'vertical';
7968
+ const primary = isVertical
7969
+ ? event.deltaY || event.deltaX
7970
+ : event.deltaX || event.deltaY;
7971
+ if (primary === 0) {
6888
7972
  return;
6889
7973
  }
6890
- const related = event.relatedTarget;
6891
- // Ignore moves between children of the tabs list
6892
- if (related && this._tabsList.contains(related)) {
6893
- return;
6894
- }
6895
- // If moving into the broader drop zone (e.g. void container,
6896
- // left actions), keep _animState alive so the external
6897
- // dragover listeners can continue the gap animation.
6898
- if (related && ((_a = this._extendedDropZone) === null || _a === void 0 ? void 0 : _a.contains(related))) {
6899
- this.resetTabTransforms();
6900
- this._animState.currentInsertionIndex = null;
7974
+ const max = isVertical
7975
+ ? this._tabsList.scrollHeight -
7976
+ this._tabsList.clientHeight
7977
+ : this._tabsList.scrollWidth -
7978
+ this._tabsList.clientWidth;
7979
+ if (max <= 0) {
6901
7980
  return;
6902
7981
  }
6903
- // When leaving toward the void container (empty header space
6904
- // to the right), keep the animation state so the drop can
6905
- // still land at the end position.
6906
- const rt = event.relatedTarget;
6907
- const isVoid = this._voidContainer &&
6908
- rt &&
6909
- (rt === this._voidContainer ||
6910
- this._voidContainer.contains(rt));
6911
- if (isVoid) {
7982
+ const current = isVertical
7983
+ ? this._tabsList.scrollTop
7984
+ : this._tabsList.scrollLeft;
7985
+ // At the edge in the wheel direction: let the page
7986
+ // scroll instead of trapping the gesture.
7987
+ if ((primary < 0 && current <= 0) ||
7988
+ (primary > 0 && current >= max)) {
6912
7989
  return;
6913
7990
  }
6914
- this.resetTabTransforms();
6915
- if (this._animState) {
6916
- if (this._animState.sourceIndex === -1) {
6917
- (_c = (_b = this.group.model.dropTargetContainer) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.clear();
6918
- this._animState = null;
6919
- }
6920
- else {
6921
- this._animState.currentInsertionIndex = null;
6922
- }
7991
+ event.preventDefault();
7992
+ // Custom-scrollbar mode wraps the tabs list and installs
7993
+ // its own wheel listener that rewrites scrollLeft from a
7994
+ // deltaY-only tracker. Without stopPropagation that
7995
+ // handler would clobber our deltaX-aware update.
7996
+ event.stopPropagation();
7997
+ if (isVertical) {
7998
+ this._tabsList.scrollTop = current + primary;
7999
+ }
8000
+ else {
8001
+ this._tabsList.scrollLeft = current + primary;
8002
+ }
8003
+ }, { passive: false }), addDisposableListener(this._tabsList, 'dragover', (event) => {
8004
+ if (this._processDragOver(event.clientX)) {
8005
+ // Allow `drop` to fire on the tabs list container.
8006
+ event.preventDefault();
6923
8007
  }
8008
+ }, true), addDisposableListener(this._tabsList, 'dragleave', (event) => {
8009
+ this._processDragLeave(event.relatedTarget);
6924
8010
  }, true), addDisposableListener(this._tabsList, 'dragend', () => {
6925
8011
  this.resetDragAnimation();
6926
8012
  }), addDisposableListener(this._tabsList, 'drop', (event) => {
@@ -7042,6 +8128,9 @@ class Tabs extends CompositeDisposable {
7042
8128
  const disposable = new CompositeDisposable(tab.onDragStart((event) => {
7043
8129
  var _a;
7044
8130
  this._onTabDragStart.fire({ nativeEvent: event, panel });
8131
+ // Both HTML5 and pointer drags initialize _animState. Cleanup
8132
+ // is wired in both paths: HTML5 via dragend/drop on _tabsList,
8133
+ // pointer via PointerDragController.onDragEnd subscriptions.
7045
8134
  if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
7046
8135
  const tabWidth = tab.element.getBoundingClientRect().width;
7047
8136
  const sourceIndex = this._tabs.findIndex((x) => x.value === tab);
@@ -7301,6 +8390,7 @@ class Tabs extends CompositeDisposable {
7301
8390
  for (const tab of this._tabs) {
7302
8391
  tab.value.updateDragAndDropState();
7303
8392
  }
8393
+ this._tabGroupManager.updateDragAndDropState();
7304
8394
  }
7305
8395
  /**
7306
8396
  * Synchronize chip elements and CSS classes for all tab groups
@@ -7312,6 +8402,13 @@ class Tabs extends CompositeDisposable {
7312
8402
  refreshTabGroupAccent() {
7313
8403
  this._tabGroupManager.refreshAccents();
7314
8404
  }
8405
+ /**
8406
+ * Tabs-list-specific side effects of a chip drag start. The chip's
8407
+ * drag sources (constructed by `TabGroupManager`) own the transfer
8408
+ * payload, iframe shielding, dataTransfer setup, and the HTML5 drag
8409
+ * image. This method just sets up the smooth-reorder anim state and
8410
+ * collapses the source-group tabs in the tabs list.
8411
+ */
7315
8412
  _handleChipDragStart(tabGroup, chip, event) {
7316
8413
  var _a;
7317
8414
  const firstPanelId = tabGroup.panelIds[0];
@@ -7342,89 +8439,79 @@ class Tabs extends CompositeDisposable {
7342
8439
  sourceGapWidth: groupGapWidth,
7343
8440
  containerLeft: this._tabsList.getBoundingClientRect().left,
7344
8441
  };
7345
- // Set LocalSelectionTransfer so drop targets recognise this as
7346
- // an internal dockview drag. panelId is null (group-level),
7347
- // tabGroupId identifies which tab group is being dragged.
7348
- const panelTransfer = LocalSelectionTransfer.getInstance();
7349
- panelTransfer.setData([
7350
- new PanelTransfer(this.accessor.id, this.group.id, null, tabGroup.id),
7351
- ], PanelTransfer.prototype);
7352
- const iframes = disableIframePointEvents();
7353
- // The dragend listener on `_tabsList` is unreachable for chip
7354
- // drags because cross-group drops detach the chip from the DOM
7355
- // before dragend fires (the source tab group becomes empty, so
7356
- // `_positionChipForGroup` removes the chip element). Without
7357
- // bubbling, the tabsList listener never runs and `_animState`,
7358
- // `_chipDragCleanup`, and the dragging CSS classes leak. Listen
7359
- // directly on the chip element so cleanup happens regardless of
7360
- // whether it's still attached. (Issue #1254.)
7361
- const chipElement = chip.element;
7362
- const onChipDragEnd = () => {
7363
- chipElement.removeEventListener('dragend', onChipDragEnd);
7364
- this.resetDragAnimation();
7365
- };
7366
- chipElement.addEventListener('dragend', onChipDragEnd);
7367
- this._chipDragCleanup = {
7368
- dispose: () => {
7369
- chipElement.removeEventListener('dragend', onChipDragEnd);
7370
- panelTransfer.clearData(PanelTransfer.prototype);
7371
- iframes.release();
7372
- },
7373
- };
7374
- if (event.dataTransfer) {
7375
- event.dataTransfer.effectAllowed = 'move';
7376
- if (event.dataTransfer.items.length === 0) {
7377
- event.dataTransfer.setData('text/plain', '');
7378
- }
7379
- }
7380
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
7381
- // Collapse group tabs + chip after the browser
7382
- // captures the drag image, then open the gap at the
7383
- // source position — all instant (no transitions).
7384
- const groupPanelIds = new Set(tabGroup.panelIds);
7385
- this._pendingCollapse = true;
7386
- requestAnimationFrame(() => {
7387
- var _a;
7388
- var _b;
7389
- this._pendingCollapse = false;
7390
- if (!this._animState) {
7391
- return;
7392
- }
7393
- // Collapse all group tabs instantly
7394
- for (const t of this._tabs) {
7395
- if (groupPanelIds.has(t.value.panel.id)) {
7396
- t.value.element.style.transition = 'none';
7397
- toggleClass(t.value.element, 'dv-tab--dragging', true);
7398
- }
7399
- }
7400
- // Collapse the group chip instantly
7401
- const chipEntry = this._tabGroupManager.chipRenderers.get(tabGroup.id);
7402
- if (chipEntry) {
7403
- chipEntry.chip.element.style.transition = 'none';
7404
- toggleClass(chipEntry.chip.element, 'dv-tab-group-chip--dragging', true);
7405
- }
7406
- // Single reflow for the entire batch
7407
- void this._tabsList.offsetHeight;
7408
- const underline = this._tabGroupManager.groupUnderlines.get(tabGroup.id);
7409
- if (underline) {
7410
- underline.style.display = 'none';
7411
- }
7412
- (_a = (_b = this._animState).currentInsertionIndex) !== null && _a !== void 0 ? _a : (_b.currentInsertionIndex = firstIdx);
7413
- // Apply gap with transitions disabled
7414
- this.applyDragOverTransforms(true);
7415
- // Re-enable transitions for subsequent moves
7416
- for (const t of this._tabs) {
7417
- if (groupPanelIds.has(t.value.panel.id)) {
7418
- t.value.element.style.removeProperty('transition');
7419
- }
8442
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) !== 'smooth') {
8443
+ return;
8444
+ }
8445
+ // Collapse group tabs + chip after the browser captures the drag
8446
+ // image, then open the gap at the source position — all instant
8447
+ // (no transitions).
8448
+ const groupPanelIds = new Set(tabGroup.panelIds);
8449
+ this._pendingCollapse = true;
8450
+ requestAnimationFrame(() => {
8451
+ var _a;
8452
+ var _b;
8453
+ this._pendingCollapse = false;
8454
+ if (!this._animState) {
8455
+ return;
8456
+ }
8457
+ // Collapse all group tabs instantly
8458
+ for (const t of this._tabs) {
8459
+ if (groupPanelIds.has(t.value.panel.id)) {
8460
+ t.value.element.style.transition = 'none';
8461
+ toggleClass(t.value.element, 'dv-tab--dragging', true);
7420
8462
  }
7421
- if (chipEntry) {
7422
- chipEntry.chip.element.style.removeProperty('transition');
8463
+ }
8464
+ // Collapse the group chip instantly
8465
+ const chipEntry = this._tabGroupManager.chipRenderers.get(tabGroup.id);
8466
+ if (chipEntry) {
8467
+ chipEntry.chip.element.style.transition = 'none';
8468
+ toggleClass(chipEntry.chip.element, 'dv-tab-group-chip--dragging', true);
8469
+ }
8470
+ // Single reflow for the entire batch
8471
+ void this._tabsList.offsetHeight;
8472
+ const underline = this._tabGroupManager.groupUnderlines.get(tabGroup.id);
8473
+ if (underline) {
8474
+ underline.style.display = 'none';
8475
+ }
8476
+ (_a = (_b = this._animState).currentInsertionIndex) !== null && _a !== void 0 ? _a : (_b.currentInsertionIndex = firstIdx);
8477
+ this.applyDragOverTransforms(true);
8478
+ for (const t of this._tabs) {
8479
+ if (groupPanelIds.has(t.value.panel.id)) {
8480
+ t.value.element.style.removeProperty('transition');
7423
8481
  }
7424
- });
8482
+ }
8483
+ if (chipEntry) {
8484
+ chipEntry.chip.element.style.removeProperty('transition');
8485
+ }
8486
+ });
8487
+ }
8488
+ /**
8489
+ * A drop on a tab group chip means "insert before this group". Resolve to
8490
+ * the index of the group's first tab, adjusting for same-group removal
8491
+ * (when the source tab is currently to the left of the target slot, its
8492
+ * removal shifts the insertion index down by one). Always clears
8493
+ * `targetTabGroupId` so the dropped tab lands outside the group.
8494
+ */
8495
+ _handleChipDrop(tabGroup, event) {
8496
+ const firstPanelId = tabGroup.panelIds[0];
8497
+ if (!firstPanelId) {
8498
+ return;
8499
+ }
8500
+ const insertionIndex = this._tabs.findIndex((x) => x.value.panel.id === firstPanelId);
8501
+ if (insertionIndex === -1) {
8502
+ return;
7425
8503
  }
7426
- // Build a composite drag image showing chip + group tabs
7427
- this._tabGroupManager.setGroupDragImage(event, tabGroup, chip.element);
8504
+ const data = getPanelData();
8505
+ const sourceIndex = data && data.groupId === this.group.id && data.panelId
8506
+ ? this._tabs.findIndex((x) => x.value.panel.id === data.panelId)
8507
+ : -1;
8508
+ const adjustedIndex = insertionIndex -
8509
+ (sourceIndex !== -1 && sourceIndex < insertionIndex ? 1 : 0);
8510
+ this._onDrop.fire({
8511
+ event: event.nativeEvent,
8512
+ index: adjustedIndex,
8513
+ targetTabGroupId: null,
8514
+ });
7428
8515
  }
7429
8516
  /**
7430
8517
  * Sets the broader container that is part of the same logical drop surface
@@ -7486,6 +8573,164 @@ class Tabs extends CompositeDisposable {
7486
8573
  }
7487
8574
  return total / this._tabs.length;
7488
8575
  }
8576
+ /**
8577
+ * Pointer-event entry point. The HTML5 path enters via the per-element
8578
+ * `dragover` listener; this one hit-tests the global pointer-drag
8579
+ * position against the tabs list and routes through the same shared
8580
+ * `_processDragOver` / `_processDragLeave` helpers.
8581
+ */
8582
+ _handlePointerDragMove(clientX, clientY) {
8583
+ var _a;
8584
+ const sourceDoc = (_a = this._tabsList.ownerDocument) !== null && _a !== void 0 ? _a : document;
8585
+ const elAtPoint = sourceDoc.elementFromPoint(clientX, clientY);
8586
+ const inside = !!elAtPoint &&
8587
+ (this._tabsList.contains(elAtPoint) ||
8588
+ (!!this._extendedDropZone &&
8589
+ this._extendedDropZone.contains(elAtPoint)));
8590
+ if (!inside) {
8591
+ if (this._pointerInsideTabsList) {
8592
+ this._pointerInsideTabsList = false;
8593
+ this._processDragLeave(elAtPoint);
8594
+ }
8595
+ return;
8596
+ }
8597
+ this._pointerInsideTabsList = true;
8598
+ this._processDragOver(clientX);
8599
+ }
8600
+ /**
8601
+ * Shared body of the dragover entry point. Refreshes stale anim state
8602
+ * for a changed drag identity, initializes anim state for incoming
8603
+ * cross-group drags, and dispatches to the gap-following math in
8604
+ * `handleDragOver`. Returns true when this tabs list has taken
8605
+ * ownership of the drag — HTML5 callers use this to gate
8606
+ * `event.preventDefault()`.
8607
+ */
8608
+ _processDragOver(clientX) {
8609
+ var _a, _b, _c, _d;
8610
+ if (this.accessor.options.disableDnd) {
8611
+ return false;
8612
+ }
8613
+ // Stale-state guard: if a previous drag's anim state is still here
8614
+ // but the current drag is a different identity, drop the stale one
8615
+ // so the new drag starts from a clean slate.
8616
+ if (this._animState) {
8617
+ const data = getPanelData();
8618
+ if ((data === null || data === void 0 ? void 0 : data.tabGroupId) &&
8619
+ data.groupId !== this.group.id &&
8620
+ this._animState.sourceTabGroupId !== data.tabGroupId) {
8621
+ this._animState = null;
8622
+ }
8623
+ }
8624
+ if (!this._animState) {
8625
+ const data = getPanelData();
8626
+ // In default animation mode, individual tab drops are handled
8627
+ // by per-tab Droptargets; only chip drags need tabs-list-level
8628
+ // handling so drops on void space still work.
8629
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'default' &&
8630
+ !(data === null || data === void 0 ? void 0 : data.tabGroupId)) {
8631
+ return false;
8632
+ }
8633
+ if (data &&
8634
+ (data.panelId || data.tabGroupId) &&
8635
+ data.groupId !== this.group.id) {
8636
+ const avgWidth = this.getAverageTabWidth();
8637
+ if (data.tabGroupId) {
8638
+ // External group drag — look up the source group to
8639
+ // size the gap.
8640
+ const sourceGroup = this.accessor.getPanel(data.groupId);
8641
+ const sourceTg = sourceGroup === null || sourceGroup === void 0 ? void 0 : sourceGroup.model.getTabGroups().find((tg) => tg.id === data.tabGroupId);
8642
+ const panelCount = (_b = sourceTg === null || sourceTg === void 0 ? void 0 : sourceTg.panelIds.length) !== null && _b !== void 0 ? _b : 1;
8643
+ const groupGapWidth = avgWidth * panelCount + avgWidth;
8644
+ this._animState = {
8645
+ sourceTabId: '',
8646
+ sourceIndex: -1,
8647
+ tabPositions: this.snapshotTabPositions(),
8648
+ chipPositions: this._tabGroupManager.snapshotChipWidths(),
8649
+ currentInsertionIndex: null,
8650
+ targetTabGroupId: null,
8651
+ sourceTabGroupId: data.tabGroupId,
8652
+ sourceGroupPanelIds: sourceTg
8653
+ ? new Set(sourceTg.panelIds)
8654
+ : new Set(),
8655
+ sourceChipWidth: avgWidth,
8656
+ cursorOffsetFromDragLeft: groupGapWidth / 2,
8657
+ sourceGapWidth: groupGapWidth,
8658
+ containerLeft: this._tabsList.getBoundingClientRect().left,
8659
+ };
8660
+ }
8661
+ else {
8662
+ this._animState = {
8663
+ sourceTabId: data.panelId,
8664
+ sourceIndex: -1,
8665
+ tabPositions: this.snapshotTabPositions(),
8666
+ chipPositions: this._tabGroupManager.snapshotChipWidths(),
8667
+ currentInsertionIndex: null,
8668
+ targetTabGroupId: null,
8669
+ sourceTabGroupId: null,
8670
+ sourceGroupPanelIds: null,
8671
+ sourceChipWidth: 0,
8672
+ cursorOffsetFromDragLeft: avgWidth / 2,
8673
+ sourceGapWidth: avgWidth,
8674
+ containerLeft: this._tabsList.getBoundingClientRect().left,
8675
+ };
8676
+ }
8677
+ }
8678
+ else {
8679
+ return false;
8680
+ }
8681
+ }
8682
+ // For intra-group drag (sourceIndex >= 0) the gap animation is the
8683
+ // sole visual indicator — clear any stale anchor overlay that may
8684
+ // have been set while the cursor was over the panel content area or
8685
+ // another zone. External drags (sourceIndex === -1) leave the
8686
+ // overlay to the individual tab Droptargets so cross-group
8687
+ // animation is not disrupted.
8688
+ if (this._animState.sourceIndex !== -1) {
8689
+ (_d = (_c = this.group.model.dropTargetContainer) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.clear();
8690
+ }
8691
+ this.handleDragOver({ clientX });
8692
+ return true;
8693
+ }
8694
+ /**
8695
+ * Shared body of the dragleave entry point. Preserves anim state when
8696
+ * the drag moves between tabs-list children, into the extended drop
8697
+ * zone, or into the void container; tears it down otherwise.
8698
+ */
8699
+ _processDragLeave(related) {
8700
+ var _a, _b, _c;
8701
+ if (!this._animState) {
8702
+ return;
8703
+ }
8704
+ // Moves between children of the tabs list aren't real leaves.
8705
+ if (related && this._tabsList.contains(related)) {
8706
+ return;
8707
+ }
8708
+ // Moving into the broader drop zone (e.g. void container, left
8709
+ // actions) — keep anim state alive so external listeners can
8710
+ // continue the gap animation.
8711
+ if (related && ((_a = this._extendedDropZone) === null || _a === void 0 ? void 0 : _a.contains(related))) {
8712
+ this.resetTabTransforms();
8713
+ this._animState.currentInsertionIndex = null;
8714
+ return;
8715
+ }
8716
+ // Leaving toward the void container (empty header space to the
8717
+ // right): keep anim state so a drop can still land at the end.
8718
+ const isVoid = this._voidContainer &&
8719
+ related &&
8720
+ (related === this._voidContainer ||
8721
+ this._voidContainer.contains(related));
8722
+ if (isVoid) {
8723
+ return;
8724
+ }
8725
+ this.resetTabTransforms();
8726
+ if (this._animState.sourceIndex === -1) {
8727
+ (_c = (_b = this.group.model.dropTargetContainer) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.clear();
8728
+ this._animState = null;
8729
+ }
8730
+ else {
8731
+ this._animState.currentInsertionIndex = null;
8732
+ }
8733
+ }
7489
8734
  handleDragOver(event) {
7490
8735
  var _a, _b, _c, _d, _e;
7491
8736
  if (!this._animState) {
@@ -7606,11 +8851,15 @@ class Tabs extends CompositeDisposable {
7606
8851
  if (!isInsideRange && !isJustBeforeGroup) {
7607
8852
  continue;
7608
8853
  }
7609
- if (isGroupDrag) {
8854
+ if (isGroupDrag && isInsideRange) {
7610
8855
  // A group cannot be dropped inside another group.
7611
8856
  // Snap the insertion index to just before or just
7612
8857
  // after this group based on cursor position relative
7613
- // to the group's midpoint.
8858
+ // to the group's midpoint. Only applies when the
8859
+ // insertion would land *inside* the group — for
8860
+ // `isJustBeforeGroup`, the index is already outside
8861
+ // (immediately left of the group) and is a valid
8862
+ // drop position, so leave it untouched (issue #1264).
7614
8863
  const groupMid = (firstIdx + lastIdx + 1) / 2;
7615
8864
  if (insertionIndex < groupMid) {
7616
8865
  insertionIndex = firstIdx;
@@ -7621,6 +8870,12 @@ class Tabs extends CompositeDisposable {
7621
8870
  // targetTabGroupId stays null
7622
8871
  break;
7623
8872
  }
8873
+ if (isGroupDrag && isJustBeforeGroup) {
8874
+ // Cursor is just before the group — accept this
8875
+ // index as-is. Groups can be dropped at the slot
8876
+ // immediately left of another group's first tab.
8877
+ break;
8878
+ }
7624
8879
  if (isJustBeforeGroup) {
7625
8880
  // Check whether only the source tab (or source group
7626
8881
  // tabs) sits between insertionIndex and firstIdx.
@@ -7885,20 +9140,23 @@ class Tabs extends CompositeDisposable {
7885
9140
  * in the model, and run a FLIP animation.
7886
9141
  */
7887
9142
  _commitGroupMove(sourceTabGroupId, insertionIndex) {
7888
- var _a, _b, _c;
7889
- // Read transfer data BEFORE disposing cleanup — disposing
7890
- // _chipDragCleanup clears the global LocalSelectionTransfer
7891
- // singleton which getPanelData() reads from.
9143
+ var _a, _b;
9144
+ // Read transfer data first.
7892
9145
  const data = getPanelData();
7893
- (_a = this._chipDragCleanup) === null || _a === void 0 ? void 0 : _a.dispose();
7894
- this._chipDragCleanup = null;
9146
+ // Synchronously dispose the source chip's drag sources, which
9147
+ // clears the panelTransfer payload + iframe shield. Cross-group
9148
+ // moves dissolve the source chip on a microtask, which is too
9149
+ // late: a synchronous `getPanelData()` after this method (or any
9150
+ // sibling dragover handler firing in the same tick) would
9151
+ // otherwise see stale data still referencing the old tabGroupId.
9152
+ this._tabGroupManager.disposeChipDrag(sourceTabGroupId);
7895
9153
  // Check if the tab group exists in this group (within-group reorder)
7896
9154
  // or in another group (cross-group move).
7897
9155
  const isLocal = this.group.model
7898
9156
  .getTabGroups()
7899
9157
  .some((tg) => tg.id === sourceTabGroupId);
7900
9158
  if (isLocal) {
7901
- if (((_b = this.accessor.options.theme) === null || _b === void 0 ? void 0 : _b.tabAnimation) === 'smooth') {
9159
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
7902
9160
  this._clearGroupDragClasses(sourceTabGroupId);
7903
9161
  const firstPositions = this.snapshotTabPositions();
7904
9162
  this.resetTabTransforms();
@@ -7926,7 +9184,7 @@ class Tabs extends CompositeDisposable {
7926
9184
  this.accessor.moveGroupOrPanel({
7927
9185
  from: {
7928
9186
  groupId: data.groupId,
7929
- tabGroupId: (_c = data.tabGroupId) !== null && _c !== void 0 ? _c : sourceTabGroupId,
9187
+ tabGroupId: (_b = data.tabGroupId) !== null && _b !== void 0 ? _b : sourceTabGroupId,
7930
9188
  },
7931
9189
  to: {
7932
9190
  group: this.group,
@@ -7954,22 +9212,27 @@ class Tabs extends CompositeDisposable {
7954
9212
  this._tabGroupManager.skipNextCollapseAnimation = true;
7955
9213
  }
7956
9214
  resetDragAnimation() {
7957
- var _a, _b;
7958
9215
  this._pendingCollapse = false;
7959
- this.resetTabTransforms();
7960
- // Clear drag-collapse classes instantly (no transition)
7961
- if ((_a = this._animState) === null || _a === void 0 ? void 0 : _a.sourceTabGroupId) {
7962
- this._clearGroupDragClasses(this._animState.sourceTabGroupId);
7963
- }
7964
- else {
7965
- this._removeClassInstantlyBatch(this._tabs.map((t) => t.value.element), 'dv-tab--dragging');
7966
- }
7967
- this._animState = null;
7968
- (_b = this._chipDragCleanup) === null || _b === void 0 ? void 0 : _b.dispose();
7969
- this._chipDragCleanup = null;
7970
- // Restore any hidden underlines from group drags
7971
- for (const [, el] of this._tabGroupManager.groupUnderlines) {
7972
- el.style.removeProperty('display');
9216
+ // After a drop, `tab.onDrop` consumes _animState (sets it to null)
9217
+ // and immediately calls `runFlipAnimation`, which sets transforms
9218
+ // and queues an rAF to trigger the CSS transition. dragend fires
9219
+ // synchronously on the source element BEFORE that rAF runs — if
9220
+ // we cleared transforms here we'd clobber the in-flight FLIP, so
9221
+ // gate the cleanup on _animState still being set (i.e. drag was
9222
+ // cancelled rather than dropped).
9223
+ if (this._animState) {
9224
+ this.resetTabTransforms();
9225
+ if (this._animState.sourceTabGroupId) {
9226
+ this._clearGroupDragClasses(this._animState.sourceTabGroupId);
9227
+ }
9228
+ else {
9229
+ this._removeClassInstantlyBatch(this._tabs.map((t) => t.value.element), 'dv-tab--dragging');
9230
+ }
9231
+ this._animState = null;
9232
+ // Restore any hidden underlines from group drags.
9233
+ for (const [, el] of this._tabGroupManager.groupUnderlines) {
9234
+ el.style.removeProperty('display');
9235
+ }
7973
9236
  }
7974
9237
  }
7975
9238
  runFlipAnimation(firstPositions, sourceTabId, isCrossGroup = false, animRange) {
@@ -8462,12 +9725,14 @@ const PROPERTY_KEYS_DOCKVIEW = (() => {
8462
9725
  disableFloatingGroups: undefined,
8463
9726
  floatingGroupBounds: undefined,
8464
9727
  popoutUrl: undefined,
9728
+ nonce: undefined,
8465
9729
  defaultRenderer: undefined,
8466
9730
  defaultHeaderPosition: undefined,
8467
9731
  debug: undefined,
8468
9732
  rootOverlayModel: undefined,
8469
9733
  locked: undefined,
8470
9734
  disableDnd: undefined,
9735
+ dndStrategy: undefined,
8471
9736
  className: undefined,
8472
9737
  noPanelsOverlay: undefined,
8473
9738
  dndEdges: undefined,
@@ -8477,6 +9742,7 @@ const PROPERTY_KEYS_DOCKVIEW = (() => {
8477
9742
  getTabContextMenuItems: undefined,
8478
9743
  getTabGroupChipContextMenuItems: undefined,
8479
9744
  createTabGroupChipComponent: undefined,
9745
+ createGroupDragGhostComponent: undefined,
8480
9746
  tabGroupColors: undefined,
8481
9747
  tabGroupAccent: undefined,
8482
9748
  };
@@ -8651,6 +9917,10 @@ class TabGroup extends CompositeDisposable {
8651
9917
  }
8652
9918
 
8653
9919
  class DockviewDidDropEvent extends DockviewEvent {
9920
+ /**
9921
+ * `PointerEvent` for touch drags has no `dataTransfer`; use
9922
+ * `getData()` for the dockview payload regardless of input method.
9923
+ */
8654
9924
  get nativeEvent() {
8655
9925
  return this.options.nativeEvent;
8656
9926
  }
@@ -8755,29 +10025,28 @@ class DockviewGroupPanelModel extends CompositeDisposable {
8755
10025
  toggleClass(this.container, 'dv-groupview-floating', false);
8756
10026
  toggleClass(this.container, 'dv-groupview-popout', false);
8757
10027
  toggleClass(this.container, 'dv-groupview-edge', false);
10028
+ // Mouse and touch drop targets must agree on accepted zones.
10029
+ const applyZones = (zones) => {
10030
+ this.contentContainer.dropTarget.setTargetZones(zones);
10031
+ this.contentContainer.pointerDropTarget.setTargetZones(zones);
10032
+ };
8758
10033
  switch (value.type) {
8759
10034
  case 'grid':
8760
- this.contentContainer.dropTarget.setTargetZones([
8761
- 'top',
8762
- 'bottom',
8763
- 'left',
8764
- 'right',
8765
- 'center',
8766
- ]);
10035
+ applyZones(['top', 'bottom', 'left', 'right', 'center']);
8767
10036
  break;
8768
10037
  case 'floating':
8769
- this.contentContainer.dropTarget.setTargetZones(['center']);
8770
- this.contentContainer.dropTarget.setTargetZones(value
10038
+ applyZones(['center']);
10039
+ applyZones(value
8771
10040
  ? ['center']
8772
10041
  : ['top', 'bottom', 'left', 'right', 'center']);
8773
10042
  toggleClass(this.container, 'dv-groupview-floating', true);
8774
10043
  break;
8775
10044
  case 'popout':
8776
- this.contentContainer.dropTarget.setTargetZones(['center']);
10045
+ applyZones(['center']);
8777
10046
  toggleClass(this.container, 'dv-groupview-popout', true);
8778
10047
  break;
8779
10048
  case 'edge':
8780
- this.contentContainer.dropTarget.setTargetZones(['center']);
10049
+ applyZones(['center']);
8781
10050
  toggleClass(this.container, 'dv-groupview-edge', true);
8782
10051
  break;
8783
10052
  }
@@ -8903,6 +10172,8 @@ class DockviewGroupPanelModel extends CompositeDisposable {
8903
10172
  // noop
8904
10173
  }), this.contentContainer.dropTarget.onDrop((event) => {
8905
10174
  this.handleDropEvent('content', event.nativeEvent, event.position);
10175
+ }), this.contentContainer.pointerDropTarget.onDrop((event) => {
10176
+ this.handleDropEvent('content', event.nativeEvent, event.position);
8906
10177
  }), this.tabsContainer.onWillShowOverlay((event) => {
8907
10178
  this._onWillShowOverlay.fire(event);
8908
10179
  }), this.contentContainer.dropTarget.onWillShowOverlay((event) => {
@@ -8913,6 +10184,14 @@ class DockviewGroupPanelModel extends CompositeDisposable {
8913
10184
  group: this.groupPanel,
8914
10185
  getData: getPanelData,
8915
10186
  }));
10187
+ }), this.contentContainer.pointerDropTarget.onWillShowOverlay((event) => {
10188
+ this._onWillShowOverlay.fire(new DockviewWillShowOverlayLocationEvent(event, {
10189
+ kind: 'content',
10190
+ panel: this.activePanel,
10191
+ api: this._api,
10192
+ group: this.groupPanel,
10193
+ getData: getPanelData,
10194
+ }));
8916
10195
  }), this._onMove, this._onDidChange, this._onDidDrop, this._onWillDrop, this._onDidAddPanel, this._onDidRemovePanel, this._onDidActivePanelChange, this._onUnhandledDragOverEvent, this._onDidPanelTitleChange, this._onDidPanelParametersChange, this._onDidCreateTabGroup, this._onDidDestroyTabGroup, this._onDidAddPanelToTabGroup, this._onDidRemovePanelFromTabGroup, this._onDidTabGroupChange, this._onDidTabGroupCollapsedChange, this._onDidCreateTabGroup.event(() => {
8917
10196
  this._scheduleTabGroupUpdate();
8918
10197
  }), this._onDidDestroyTabGroup.event(() => {
@@ -9169,6 +10448,15 @@ class DockviewGroupPanelModel extends CompositeDisposable {
9169
10448
  refreshTabGroupAccent() {
9170
10449
  this.tabsContainer.refreshTabGroupAccent();
9171
10450
  }
10451
+ refreshWatermark() {
10452
+ var _a, _b;
10453
+ if (this.watermark) {
10454
+ this.watermark.element.remove();
10455
+ (_b = (_a = this.watermark).dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
10456
+ this.watermark = undefined;
10457
+ }
10458
+ this.updateContainer();
10459
+ }
9172
10460
  getTabGroupForPanel(panelId) {
9173
10461
  return this._findTabGroupForPanel(panelId);
9174
10462
  }
@@ -9999,6 +11287,18 @@ class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
9999
11287
  }
10000
11288
  return this._group.model.location;
10001
11289
  }
11290
+ get locked() {
11291
+ if (!this._group) {
11292
+ throw new Error(NOT_INITIALIZED_MESSAGE);
11293
+ }
11294
+ return this._group.locked;
11295
+ }
11296
+ set locked(value) {
11297
+ if (!this._group) {
11298
+ throw new Error(NOT_INITIALIZED_MESSAGE);
11299
+ }
11300
+ this._group.locked = value;
11301
+ }
10002
11302
  constructor(id, accessor) {
10003
11303
  super(id, '__dockviewgroup__');
10004
11304
  this.accessor = accessor;
@@ -10589,6 +11889,11 @@ class DockviewPanel extends CompositeDisposable {
10589
11889
  const didTitleChange = title !== this.title;
10590
11890
  if (didTitleChange) {
10591
11891
  this._title = title;
11892
+ // keep the view-model's cached init params in sync so that tab
11893
+ // renderers constructed lazily (e.g. the header overflow
11894
+ // dropdown via createTabRenderer) see the updated title
11895
+ // (#914).
11896
+ this.view.setTitle(title);
10592
11897
  this.api._onDidTitleChange.fire({ title });
10593
11898
  }
10594
11899
  }
@@ -10749,6 +12054,14 @@ class DockviewPanelModel {
10749
12054
  this.content.init(params);
10750
12055
  this.tab.init(Object.assign(Object.assign({}, params), { tabLocation: 'header' }));
10751
12056
  }
12057
+ setTitle(title) {
12058
+ // keep the cached init params in sync so that tab renderers created
12059
+ // lazily after the title changes (e.g. for the header overflow
12060
+ // dropdown) see the current title rather than the stale original.
12061
+ if (this._params) {
12062
+ this._params.title = title;
12063
+ }
12064
+ }
10752
12065
  layout(width, height) {
10753
12066
  var _a, _b;
10754
12067
  (_b = (_a = this.content).layout) === null || _b === void 0 ? void 0 : _b.call(_a, width, height);
@@ -10882,7 +12195,12 @@ class Overlay extends CompositeDisposable {
10882
12195
  this.onDidChange = this._onDidChange.event;
10883
12196
  this._onDidChangeEnd = new Emitter();
10884
12197
  this.onDidChangeEnd = this._onDidChangeEnd.event;
10885
- this.addDisposables(this._onDidChange, this._onDidChangeEnd);
12198
+ this._onDidStartMoving = new Emitter();
12199
+ /** Fires once per drag, the first time the float actually moves. */
12200
+ this.onDidStartMoving = this._onDidStartMoving.event;
12201
+ this._dragMove = new MutableDisposable();
12202
+ this._dragCancelled = false;
12203
+ this.addDisposables(this._onDidChange, this._onDidChangeEnd, this._onDidStartMoving, this._dragMove);
10886
12204
  this._element.className = 'dv-resize-container';
10887
12205
  this._isVisible = true;
10888
12206
  this.setupResize('top');
@@ -10991,16 +12309,60 @@ class Overlay extends CompositeDisposable {
10991
12309
  result.height = element.height;
10992
12310
  return result;
10993
12311
  }
12312
+ /**
12313
+ * Abort an in-flight move-the-float drag. Used by the void container
12314
+ * when a redock long-press fires after the move started, so the ghost
12315
+ * gesture wins without the float continuing to follow the finger.
12316
+ * Does not emit `onDidChangeEnd` because no change is being committed.
12317
+ */
12318
+ cancelPendingDrag() {
12319
+ if (!this._dragMove.value) {
12320
+ return;
12321
+ }
12322
+ this._dragCancelled = true;
12323
+ toggleClass(this._element, 'dv-resize-container-dragging', false);
12324
+ this._dragMove.value = exports.DockviewDisposable.NONE;
12325
+ }
10994
12326
  setupDrag(dragTarget, options = { inDragMode: false }) {
10995
- const move = new MutableDisposable();
10996
- const track = () => {
12327
+ const track = (captureTarget, pointerId) => {
10997
12328
  let offset = null;
12329
+ let hasMoved = false;
12330
+ this._dragCancelled = false;
10998
12331
  const iframes = disableIframePointEvents();
10999
- move.value = new CompositeDisposable({
12332
+ if (captureTarget &&
12333
+ typeof pointerId === 'number' &&
12334
+ typeof captureTarget.setPointerCapture === 'function') {
12335
+ try {
12336
+ captureTarget.setPointerCapture(pointerId);
12337
+ }
12338
+ catch (_a) {
12339
+ // ignore – non-fatal if the browser refuses capture
12340
+ }
12341
+ }
12342
+ const end = () => {
12343
+ toggleClass(this._element, 'dv-resize-container-dragging', false);
12344
+ this._dragMove.value = exports.DockviewDisposable.NONE;
12345
+ this._onDidChangeEnd.fire();
12346
+ };
12347
+ this._dragMove.value = new CompositeDisposable({
11000
12348
  dispose: () => {
11001
12349
  iframes.release();
12350
+ if (captureTarget &&
12351
+ typeof pointerId === 'number' &&
12352
+ typeof captureTarget.releasePointerCapture ===
12353
+ 'function') {
12354
+ try {
12355
+ captureTarget.releasePointerCapture(pointerId);
12356
+ }
12357
+ catch (_a) {
12358
+ // ignore – pointer may already be released
12359
+ }
12360
+ }
11002
12361
  },
11003
12362
  }, addDisposableListener(window, 'pointermove', (e) => {
12363
+ if (this._dragCancelled) {
12364
+ return;
12365
+ }
11004
12366
  const containerRect = this.options.container.getBoundingClientRect();
11005
12367
  const x = e.clientX - containerRect.left;
11006
12368
  const y = e.clientY - containerRect.top;
@@ -11037,13 +12399,13 @@ class Overlay extends CompositeDisposable {
11037
12399
  bounds.right = right;
11038
12400
  }
11039
12401
  this.setBounds(bounds);
11040
- }), addDisposableListener(window, 'pointerup', () => {
11041
- toggleClass(this._element, 'dv-resize-container-dragging', false);
11042
- move.dispose();
11043
- this._onDidChangeEnd.fire();
11044
- }));
12402
+ if (!hasMoved) {
12403
+ hasMoved = true;
12404
+ this._onDidStartMoving.fire();
12405
+ }
12406
+ }), addDisposableListener(window, 'pointerup', end), addDisposableListener(window, 'pointercancel', end));
11045
12407
  };
11046
- this.addDisposables(move, addDisposableListener(dragTarget, 'pointerdown', (event) => {
12408
+ this.addDisposables(addDisposableListener(dragTarget, 'pointerdown', (event) => {
11047
12409
  if (event.defaultPrevented) {
11048
12410
  event.preventDefault();
11049
12411
  return;
@@ -11053,7 +12415,7 @@ class Overlay extends CompositeDisposable {
11053
12415
  if (quasiDefaultPrevented(event)) {
11054
12416
  return;
11055
12417
  }
11056
- track();
12418
+ track(dragTarget, event.pointerId);
11057
12419
  }), addDisposableListener(this.options.content, 'pointerdown', (event) => {
11058
12420
  if (event.defaultPrevented) {
11059
12421
  return;
@@ -11064,7 +12426,7 @@ class Overlay extends CompositeDisposable {
11064
12426
  return;
11065
12427
  }
11066
12428
  if (event.shiftKey) {
11067
- track();
12429
+ track(this.options.content, event.pointerId);
11068
12430
  }
11069
12431
  }), addDisposableListener(this.options.content, 'pointerdown', () => {
11070
12432
  arialLevelTracker.push(this._element);
@@ -11082,6 +12444,19 @@ class Overlay extends CompositeDisposable {
11082
12444
  e.preventDefault();
11083
12445
  let startPosition = null;
11084
12446
  const iframes = disableIframePointEvents();
12447
+ const pointerId = e.pointerId;
12448
+ if (typeof resizeHandleElement.setPointerCapture === 'function') {
12449
+ try {
12450
+ resizeHandleElement.setPointerCapture(pointerId);
12451
+ }
12452
+ catch (_a) {
12453
+ // ignore – non-fatal if the browser refuses capture
12454
+ }
12455
+ }
12456
+ const end = () => {
12457
+ move.dispose();
12458
+ this._onDidChangeEnd.fire();
12459
+ };
11085
12460
  move.value = new CompositeDisposable(addDisposableListener(window, 'pointermove', (e) => {
11086
12461
  const containerRect = this.options.container.getBoundingClientRect();
11087
12462
  const overlayRect = this._element.getBoundingClientRect();
@@ -11216,11 +12591,17 @@ class Overlay extends CompositeDisposable {
11216
12591
  }), {
11217
12592
  dispose: () => {
11218
12593
  iframes.release();
12594
+ if (typeof resizeHandleElement.releasePointerCapture ===
12595
+ 'function') {
12596
+ try {
12597
+ resizeHandleElement.releasePointerCapture(pointerId);
12598
+ }
12599
+ catch (_a) {
12600
+ // ignore – pointer may already be released
12601
+ }
12602
+ }
11219
12603
  },
11220
- }, addDisposableListener(window, 'pointerup', () => {
11221
- move.dispose();
11222
- this._onDidChangeEnd.fire();
11223
- }));
12604
+ }, addDisposableListener(window, 'pointerup', end), addDisposableListener(window, 'pointercancel', end));
11224
12605
  }));
11225
12606
  }
11226
12607
  getMinimumWidth(width) {
@@ -11668,7 +13049,9 @@ class PopoutWindow extends CompositeDisposable {
11668
13049
  const externalDocument = externalWindow.document;
11669
13050
  externalDocument.title = document.title;
11670
13051
  externalDocument.body.appendChild(container);
11671
- addStyles(externalDocument, window.document.styleSheets);
13052
+ addStyles(externalDocument, window.document.styleSheets, {
13053
+ nonce: this.options.nonce,
13054
+ });
11672
13055
  /**
11673
13056
  * beforeunload must be registered after load for reasons I could not determine
11674
13057
  * otherwise the beforeunload event will not fire when the window is closed
@@ -11743,6 +13126,14 @@ class StrictEventsSequencing extends CompositeDisposable {
11743
13126
  }
11744
13127
  }
11745
13128
 
13129
+ function isCoarsePrimaryInput$1(win) {
13130
+ if (!win.matchMedia) {
13131
+ return false;
13132
+ }
13133
+ const coarse = win.matchMedia('(pointer: coarse)').matches;
13134
+ const fine = win.matchMedia('(pointer: fine)').matches;
13135
+ return coarse && !fine;
13136
+ }
11746
13137
  class PopupService extends CompositeDisposable {
11747
13138
  constructor(root, win = window) {
11748
13139
  super();
@@ -11781,8 +13172,22 @@ class PopupService extends CompositeDisposable {
11781
13172
  wrapper.style.left = `${position.x - offsetX}px`;
11782
13173
  this._element.appendChild(wrapper);
11783
13174
  this._active = wrapper;
13175
+ // Outside-pointerdown dismissal is suppressed for a short grace
13176
+ // window after opening. Touch long-press callers (chip / tab context
13177
+ // menus) open the popover while the user's finger is still pressing
13178
+ // the source element — Android Chrome can dispatch a follow-up
13179
+ // synthetic pointerdown tied to the gesture, and the release-then-
13180
+ // retap motion can land just outside the wrapper. Either would
13181
+ // dismiss the popover before the user can see or interact with it.
13182
+ // The grace window is short enough that intentional outside taps
13183
+ // still feel responsive.
13184
+ const openedAt = Date.now();
13185
+ const POINTERDOWN_GRACE_MS = 200;
11784
13186
  this._activeDisposable.value = new CompositeDisposable(addDisposableListener(this._window, 'pointerdown', (event) => {
11785
13187
  var _a;
13188
+ if (Date.now() - openedAt < POINTERDOWN_GRACE_MS) {
13189
+ return;
13190
+ }
11786
13191
  const target = event.target;
11787
13192
  if (!(target instanceof HTMLElement)) {
11788
13193
  return;
@@ -11800,6 +13205,18 @@ class PopupService extends CompositeDisposable {
11800
13205
  this.close();
11801
13206
  }
11802
13207
  }), addDisposableListener(this._window, 'resize', () => {
13208
+ // On touch-primary devices, common interactions resize the
13209
+ // window: on-screen keyboard pop, orientation change, browser
13210
+ // address-bar collapse. None of these mean "the user wants
13211
+ // the popover dismissed". Specifically, focusing the chip
13212
+ // context menu's rename input pops the keyboard, which would
13213
+ // otherwise close the menu the moment the user goes to edit
13214
+ // it. Desktop / hybrid input keeps the existing behaviour —
13215
+ // there a resize genuinely means the user has resized the
13216
+ // window and the popover position is now stale.
13217
+ if (isCoarsePrimaryInput$1(this._window)) {
13218
+ return;
13219
+ }
11803
13220
  this.close();
11804
13221
  }));
11805
13222
  this._window.requestAnimationFrame(() => {
@@ -11856,6 +13273,14 @@ function buildSeparator() {
11856
13273
  el.setAttribute('role', 'separator');
11857
13274
  return el;
11858
13275
  }
13276
+ function isCoarsePrimaryInput() {
13277
+ if (typeof window === 'undefined' || !window.matchMedia) {
13278
+ return false;
13279
+ }
13280
+ const coarse = window.matchMedia('(pointer: coarse)').matches;
13281
+ const fine = window.matchMedia('(pointer: fine)').matches;
13282
+ return coarse && !fine;
13283
+ }
11859
13284
  function buildRenameInput(tabGroup) {
11860
13285
  const wrapper = document.createElement('div');
11861
13286
  wrapper.className = 'dv-context-menu-rename';
@@ -11876,10 +13301,17 @@ function buildRenameInput(tabGroup) {
11876
13301
  e.stopPropagation();
11877
13302
  });
11878
13303
  wrapper.appendChild(input);
11879
- requestAnimationFrame(() => {
11880
- input.focus();
11881
- input.select();
11882
- });
13304
+ // Skip auto-focus on touch-primary devices: focusing the input pops the
13305
+ // on-screen keyboard, which fires `window resize`, which `PopupService`
13306
+ // listens to and uses to dismiss the popover — so the menu opens, the
13307
+ // keyboard appears, and the menu immediately closes before the user can
13308
+ // type. The user can still tap the input to focus it intentionally.
13309
+ if (!isCoarsePrimaryInput()) {
13310
+ requestAnimationFrame(() => {
13311
+ input.focus();
13312
+ input.select();
13313
+ });
13314
+ }
11883
13315
  return wrapper;
11884
13316
  }
11885
13317
  function buildColorPicker(tabGroup, palette) {
@@ -12794,7 +14226,7 @@ class DockviewComponent extends BaseGrid {
12794
14226
  return this._popoutRestorationPromise;
12795
14227
  }
12796
14228
  constructor(container, options) {
12797
- var _a, _b, _c, _d, _e, _f;
14229
+ var _a, _b, _c, _d, _e, _f, _g;
12798
14230
  super(container, {
12799
14231
  proportionalLayout: true,
12800
14232
  orientation: exports.Orientation.HORIZONTAL,
@@ -12858,6 +14290,7 @@ class DockviewComponent extends BaseGrid {
12858
14290
  this._floatingGroups = [];
12859
14291
  this._popoutGroups = [];
12860
14292
  this._popoutRestorationPromise = Promise.resolve();
14293
+ this._popoutRestorationCleanups = new Set();
12861
14294
  this._onDidRemoveGroup = new Emitter();
12862
14295
  this.onDidRemoveGroup = this._onDidRemoveGroup.event;
12863
14296
  this._onDidAddGroup = new Emitter();
@@ -12892,37 +14325,51 @@ class DockviewComponent extends BaseGrid {
12892
14325
  this._floatingOverlayHost = document.createElement('div');
12893
14326
  this._floatingOverlayHost.className = 'dv-floating-overlay-host';
12894
14327
  this._shellManager.element.appendChild(this._floatingOverlayHost);
12895
- this._rootDropTarget = new Droptarget(this.element, {
12896
- className: 'dv-drop-target-edge',
12897
- canDisplayOverlay: (event, position) => {
12898
- const data = getPanelData();
12899
- if (data) {
12900
- if (data.viewId !== this.id) {
12901
- return false;
12902
- }
12903
- if (position === 'center') {
12904
- // center drop target is only allowed if there are no panels in the grid
12905
- // floating panels are allowed
12906
- return this.gridview.length === 0;
12907
- }
12908
- return true;
12909
- }
12910
- if (position === 'center' && this.gridview.length !== 0) {
12911
- /**
12912
- * for external events only show the four-corner drag overlays, disable
12913
- * the center position so that external drag events can fall through to the group
12914
- * and panel drop target handlers
12915
- */
14328
+ const rootCanDisplayOverlay = (event, position) => {
14329
+ const data = getPanelData();
14330
+ if (data) {
14331
+ if (data.viewId !== this.id) {
12916
14332
  return false;
12917
14333
  }
12918
- const firedEvent = new DockviewUnhandledDragOverEvent(event, 'edge', position, getPanelData);
12919
- this._onUnhandledDragOverEvent.fire(firedEvent);
12920
- return firedEvent.isAccepted;
12921
- },
14334
+ if (position === 'center') {
14335
+ // center drop target is only allowed if there are no panels in the grid
14336
+ // floating panels are allowed
14337
+ return this.gridview.length === 0;
14338
+ }
14339
+ return true;
14340
+ }
14341
+ if (position === 'center' && this.gridview.length !== 0) {
14342
+ /**
14343
+ * for external events only show the four-corner drag overlays, disable
14344
+ * the center position so that external drag events can fall through to the group
14345
+ * and panel drop target handlers
14346
+ */
14347
+ return false;
14348
+ }
14349
+ const firedEvent = new DockviewUnhandledDragOverEvent(event, 'edge', position, getPanelData);
14350
+ this._onUnhandledDragOverEvent.fire(firedEvent);
14351
+ return firedEvent.isAccepted;
14352
+ };
14353
+ this._rootDropTarget = html5Backend.createDropTarget(this.element, {
14354
+ className: 'dv-drop-target-edge',
14355
+ canDisplayOverlay: rootCanDisplayOverlay,
12922
14356
  acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
12923
14357
  overlayModel: (_f = options.rootOverlayModel) !== null && _f !== void 0 ? _f : DEFAULT_ROOT_OVERLAY_MODEL,
12924
14358
  getOverrideTarget: () => { var _a; return (_a = this.rootDropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
12925
14359
  });
14360
+ this._rootPointerDropTarget = pointerBackend.createDropTarget(this.element, {
14361
+ className: 'dv-drop-target-edge',
14362
+ canDisplayOverlay: rootCanDisplayOverlay,
14363
+ acceptedTargetZones: [
14364
+ 'top',
14365
+ 'bottom',
14366
+ 'left',
14367
+ 'right',
14368
+ 'center',
14369
+ ],
14370
+ overlayModel: (_g = options.rootOverlayModel) !== null && _g !== void 0 ? _g : DEFAULT_ROOT_OVERLAY_MODEL,
14371
+ getOverrideTarget: () => { var _a; return (_a = this.rootDropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
14372
+ });
12926
14373
  this.updateDropTargetModel(options);
12927
14374
  toggleClass(this.gridview.element, 'dv-dockview', true);
12928
14375
  toggleClass(this.element, 'dv-debug', !!options.debug);
@@ -12963,6 +14410,14 @@ class DockviewComponent extends BaseGrid {
12963
14410
  this._bufferOnDidLayoutChange.fire();
12964
14411
  }), exports.DockviewDisposable.from(() => {
12965
14412
  var _a;
14413
+ // Cancel any pending popout-restoration timers scheduled by
14414
+ // fromJSON so they don't open new browser windows after
14415
+ // dispose, and resolve their promises so callers awaiting
14416
+ // popoutRestorationPromise don't hang. See issue #851.
14417
+ for (const cleanup of [...this._popoutRestorationCleanups]) {
14418
+ cleanup();
14419
+ }
14420
+ this._popoutRestorationCleanups.clear();
12966
14421
  // iterate over a copy of the array since .dispose() mutates the original array
12967
14422
  for (const group of [...this._floatingGroups]) {
12968
14423
  group.dispose();
@@ -12976,7 +14431,7 @@ class DockviewComponent extends BaseGrid {
12976
14431
  d.dispose();
12977
14432
  }
12978
14433
  this._edgeGroupDisposables.clear();
12979
- }), this._rootDropTarget, this._rootDropTarget.onWillShowOverlay((event) => {
14434
+ }), this._rootDropTarget, this._rootPointerDropTarget, exports.DockviewEvent.any(this._rootDropTarget.onWillShowOverlay, this._rootPointerDropTarget.onWillShowOverlay)((event) => {
12980
14435
  if (this.gridview.length > 0 && event.position === 'center') {
12981
14436
  // option only available when no panels in primary grid
12982
14437
  return;
@@ -12988,7 +14443,7 @@ class DockviewComponent extends BaseGrid {
12988
14443
  group: undefined,
12989
14444
  getData: getPanelData,
12990
14445
  }));
12991
- }), this._rootDropTarget.onDrop((event) => {
14446
+ }), exports.DockviewEvent.any(this._rootDropTarget.onDrop, this._rootPointerDropTarget.onDrop)((event) => {
12992
14447
  var _a;
12993
14448
  const willDropEvent = new DockviewWillDropEvent({
12994
14449
  nativeEvent: event.nativeEvent,
@@ -13026,7 +14481,7 @@ class DockviewComponent extends BaseGrid {
13026
14481
  getData: getPanelData,
13027
14482
  }));
13028
14483
  }
13029
- }), this._rootDropTarget);
14484
+ }));
13030
14485
  }
13031
14486
  setVisible(panel, visible) {
13032
14487
  switch (panel.api.location.type) {
@@ -13059,7 +14514,7 @@ class DockviewComponent extends BaseGrid {
13059
14514
  return (_a = this._popoutPopupServices.get(group.id)) !== null && _a !== void 0 ? _a : this.popupService;
13060
14515
  }
13061
14516
  addPopoutGroup(itemToPopout, options) {
13062
- var _a, _b, _c, _d, _e;
14517
+ var _a, _b, _c, _d, _e, _f;
13063
14518
  if (itemToPopout instanceof DockviewGroupPanel &&
13064
14519
  itemToPopout.model.location.type === 'edge') {
13065
14520
  // edge groups are permanent structural elements and cannot be popped out
@@ -13094,6 +14549,7 @@ class DockviewComponent extends BaseGrid {
13094
14549
  height: box.height,
13095
14550
  onDidOpen: options === null || options === void 0 ? void 0 : options.onDidOpen,
13096
14551
  onWillClose: options === null || options === void 0 ? void 0 : options.onWillClose,
14552
+ nonce: (_f = this.options) === null || _f === void 0 ? void 0 : _f.nonce,
13097
14553
  });
13098
14554
  const popoutWindowDisposable = new CompositeDisposable(_window, _window.onDidClose(() => {
13099
14555
  popoutWindowDisposable.dispose();
@@ -13511,7 +14967,7 @@ class DockviewComponent extends BaseGrid {
13511
14967
  }
13512
14968
  }
13513
14969
  updateOptions(options) {
13514
- var _a, _b, _c;
14970
+ var _a, _b, _c, _d, _e;
13515
14971
  super.updateOptions(options);
13516
14972
  if ('floatingGroupBounds' in options) {
13517
14973
  for (const group of this._floatingGroups) {
@@ -13537,9 +14993,12 @@ class DockviewComponent extends BaseGrid {
13537
14993
  }
13538
14994
  this.updateDropTargetModel(options);
13539
14995
  const oldDisableDnd = this.options.disableDnd;
14996
+ const oldDndStrategy = this.options.dndStrategy;
13540
14997
  this._options = Object.assign(Object.assign({}, this.options), options);
13541
14998
  const newDisableDnd = this.options.disableDnd;
13542
- if (oldDisableDnd !== newDisableDnd) {
14999
+ const newDndStrategy = this.options.dndStrategy;
15000
+ if (oldDisableDnd !== newDisableDnd ||
15001
+ oldDndStrategy !== newDndStrategy) {
13543
15002
  this.updateDragAndDropState();
13544
15003
  }
13545
15004
  if ('theme' in options) {
@@ -13552,8 +15011,19 @@ class DockviewComponent extends BaseGrid {
13552
15011
  group.model.updateHeaderActions();
13553
15012
  }
13554
15013
  }
15014
+ if ('createWatermarkComponent' in options) {
15015
+ if (this._watermark) {
15016
+ this._watermark.element.parentElement.remove();
15017
+ (_d = (_c = this._watermark).dispose) === null || _d === void 0 ? void 0 : _d.call(_c);
15018
+ this._watermark = null;
15019
+ }
15020
+ this.updateWatermark();
15021
+ for (const group of this.groups) {
15022
+ group.model.refreshWatermark();
15023
+ }
15024
+ }
13555
15025
  if ('tabGroupColors' in options || 'tabGroupAccent' in options) {
13556
- this._tabGroupColorPalette.setEntries((_c = this._options.tabGroupColors) !== null && _c !== void 0 ? _c : DEFAULT_TAB_GROUP_COLORS);
15026
+ this._tabGroupColorPalette.setEntries((_e = this._options.tabGroupColors) !== null && _e !== void 0 ? _e : DEFAULT_TAB_GROUP_COLORS);
13557
15027
  this._tabGroupColorPalette.enabled =
13558
15028
  this._options.tabGroupAccent !== 'off';
13559
15029
  for (const group of this.groups) {
@@ -13979,7 +15449,23 @@ class DockviewComponent extends BaseGrid {
13979
15449
  const group = createGroupFromSerializedState(data);
13980
15450
  // Add a small delay for each popup after the first to avoid browser popup blocking
13981
15451
  const popoutPromise = new Promise((resolve) => {
13982
- setTimeout(() => {
15452
+ const cleanup = () => {
15453
+ this._popoutRestorationCleanups.delete(cleanup);
15454
+ clearTimeout(handle);
15455
+ resolve();
15456
+ };
15457
+ const handle = setTimeout(() => {
15458
+ this._popoutRestorationCleanups.delete(cleanup);
15459
+ // Guard against the component being disposed before
15460
+ // this timer fires. Under React StrictMode the
15461
+ // component is mounted -> disposed -> remounted, and
15462
+ // without this guard the first instance's queued
15463
+ // restoration would open a second popout window.
15464
+ // See issue #851.
15465
+ if (this.isDisposed) {
15466
+ resolve();
15467
+ return;
15468
+ }
13983
15469
  this.addPopoutGroup(group, {
13984
15470
  position: position !== null && position !== void 0 ? position : undefined,
13985
15471
  overridePopoutGroup: gridReferenceGroup
@@ -13992,6 +15478,7 @@ class DockviewComponent extends BaseGrid {
13992
15478
  });
13993
15479
  resolve();
13994
15480
  }, index * DESERIALIZATION_POPOUT_DELAY_MS); // 100ms delay between each popup
15481
+ this._popoutRestorationCleanups.add(cleanup);
13995
15482
  });
13996
15483
  popoutPromises.push(popoutPromise);
13997
15484
  });
@@ -14522,8 +16009,11 @@ class DockviewComponent extends BaseGrid {
14522
16009
  * the source group is a popout group with a single panel
14523
16010
  *
14524
16011
  * 1. remove the panel from the group without triggering any events
14525
- * 2. remove the popout group
14526
- * 3. create a new group at the requested location and add that panel
16012
+ * 2. remove the popout group — this may cascade-remove the empty
16013
+ * reference group it left behind in the main grid (see
16014
+ * doRemoveGroup for popout groups), which can shift grid indices
16015
+ * 3. recompute the target location now that the grid is stable
16016
+ * 4. create a new group at the recomputed location and add that panel
14527
16017
  */
14528
16018
  const popoutGroup = this._popoutGroups.find((group) => group.popoutGroup === sourceGroup);
14529
16019
  const removedPanel = this.movingLock(() => popoutGroup.popoutGroup.model.removePanel(popoutGroup.popoutGroup.panels[0], {
@@ -14531,7 +16021,8 @@ class DockviewComponent extends BaseGrid {
14531
16021
  skipSetActiveGroup: true,
14532
16022
  }));
14533
16023
  this.doRemoveGroup(sourceGroup, { skipActive: true });
14534
- const newGroup = this.createGroupAtLocation(targetLocation);
16024
+ const updatedTargetLocation = getRelativeLocation(this.gridview.orientation, getGridLocation(destinationGroup.element), destinationTarget);
16025
+ const newGroup = this.createGroupAtLocation(updatedTargetLocation);
14535
16026
  this.movingLock(() => newGroup.model.openPanel(removedPanel, {
14536
16027
  skipSetActive: true,
14537
16028
  }));
@@ -15071,15 +16562,18 @@ class DockviewComponent extends BaseGrid {
15071
16562
  }
15072
16563
  updateDropTargetModel(options) {
15073
16564
  if ('dndEdges' in options) {
15074
- this._rootDropTarget.disabled =
15075
- typeof options.dndEdges === 'boolean' &&
15076
- options.dndEdges === false;
16565
+ const disabled = typeof options.dndEdges === 'boolean' &&
16566
+ options.dndEdges === false;
16567
+ this._rootDropTarget.disabled = disabled;
16568
+ this._rootPointerDropTarget.disabled = disabled;
15077
16569
  if (typeof options.dndEdges === 'object' &&
15078
16570
  options.dndEdges !== null) {
15079
16571
  this._rootDropTarget.setOverlayModel(options.dndEdges);
16572
+ this._rootPointerDropTarget.setOverlayModel(options.dndEdges);
15080
16573
  }
15081
16574
  else {
15082
16575
  this._rootDropTarget.setOverlayModel(DEFAULT_ROOT_OVERLAY_MODEL);
16576
+ this._rootPointerDropTarget.setOverlayModel(DEFAULT_ROOT_OVERLAY_MODEL);
15083
16577
  }
15084
16578
  }
15085
16579
  if ('rootOverlayModel' in options) {