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