dockview-core 6.3.0 → 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 (123) hide show
  1. package/README.md +1 -0
  2. package/dist/cjs/dnd/backend.d.ts +70 -0
  3. package/dist/cjs/dnd/backend.js +171 -0
  4. package/dist/cjs/dnd/dropOverlay.d.ts +20 -0
  5. package/dist/cjs/dnd/dropOverlay.js +197 -0
  6. package/dist/cjs/dnd/droptarget.d.ts +20 -6
  7. package/dist/cjs/dnd/droptarget.js +14 -208
  8. package/dist/cjs/dnd/pointer/index.d.ts +11 -0
  9. package/dist/cjs/dnd/pointer/index.js +13 -0
  10. package/dist/cjs/dnd/pointer/longPress.d.ts +32 -0
  11. package/dist/cjs/dnd/pointer/longPress.js +151 -0
  12. package/dist/cjs/dnd/pointer/pointerDragController.d.ts +60 -0
  13. package/dist/cjs/dnd/pointer/pointerDragController.js +241 -0
  14. package/dist/cjs/dnd/pointer/pointerDragSource.d.ts +61 -0
  15. package/dist/cjs/dnd/pointer/pointerDragSource.js +195 -0
  16. package/dist/cjs/dnd/pointer/pointerDropTarget.d.ts +39 -0
  17. package/dist/cjs/dnd/pointer/pointerDropTarget.js +198 -0
  18. package/dist/cjs/dnd/pointer/pointerGhost.d.ts +30 -0
  19. package/dist/cjs/dnd/pointer/pointerGhost.js +44 -0
  20. package/dist/cjs/dnd/pointer/types.d.ts +16 -0
  21. package/dist/cjs/dnd/pointer/types.js +2 -0
  22. package/dist/cjs/dockview/components/panel/content.d.ts +3 -1
  23. package/dist/cjs/dockview/components/panel/content.js +33 -16
  24. package/dist/cjs/dockview/components/popupService.js +34 -0
  25. package/dist/cjs/dockview/components/tab/tab.d.ts +11 -3
  26. package/dist/cjs/dockview/components/tab/tab.js +151 -117
  27. package/dist/cjs/dockview/components/titlebar/tabGroupChip.d.ts +9 -2
  28. package/dist/cjs/dockview/components/titlebar/tabGroupChip.js +15 -6
  29. package/dist/cjs/dockview/components/titlebar/tabGroups.d.ts +33 -5
  30. package/dist/cjs/dockview/components/titlebar/tabGroups.js +231 -40
  31. package/dist/cjs/dockview/components/titlebar/tabs.d.ts +38 -1
  32. package/dist/cjs/dockview/components/titlebar/tabs.js +372 -251
  33. package/dist/cjs/dockview/components/titlebar/tabsContainer.d.ts +5 -3
  34. package/dist/cjs/dockview/components/titlebar/voidContainer.d.ts +6 -2
  35. package/dist/cjs/dockview/components/titlebar/voidContainer.js +189 -27
  36. package/dist/cjs/dockview/contextMenu.js +19 -4
  37. package/dist/cjs/dockview/dndCapabilities.d.ts +19 -0
  38. package/dist/cjs/dockview/dndCapabilities.js +39 -0
  39. package/dist/cjs/dockview/dockviewComponent.d.ts +1 -0
  40. package/dist/cjs/dockview/dockviewComponent.js +54 -33
  41. package/dist/cjs/dockview/dockviewGroupPanelModel.d.ts +9 -5
  42. package/dist/cjs/dockview/dockviewGroupPanelModel.js +25 -11
  43. package/dist/cjs/dockview/events.d.ts +2 -1
  44. package/dist/cjs/dockview/events.js +1 -0
  45. package/dist/cjs/dockview/options.d.ts +18 -3
  46. package/dist/cjs/dockview/options.js +1 -0
  47. package/dist/cjs/dom.js +7 -3
  48. package/dist/cjs/overlay/overlay.d.ts +12 -0
  49. package/dist/cjs/overlay/overlay.js +84 -16
  50. package/dist/cjs/paneview/draggablePaneviewPanel.d.ts +3 -1
  51. package/dist/cjs/paneview/draggablePaneviewPanel.js +27 -26
  52. package/dist/cjs/paneview/options.d.ts +4 -3
  53. package/dist/dockview-core.js +2199 -834
  54. package/dist/dockview-core.min.js +2 -2
  55. package/dist/dockview-core.min.js.map +1 -1
  56. package/dist/dockview-core.min.noStyle.js +2 -2
  57. package/dist/dockview-core.min.noStyle.js.map +1 -1
  58. package/dist/dockview-core.noStyle.js +2202 -837
  59. package/dist/esm/dnd/backend.d.ts +70 -0
  60. package/dist/esm/dnd/backend.js +148 -0
  61. package/dist/esm/dnd/dropOverlay.d.ts +20 -0
  62. package/dist/esm/dnd/dropOverlay.js +192 -0
  63. package/dist/esm/dnd/droptarget.d.ts +20 -6
  64. package/dist/esm/dnd/droptarget.js +16 -210
  65. package/dist/esm/dnd/pointer/index.d.ts +11 -0
  66. package/dist/esm/dnd/pointer/index.js +5 -0
  67. package/dist/esm/dnd/pointer/longPress.d.ts +32 -0
  68. package/dist/esm/dnd/pointer/longPress.js +127 -0
  69. package/dist/esm/dnd/pointer/pointerDragController.d.ts +60 -0
  70. package/dist/esm/dnd/pointer/pointerDragController.js +191 -0
  71. package/dist/esm/dnd/pointer/pointerDragSource.d.ts +61 -0
  72. package/dist/esm/dnd/pointer/pointerDragSource.js +171 -0
  73. package/dist/esm/dnd/pointer/pointerDropTarget.d.ts +39 -0
  74. package/dist/esm/dnd/pointer/pointerDropTarget.js +168 -0
  75. package/dist/esm/dnd/pointer/pointerGhost.d.ts +30 -0
  76. package/dist/esm/dnd/pointer/pointerGhost.js +39 -0
  77. package/dist/esm/dnd/pointer/types.d.ts +16 -0
  78. package/dist/esm/dnd/pointer/types.js +1 -0
  79. package/dist/esm/dockview/components/panel/content.d.ts +3 -1
  80. package/dist/esm/dockview/components/panel/content.js +33 -16
  81. package/dist/esm/dockview/components/popupService.js +34 -0
  82. package/dist/esm/dockview/components/tab/tab.d.ts +11 -3
  83. package/dist/esm/dockview/components/tab/tab.js +139 -114
  84. package/dist/esm/dockview/components/titlebar/tabGroupChip.d.ts +9 -2
  85. package/dist/esm/dockview/components/titlebar/tabGroupChip.js +15 -6
  86. package/dist/esm/dockview/components/titlebar/tabGroups.d.ts +33 -5
  87. package/dist/esm/dockview/components/titlebar/tabGroups.js +177 -12
  88. package/dist/esm/dockview/components/titlebar/tabs.d.ts +38 -1
  89. package/dist/esm/dockview/components/titlebar/tabs.js +348 -227
  90. package/dist/esm/dockview/components/titlebar/tabsContainer.d.ts +5 -3
  91. package/dist/esm/dockview/components/titlebar/voidContainer.d.ts +6 -2
  92. package/dist/esm/dockview/components/titlebar/voidContainer.js +179 -31
  93. package/dist/esm/dockview/contextMenu.js +19 -4
  94. package/dist/esm/dockview/dndCapabilities.d.ts +19 -0
  95. package/dist/esm/dockview/dndCapabilities.js +36 -0
  96. package/dist/esm/dockview/dockviewComponent.d.ts +1 -0
  97. package/dist/esm/dockview/dockviewComponent.js +55 -34
  98. package/dist/esm/dockview/dockviewGroupPanelModel.d.ts +9 -5
  99. package/dist/esm/dockview/dockviewGroupPanelModel.js +24 -11
  100. package/dist/esm/dockview/events.d.ts +2 -1
  101. package/dist/esm/dockview/events.js +1 -0
  102. package/dist/esm/dockview/options.d.ts +18 -3
  103. package/dist/esm/dockview/options.js +1 -0
  104. package/dist/esm/dom.js +7 -3
  105. package/dist/esm/overlay/overlay.d.ts +12 -0
  106. package/dist/esm/overlay/overlay.js +85 -17
  107. package/dist/esm/paneview/draggablePaneviewPanel.d.ts +3 -1
  108. package/dist/esm/paneview/draggablePaneviewPanel.js +26 -20
  109. package/dist/esm/paneview/options.d.ts +4 -3
  110. package/dist/package/main.cjs.js +2202 -837
  111. package/dist/package/main.cjs.min.js +2 -2
  112. package/dist/package/main.esm.min.mjs +2 -2
  113. package/dist/package/main.esm.mjs +2202 -837
  114. package/dist/styles/dockview.css +117 -1
  115. package/package.json +3 -1
  116. package/dist/cjs/dnd/abstractDragHandler.d.ts +0 -14
  117. package/dist/cjs/dnd/abstractDragHandler.js +0 -86
  118. package/dist/cjs/dnd/groupDragHandler.d.ts +0 -12
  119. package/dist/cjs/dnd/groupDragHandler.js +0 -104
  120. package/dist/esm/dnd/abstractDragHandler.d.ts +0 -14
  121. package/dist/esm/dnd/abstractDragHandler.js +0 -63
  122. package/dist/esm/dnd/groupDragHandler.d.ts +0 -12
  123. package/dist/esm/dnd/groupDragHandler.js +0 -81
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * dockview-core
3
- * @version 6.3.0
3
+ * @version 6.4.0
4
4
  * @link https://github.com/mathuo/dockview
5
5
  * @license MIT
6
6
  */
@@ -551,7 +551,7 @@
551
551
  * Should be more efficient than element.querySelectorAll("*") since there
552
552
  * is no need to store every element in-memory using this approach
553
553
  */
554
- function allTagsNamesInclusiveOfShadowDoms(tagNames) {
554
+ function allTagsNamesInclusiveOfShadowDoms(tagNames, rootNode) {
555
555
  const iframes = [];
556
556
  function findIframesInNode(node) {
557
557
  if (node.nodeType === Node.ELEMENT_NODE) {
@@ -566,11 +566,15 @@
566
566
  }
567
567
  }
568
568
  }
569
- findIframesInNode(document.documentElement);
569
+ // Document → walk from its root element. Element → walk from itself.
570
+ const startEl = rootNode instanceof Document
571
+ ? rootNode.documentElement
572
+ : rootNode;
573
+ findIframesInNode(startEl);
570
574
  return iframes;
571
575
  }
572
576
  function disableIframePointEvents(rootNode = document) {
573
- const iframes = allTagsNamesInclusiveOfShadowDoms(['IFRAME', 'WEBVIEW']);
577
+ const iframes = allTagsNamesInclusiveOfShadowDoms(['IFRAME', 'WEBVIEW'], rootNode);
574
578
  const original = new WeakMap(); // don't hold onto HTMLElement references longer than required
575
579
  for (const iframe of iframes) {
576
580
  original.set(iframe, iframe.style.pointerEvents);
@@ -3990,67 +3994,6 @@
3990
3994
  }
3991
3995
  }
3992
3996
 
3993
- class DragHandler extends CompositeDisposable {
3994
- constructor(el, disabled) {
3995
- super();
3996
- this.el = el;
3997
- this.disabled = disabled;
3998
- this.dataDisposable = new MutableDisposable();
3999
- this.pointerEventsDisposable = new MutableDisposable();
4000
- this._onDragStart = new Emitter();
4001
- this.onDragStart = this._onDragStart.event;
4002
- this.addDisposables(this._onDragStart, this.dataDisposable, this.pointerEventsDisposable);
4003
- this.configure();
4004
- }
4005
- setDisabled(disabled) {
4006
- this.disabled = disabled;
4007
- }
4008
- isCancelled(_event) {
4009
- return false;
4010
- }
4011
- configure() {
4012
- this.addDisposables(this._onDragStart, addDisposableListener(this.el, 'dragstart', (event) => {
4013
- if (event.defaultPrevented ||
4014
- this.isCancelled(event) ||
4015
- this.disabled) {
4016
- event.preventDefault();
4017
- return;
4018
- }
4019
- const iframes = disableIframePointEvents();
4020
- this.pointerEventsDisposable.value = {
4021
- dispose: () => {
4022
- iframes.release();
4023
- },
4024
- };
4025
- this.el.classList.add('dv-dragged');
4026
- setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
4027
- this.dataDisposable.value = this.getData(event);
4028
- this._onDragStart.fire(event);
4029
- if (event.dataTransfer) {
4030
- event.dataTransfer.effectAllowed = 'move';
4031
- const hasData = event.dataTransfer.items.length > 0;
4032
- if (!hasData) {
4033
- /**
4034
- * Although this is not used by dockview many third party dnd libraries will check
4035
- * dataTransfer.types to determine valid drag events.
4036
- *
4037
- * For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled
4038
- * through .preventDefault(). Since this is applied globally to all drag events this would break dockviews
4039
- * dnd logic. You can see the code at
4040
- P * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
4041
- */
4042
- event.dataTransfer.setData('text/plain', '');
4043
- }
4044
- }
4045
- }), addDisposableListener(this.el, 'dragend', () => {
4046
- this.pointerEventsDisposable.dispose();
4047
- setTimeout(() => {
4048
- this.dataDisposable.dispose(); // allow the data to be read by other handlers before disposing
4049
- }, 0);
4050
- }));
4051
- }
4052
- }
4053
-
4054
3997
  class DragAndDropObserver extends CompositeDisposable {
4055
3998
  constructor(element, callbacks) {
4056
3999
  super();
@@ -4101,48 +4044,197 @@
4101
4044
  }
4102
4045
  }
4103
4046
 
4104
- function setGPUOptimizedBounds(element, bounds) {
4105
- const { top, left, width, height } = bounds;
4106
- const topPx = `${Math.round(top)}px`;
4107
- const leftPx = `${Math.round(left)}px`;
4108
- const widthPx = `${Math.round(width)}px`;
4109
- const heightPx = `${Math.round(height)}px`;
4110
- // Use traditional positioning but maintain GPU layer
4111
- element.style.top = topPx;
4112
- element.style.left = leftPx;
4113
- element.style.width = widthPx;
4114
- element.style.height = heightPx;
4115
- element.style.visibility = 'visible';
4116
- // Ensure GPU layer is maintained
4117
- if (!element.style.transform || element.style.transform === '') {
4118
- element.style.transform = 'translate3d(0, 0, 0)';
4119
- }
4120
- }
4121
- function setGPUOptimizedBoundsFromStrings(element, bounds) {
4122
- const { top, left, width, height } = bounds;
4123
- // Use traditional positioning but maintain GPU layer
4124
- element.style.top = top;
4125
- element.style.left = left;
4126
- element.style.width = width;
4127
- element.style.height = height;
4128
- element.style.visibility = 'visible';
4129
- // Ensure GPU layer is maintained
4130
- if (!element.style.transform || element.style.transform === '') {
4131
- element.style.transform = 'translate3d(0, 0, 0)';
4132
- }
4133
- }
4134
- function checkBoundsChanged(element, bounds) {
4135
- const { top, left, width, height } = bounds;
4136
- const topPx = `${Math.round(top)}px`;
4137
- const leftPx = `${Math.round(left)}px`;
4138
- const widthPx = `${Math.round(width)}px`;
4139
- const heightPx = `${Math.round(height)}px`;
4140
- // Check if position or size changed (back to traditional method)
4141
- return (element.style.top !== topPx ||
4142
- element.style.left !== leftPx ||
4143
- element.style.width !== widthPx ||
4144
- element.style.height !== heightPx);
4047
+ // Two render paths: in-place (dropzone appended to drop element) and
4048
+ // anchored (overlay rendered into an external anchor container).
4049
+ const DEFAULT_SIZE = { value: 50, type: 'percentage' };
4050
+ const SMALL_WIDTH_BOUNDARY = 100;
4051
+ const SMALL_HEIGHT_BOUNDARY = 100;
4052
+ function createOverlayElements() {
4053
+ const dropzone = document.createElement('div');
4054
+ dropzone.className = 'dv-drop-target-dropzone';
4055
+ const selection = document.createElement('div');
4056
+ selection.className = 'dv-drop-target-selection';
4057
+ dropzone.appendChild(selection);
4058
+ return { dropzone, selection };
4059
+ }
4060
+ function computeOverlayShape(quadrant, width, height, overlayModel) {
4061
+ var _a, _b, _c;
4062
+ const smallWidthBoundary = (_a = overlayModel === null || overlayModel === void 0 ? void 0 : overlayModel.smallWidthBoundary) !== null && _a !== void 0 ? _a : SMALL_WIDTH_BOUNDARY;
4063
+ const smallHeightBoundary = (_b = overlayModel === null || overlayModel === void 0 ? void 0 : overlayModel.smallHeightBoundary) !== null && _b !== void 0 ? _b : SMALL_HEIGHT_BOUNDARY;
4064
+ const isSmallX = width < smallWidthBoundary;
4065
+ const isSmallY = height < smallHeightBoundary;
4066
+ const isLeft = quadrant === 'left';
4067
+ const isRight = quadrant === 'right';
4068
+ const isTop = quadrant === 'top';
4069
+ const isBottom = quadrant === 'bottom';
4070
+ const rightClass = !isSmallX && isRight;
4071
+ const leftClass = !isSmallX && isLeft;
4072
+ const topClass = !isSmallY && isTop;
4073
+ const bottomClass = !isSmallY && isBottom;
4074
+ let size = 1;
4075
+ const sizeOptions = (_c = overlayModel === null || overlayModel === void 0 ? void 0 : overlayModel.size) !== null && _c !== void 0 ? _c : DEFAULT_SIZE;
4076
+ if (sizeOptions.type === 'percentage') {
4077
+ size = clamp(sizeOptions.value, 0, 100) / 100;
4078
+ }
4079
+ else {
4080
+ if (rightClass || leftClass) {
4081
+ size = clamp(0, sizeOptions.value, width) / width;
4082
+ }
4083
+ if (topClass || bottomClass) {
4084
+ size = clamp(0, sizeOptions.value, height) / height;
4085
+ }
4086
+ }
4087
+ return {
4088
+ isSmallX,
4089
+ isSmallY,
4090
+ isLeft,
4091
+ isRight,
4092
+ isTop,
4093
+ isBottom,
4094
+ rightClass,
4095
+ leftClass,
4096
+ topClass,
4097
+ bottomClass,
4098
+ size,
4099
+ };
4100
+ }
4101
+ function renderInPlaceOverlay(overlay, quadrant, width, height, overlayModel) {
4102
+ const shape = computeOverlayShape(quadrant, width, height, overlayModel);
4103
+ const { rightClass, leftClass, topClass, bottomClass, size } = shape;
4104
+ const box = { top: '0px', left: '0px', width: '100%', height: '100%' };
4105
+ if (rightClass) {
4106
+ box.left = `${100 * (1 - size)}%`;
4107
+ box.width = `${100 * size}%`;
4108
+ }
4109
+ else if (leftClass) {
4110
+ box.width = `${100 * size}%`;
4111
+ }
4112
+ else if (topClass) {
4113
+ box.height = `${100 * size}%`;
4114
+ }
4115
+ else if (bottomClass) {
4116
+ box.top = `${100 * (1 - size)}%`;
4117
+ box.height = `${100 * size}%`;
4118
+ }
4119
+ if (shape.isSmallX && shape.isLeft) {
4120
+ box.width = '4px';
4121
+ }
4122
+ if (shape.isSmallX && shape.isRight) {
4123
+ box.left = `${width - 4}px`;
4124
+ box.width = '4px';
4125
+ }
4126
+ if (shape.isSmallY && shape.isTop) {
4127
+ box.height = '4px';
4128
+ }
4129
+ if (shape.isSmallY && shape.isBottom) {
4130
+ box.top = `${height - 4}px`;
4131
+ box.height = '4px';
4132
+ }
4133
+ overlay.style.top = box.top;
4134
+ overlay.style.left = box.left;
4135
+ overlay.style.width = box.width;
4136
+ overlay.style.height = box.height;
4137
+ overlay.style.visibility = 'visible';
4138
+ if (!overlay.style.transform || overlay.style.transform === '') {
4139
+ overlay.style.transform = 'translate3d(0, 0, 0)';
4140
+ }
4141
+ const isLine = (shape.isSmallX && (shape.isLeft || shape.isRight)) ||
4142
+ (shape.isSmallY && (shape.isTop || shape.isBottom));
4143
+ toggleClass(overlay, 'dv-drop-target-small-vertical', shape.isSmallY);
4144
+ toggleClass(overlay, 'dv-drop-target-small-horizontal', shape.isSmallX);
4145
+ toggleClass(overlay, 'dv-drop-target-selection-line', isLine);
4146
+ toggleClass(overlay, 'dv-drop-target-left', shape.isLeft);
4147
+ toggleClass(overlay, 'dv-drop-target-right', shape.isRight);
4148
+ toggleClass(overlay, 'dv-drop-target-top', shape.isTop);
4149
+ toggleClass(overlay, 'dv-drop-target-bottom', shape.isBottom);
4150
+ toggleClass(overlay, 'dv-drop-target-center', quadrant === 'center');
4151
+ }
4152
+ function checkAnchoredBoundsChanged(overlay, bounds) {
4153
+ const topPx = `${Math.round(bounds.top)}px`;
4154
+ const leftPx = `${Math.round(bounds.left)}px`;
4155
+ const widthPx = `${Math.round(bounds.width)}px`;
4156
+ const heightPx = `${Math.round(bounds.height)}px`;
4157
+ return (overlay.style.top !== topPx ||
4158
+ overlay.style.left !== leftPx ||
4159
+ overlay.style.width !== widthPx ||
4160
+ overlay.style.height !== heightPx);
4161
+ }
4162
+ function applyAnchoredBounds(overlay, bounds) {
4163
+ overlay.style.top = `${Math.round(bounds.top)}px`;
4164
+ overlay.style.left = `${Math.round(bounds.left)}px`;
4165
+ overlay.style.width = `${Math.round(bounds.width)}px`;
4166
+ overlay.style.height = `${Math.round(bounds.height)}px`;
4167
+ overlay.style.visibility = 'visible';
4168
+ if (!overlay.style.transform || overlay.style.transform === '') {
4169
+ overlay.style.transform = 'translate3d(0, 0, 0)';
4170
+ }
4171
+ }
4172
+ /** `boundsChanged: false` lets callers skip redundant work on tight drag loops. */
4173
+ function renderAnchoredOverlay(args) {
4174
+ const shape = computeOverlayShape(args.quadrant, args.width, args.height, args.overlayModel);
4175
+ const { rightClass, leftClass, topClass, bottomClass, size } = shape;
4176
+ const elBox = args.outlineElement.getBoundingClientRect();
4177
+ const ta = args.targetModel.getElements(undefined, args.outlineElement);
4178
+ const el = ta.root;
4179
+ const overlay = ta.overlay;
4180
+ const bigbox = el.getBoundingClientRect();
4181
+ const rootTop = elBox.top - bigbox.top;
4182
+ const rootLeft = elBox.left - bigbox.left;
4183
+ const box = {
4184
+ top: rootTop,
4185
+ left: rootLeft,
4186
+ width: args.width,
4187
+ height: args.height,
4188
+ };
4189
+ if (rightClass) {
4190
+ box.left = rootLeft + args.width * (1 - size);
4191
+ box.width = args.width * size;
4192
+ }
4193
+ else if (leftClass) {
4194
+ box.width = args.width * size;
4195
+ }
4196
+ else if (topClass) {
4197
+ box.height = args.height * size;
4198
+ }
4199
+ else if (bottomClass) {
4200
+ box.top = rootTop + args.height * (1 - size);
4201
+ box.height = args.height * size;
4202
+ }
4203
+ if (shape.isSmallX && shape.isLeft) {
4204
+ box.width = 4;
4205
+ }
4206
+ if (shape.isSmallX && shape.isRight) {
4207
+ box.left = rootLeft + args.width - 4;
4208
+ box.width = 4;
4209
+ }
4210
+ if (shape.isSmallY && shape.isTop) {
4211
+ box.height = 4;
4212
+ }
4213
+ if (shape.isSmallY && shape.isBottom) {
4214
+ box.top = rootTop + args.height - 4;
4215
+ box.height = 4;
4216
+ }
4217
+ if (!checkAnchoredBoundsChanged(overlay, box)) {
4218
+ return { boundsChanged: false, targetChanged: ta.changed };
4219
+ }
4220
+ applyAnchoredBounds(overlay, box);
4221
+ overlay.className = `dv-drop-target-anchor${args.className ? ` ${args.className}` : ''}`;
4222
+ toggleClass(overlay, 'dv-drop-target-left', shape.isLeft);
4223
+ toggleClass(overlay, 'dv-drop-target-right', shape.isRight);
4224
+ toggleClass(overlay, 'dv-drop-target-top', shape.isTop);
4225
+ toggleClass(overlay, 'dv-drop-target-bottom', shape.isBottom);
4226
+ toggleClass(overlay, 'dv-drop-target-anchor-line', (shape.isSmallX && (shape.isLeft || shape.isRight)) ||
4227
+ (shape.isSmallY && (shape.isTop || shape.isBottom)));
4228
+ toggleClass(overlay, 'dv-drop-target-center', args.quadrant === 'center');
4229
+ if (ta.changed) {
4230
+ toggleClass(overlay, 'dv-drop-target-anchor-container-changed', true);
4231
+ setTimeout(() => {
4232
+ toggleClass(overlay, 'dv-drop-target-anchor-container-changed', false);
4233
+ }, 10);
4234
+ }
4235
+ return { boundsChanged: true, targetChanged: ta.changed };
4145
4236
  }
4237
+
4146
4238
  class WillShowOverlayEvent extends DockviewEvent {
4147
4239
  get nativeEvent() {
4148
4240
  return this.options.nativeEvent;
@@ -4187,16 +4279,10 @@
4187
4279
  throw new Error(`invalid position '${position}'`);
4188
4280
  }
4189
4281
  }
4190
- const DEFAULT_ACTIVATION_SIZE = {
4282
+ const DEFAULT_ACTIVATION_SIZE$1 = {
4191
4283
  value: 20,
4192
4284
  type: 'percentage',
4193
4285
  };
4194
- const DEFAULT_SIZE = {
4195
- value: 50,
4196
- type: 'percentage',
4197
- };
4198
- const SMALL_WIDTH_BOUNDARY = 100;
4199
- const SMALL_HEIGHT_BOUNDARY = 100;
4200
4286
  class Droptarget extends CompositeDisposable {
4201
4287
  get disabled() {
4202
4288
  return this._disabled;
@@ -4277,20 +4363,12 @@
4277
4363
  this.markAsUsed(e);
4278
4364
  if (overrideTarget) ;
4279
4365
  else if (!this.targetElement) {
4280
- this.targetElement = document.createElement('div');
4281
- this.targetElement.className = 'dv-drop-target-dropzone';
4282
- this.overlayElement = document.createElement('div');
4283
- this.overlayElement.className = 'dv-drop-target-selection';
4366
+ const els = createOverlayElements();
4367
+ this.targetElement = els.dropzone;
4368
+ this.overlayElement = els.selection;
4284
4369
  this._state = 'center';
4285
- this.targetElement.appendChild(this.overlayElement);
4286
4370
  target.classList.add('dv-drop-target');
4287
4371
  target.append(this.targetElement);
4288
- // this.overlayElement.style.opacity = '0';
4289
- // requestAnimationFrame(() => {
4290
- // if (this.overlayElement) {
4291
- // this.overlayElement.style.opacity = '';
4292
- // }
4293
- // });
4294
4372
  }
4295
4373
  this.toggleClasses(quadrant, width, height);
4296
4374
  this._state = quadrant;
@@ -4360,166 +4438,29 @@
4360
4438
  return typeof value === 'boolean' && value;
4361
4439
  }
4362
4440
  toggleClasses(quadrant, width, height) {
4363
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
4441
+ var _a, _b, _c, _d, _e;
4364
4442
  const target = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
4365
- if (!target && !this.overlayElement) {
4366
- return;
4367
- }
4368
- const smallWidthBoundary = (_d = (_c = this.options.overlayModel) === null || _c === void 0 ? void 0 : _c.smallWidthBoundary) !== null && _d !== void 0 ? _d : SMALL_WIDTH_BOUNDARY;
4369
- const smallHeightBoundary = (_f = (_e = this.options.overlayModel) === null || _e === void 0 ? void 0 : _e.smallHeightBoundary) !== null && _f !== void 0 ? _f : SMALL_HEIGHT_BOUNDARY;
4370
- const isSmallX = width < smallWidthBoundary;
4371
- const isSmallY = height < smallHeightBoundary;
4372
- const isLeft = quadrant === 'left';
4373
- const isRight = quadrant === 'right';
4374
- const isTop = quadrant === 'top';
4375
- const isBottom = quadrant === 'bottom';
4376
- const rightClass = !isSmallX && isRight;
4377
- const leftClass = !isSmallX && isLeft;
4378
- const topClass = !isSmallY && isTop;
4379
- const bottomClass = !isSmallY && isBottom;
4380
- let size = 1;
4381
- const sizeOptions = (_h = (_g = this.options.overlayModel) === null || _g === void 0 ? void 0 : _g.size) !== null && _h !== void 0 ? _h : DEFAULT_SIZE;
4382
- if (sizeOptions.type === 'percentage') {
4383
- size = clamp(sizeOptions.value, 0, 100) / 100;
4384
- }
4385
- else {
4386
- if (rightClass || leftClass) {
4387
- size = clamp(0, sizeOptions.value, width) / width;
4388
- }
4389
- if (topClass || bottomClass) {
4390
- size = clamp(0, sizeOptions.value, height) / height;
4391
- }
4392
- }
4393
4443
  if (target) {
4394
- const outlineEl = (_l = (_k = (_j = this.options).getOverlayOutline) === null || _k === void 0 ? void 0 : _k.call(_j)) !== null && _l !== void 0 ? _l : this.element;
4395
- const elBox = outlineEl.getBoundingClientRect();
4396
- const ta = target.getElements(undefined, outlineEl);
4397
- const el = ta.root;
4398
- const overlay = ta.overlay;
4399
- const bigbox = el.getBoundingClientRect();
4400
- const rootTop = elBox.top - bigbox.top;
4401
- const rootLeft = elBox.left - bigbox.left;
4402
- const box = {
4403
- top: rootTop,
4404
- left: rootLeft,
4405
- width: width,
4406
- height: height,
4407
- };
4408
- if (rightClass) {
4409
- box.left = rootLeft + width * (1 - size);
4410
- box.width = width * size;
4411
- }
4412
- else if (leftClass) {
4413
- box.width = width * size;
4414
- }
4415
- else if (topClass) {
4416
- box.height = height * size;
4417
- }
4418
- else if (bottomClass) {
4419
- box.top = rootTop + height * (1 - size);
4420
- box.height = height * size;
4421
- }
4422
- if (isSmallX && isLeft) {
4423
- box.width = 4;
4424
- }
4425
- if (isSmallX && isRight) {
4426
- box.left = rootLeft + width - 4;
4427
- box.width = 4;
4428
- }
4429
- if (isSmallY && isTop) {
4430
- box.height = 4;
4431
- }
4432
- if (isSmallY && isBottom) {
4433
- box.top = rootTop + height - 4;
4434
- box.height = 4;
4435
- }
4436
- // Use GPU-optimized bounds checking and setting
4437
- if (!checkBoundsChanged(overlay, box)) {
4438
- return;
4439
- }
4440
- setGPUOptimizedBounds(overlay, box);
4441
- overlay.className = `dv-drop-target-anchor${this.options.className ? ` ${this.options.className}` : ''}`;
4442
- toggleClass(overlay, 'dv-drop-target-left', isLeft);
4443
- toggleClass(overlay, 'dv-drop-target-right', isRight);
4444
- toggleClass(overlay, 'dv-drop-target-top', isTop);
4445
- toggleClass(overlay, 'dv-drop-target-bottom', isBottom);
4446
- toggleClass(overlay, 'dv-drop-target-anchor-line', (isSmallX && (isLeft || isRight)) ||
4447
- (isSmallY && (isTop || isBottom)));
4448
- toggleClass(overlay, 'dv-drop-target-center', quadrant === 'center');
4449
- if (ta.changed) {
4450
- toggleClass(overlay, 'dv-drop-target-anchor-container-changed', true);
4451
- setTimeout(() => {
4452
- toggleClass(overlay, 'dv-drop-target-anchor-container-changed', false);
4453
- }, 10);
4454
- }
4444
+ const outlineEl = (_e = (_d = (_c = this.options).getOverlayOutline) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : this.element;
4445
+ renderAnchoredOverlay({
4446
+ outlineElement: outlineEl,
4447
+ targetModel: target,
4448
+ quadrant,
4449
+ width,
4450
+ height,
4451
+ overlayModel: this.options.overlayModel,
4452
+ className: this.options.className,
4453
+ });
4455
4454
  return;
4456
4455
  }
4457
4456
  if (!this.overlayElement) {
4458
4457
  return;
4459
4458
  }
4460
- const box = { top: '0px', left: '0px', width: '100%', height: '100%' };
4461
- /**
4462
- * You can also achieve the overlay placement using the transform CSS property
4463
- * to translate and scale the element however this has the undesired effect of
4464
- * 'skewing' the element. Comment left here for anybody that ever revisits this.
4465
- *
4466
- * @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform
4467
- *
4468
- * right
4469
- * translateX(${100 * (1 - size) / 2}%) scaleX(${scale})
4470
- *
4471
- * left
4472
- * translateX(-${100 * (1 - size) / 2}%) scaleX(${scale})
4473
- *
4474
- * top
4475
- * translateY(-${100 * (1 - size) / 2}%) scaleY(${scale})
4476
- *
4477
- * bottom
4478
- * translateY(${100 * (1 - size) / 2}%) scaleY(${scale})
4479
- */
4480
- if (rightClass) {
4481
- box.left = `${100 * (1 - size)}%`;
4482
- box.width = `${100 * size}%`;
4483
- }
4484
- else if (leftClass) {
4485
- box.width = `${100 * size}%`;
4486
- }
4487
- else if (topClass) {
4488
- box.height = `${100 * size}%`;
4489
- }
4490
- else if (bottomClass) {
4491
- box.top = `${100 * (1 - size)}%`;
4492
- box.height = `${100 * size}%`;
4493
- }
4494
- if (isSmallX && isLeft) {
4495
- box.width = '4px';
4496
- }
4497
- if (isSmallX && isRight) {
4498
- box.left = `${width - 4}px`;
4499
- box.width = '4px';
4500
- }
4501
- if (isSmallY && isTop) {
4502
- box.height = '4px';
4503
- }
4504
- if (isSmallY && isBottom) {
4505
- box.top = `${height - 4}px`;
4506
- box.height = '4px';
4507
- }
4508
- setGPUOptimizedBoundsFromStrings(this.overlayElement, box);
4509
- const isLine = (isSmallX && (isLeft || isRight)) ||
4510
- (isSmallY && (isTop || isBottom));
4511
- toggleClass(this.overlayElement, 'dv-drop-target-small-vertical', isSmallY);
4512
- toggleClass(this.overlayElement, 'dv-drop-target-small-horizontal', isSmallX);
4513
- toggleClass(this.overlayElement, 'dv-drop-target-selection-line', isLine);
4514
- toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft);
4515
- toggleClass(this.overlayElement, 'dv-drop-target-right', isRight);
4516
- toggleClass(this.overlayElement, 'dv-drop-target-top', isTop);
4517
- toggleClass(this.overlayElement, 'dv-drop-target-bottom', isBottom);
4518
- toggleClass(this.overlayElement, 'dv-drop-target-center', quadrant === 'center');
4459
+ renderInPlaceOverlay(this.overlayElement, quadrant, width, height, this.options.overlayModel);
4519
4460
  }
4520
4461
  calculateQuadrant(overlayType, x, y, width, height) {
4521
4462
  var _a, _b;
4522
- const activationSizeOptions = (_b = (_a = this.options.overlayModel) === null || _a === void 0 ? void 0 : _a.activationSize) !== null && _b !== void 0 ? _b : DEFAULT_ACTIVATION_SIZE;
4463
+ const activationSizeOptions = (_b = (_a = this.options.overlayModel) === null || _a === void 0 ? void 0 : _a.activationSize) !== null && _b !== void 0 ? _b : DEFAULT_ACTIVATION_SIZE$1;
4523
4464
  const isPercentage = activationSizeOptions.type === 'percentage';
4524
4465
  if (isPercentage) {
4525
4466
  return calculateQuadrantAsPercentage(overlayType, x, y, width, height, activationSizeOptions.value);
@@ -4577,6 +4518,723 @@
4577
4518
  return 'center';
4578
4519
  }
4579
4520
 
4521
+ function addGhostImage(dataTransfer, ghostElement, options) {
4522
+ var _a, _b;
4523
+ // class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
4524
+ addClasses(ghostElement, 'dv-dragged');
4525
+ // move the element off-screen initially otherwise it may in some cases be rendered at (0,0) momentarily
4526
+ ghostElement.style.top = '-9999px';
4527
+ document.body.appendChild(ghostElement);
4528
+ 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);
4529
+ setTimeout(() => {
4530
+ removeClasses(ghostElement, 'dv-dragged');
4531
+ ghostElement.remove();
4532
+ }, 0);
4533
+ }
4534
+
4535
+ /**
4536
+ * Singleton — only one pointer-driven drag active at a time.
4537
+ *
4538
+ * State is shared across every Dockview instance on the page. Targets
4539
+ * from instance B receive hit-tests from drags originating in instance A;
4540
+ * that's intentional for cross-instance drops since `LocalSelectionTransfer`
4541
+ * is also process-wide. The corollary is that every Tabs subscriber to
4542
+ * `onDragMove` fires for every pointer drag globally — each subscriber
4543
+ * hit-tests against its own DOM, so this is O(N) per pointermove where N
4544
+ * is the number of registered listeners across all instances.
4545
+ */
4546
+ class PointerDragController extends CompositeDisposable {
4547
+ static getInstance() {
4548
+ if (!PointerDragController._instance) {
4549
+ PointerDragController._instance = new PointerDragController();
4550
+ }
4551
+ return PointerDragController._instance;
4552
+ }
4553
+ constructor() {
4554
+ super();
4555
+ this._targets = new Set();
4556
+ /** Kept in sync with `_targets` so hit-testing is allocation-free. */
4557
+ this._targetByElement = new Map();
4558
+ this._onDragStart = new Emitter();
4559
+ this.onDragStart = this._onDragStart.event;
4560
+ this._onDragMove = new Emitter();
4561
+ this.onDragMove = this._onDragMove.event;
4562
+ this._onDragEnd = new Emitter();
4563
+ this.onDragEnd = this._onDragEnd.event;
4564
+ this.addDisposables(this._onDragStart, this._onDragMove, this._onDragEnd);
4565
+ }
4566
+ get active() {
4567
+ return this._active;
4568
+ }
4569
+ registerTarget(target) {
4570
+ this._targets.add(target);
4571
+ this._targetByElement.set(target.element, target);
4572
+ return {
4573
+ dispose: () => {
4574
+ this._targets.delete(target);
4575
+ if (this._targetByElement.get(target.element) === target) {
4576
+ this._targetByElement.delete(target.element);
4577
+ }
4578
+ if (this._currentTarget === target) {
4579
+ this._currentTarget = undefined;
4580
+ }
4581
+ },
4582
+ };
4583
+ }
4584
+ beginDrag(args) {
4585
+ var _a, _b, _c;
4586
+ if (this._active) {
4587
+ this.cancel();
4588
+ }
4589
+ const { pointerEvent, source } = args;
4590
+ // Call `getData()` before mutating controller state — a throw
4591
+ // here would otherwise leave `_active` populated with no window
4592
+ // listeners installed, blocking every subsequent drag.
4593
+ const dataDisposable = args.getData();
4594
+ this._active = {
4595
+ pointerId: pointerEvent.pointerId,
4596
+ startX: pointerEvent.clientX,
4597
+ startY: pointerEvent.clientY,
4598
+ source,
4599
+ };
4600
+ this._onDragMoveCallback = args.onDragMove;
4601
+ this._onDragEndCallback = args.onDragEnd;
4602
+ this._dataDisposable = dataDisposable;
4603
+ this._ghost = args.ghost;
4604
+ // Iframes capture pointermove once the cursor crosses into them,
4605
+ // which would freeze the drag from the parent window's POV.
4606
+ this._iframeShield = disableIframePointEvents((_a = source.ownerDocument) !== null && _a !== void 0 ? _a : document);
4607
+ const startEvent = {
4608
+ clientX: pointerEvent.clientX,
4609
+ clientY: pointerEvent.clientY,
4610
+ pointerEvent,
4611
+ };
4612
+ this._onDragStart.fire(startEvent);
4613
+ // Source's owning window — popout drags fire on their own window,
4614
+ // not the main one.
4615
+ const targetWindow = (_c = (_b = source.ownerDocument) === null || _b === void 0 ? void 0 : _b.defaultView) !== null && _c !== void 0 ? _c : window;
4616
+ this._moveListener = addDisposableListener(targetWindow, 'pointermove', (e) => {
4617
+ if (!this._active || e.pointerId !== this._active.pointerId) {
4618
+ return;
4619
+ }
4620
+ this._handleMove(e);
4621
+ });
4622
+ this._upListener = addDisposableListener(targetWindow, 'pointerup', (e) => {
4623
+ if (!this._active || e.pointerId !== this._active.pointerId) {
4624
+ return;
4625
+ }
4626
+ this._handleEnd(e, true);
4627
+ });
4628
+ this._cancelListener = addDisposableListener(targetWindow, 'pointercancel', (e) => {
4629
+ if (!this._active || e.pointerId !== this._active.pointerId) {
4630
+ return;
4631
+ }
4632
+ this._handleEnd(e, false);
4633
+ });
4634
+ }
4635
+ cancel() {
4636
+ var _a, _b;
4637
+ if (!this._active) {
4638
+ return;
4639
+ }
4640
+ (_a = this._currentTarget) === null || _a === void 0 ? void 0 : _a.handleDragLeave();
4641
+ this._teardown();
4642
+ (_b = this._dataDisposable) === null || _b === void 0 ? void 0 : _b.dispose();
4643
+ this._dataDisposable = undefined;
4644
+ }
4645
+ _findTargetUnder(x, y) {
4646
+ var _a, _b;
4647
+ // `elementsFromPoint` is topmost-first; walk up to find the closest
4648
+ // registered ancestor (so a tab beats the layout-root that contains it).
4649
+ // Use the source's owning document so popout drags hit their own targets.
4650
+ const sourceDoc = (_b = (_a = this._active) === null || _a === void 0 ? void 0 : _a.source.ownerDocument) !== null && _b !== void 0 ? _b : document;
4651
+ const elements = sourceDoc.elementsFromPoint(x, y);
4652
+ for (const el of elements) {
4653
+ let current = el;
4654
+ while (current) {
4655
+ const target = this._targetByElement.get(current);
4656
+ if (target) {
4657
+ return target;
4658
+ }
4659
+ current = current.parentElement;
4660
+ }
4661
+ }
4662
+ return undefined;
4663
+ }
4664
+ _handleMove(e) {
4665
+ var _a, _b, _c;
4666
+ (_a = this._ghost) === null || _a === void 0 ? void 0 : _a.update(e.clientX, e.clientY);
4667
+ const dragEvent = {
4668
+ clientX: e.clientX,
4669
+ clientY: e.clientY,
4670
+ pointerEvent: e,
4671
+ };
4672
+ const newTarget = this._findTargetUnder(e.clientX, e.clientY);
4673
+ if (newTarget !== this._currentTarget) {
4674
+ (_b = this._currentTarget) === null || _b === void 0 ? void 0 : _b.handleDragLeave();
4675
+ this._currentTarget = newTarget;
4676
+ }
4677
+ if (newTarget) {
4678
+ newTarget.handleDragOver(dragEvent);
4679
+ }
4680
+ (_c = this._onDragMoveCallback) === null || _c === void 0 ? void 0 : _c.call(this, dragEvent);
4681
+ this._onDragMove.fire(dragEvent);
4682
+ }
4683
+ _handleEnd(e, dropped) {
4684
+ var _a;
4685
+ const dragEvent = {
4686
+ clientX: e.clientX,
4687
+ clientY: e.clientY,
4688
+ pointerEvent: e,
4689
+ };
4690
+ if (dropped && this._currentTarget) {
4691
+ this._currentTarget.handleDrop(dragEvent);
4692
+ }
4693
+ else {
4694
+ (_a = this._currentTarget) === null || _a === void 0 ? void 0 : _a.handleDragLeave();
4695
+ }
4696
+ const onEnd = this._onDragEndCallback;
4697
+ const dataDisposable = this._dataDisposable;
4698
+ this._teardown();
4699
+ this._dataDisposable = undefined;
4700
+ // Defer disposal so drop handlers can still read the transfer data.
4701
+ setTimeout(() => dataDisposable === null || dataDisposable === void 0 ? void 0 : dataDisposable.dispose(), 0);
4702
+ onEnd === null || onEnd === void 0 ? void 0 : onEnd(dragEvent, dropped);
4703
+ this._onDragEnd.fire(dragEvent);
4704
+ }
4705
+ _teardown() {
4706
+ var _a, _b, _c, _d, _e;
4707
+ this._currentTarget = undefined;
4708
+ this._active = undefined;
4709
+ this._onDragMoveCallback = undefined;
4710
+ this._onDragEndCallback = undefined;
4711
+ (_a = this._ghost) === null || _a === void 0 ? void 0 : _a.dispose();
4712
+ this._ghost = undefined;
4713
+ (_b = this._iframeShield) === null || _b === void 0 ? void 0 : _b.release();
4714
+ this._iframeShield = undefined;
4715
+ (_c = this._moveListener) === null || _c === void 0 ? void 0 : _c.dispose();
4716
+ (_d = this._upListener) === null || _d === void 0 ? void 0 : _d.dispose();
4717
+ (_e = this._cancelListener) === null || _e === void 0 ? void 0 : _e.dispose();
4718
+ this._moveListener = undefined;
4719
+ this._upListener = undefined;
4720
+ this._cancelListener = undefined;
4721
+ }
4722
+ }
4723
+
4724
+ const DEFAULT_ACTIVATION_SIZE = {
4725
+ value: 20,
4726
+ type: 'percentage',
4727
+ };
4728
+ /** Pointer-driven counterpart to `Droptarget` with identical visual output. */
4729
+ class PointerDropTarget extends CompositeDisposable {
4730
+ get disabled() {
4731
+ return this._disabled;
4732
+ }
4733
+ set disabled(value) {
4734
+ this._disabled = value;
4735
+ if (value) {
4736
+ this._removeOverlay();
4737
+ }
4738
+ }
4739
+ get state() {
4740
+ return this._state;
4741
+ }
4742
+ constructor(element, options) {
4743
+ super();
4744
+ this.element = element;
4745
+ this.options = options;
4746
+ this._onDrop = new Emitter();
4747
+ this.onDrop = this._onDrop.event;
4748
+ this._onWillShowOverlay = new Emitter();
4749
+ this.onWillShowOverlay = this._onWillShowOverlay.event;
4750
+ this._disabled = false;
4751
+ this._acceptedTargetZonesSet = new Set(options.acceptedTargetZones);
4752
+ const handle = {
4753
+ element: this.element,
4754
+ handleDragOver: (e) => this._onDragOver(e),
4755
+ handleDragLeave: () => this._onDragLeave(),
4756
+ handleDrop: (e) => this._onDropEvent(e),
4757
+ };
4758
+ this.addDisposables(this._onDrop, this._onWillShowOverlay, PointerDragController.getInstance().registerTarget(handle));
4759
+ }
4760
+ setTargetZones(zones) {
4761
+ this._acceptedTargetZonesSet = new Set(zones);
4762
+ }
4763
+ setOverlayModel(model) {
4764
+ this.options.overlayModel = model;
4765
+ }
4766
+ dispose() {
4767
+ this._removeOverlay();
4768
+ super.dispose();
4769
+ }
4770
+ _onDragOver(event) {
4771
+ var _a, _b, _c, _d, _e;
4772
+ if (this._disabled) {
4773
+ this._removeOverlay();
4774
+ return;
4775
+ }
4776
+ const overrideTarget = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
4777
+ if (this._acceptedTargetZonesSet.size === 0) {
4778
+ if (overrideTarget) {
4779
+ return;
4780
+ }
4781
+ this._removeOverlay();
4782
+ return;
4783
+ }
4784
+ const outlineEl = (_e = (_d = (_c = this.options).getOverlayOutline) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : this.element;
4785
+ const width = outlineEl.offsetWidth;
4786
+ const height = outlineEl.offsetHeight;
4787
+ if (width === 0 || height === 0) {
4788
+ return;
4789
+ }
4790
+ const rect = outlineEl.getBoundingClientRect();
4791
+ const x = event.clientX - rect.left;
4792
+ const y = event.clientY - rect.top;
4793
+ const quadrant = this._calculateQuadrant(x, y, width, height);
4794
+ if (quadrant === null) {
4795
+ this._removeOverlay();
4796
+ return;
4797
+ }
4798
+ if (!this.options.canDisplayOverlay(event.pointerEvent, quadrant)) {
4799
+ if (overrideTarget) {
4800
+ return;
4801
+ }
4802
+ this._removeOverlay();
4803
+ return;
4804
+ }
4805
+ const willShow = new WillShowOverlayEvent({
4806
+ nativeEvent: event.pointerEvent,
4807
+ position: quadrant,
4808
+ });
4809
+ this._onWillShowOverlay.fire(willShow);
4810
+ if (willShow.defaultPrevented) {
4811
+ this._removeOverlay();
4812
+ return;
4813
+ }
4814
+ if (overrideTarget) {
4815
+ renderAnchoredOverlay({
4816
+ outlineElement: outlineEl,
4817
+ targetModel: overrideTarget,
4818
+ quadrant,
4819
+ width,
4820
+ height,
4821
+ overlayModel: this.options.overlayModel,
4822
+ className: this.options.className,
4823
+ });
4824
+ this._state = quadrant;
4825
+ return;
4826
+ }
4827
+ if (!this._targetElement) {
4828
+ const els = createOverlayElements();
4829
+ this._targetElement = els.dropzone;
4830
+ this._overlayElement = els.selection;
4831
+ this._state = 'center';
4832
+ this.element.classList.add('dv-drop-target');
4833
+ this.element.append(this._targetElement);
4834
+ }
4835
+ if (this._overlayElement) {
4836
+ renderInPlaceOverlay(this._overlayElement, quadrant, width, height, this.options.overlayModel);
4837
+ }
4838
+ this._state = quadrant;
4839
+ }
4840
+ _onDragLeave() {
4841
+ var _a, _b;
4842
+ const overrideTarget = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
4843
+ // Anchor target owns its own lifecycle; just clear our latched
4844
+ // state so a subsequent pointerup doesn't fire a stale drop.
4845
+ if (overrideTarget) {
4846
+ this._state = undefined;
4847
+ overrideTarget.clear();
4848
+ return;
4849
+ }
4850
+ this._removeOverlay();
4851
+ }
4852
+ _onDropEvent(event) {
4853
+ var _a, _b;
4854
+ const state = this._state;
4855
+ const overrideTarget = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
4856
+ this._removeOverlay();
4857
+ overrideTarget === null || overrideTarget === void 0 ? void 0 : overrideTarget.clear();
4858
+ if (state) {
4859
+ this._onDrop.fire({
4860
+ position: state,
4861
+ nativeEvent: event.pointerEvent,
4862
+ });
4863
+ }
4864
+ }
4865
+ _calculateQuadrant(x, y, width, height) {
4866
+ var _a, _b;
4867
+ const activation = (_b = (_a = this.options.overlayModel) === null || _a === void 0 ? void 0 : _a.activationSize) !== null && _b !== void 0 ? _b : DEFAULT_ACTIVATION_SIZE;
4868
+ if (activation.type === 'percentage') {
4869
+ return calculateQuadrantAsPercentage(this._acceptedTargetZonesSet, x, y, width, height, activation.value);
4870
+ }
4871
+ return calculateQuadrantAsPixels(this._acceptedTargetZonesSet, x, y, width, height, activation.value);
4872
+ }
4873
+ _removeOverlay() {
4874
+ var _a;
4875
+ if (this._targetElement) {
4876
+ this._state = undefined;
4877
+ (_a = this._targetElement.parentElement) === null || _a === void 0 ? void 0 : _a.classList.remove('dv-drop-target');
4878
+ this._targetElement.remove();
4879
+ this._targetElement = undefined;
4880
+ this._overlayElement = undefined;
4881
+ }
4882
+ else {
4883
+ this._state = undefined;
4884
+ }
4885
+ }
4886
+ }
4887
+
4888
+ const DEFAULT_THRESHOLD = 5;
4889
+ const DEFAULT_TOUCH_INITIATION_DELAY = 250;
4890
+ const DEFAULT_PRESS_TOLERANCE = 8;
4891
+ /**
4892
+ * Pointer-event drag source. Waits for movement past `threshold` (and
4893
+ * touch-only `touchInitiationDelay`) before promoting to a drag so taps
4894
+ * pass through unaffected.
4895
+ */
4896
+ class PointerDragSource extends CompositeDisposable {
4897
+ constructor(element, options) {
4898
+ var _a;
4899
+ super();
4900
+ this.element = element;
4901
+ this.options = options;
4902
+ this._disabled = false;
4903
+ this._armed = false;
4904
+ this._startX = 0;
4905
+ this._startY = 0;
4906
+ this._touchOnly = (_a = options.touchOnly) !== null && _a !== void 0 ? _a : true;
4907
+ this.addDisposables(addDisposableListener(this.element, 'pointerdown', (e) => {
4908
+ this._onPointerDown(e);
4909
+ }));
4910
+ }
4911
+ setDisabled(value) {
4912
+ this._disabled = value;
4913
+ if (value) {
4914
+ this._cancelPending();
4915
+ }
4916
+ }
4917
+ /**
4918
+ * `false` lets the pointer source also handle mouse pointers; used when
4919
+ * `dndStrategy: 'pointer'` to drive every input type through this path.
4920
+ */
4921
+ setTouchOnly(value) {
4922
+ if (this._touchOnly === value) {
4923
+ return;
4924
+ }
4925
+ this._touchOnly = value;
4926
+ // A pending mouse-tracked drag should be abandoned if we re-enable
4927
+ // the touch-only filter mid-flight.
4928
+ if (value) {
4929
+ this._cancelPending();
4930
+ }
4931
+ }
4932
+ _shouldHandle(event) {
4933
+ var _a, _b;
4934
+ if (this._disabled) {
4935
+ return false;
4936
+ }
4937
+ // Pointer-type filter runs before isCancelled — consumer state read
4938
+ // by isCancelled may not be populated for events we'll never handle.
4939
+ if (this._touchOnly &&
4940
+ event.pointerType !== 'touch' &&
4941
+ event.pointerType !== 'pen') {
4942
+ return false;
4943
+ }
4944
+ if ((_b = (_a = this.options).isCancelled) === null || _b === void 0 ? void 0 : _b.call(_a, event)) {
4945
+ return false;
4946
+ }
4947
+ return true;
4948
+ }
4949
+ _onPointerDown(event) {
4950
+ var _a, _b, _c, _d, _e;
4951
+ if (!this._shouldHandle(event)) {
4952
+ return;
4953
+ }
4954
+ // Defensive: a fresh pointerdown supersedes any in-flight tracking.
4955
+ this._cancelPending();
4956
+ this._pendingPointerId = event.pointerId;
4957
+ this._startX = event.clientX;
4958
+ this._startY = event.clientY;
4959
+ this._startEvent = event;
4960
+ const isTouch = event.pointerType === 'touch' || event.pointerType === 'pen';
4961
+ // Touch waits a short window so a still finger can press-and-hold
4962
+ // before drifting; once the timer fires, any motion past `threshold`
4963
+ // begins the drag.
4964
+ const initiationDelayOpt = this.options.touchInitiationDelay;
4965
+ const initiationDelay = (_a = (typeof initiationDelayOpt === 'function'
4966
+ ? initiationDelayOpt()
4967
+ : initiationDelayOpt)) !== null && _a !== void 0 ? _a : DEFAULT_TOUCH_INITIATION_DELAY;
4968
+ this._armed = !isTouch || initiationDelay <= 0;
4969
+ if (isTouch && initiationDelay > 0 && isFinite(initiationDelay)) {
4970
+ this._armTimer = setTimeout(() => {
4971
+ this._armTimer = undefined;
4972
+ this._armed = true;
4973
+ }, initiationDelay);
4974
+ }
4975
+ const threshold = (_b = this.options.threshold) !== null && _b !== void 0 ? _b : DEFAULT_THRESHOLD;
4976
+ const pressToleranceOpt = this.options.pressTolerance;
4977
+ const pressTolerance = (_c = (typeof pressToleranceOpt === 'function'
4978
+ ? pressToleranceOpt()
4979
+ : pressToleranceOpt)) !== null && _c !== void 0 ? _c : DEFAULT_PRESS_TOLERANCE;
4980
+ // Source's owning window — popout drags fire on their own window.
4981
+ const targetWindow = (_e = (_d = this.element.ownerDocument) === null || _d === void 0 ? void 0 : _d.defaultView) !== null && _e !== void 0 ? _e : window;
4982
+ this._pendingMoveListener = addDisposableListener(targetWindow, 'pointermove', (moveEvent) => {
4983
+ if (moveEvent.pointerId !== this._pendingPointerId) {
4984
+ return;
4985
+ }
4986
+ const dx = moveEvent.clientX - this._startX;
4987
+ const dy = moveEvent.clientY - this._startY;
4988
+ const distance = Math.hypot(dx, dy);
4989
+ if (this._armed) {
4990
+ if (distance >= threshold) {
4991
+ this._beginDrag(moveEvent);
4992
+ }
4993
+ return;
4994
+ }
4995
+ // Pre-arm phase: a flick past `pressTolerance` in any
4996
+ // direction is treated as drag intent. The element opts out
4997
+ // of native scroll via `touch-action: none`; container-level
4998
+ // scrolling lives on the surrounding strip's empty space.
4999
+ if (distance > pressTolerance) {
5000
+ this._beginDrag(moveEvent);
5001
+ }
5002
+ });
5003
+ this._pendingUpListener = addDisposableListener(targetWindow, 'pointerup', (upEvent) => {
5004
+ if (upEvent.pointerId !== this._pendingPointerId) {
5005
+ return;
5006
+ }
5007
+ this._cancelPending();
5008
+ });
5009
+ this._pendingCancelListener = addDisposableListener(targetWindow, 'pointercancel', (cancelEvent) => {
5010
+ if (cancelEvent.pointerId !== this._pendingPointerId) {
5011
+ return;
5012
+ }
5013
+ this._cancelPending();
5014
+ });
5015
+ }
5016
+ /** For sibling gesture detectors (e.g. LongPressDetector) to dismiss a pending drag. */
5017
+ cancelPending() {
5018
+ this._cancelPending();
5019
+ }
5020
+ _cancelPending() {
5021
+ var _a, _b, _c;
5022
+ this._pendingPointerId = undefined;
5023
+ if (this._armTimer !== undefined) {
5024
+ clearTimeout(this._armTimer);
5025
+ this._armTimer = undefined;
5026
+ }
5027
+ this._armed = false;
5028
+ (_a = this._pendingMoveListener) === null || _a === void 0 ? void 0 : _a.dispose();
5029
+ (_b = this._pendingUpListener) === null || _b === void 0 ? void 0 : _b.dispose();
5030
+ (_c = this._pendingCancelListener) === null || _c === void 0 ? void 0 : _c.dispose();
5031
+ this._pendingMoveListener = undefined;
5032
+ this._pendingUpListener = undefined;
5033
+ this._pendingCancelListener = undefined;
5034
+ this._startEvent = undefined;
5035
+ }
5036
+ _beginDrag(triggerEvent) {
5037
+ var _a, _b, _c, _d, _e;
5038
+ const startEvent = (_a = this._startEvent) !== null && _a !== void 0 ? _a : triggerEvent;
5039
+ this._cancelPending();
5040
+ (_c = (_b = this.options).onDragStart) === null || _c === void 0 ? void 0 : _c.call(_b, startEvent);
5041
+ const ghost = (_e = (_d = this.options).createGhost) === null || _e === void 0 ? void 0 : _e.call(_d, startEvent);
5042
+ PointerDragController.getInstance().beginDrag({
5043
+ pointerEvent: triggerEvent,
5044
+ source: this.element,
5045
+ getData: () => this.options.getData(startEvent),
5046
+ ghost,
5047
+ onDragMove: this.options.onDragMove,
5048
+ onDragEnd: this.options.onDragEnd,
5049
+ });
5050
+ }
5051
+ dispose() {
5052
+ this._cancelPending();
5053
+ super.dispose();
5054
+ }
5055
+ }
5056
+
5057
+ /**
5058
+ * Floating clone that follows the pointer; appended to the owning
5059
+ * document's body with `pointer-events: none` so it doesn't intercept
5060
+ * hit-testing.
5061
+ */
5062
+ class PointerGhost {
5063
+ constructor(opts) {
5064
+ var _a, _b, _c, _d, _e;
5065
+ this._disposed = false;
5066
+ this.element = opts.element;
5067
+ this.offsetX = (_a = opts.offsetX) !== null && _a !== void 0 ? _a : 0;
5068
+ this.offsetY = (_b = opts.offsetY) !== null && _b !== void 0 ? _b : 0;
5069
+ // Animate via transform (see update); position:fixed for scroll-independence.
5070
+ this.element.style.position = 'fixed';
5071
+ this.element.style.left = '0px';
5072
+ this.element.style.top = '0px';
5073
+ this.element.style.pointerEvents = 'none';
5074
+ this.element.style.zIndex = '99999';
5075
+ this.element.style.opacity = String((_c = opts.opacity) !== null && _c !== void 0 ? _c : 0.8);
5076
+ this.element.style.willChange = 'transform';
5077
+ this.element.style.transform = `translate3d(${opts.initialX - this.offsetX}px, ${opts.initialY - this.offsetY}px, 0)`;
5078
+ const ownerDocument = (_e = (_d = opts.owner) === null || _d === void 0 ? void 0 : _d.ownerDocument) !== null && _e !== void 0 ? _e : document;
5079
+ ownerDocument.body.appendChild(this.element);
5080
+ }
5081
+ update(clientX, clientY) {
5082
+ if (this._disposed) {
5083
+ return;
5084
+ }
5085
+ // translate3d composites on the GPU — no layout per pointermove.
5086
+ this.element.style.transform = `translate3d(${clientX - this.offsetX}px, ${clientY - this.offsetY}px, 0)`;
5087
+ }
5088
+ dispose() {
5089
+ if (this._disposed) {
5090
+ return;
5091
+ }
5092
+ this._disposed = true;
5093
+ this.element.remove();
5094
+ }
5095
+ }
5096
+
5097
+ /**
5098
+ * HTML5 drag source. Listens for the native `dragstart` event, calls
5099
+ * `getData` to populate transfer, optionally renders the ghost via
5100
+ * `setDragImage`, fires `onDragStart` / `onDragEnd`, and tears down the
5101
+ * transfer disposer after `dragend`.
5102
+ */
5103
+ class Html5DragSource extends CompositeDisposable {
5104
+ constructor(el, opts) {
5105
+ super();
5106
+ this.el = el;
5107
+ this.opts = opts;
5108
+ this._dataDisposable = new MutableDisposable();
5109
+ this._pointerEventsDisposable = new MutableDisposable();
5110
+ this._disabled = !!opts.disabled;
5111
+ this.addDisposables(this._dataDisposable, this._pointerEventsDisposable, addDisposableListener(this.el, 'dragstart', (event) => {
5112
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
5113
+ if (event.defaultPrevented ||
5114
+ this._disabled ||
5115
+ ((_b = (_a = this.opts).isCancelled) === null || _b === void 0 ? void 0 : _b.call(_a, event))) {
5116
+ event.preventDefault();
5117
+ return;
5118
+ }
5119
+ // Iframes capture pointermove once the cursor enters them,
5120
+ // which freezes drag tracking from the parent window's
5121
+ // POV. Shield the source's owning document so popout-window
5122
+ // drags shield the popout, not the main window.
5123
+ const iframes = disableIframePointEvents((_c = this.el.ownerDocument) !== null && _c !== void 0 ? _c : document);
5124
+ this._pointerEventsDisposable.value = {
5125
+ dispose: () => iframes.release(),
5126
+ };
5127
+ this.el.classList.add('dv-dragged');
5128
+ setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
5129
+ this._dataDisposable.value = this.opts.getData(event);
5130
+ const ghost = (_e = (_d = this.opts).createGhost) === null || _e === void 0 ? void 0 : _e.call(_d, event);
5131
+ if (ghost && event.dataTransfer) {
5132
+ addGhostImage(event.dataTransfer, ghost.element, {
5133
+ x: (_f = ghost.offsetX) !== null && _f !== void 0 ? _f : 0,
5134
+ y: (_g = ghost.offsetY) !== null && _g !== void 0 ? _g : 0,
5135
+ });
5136
+ if (ghost.dispose) {
5137
+ // addGhostImage removes the element from the DOM on
5138
+ // the next tick; dispose the framework renderer on
5139
+ // the same schedule.
5140
+ const disposeGhost = ghost.dispose;
5141
+ setTimeout(() => disposeGhost(), 0);
5142
+ }
5143
+ }
5144
+ if (event.dataTransfer) {
5145
+ event.dataTransfer.effectAllowed = 'move';
5146
+ // Some third-party DnD libs (e.g. react-dnd) cancel the
5147
+ // dragstart when `dataTransfer.types` is empty.
5148
+ if (event.dataTransfer.items.length === 0) {
5149
+ event.dataTransfer.setData('text/plain', '');
5150
+ }
5151
+ }
5152
+ (_j = (_h = this.opts).onDragStart) === null || _j === void 0 ? void 0 : _j.call(_h, event);
5153
+ }), addDisposableListener(this.el, 'dragend', (event) => {
5154
+ var _a, _b;
5155
+ this._pointerEventsDisposable.dispose();
5156
+ // Defer disposal so drop handlers can still read the
5157
+ // transfer payload before it clears.
5158
+ setTimeout(() => this._dataDisposable.dispose(), 0);
5159
+ (_b = (_a = this.opts).onDragEnd) === null || _b === void 0 ? void 0 : _b.call(_a, event);
5160
+ }));
5161
+ }
5162
+ setDisabled(value) {
5163
+ this._disabled = value;
5164
+ }
5165
+ setTouchOnly(_) {
5166
+ // No-op — HTML5 path can't filter by pointer type.
5167
+ }
5168
+ cancelPending() {
5169
+ // No-op — HTML5 has no pre-arm phase to cancel.
5170
+ }
5171
+ }
5172
+ class Html5DragBackend {
5173
+ constructor() {
5174
+ this.kind = 'html5';
5175
+ }
5176
+ createDropTarget(element, options) {
5177
+ return new Droptarget(element, options);
5178
+ }
5179
+ createDragSource(element, options) {
5180
+ return new Html5DragSource(element, options);
5181
+ }
5182
+ }
5183
+ class PointerDragBackend {
5184
+ constructor() {
5185
+ this.kind = 'pointer';
5186
+ }
5187
+ createDropTarget(element, options) {
5188
+ return new PointerDropTarget(element, options);
5189
+ }
5190
+ createDragSource(element, options) {
5191
+ const pointerCreateGhost = options.createGhost
5192
+ ? (event) => {
5193
+ const spec = options.createGhost(event);
5194
+ if (!spec) {
5195
+ return undefined;
5196
+ }
5197
+ const ghost = new PointerGhost({
5198
+ element: spec.element,
5199
+ initialX: event.clientX,
5200
+ initialY: event.clientY,
5201
+ offsetX: spec.offsetX,
5202
+ offsetY: spec.offsetY,
5203
+ owner: element,
5204
+ });
5205
+ if (spec.dispose) {
5206
+ const baseDispose = ghost.dispose.bind(ghost);
5207
+ const disposeSpec = spec.dispose;
5208
+ ghost.dispose = () => {
5209
+ baseDispose();
5210
+ disposeSpec();
5211
+ };
5212
+ }
5213
+ return ghost;
5214
+ }
5215
+ : undefined;
5216
+ const source = new PointerDragSource(element, {
5217
+ getData: options.getData,
5218
+ isCancelled: options.isCancelled,
5219
+ onDragStart: options.onDragStart,
5220
+ onDragEnd: options.onDragEnd
5221
+ ? (event) => options.onDragEnd(event.pointerEvent)
5222
+ : undefined,
5223
+ createGhost: pointerCreateGhost,
5224
+ touchOnly: options.touchOnly,
5225
+ touchInitiationDelay: options.touchInitiationDelay,
5226
+ pressTolerance: options.pressTolerance,
5227
+ threshold: options.threshold,
5228
+ });
5229
+ if (options.disabled) {
5230
+ source.setDisabled(true);
5231
+ }
5232
+ return source;
5233
+ }
5234
+ }
5235
+ const html5Backend = new Html5DragBackend();
5236
+ const pointerBackend = new PointerDragBackend();
5237
+
4580
5238
  const PROPERTY_KEYS_PANEVIEW = (() => {
4581
5239
  /**
4582
5240
  * by readong the keys from an empty value object TypeScript will error
@@ -5042,35 +5700,42 @@
5042
5700
  const id = this.id;
5043
5701
  const accessorId = this.accessor.id;
5044
5702
  this.header.draggable = true;
5045
- this.handler = new (class PaneDragHandler extends DragHandler {
5046
- getData() {
5703
+ const sharedDragOptions = {
5704
+ getData: () => {
5047
5705
  LocalSelectionTransfer.getInstance().setData([new PaneTransfer(accessorId, id)], PaneTransfer.prototype);
5048
5706
  return {
5049
5707
  dispose: () => {
5050
5708
  LocalSelectionTransfer.getInstance().clearData(PaneTransfer.prototype);
5051
5709
  },
5052
5710
  };
5711
+ },
5712
+ };
5713
+ this.html5DragSource = html5Backend.createDragSource(this.header, sharedDragOptions);
5714
+ this.pointerDragSource = pointerBackend.createDragSource(this.header, sharedDragOptions);
5715
+ const canDisplayOverlay = (event, position) => {
5716
+ const data = getPaneData();
5717
+ if (data) {
5718
+ if (data.paneId !== this.id &&
5719
+ data.viewId === this.accessor.id) {
5720
+ return true;
5721
+ }
5053
5722
  }
5054
- })(this.header);
5055
- this.target = new Droptarget(this.element, {
5723
+ const firedEvent = new PaneviewUnhandledDragOverEvent(event, position, getPaneData, this);
5724
+ this._onUnhandledDragOverEvent.fire(firedEvent);
5725
+ return firedEvent.isAccepted;
5726
+ };
5727
+ const dropTargetOptions = {
5056
5728
  acceptedTargetZones: ['top', 'bottom'],
5057
5729
  overlayModel: {
5058
5730
  activationSize: { type: 'percentage', value: 50 },
5059
5731
  },
5060
- canDisplayOverlay: (event, position) => {
5061
- const data = getPaneData();
5062
- if (data) {
5063
- if (data.paneId !== this.id &&
5064
- data.viewId === this.accessor.id) {
5065
- return true;
5066
- }
5067
- }
5068
- const firedEvent = new PaneviewUnhandledDragOverEvent(event, position, getPaneData, this);
5069
- this._onUnhandledDragOverEvent.fire(firedEvent);
5070
- return firedEvent.isAccepted;
5071
- },
5072
- });
5073
- this.addDisposables(this._onDidDrop, this.handler, this.target, this.target.onDrop((event) => {
5732
+ canDisplayOverlay,
5733
+ };
5734
+ this.target = html5Backend.createDropTarget(this.element, dropTargetOptions);
5735
+ this.pointerTarget = pointerBackend.createDropTarget(this.element, dropTargetOptions);
5736
+ this.addDisposables(this._onDidDrop, this.html5DragSource, this.pointerDragSource, this.target, this.pointerTarget, this.target.onDrop((event) => {
5737
+ this.onDrop(event);
5738
+ }), this.pointerTarget.onDrop((event) => {
5074
5739
  this.onDrop(event);
5075
5740
  }));
5076
5741
  }
@@ -5125,6 +5790,25 @@
5125
5790
  this._element.tabIndex = -1;
5126
5791
  this.addDisposables(this._onDidFocus, this._onDidBlur);
5127
5792
  const target = group.dropTargetContainer;
5793
+ const canDisplayOverlay = (event, position) => {
5794
+ if (this.group.locked === 'no-drop-target' ||
5795
+ (this.group.locked && position === 'center')) {
5796
+ return false;
5797
+ }
5798
+ const data = getPanelData();
5799
+ if (!data &&
5800
+ event.shiftKey &&
5801
+ this.group.location.type !== 'floating') {
5802
+ return false;
5803
+ }
5804
+ if (data && data.viewId === this.accessor.id) {
5805
+ return true;
5806
+ }
5807
+ return this.group.canDisplayOverlay(event, position, 'content');
5808
+ };
5809
+ // `dropTarget` stays the concrete `Droptarget` (not via the backend
5810
+ // factory) because overlayRenderContainer forwards HTML5 drag events
5811
+ // through `dropTarget.dnd` — that field is not part of `IDropTarget`.
5128
5812
  this.dropTarget = new Droptarget(this.element, {
5129
5813
  getOverlayOutline: () => {
5130
5814
  var _a;
@@ -5134,25 +5818,22 @@
5134
5818
  },
5135
5819
  className: 'dv-drop-target-content',
5136
5820
  acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
5137
- canDisplayOverlay: (event, position) => {
5138
- if (this.group.locked === 'no-drop-target' ||
5139
- (this.group.locked && position === 'center')) {
5140
- return false;
5141
- }
5142
- const data = getPanelData();
5143
- if (!data &&
5144
- event.shiftKey &&
5145
- this.group.location.type !== 'floating') {
5146
- return false;
5147
- }
5148
- if (data && data.viewId === this.accessor.id) {
5149
- return true;
5150
- }
5151
- return this.group.canDisplayOverlay(event, position, 'content');
5821
+ canDisplayOverlay,
5822
+ getOverrideTarget: target ? () => target.model : undefined,
5823
+ });
5824
+ this.pointerDropTarget = pointerBackend.createDropTarget(this.element, {
5825
+ acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
5826
+ canDisplayOverlay,
5827
+ getOverlayOutline: () => {
5828
+ var _a;
5829
+ return ((_a = accessor.options.theme) === null || _a === void 0 ? void 0 : _a.dndPanelOverlay) === 'group'
5830
+ ? this.element.parentElement
5831
+ : null;
5152
5832
  },
5833
+ className: 'dv-drop-target-content',
5153
5834
  getOverrideTarget: target ? () => target.model : undefined,
5154
5835
  });
5155
- this.addDisposables(this.dropTarget);
5836
+ this.addDisposables(this.dropTarget, this.pointerDropTarget);
5156
5837
  }
5157
5838
  show() {
5158
5839
  this.element.style.display = '';
@@ -5240,37 +5921,169 @@
5240
5921
  }
5241
5922
  }
5242
5923
 
5243
- function addGhostImage(dataTransfer, ghostElement, options) {
5244
- var _a, _b;
5245
- // class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
5246
- addClasses(ghostElement, 'dv-dragged');
5247
- // move the element off-screen initially otherwise it may in some cases be rendered at (0,0) momentarily
5248
- ghostElement.style.top = '-9999px';
5249
- document.body.appendChild(ghostElement);
5250
- 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);
5251
- setTimeout(() => {
5252
- removeClasses(ghostElement, 'dv-dragged');
5253
- ghostElement.remove();
5254
- }, 0);
5924
+ const DEFAULT_DELAY = 500;
5925
+ const DEFAULT_TOLERANCE = 8;
5926
+ /**
5927
+ * Passive — does not consume the pointer; movement past `tolerance`
5928
+ * cancels silently so a sibling `PointerDragSource` can take over.
5929
+ */
5930
+ class LongPressDetector extends CompositeDisposable {
5931
+ constructor(element, options) {
5932
+ super();
5933
+ this.element = element;
5934
+ this.options = options;
5935
+ this._startX = 0;
5936
+ this._startY = 0;
5937
+ this.addDisposables(addDisposableListener(this.element, 'pointerdown', (e) => {
5938
+ this._onPointerDown(e);
5939
+ }));
5940
+ }
5941
+ _onPointerDown(event) {
5942
+ var _a, _b, _c, _d, _e;
5943
+ const touchOnly = (_a = this.options.touchOnly) !== null && _a !== void 0 ? _a : true;
5944
+ if (touchOnly &&
5945
+ event.pointerType !== 'touch' &&
5946
+ event.pointerType !== 'pen') {
5947
+ return;
5948
+ }
5949
+ // Defensive — supersede any in-flight press.
5950
+ this._cancelPending();
5951
+ this._pointerId = event.pointerId;
5952
+ this._startX = event.clientX;
5953
+ this._startY = event.clientY;
5954
+ const delay = (_b = this.options.delay) !== null && _b !== void 0 ? _b : DEFAULT_DELAY;
5955
+ const tolerance = (_c = this.options.tolerance) !== null && _c !== void 0 ? _c : DEFAULT_TOLERANCE;
5956
+ // Source's owning window — popout drags fire on their own window.
5957
+ const targetWindow = (_e = (_d = this.element.ownerDocument) === null || _d === void 0 ? void 0 : _d.defaultView) !== null && _e !== void 0 ? _e : window;
5958
+ this._timer = setTimeout(() => {
5959
+ this._timer = undefined;
5960
+ this._cancelPending();
5961
+ // Touch browsers synthesize a compatibility `contextmenu` event
5962
+ // for long-press. preventDefault on the original pointerdown is
5963
+ // too late (already dispatched), so install a one-shot
5964
+ // capture-phase guard for the next contextmenu. Without this,
5965
+ // consumers that don't preventDefault inside their onLongPress
5966
+ // (or that early-return before doing so) leak the browser's
5967
+ // native menu on top of theirs.
5968
+ this._installContextMenuGuard(targetWindow);
5969
+ // Same idea for `click`: when the user releases their finger
5970
+ // after the long-press, touch browsers dispatch a `click` to
5971
+ // the element the touch ended on (the source). Consumers
5972
+ // typically wire click to a primary action (e.g. tab activate,
5973
+ // tab-group chip collapse-toggle). Without this guard, the
5974
+ // long-press immediately fires both the context menu AND the
5975
+ // primary action — and the action's side effects (e.g. a chip
5976
+ // collapse animation) read as a screen wobble while the menu
5977
+ // is supposed to be open. Scoped to the source element so
5978
+ // clicks on menu items elsewhere remain effective.
5979
+ this._installClickGuard(targetWindow);
5980
+ this.options.onLongPress(event);
5981
+ }, delay);
5982
+ this._moveListener = addDisposableListener(targetWindow, 'pointermove', (moveEvent) => {
5983
+ if (moveEvent.pointerId !== this._pointerId) {
5984
+ return;
5985
+ }
5986
+ const dx = moveEvent.clientX - this._startX;
5987
+ const dy = moveEvent.clientY - this._startY;
5988
+ if (Math.hypot(dx, dy) > tolerance) {
5989
+ this._cancelPending();
5990
+ }
5991
+ });
5992
+ this._upListener = addDisposableListener(targetWindow, 'pointerup', (upEvent) => {
5993
+ if (upEvent.pointerId !== this._pointerId) {
5994
+ return;
5995
+ }
5996
+ this._cancelPending();
5997
+ });
5998
+ this._cancelListener = addDisposableListener(targetWindow, 'pointercancel', (cancelEvent) => {
5999
+ if (cancelEvent.pointerId !== this._pointerId) {
6000
+ return;
6001
+ }
6002
+ this._cancelPending();
6003
+ });
6004
+ }
6005
+ _installContextMenuGuard(targetWindow) {
6006
+ let guard;
6007
+ const timeout = setTimeout(() => guard === null || guard === void 0 ? void 0 : guard.dispose(), 500);
6008
+ guard = addDisposableListener(targetWindow, 'contextmenu', (event) => {
6009
+ event.preventDefault();
6010
+ clearTimeout(timeout);
6011
+ guard === null || guard === void 0 ? void 0 : guard.dispose();
6012
+ }, { capture: true });
6013
+ }
6014
+ _installClickGuard(targetWindow) {
6015
+ let guard;
6016
+ const timeout = setTimeout(() => guard === null || guard === void 0 ? void 0 : guard.dispose(), 500);
6017
+ guard = addDisposableListener(targetWindow, 'click', (event) => {
6018
+ // Only suppress clicks targeted at the long-pressed element
6019
+ // or its descendants. A user tap on a context menu item (or
6020
+ // anywhere else) still gets through unchanged.
6021
+ const target = event.target;
6022
+ if (target && this.element.contains(target)) {
6023
+ event.preventDefault();
6024
+ event.stopPropagation();
6025
+ }
6026
+ clearTimeout(timeout);
6027
+ guard === null || guard === void 0 ? void 0 : guard.dispose();
6028
+ }, { capture: true });
6029
+ }
6030
+ _cancelPending() {
6031
+ var _a, _b, _c;
6032
+ if (this._timer !== undefined) {
6033
+ clearTimeout(this._timer);
6034
+ this._timer = undefined;
6035
+ }
6036
+ this._pointerId = undefined;
6037
+ (_a = this._moveListener) === null || _a === void 0 ? void 0 : _a.dispose();
6038
+ (_b = this._upListener) === null || _b === void 0 ? void 0 : _b.dispose();
6039
+ (_c = this._cancelListener) === null || _c === void 0 ? void 0 : _c.dispose();
6040
+ this._moveListener = undefined;
6041
+ this._upListener = undefined;
6042
+ this._cancelListener = undefined;
6043
+ }
6044
+ dispose() {
6045
+ this._cancelPending();
6046
+ super.dispose();
6047
+ }
5255
6048
  }
5256
6049
 
5257
- class TabDragHandler extends DragHandler {
5258
- constructor(element, accessor, group, panel, disabled) {
5259
- super(element, disabled);
5260
- this.accessor = accessor;
5261
- this.group = group;
5262
- this.panel = panel;
5263
- this.panelTransfer = LocalSelectionTransfer.getInstance();
6050
+ function resolveDndCapabilities(options) {
6051
+ if (options.disableDnd) {
6052
+ return { html5: false, pointer: false, pointerHandlesMouse: false };
6053
+ }
6054
+ switch (options.dndStrategy) {
6055
+ case 'pointer':
6056
+ return { html5: false, pointer: true, pointerHandlesMouse: true };
6057
+ case 'html5':
6058
+ return { html5: true, pointer: false, pointerHandlesMouse: false };
6059
+ case 'auto':
6060
+ case undefined:
6061
+ default:
6062
+ // On touch-primary devices (phones / basic tablets) HTML5 DnD's
6063
+ // native long-press intercepts the gesture before our pointer
6064
+ // backend can react — Android Chrome launches a system drag with
6065
+ // its half-transparent thumbnail, and the long-press context menu
6066
+ // never opens. Disable HTML5 there so the pointer backend owns
6067
+ // every gesture. Hybrid devices (touchscreen laptops, Surface,
6068
+ // iPad with mouse) keep both backends — mouse uses HTML5, touch
6069
+ // falls back to whichever backend the underlying element wired.
6070
+ return isCoarsePrimaryInput$2()
6071
+ ? { html5: false, pointer: true, pointerHandlesMouse: true }
6072
+ : { html5: true, pointer: true, pointerHandlesMouse: false };
5264
6073
  }
5265
- getData(event) {
5266
- this.panelTransfer.setData([new PanelTransfer(this.accessor.id, this.group.id, this.panel.id)], PanelTransfer.prototype);
5267
- return {
5268
- dispose: () => {
5269
- this.panelTransfer.clearData(PanelTransfer.prototype);
5270
- },
5271
- };
6074
+ }
6075
+ function isCoarsePrimaryInput$2() {
6076
+ if (typeof window === 'undefined' || !window.matchMedia) {
6077
+ return false;
5272
6078
  }
6079
+ // Coarse pointer without any fine pointer = phone-class device. A laptop
6080
+ // touchscreen reports both, and we want HTML5 to remain available there
6081
+ // because a real mouse is also plugged in.
6082
+ const coarse = window.matchMedia('(pointer: coarse)').matches;
6083
+ const fine = window.matchMedia('(pointer: fine)').matches;
6084
+ return coarse && !fine;
5273
6085
  }
6086
+
5274
6087
  class Tab extends CompositeDisposable {
5275
6088
  get element() {
5276
6089
  return this._element;
@@ -5281,6 +6094,7 @@
5281
6094
  this.accessor = accessor;
5282
6095
  this.group = group;
5283
6096
  this.content = undefined;
6097
+ this.panelTransfer = LocalSelectionTransfer.getInstance();
5284
6098
  this._direction = 'horizontal';
5285
6099
  this._onPointDown = new Emitter();
5286
6100
  this.onPointerDown = this._onPointDown.event;
@@ -5292,114 +6106,106 @@
5292
6106
  this.onDragStart = this._onDragStart.event;
5293
6107
  this._onDragEnd = new Emitter();
5294
6108
  this.onDragEnd = this._onDragEnd.event;
6109
+ const caps = resolveDndCapabilities(this.accessor.options);
5295
6110
  this._element = document.createElement('div');
5296
6111
  this._element.className = 'dv-tab';
5297
6112
  this._element.tabIndex = 0;
5298
- this._element.draggable = !this.accessor.options.disableDnd;
6113
+ this._element.draggable = caps.html5;
5299
6114
  toggleClass(this.element, 'dv-inactive-tab', true);
5300
- this.dragHandler = new TabDragHandler(this._element, this.accessor, this.group, this.panel, !!this.accessor.options.disableDnd);
5301
- this.dropTarget = new Droptarget(this._element, {
6115
+ const canDisplayOverlay = (event, position) => {
6116
+ var _a;
6117
+ if (this.group.locked) {
6118
+ return false;
6119
+ }
6120
+ const data = getPanelData();
6121
+ if (data && this.accessor.id === data.viewId) {
6122
+ // Smooth-reorder takes over the in-flight visual when active,
6123
+ // so individual tab overlays are suppressed for internal drags.
6124
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
6125
+ return false;
6126
+ }
6127
+ return true;
6128
+ }
6129
+ return this.group.model.canDisplayOverlay(event, position, 'tab');
6130
+ };
6131
+ this.dropTarget = html5Backend.createDropTarget(this._element, {
5302
6132
  acceptedTargetZones: ['left', 'right'],
5303
6133
  overlayModel: this._buildOverlayModel(),
5304
- canDisplayOverlay: (event, position) => {
6134
+ canDisplayOverlay,
6135
+ getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
6136
+ });
6137
+ this.pointerDropTarget = pointerBackend.createDropTarget(this._element, {
6138
+ acceptedTargetZones: ['left', 'right'],
6139
+ overlayModel: this._buildOverlayModel(),
6140
+ canDisplayOverlay,
6141
+ getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
6142
+ });
6143
+ const sharedDragOptions = {
6144
+ getData: () => {
6145
+ this.panelTransfer.setData([
6146
+ new PanelTransfer(this.accessor.id, this.group.id, this.panel.id),
6147
+ ], PanelTransfer.prototype);
6148
+ return {
6149
+ dispose: () => {
6150
+ this.panelTransfer.clearData(PanelTransfer.prototype);
6151
+ },
6152
+ };
6153
+ },
6154
+ // 30/-10 matches the HTML5 setDragImage offset that has been
6155
+ // shipped for years; pointer backend wraps in PointerGhost,
6156
+ // HTML5 backend feeds into setDragImage.
6157
+ createGhost: () => ({
6158
+ element: this._buildGhostElement(),
6159
+ offsetX: 30,
6160
+ offsetY: -10,
6161
+ }),
6162
+ onDragStart: (event) => {
5305
6163
  var _a;
5306
- if (this.group.locked) {
5307
- return false;
5308
- }
5309
- const data = getPanelData();
5310
- if (data && this.accessor.id === data.viewId) {
5311
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
5312
- // When smooth reorder is enabled, the Tabs
5313
- // container handles all intra-accessor drops
5314
- // (both same-group and cross-group) via
5315
- // animation. Suppress the per-tab overlay so
5316
- // the tab is dropped *beside* rather than *on*.
5317
- return false;
5318
- }
5319
- return true;
6164
+ this._onDragStart.fire(event);
6165
+ if (!(event instanceof PointerEvent) &&
6166
+ ((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
6167
+ // Delay collapse to next frame so the browser
6168
+ // captures the full drag image first.
6169
+ requestAnimationFrame(() => {
6170
+ toggleClass(this.element, 'dv-tab--dragging', true);
6171
+ });
5320
6172
  }
5321
- return this.group.model.canDisplayOverlay(event, position, 'tab');
5322
6173
  },
5323
- getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
5324
- });
5325
- this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
6174
+ onDragEnd: (event) => {
6175
+ this._onDragEnd.fire(event);
6176
+ },
6177
+ };
6178
+ this.html5DragSource = html5Backend.createDragSource(this._element, Object.assign(Object.assign({}, sharedDragOptions), { disabled: !caps.html5 }));
6179
+ this.pointerDragSource = pointerBackend.createDragSource(this._element, Object.assign(Object.assign({}, sharedDragOptions), { disabled: !caps.pointer, touchOnly: !caps.pointerHandlesMouse, isCancelled: () => !resolveDndCapabilities(this.accessor.options).pointer }));
6180
+ // Both droptargets feed the same downstream stream; consumers don't
6181
+ // need to know which path produced the overlay.
6182
+ this.onWillShowOverlay = exports.DockviewEvent.any(this.dropTarget.onWillShowOverlay, this.pointerDropTarget.onWillShowOverlay);
5326
6183
  this.addDisposables(this._onPointDown, this._onTabClick, this._onDropped, this._onDragStart, this._onDragEnd, this.accessor.onDidOptionsChange(() => {
5327
- this.dropTarget.setOverlayModel(this._buildOverlayModel());
5328
- }), this.dragHandler.onDragStart((event) => {
5329
- var _a;
5330
- if (event.dataTransfer) {
5331
- const style = getComputedStyle(this.element);
5332
- const newNode = this.element.cloneNode(true);
5333
- const isVertical = this._direction === 'vertical';
5334
- /**
5335
- * Properties to skip when copying computed styles for a
5336
- * vertical tab ghost. `writing-mode` is excluded so we
5337
- * can force `horizontal-tb`. Size and margin logical
5338
- * properties are excluded because their physical meaning
5339
- * flips when writing-mode changes, which would produce
5340
- * incorrect dimensions.
5341
- */
5342
- const verticalSkip = new Set([
5343
- 'writing-mode',
5344
- 'inline-size',
5345
- 'block-size',
5346
- 'min-inline-size',
5347
- 'min-block-size',
5348
- 'max-inline-size',
5349
- 'max-block-size',
5350
- 'margin-inline',
5351
- 'margin-inline-start',
5352
- 'margin-inline-end',
5353
- 'margin-block',
5354
- 'margin-block-start',
5355
- 'margin-block-end',
5356
- 'padding-inline',
5357
- 'padding-inline-start',
5358
- 'padding-inline-end',
5359
- 'padding-block',
5360
- 'padding-block-start',
5361
- 'padding-block-end',
5362
- ]);
5363
- Array.from(style).forEach((key) => {
5364
- if (isVertical && verticalSkip.has(key)) {
5365
- return;
5366
- }
5367
- newNode.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key));
5368
- });
5369
- if (isVertical) {
5370
- // Force horizontal text flow and swap the physical
5371
- // dimensions so the ghost appears as a horizontal tab.
5372
- newNode.style.setProperty('writing-mode', 'horizontal-tb');
5373
- newNode.style.setProperty('width', style.height);
5374
- newNode.style.setProperty('height', style.width);
5375
- }
5376
- newNode.style.position = 'absolute';
5377
- newNode.classList.add('dv-tab-ghost-drag');
5378
- addGhostImage(event.dataTransfer, newNode, {
5379
- y: -10,
5380
- x: 30,
5381
- });
5382
- }
5383
- this._onDragStart.fire(event);
5384
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
5385
- // Delay collapse to next frame so the browser
5386
- // captures the full drag image first
5387
- requestAnimationFrame(() => {
5388
- toggleClass(this.element, 'dv-tab--dragging', true);
5389
- });
5390
- }
5391
- }), addDisposableListener(this._element, 'dragend', (event) => {
6184
+ const model = this._buildOverlayModel();
6185
+ this.dropTarget.setOverlayModel(model);
6186
+ this.pointerDropTarget.setOverlayModel(model);
6187
+ }), addDisposableListener(this._element, 'dragend', () => {
6188
+ // The shared onDragEnd handler already fires _onDragEnd via
6189
+ // the HTML5 backend; just strip the dragging class here.
5392
6190
  toggleClass(this.element, 'dv-tab--dragging', false);
5393
- this._onDragEnd.fire(event);
5394
- }), this.dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => {
6191
+ }), this.html5DragSource, addDisposableListener(this._element, 'pointerdown', (event) => {
5395
6192
  this._onPointDown.fire(event);
5396
6193
  }), addDisposableListener(this._element, 'click', (event) => {
5397
6194
  this._onTabClick.fire(event);
5398
6195
  }), addDisposableListener(this._element, 'contextmenu', (event) => {
5399
6196
  this.accessor.contextMenuController.show(this.panel, this.group, event);
6197
+ }), new LongPressDetector(this._element, {
6198
+ onLongPress: (event) => {
6199
+ // Don't let a subsequent finger move arm a drag on top
6200
+ // of the just-opened menu.
6201
+ this.pointerDragSource.cancelPending();
6202
+ this.accessor.contextMenuController.show(this.panel, this.group, event);
6203
+ },
5400
6204
  }), this.dropTarget.onDrop((event) => {
5401
6205
  this._onDropped.fire(event);
5402
- }), this.dropTarget);
6206
+ }), this.pointerDropTarget.onDrop((event) => {
6207
+ this._onDropped.fire(event);
6208
+ }), this.dropTarget, this.pointerDropTarget, this.pointerDragSource);
5403
6209
  }
5404
6210
  setActive(isActive) {
5405
6211
  toggleClass(this.element, 'dv-active-tab', isActive);
@@ -5429,11 +6235,60 @@
5429
6235
  }
5430
6236
  setDirection(direction) {
5431
6237
  this._direction = direction;
5432
- this.dropTarget.setTargetZones(direction === 'vertical' ? ['top', 'bottom'] : ['left', 'right']);
6238
+ const zones = direction === 'vertical' ? ['top', 'bottom'] : ['left', 'right'];
6239
+ this.dropTarget.setTargetZones(zones);
6240
+ this.pointerDropTarget.setTargetZones(zones);
5433
6241
  }
5434
6242
  updateDragAndDropState() {
5435
- this._element.draggable = !this.accessor.options.disableDnd;
5436
- this.dragHandler.setDisabled(!!this.accessor.options.disableDnd);
6243
+ const caps = resolveDndCapabilities(this.accessor.options);
6244
+ this._element.draggable = caps.html5;
6245
+ this.html5DragSource.setDisabled(!caps.html5);
6246
+ this.pointerDragSource.setDisabled(!caps.pointer);
6247
+ this.pointerDragSource.setTouchOnly(!caps.pointerHandlesMouse);
6248
+ }
6249
+ /**
6250
+ * Vertical tabs are flipped to horizontal so the ghost stays readable
6251
+ * during the drag rather than appearing sideways-rotated.
6252
+ */
6253
+ _buildGhostElement() {
6254
+ const style = getComputedStyle(this.element);
6255
+ const newNode = this.element.cloneNode(true);
6256
+ const isVertical = this._direction === 'vertical';
6257
+ const verticalSkip = new Set([
6258
+ 'writing-mode',
6259
+ 'inline-size',
6260
+ 'block-size',
6261
+ 'min-inline-size',
6262
+ 'min-block-size',
6263
+ 'max-inline-size',
6264
+ 'max-block-size',
6265
+ 'margin-inline',
6266
+ 'margin-inline-start',
6267
+ 'margin-inline-end',
6268
+ 'margin-block',
6269
+ 'margin-block-start',
6270
+ 'margin-block-end',
6271
+ 'padding-inline',
6272
+ 'padding-inline-start',
6273
+ 'padding-inline-end',
6274
+ 'padding-block',
6275
+ 'padding-block-start',
6276
+ 'padding-block-end',
6277
+ ]);
6278
+ Array.from(style).forEach((key) => {
6279
+ if (isVertical && verticalSkip.has(key)) {
6280
+ return;
6281
+ }
6282
+ newNode.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key));
6283
+ });
6284
+ if (isVertical) {
6285
+ newNode.style.setProperty('writing-mode', 'horizontal-tb');
6286
+ newNode.style.setProperty('width', style.height);
6287
+ newNode.style.setProperty('height', style.width);
6288
+ }
6289
+ newNode.style.position = 'absolute';
6290
+ newNode.classList.add('dv-tab-ghost-drag');
6291
+ return newNode;
5437
6292
  }
5438
6293
  }
5439
6294
 
@@ -5441,6 +6296,7 @@
5441
6296
  get kind() {
5442
6297
  return this.options.kind;
5443
6298
  }
6299
+ /** Narrow with `instanceof DragEvent` before reading `dataTransfer`. */
5444
6300
  get nativeEvent() {
5445
6301
  return this.event.nativeEvent;
5446
6302
  }
@@ -5471,131 +6327,202 @@
5471
6327
  }
5472
6328
  }
5473
6329
 
5474
- class GroupDragHandler extends DragHandler {
5475
- constructor(element, accessor, group, disabled) {
5476
- super(element, disabled);
5477
- this.accessor = accessor;
5478
- this.group = group;
5479
- this.panelTransfer = LocalSelectionTransfer.getInstance();
5480
- this.addDisposables(addDisposableListener(element, 'pointerdown', (e) => {
5481
- if (e.shiftKey) {
5482
- /**
5483
- * You cannot call e.preventDefault() because that will prevent drag events from firing
5484
- * but we also need to stop any group overlay drag events from occuring
5485
- * Use a custom event marker that can be checked by the overlay drag events
5486
- */
5487
- quasiPreventDefault(e);
5488
- }
5489
- }, true));
5490
- }
5491
- isCancelled(_event) {
5492
- if (this.group.api.location.type === 'floating' && !_event.shiftKey) {
5493
- return true;
5494
- }
5495
- if (this.group.api.location.type === 'edge' && this.group.size === 0) {
5496
- return true;
5497
- }
5498
- return false;
5499
- }
5500
- getData(dragEvent) {
5501
- const dataTransfer = dragEvent.dataTransfer;
5502
- this.panelTransfer.setData([new PanelTransfer(this.accessor.id, this.group.id, null)], PanelTransfer.prototype);
5503
- const style = window.getComputedStyle(this.el);
5504
- const bgColor = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-background-color');
5505
- const color = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-color');
5506
- if (dataTransfer) {
5507
- const createGhost = this.accessor.options.createGroupDragGhostComponent;
5508
- let ghostElement;
5509
- let customRenderer;
5510
- if (createGhost) {
5511
- customRenderer = createGhost(this.group);
5512
- customRenderer.init({
5513
- group: this.group,
5514
- api: this.accessor.api,
5515
- });
5516
- ghostElement = customRenderer.element;
5517
- ghostElement.style.position = 'absolute';
5518
- ghostElement.style.pointerEvents = 'none';
5519
- ghostElement.style.top = '-9999px';
5520
- }
5521
- else {
5522
- ghostElement = document.createElement('div');
5523
- ghostElement.style.backgroundColor = bgColor;
5524
- ghostElement.style.color = color;
5525
- ghostElement.style.padding = '2px 8px';
5526
- ghostElement.style.height = '24px';
5527
- ghostElement.style.fontSize = '11px';
5528
- ghostElement.style.lineHeight = '20px';
5529
- ghostElement.style.borderRadius = '12px';
5530
- ghostElement.style.position = 'absolute';
5531
- ghostElement.style.pointerEvents = 'none';
5532
- ghostElement.style.top = '-9999px';
5533
- ghostElement.textContent = `Multiple Panels (${this.group.size})`;
5534
- }
5535
- addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 });
5536
- if (customRenderer === null || customRenderer === void 0 ? void 0 : customRenderer.dispose) {
5537
- // addGhostImage removes the element from the DOM on the next
5538
- // tick; dispose the framework renderer on the same schedule.
5539
- const renderer = customRenderer;
5540
- setTimeout(() => { var _a; return (_a = renderer.dispose) === null || _a === void 0 ? void 0 : _a.call(renderer); }, 0);
5541
- }
5542
- }
5543
- return {
5544
- dispose: () => {
5545
- this.panelTransfer.clearData(PanelTransfer.prototype);
5546
- },
5547
- };
5548
- }
5549
- }
5550
-
6330
+ // Floating-group redock via touch: require a deliberate long press so the
6331
+ // "move the float around" gesture doesn't double-trigger the redock ghost.
6332
+ // Infinity pressTolerance disables the pre-arm flick override; any motion
6333
+ // during the wait is treated as drag-the-float, not redock intent.
6334
+ const FLOATING_REDOCK_INITIATION_DELAY_MS = 500;
5551
6335
  class VoidContainer extends CompositeDisposable {
5552
6336
  get element() {
5553
6337
  return this._element;
5554
6338
  }
5555
6339
  constructor(accessor, group) {
6340
+ var _a, _b;
5556
6341
  super();
5557
6342
  this.accessor = accessor;
5558
6343
  this.group = group;
6344
+ this.panelTransfer = LocalSelectionTransfer.getInstance();
5559
6345
  this._onDrop = new Emitter();
5560
6346
  this.onDrop = this._onDrop.event;
5561
6347
  this._onDragStart = new Emitter();
5562
6348
  this.onDragStart = this._onDragStart.event;
6349
+ const caps = resolveDndCapabilities(this.accessor.options);
5563
6350
  this._element = document.createElement('div');
5564
6351
  this._element.className = 'dv-void-container';
5565
- this._element.draggable = !this.accessor.options.disableDnd;
5566
- toggleClass(this._element, 'dv-draggable', !this.accessor.options.disableDnd);
6352
+ this._element.draggable = caps.html5;
6353
+ toggleClass(this._element, 'dv-draggable', caps.html5 || caps.pointer);
5567
6354
  this.addDisposables(this._onDrop, this._onDragStart, addDisposableListener(this._element, 'pointerdown', () => {
5568
6355
  this.accessor.doSetGroupActive(this.group);
5569
- }));
5570
- this.handler = new GroupDragHandler(this._element, accessor, group, !!this.accessor.options.disableDnd);
5571
- this.dropTarget = new Droptarget(this._element, {
6356
+ }),
6357
+ // Shift+pointerdown marks the event so the group's overlay
6358
+ // drag (move-by-floating) sees it was consumed and doesn't
6359
+ // fire alongside the HTML5 drag. quasiPreventDefault sets the
6360
+ // marker without calling preventDefault — that would also
6361
+ // block dragstart, which we need to fire.
6362
+ addDisposableListener(this._element, 'pointerdown', (e) => {
6363
+ if (e.shiftKey) {
6364
+ quasiPreventDefault(e);
6365
+ }
6366
+ }, true));
6367
+ const canDisplayOverlay = (event, position) => {
6368
+ if (this.group.api.locked) {
6369
+ // Dropping on the void/header space adds the panel
6370
+ // to this group, which `locked` is meant to prevent
6371
+ // (both `true` and `'no-drop-target'`).
6372
+ return false;
6373
+ }
6374
+ const data = getPanelData();
6375
+ if (data && this.accessor.id === data.viewId) {
6376
+ return true;
6377
+ }
6378
+ return group.model.canDisplayOverlay(event, position, 'header_space');
6379
+ };
6380
+ this.dropTarget = html5Backend.createDropTarget(this._element, {
5572
6381
  acceptedTargetZones: ['center'],
5573
- canDisplayOverlay: (event, position) => {
5574
- if (this.group.api.locked) {
5575
- // Dropping on the void/header space adds the panel
5576
- // to this group, which `locked` is meant to prevent
5577
- // (both `true` and `'no-drop-target'`).
5578
- return false;
6382
+ canDisplayOverlay,
6383
+ getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
6384
+ });
6385
+ this.pointerDropTarget = pointerBackend.createDropTarget(this._element, {
6386
+ acceptedTargetZones: ['center'],
6387
+ canDisplayOverlay,
6388
+ getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
6389
+ });
6390
+ const buildMultiPanelsGhost = () => {
6391
+ const ghostEl = document.createElement('div');
6392
+ const style = window.getComputedStyle(this._element);
6393
+ const bgColor = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-background-color');
6394
+ const color = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-color');
6395
+ ghostEl.style.backgroundColor = bgColor;
6396
+ ghostEl.style.color = color;
6397
+ ghostEl.style.padding = '2px 8px';
6398
+ ghostEl.style.height = '24px';
6399
+ ghostEl.style.fontSize = '11px';
6400
+ ghostEl.style.lineHeight = '20px';
6401
+ ghostEl.style.borderRadius = '12px';
6402
+ ghostEl.style.whiteSpace = 'nowrap';
6403
+ ghostEl.style.boxSizing = 'border-box';
6404
+ // HTML5 setDragImage snapshots the element as appended to the
6405
+ // document; a default block-level div would stretch to the
6406
+ // body's width and render as a viewport-wide bar.
6407
+ ghostEl.style.display = 'inline-block';
6408
+ ghostEl.textContent = `Multiple Panels (${this.group.size})`;
6409
+ return ghostEl;
6410
+ };
6411
+ const buildGhostSpec = () => {
6412
+ const createGhost = this.accessor.options.createGroupDragGhostComponent;
6413
+ if (createGhost) {
6414
+ const renderer = createGhost(this.group);
6415
+ renderer.init({
6416
+ group: this.group,
6417
+ api: this.accessor.api,
6418
+ });
6419
+ return {
6420
+ element: renderer.element,
6421
+ offsetX: 30,
6422
+ offsetY: -10,
6423
+ dispose: renderer.dispose
6424
+ ? () => { var _a; return (_a = renderer.dispose) === null || _a === void 0 ? void 0 : _a.call(renderer); }
6425
+ : undefined,
6426
+ };
6427
+ }
6428
+ return {
6429
+ element: buildMultiPanelsGhost(),
6430
+ offsetX: 30,
6431
+ offsetY: -10,
6432
+ };
6433
+ };
6434
+ const sharedDragOptions = {
6435
+ getData: () => {
6436
+ this.panelTransfer.setData([new PanelTransfer(this.accessor.id, this.group.id, null)], PanelTransfer.prototype);
6437
+ return {
6438
+ dispose: () => {
6439
+ this.panelTransfer.clearData(PanelTransfer.prototype);
6440
+ },
6441
+ };
6442
+ },
6443
+ createGhost: buildGhostSpec,
6444
+ onDragStart: (event) => {
6445
+ this._onDragStart.fire(event);
6446
+ },
6447
+ };
6448
+ this.html5DragSource = html5Backend.createDragSource(this._element, Object.assign(Object.assign({}, sharedDragOptions), { disabled: !caps.html5, isCancelled: (event) => {
6449
+ // HTML5: floating groups need shift+drag as the explicit
6450
+ // detach gesture (otherwise click-and-drag conflicts with
6451
+ // moving the floating group itself).
6452
+ if (this.group.api.location.type === 'floating' &&
6453
+ !event.shiftKey) {
6454
+ return true;
5579
6455
  }
5580
- const data = getPanelData();
5581
- if (data && this.accessor.id === data.viewId) {
6456
+ if (this.group.api.location.type === 'edge' &&
6457
+ this.group.size === 0) {
5582
6458
  return true;
5583
6459
  }
5584
- return group.model.canDisplayOverlay(event, position, 'header_space');
5585
- },
5586
- getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
5587
- });
5588
- this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
5589
- this.addDisposables(this.handler, this.handler.onDragStart((event) => {
5590
- this._onDragStart.fire(event);
5591
- }), this.dropTarget.onDrop((event) => {
6460
+ return false;
6461
+ } }));
6462
+ 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'; };
6463
+ this.pointerDragSource = pointerBackend.createDragSource(this._element, Object.assign(Object.assign({}, sharedDragOptions), { disabled: !caps.pointer, touchOnly: !caps.pointerHandlesMouse,
6464
+ // Floating groups share this element with the overlay's
6465
+ // move-the-float drag. Without a longer hold + tolerance
6466
+ // override, both gestures commit simultaneously and the
6467
+ // user sees the float follow their finger *and* a ghost.
6468
+ touchInitiationDelay: () => isFloating() ? FLOATING_REDOCK_INITIATION_DELAY_MS : 250, pressTolerance: () => (isFloating() ? Infinity : 8), isCancelled: () => {
6469
+ if (!resolveDndCapabilities(this.accessor.options).pointer) {
6470
+ return true;
6471
+ }
6472
+ // Pointer: long-press IS the deliberate gesture, so
6473
+ // floating groups don't need the shift gate.
6474
+ if (this.group.api.location.type === 'edge' &&
6475
+ this.group.size === 0) {
6476
+ return true;
6477
+ }
6478
+ return false;
6479
+ }, onDragStart: (event) => {
6480
+ var _a;
6481
+ // Redock just committed — abort any in-flight overlay
6482
+ // move so the float stops following the finger while
6483
+ // the ghost takes over.
6484
+ (_a = this.getFloatingOverlay()) === null || _a === void 0 ? void 0 : _a.cancelPendingDrag();
6485
+ this._onDragStart.fire(event);
6486
+ } }));
6487
+ // Mirror direction: once the overlay's move-the-float gesture has
6488
+ // actually moved something, cancel the pending redock arm so the
6489
+ // ghost doesn't appear mid-drag if the user holds past 500ms.
6490
+ const overlayMoveSub = new MutableDisposable();
6491
+ const refreshOverlayMoveSub = () => {
6492
+ const overlay = this.getFloatingOverlay();
6493
+ overlayMoveSub.value = overlay
6494
+ ? overlay.onDidStartMoving(() => {
6495
+ this.pointerDragSource.cancelPending();
6496
+ })
6497
+ : exports.DockviewDisposable.NONE;
6498
+ };
6499
+ refreshOverlayMoveSub();
6500
+ this.addDisposables(overlayMoveSub);
6501
+ const locationChange = (_b = (_a = this.group) === null || _a === void 0 ? void 0 : _a.api) === null || _b === void 0 ? void 0 : _b.onDidLocationChange;
6502
+ if (locationChange) {
6503
+ this.addDisposables(locationChange(refreshOverlayMoveSub));
6504
+ }
6505
+ this.onWillShowOverlay = exports.DockviewEvent.any(this.dropTarget.onWillShowOverlay, this.pointerDropTarget.onWillShowOverlay);
6506
+ this.addDisposables(this.html5DragSource, this.dropTarget.onDrop((event) => {
6507
+ this._onDrop.fire(event);
6508
+ }), this.pointerDropTarget.onDrop((event) => {
5592
6509
  this._onDrop.fire(event);
5593
- }), this.dropTarget);
6510
+ }), this.dropTarget, this.pointerDropTarget, this.pointerDragSource);
5594
6511
  }
5595
6512
  updateDragAndDropState() {
5596
- this._element.draggable = !this.accessor.options.disableDnd;
5597
- toggleClass(this._element, 'dv-draggable', !this.accessor.options.disableDnd);
5598
- this.handler.setDisabled(!!this.accessor.options.disableDnd);
6513
+ const caps = resolveDndCapabilities(this.accessor.options);
6514
+ this._element.draggable = caps.html5;
6515
+ toggleClass(this._element, 'dv-draggable', caps.html5 || caps.pointer);
6516
+ this.html5DragSource.setDisabled(!caps.html5);
6517
+ this.pointerDragSource.setDisabled(!caps.pointer);
6518
+ this.pointerDragSource.setTouchOnly(!caps.pointerHandlesMouse);
6519
+ }
6520
+ getFloatingOverlay() {
6521
+ var _a, _b;
6522
+ if (!this.group) {
6523
+ return undefined;
6524
+ }
6525
+ 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;
5599
6526
  }
5600
6527
  }
5601
6528
 
@@ -5851,6 +6778,14 @@
5851
6778
  return (palette !== null && palette !== void 0 ? palette : getFallbackPalette()).resolveValue(color);
5852
6779
  }
5853
6780
 
6781
+ /**
6782
+ * Visual chip for a tab group. Owns the DOM element, label, click /
6783
+ * context-menu interactions, and exposes a long-press gesture as a
6784
+ * second `onContextMenu` source. Drag-and-drop wiring lives in
6785
+ * `TabGroupManager` — the manager constructs the drag sources on this
6786
+ * chip's element so it can include tabs-list context (custom group
6787
+ * drag image, tab-group transfer payload).
6788
+ */
5854
6789
  class TabGroupChip extends CompositeDisposable {
5855
6790
  get element() {
5856
6791
  return this._element;
@@ -5861,22 +6796,22 @@
5861
6796
  this._onClick = new Emitter();
5862
6797
  this.onClick = this._onClick.event;
5863
6798
  this._onContextMenu = new Emitter();
6799
+ /** Fires on right-click and on touch long-press. */
5864
6800
  this.onContextMenu = this._onContextMenu.event;
5865
- this._onDragStart = new Emitter();
5866
- this.onDragStart = this._onDragStart.event;
5867
6801
  this._element = document.createElement('div');
5868
6802
  this._element.className = 'dv-tab-group-chip';
5869
6803
  this._element.tabIndex = 0;
5870
- this._element.draggable = true;
5871
6804
  this._label = document.createElement('span');
5872
6805
  this._label.className = 'dv-tab-group-chip-label';
5873
6806
  this._element.appendChild(this._label);
5874
- this.addDisposables(this._onClick, this._onContextMenu, this._onDragStart, addDisposableListener(this._element, 'click', (event) => {
6807
+ this.addDisposables(this._onClick, this._onContextMenu, new LongPressDetector(this._element, {
6808
+ onLongPress: (event) => {
6809
+ this._onContextMenu.fire(event);
6810
+ },
6811
+ }), addDisposableListener(this._element, 'click', (event) => {
5875
6812
  this._onClick.fire(event);
5876
6813
  }), addDisposableListener(this._element, 'contextmenu', (event) => {
5877
6814
  this._onContextMenu.fire(event);
5878
- }), addDisposableListener(this._element, 'dragstart', (event) => {
5879
- this._onDragStart.fire(event);
5880
6815
  }));
5881
6816
  }
5882
6817
  init(params) {
@@ -6340,6 +7275,12 @@
6340
7275
  this._positionChipForGroup(tabGroup);
6341
7276
  }
6342
7277
  }
7278
+ updateDirection() {
7279
+ const isVertical = this._ctx.getDirection() === 'vertical';
7280
+ for (const [, entry] of this._chipRenderers) {
7281
+ entry.dropTarget.setTargetZones(isVertical ? ['top'] : ['left']);
7282
+ }
7283
+ }
6343
7284
  snapshotChipWidths() {
6344
7285
  const widths = new Map();
6345
7286
  for (const [groupId, entry] of this._chipRenderers) {
@@ -6433,6 +7374,45 @@
6433
7374
  (_a = this._pendingTransitionCleanups.get(panelId)) === null || _a === void 0 ? void 0 : _a();
6434
7375
  this._pendingTransitionCleanups.delete(panelId);
6435
7376
  }
7377
+ updateDragAndDropState() {
7378
+ const caps = resolveDndCapabilities(this._ctx.accessor.options);
7379
+ for (const entry of this._chipRenderers.values()) {
7380
+ entry.chip.element.draggable = caps.html5;
7381
+ entry.html5DragSource.setDisabled(!caps.html5);
7382
+ entry.pointerDragSource.setDisabled(!caps.pointer);
7383
+ entry.pointerDragSource.setTouchOnly(!caps.pointerHandlesMouse);
7384
+ }
7385
+ }
7386
+ /**
7387
+ * Synchronously dispose the chip drag sources for an in-flight chip
7388
+ * drag. Called from `_commitGroupMove` so the transfer payload +
7389
+ * iframe shield are released BEFORE the cross-group move detaches
7390
+ * the chip (chip dispose is scheduled on a microtask via
7391
+ * `_scheduleTabGroupUpdate`, which is too late for callers that read
7392
+ * `getPanelData()` synchronously after the move). Idempotent — the
7393
+ * subsequent `update()` will also dispose the sources.
7394
+ */
7395
+ disposeChipDrag(tabGroupId) {
7396
+ var _a, _b;
7397
+ const entry = this._chipRenderers.get(tabGroupId);
7398
+ if (!entry) {
7399
+ return;
7400
+ }
7401
+ // Optional-chained because tests may inject minimal entries
7402
+ // that skip the manager's normal `_ensureChipForGroup` flow.
7403
+ (_a = entry.html5DragSource) === null || _a === void 0 ? void 0 : _a.dispose();
7404
+ (_b = entry.pointerDragSource) === null || _b === void 0 ? void 0 : _b.dispose();
7405
+ }
7406
+ /** Cloned chip rect used as the pointer follow-finger ghost. */
7407
+ _buildChipGhostElement(chipEl) {
7408
+ const style = getComputedStyle(chipEl);
7409
+ const clone = chipEl.cloneNode(true);
7410
+ Array.from(style).forEach((key) => {
7411
+ clone.style.setProperty(key, style.getPropertyValue(key), style.getPropertyPriority(key));
7412
+ });
7413
+ clone.style.position = 'absolute';
7414
+ return clone;
7415
+ }
6436
7416
  disposeAll() {
6437
7417
  var _a;
6438
7418
  (_a = this._indicator) === null || _a === void 0 ? void 0 : _a.dispose();
@@ -6478,6 +7458,74 @@
6478
7458
  ? createChip(tabGroup)
6479
7459
  : new TabGroupChip(this._ctx.accessor.tabGroupColorPalette);
6480
7460
  chip.init({ tabGroup, api: this._ctx.accessor.api });
7461
+ const caps = resolveDndCapabilities(this._ctx.accessor.options);
7462
+ chip.element.draggable = caps.html5;
7463
+ const panelTransfer = LocalSelectionTransfer.getInstance();
7464
+ // Shared `getData` for both backends. Sets a group-level
7465
+ // PanelTransfer (panelId=null, tabGroupId identifies the group).
7466
+ // The returned disposer clears it on drag end.
7467
+ const getData = () => {
7468
+ panelTransfer.setData([
7469
+ new PanelTransfer(this._ctx.accessor.id, this._ctx.group.id, null, tabGroup.id),
7470
+ ], PanelTransfer.prototype);
7471
+ return {
7472
+ dispose: () => {
7473
+ panelTransfer.clearData(PanelTransfer.prototype);
7474
+ },
7475
+ };
7476
+ };
7477
+ // The chip's HTML5 drag image is the cloned tabs list (chip only),
7478
+ // mounted inside the dockview root for CSS-variable inheritance and
7479
+ // positioned against the chip's in-place rect. Layout-dependent
7480
+ // offset means we set the drag image directly in `onDragStart`
7481
+ // (inside the dragstart handler) rather than via the generic
7482
+ // `createGhost` factory, which only knows about ghost specs that
7483
+ // can be appended to `document.body`.
7484
+ const html5DragSource = html5Backend.createDragSource(chip.element, {
7485
+ getData,
7486
+ disabled: !caps.html5,
7487
+ isCancelled: () => !resolveDndCapabilities(this._ctx.accessor.options).html5,
7488
+ onDragStart: (event) => {
7489
+ // Type guard via `dataTransfer` — `instanceof DragEvent`
7490
+ // would throw in jsdom which doesn't ship a DragEvent
7491
+ // constructor.
7492
+ if ('dataTransfer' in event && event.dataTransfer) {
7493
+ this.setGroupDragImage(event, tabGroup, chip.element);
7494
+ }
7495
+ this._callbacks.onChipDragStart(tabGroup, chip, event);
7496
+ },
7497
+ onDragEnd: (event) => {
7498
+ var _a, _b;
7499
+ (_b = (_a = this._callbacks).onChipDragEnd) === null || _b === void 0 ? void 0 : _b.call(_a, tabGroup, chip, event);
7500
+ },
7501
+ });
7502
+ // Synchronous panelTransfer cleanup directly on the chip element.
7503
+ // `Html5DragSource`'s dragend defers data disposal via `setTimeout(0)`
7504
+ // so drop handlers can read the payload — but a chip drag that
7505
+ // ends via `moveGroupOrPanel` (no actual drop event) needs the
7506
+ // singleton cleared immediately, otherwise a synchronous
7507
+ // `getPanelData()` after the move still sees the stale chip
7508
+ // payload. Attached directly (not via `addDisposableListener`) so
7509
+ // the listener survives chip disposal in the detach-then-dragend
7510
+ // cross-group path; `once: true` auto-removes after the single
7511
+ // dragend that we care about. (#1254)
7512
+ chip.element.addEventListener('dragend', () => {
7513
+ panelTransfer.clearData(PanelTransfer.prototype);
7514
+ }, { once: true });
7515
+ const pointerDragSource = pointerBackend.createDragSource(chip.element, {
7516
+ getData,
7517
+ disabled: !caps.pointer,
7518
+ touchOnly: !caps.pointerHandlesMouse,
7519
+ isCancelled: () => !resolveDndCapabilities(this._ctx.accessor.options).pointer,
7520
+ createGhost: () => ({
7521
+ element: this._buildChipGhostElement(chip.element),
7522
+ offsetX: 8,
7523
+ offsetY: 8,
7524
+ }),
7525
+ onDragStart: (event) => {
7526
+ this._callbacks.onChipDragStart(tabGroup, chip, event);
7527
+ },
7528
+ });
6481
7529
  const disposables = [
6482
7530
  tabGroup.onDidChange(() => {
6483
7531
  var _a;
@@ -6491,24 +7539,70 @@
6491
7539
  tabGroup.onDidCollapseChange(() => {
6492
7540
  this._updateTabGroupClasses();
6493
7541
  }),
7542
+ html5DragSource,
7543
+ pointerDragSource,
6494
7544
  ];
6495
- // Wire chip context menu and drag for all chip renderers
7545
+ // Context menu: built-in TabGroupChip already aggregates right-click
7546
+ // + touch long-press into `onContextMenu`. Custom chip renderers
7547
+ // don't, so attach a long-press detector and contextmenu listener
7548
+ // directly on their element.
7549
+ const onContextMenu = (event) => {
7550
+ // A long-press on a chip should preempt the in-flight pointer
7551
+ // drag and open the menu instead.
7552
+ pointerDragSource.cancelPending();
7553
+ this._callbacks.onChipContextMenu(tabGroup, event);
7554
+ };
6496
7555
  if (chip instanceof TabGroupChip) {
6497
- disposables.push(chip.onContextMenu((event) => {
6498
- this._callbacks.onChipContextMenu(tabGroup, event);
6499
- }), chip.onDragStart((event) => {
6500
- this._callbacks.onChipDragStart(tabGroup, chip, event);
6501
- }));
7556
+ disposables.push(chip.onContextMenu(onContextMenu));
6502
7557
  }
6503
7558
  else {
6504
- disposables.push(addDisposableListener(chip.element, 'contextmenu', (event) => {
6505
- this._callbacks.onChipContextMenu(tabGroup, event);
6506
- }), addDisposableListener(chip.element, 'dragstart', (event) => {
6507
- this._callbacks.onChipDragStart(tabGroup, chip, event);
6508
- }));
6509
- }
7559
+ disposables.push(new LongPressDetector(chip.element, {
7560
+ onLongPress: onContextMenu,
7561
+ }), addDisposableListener(chip.element, 'contextmenu', onContextMenu));
7562
+ }
7563
+ // The chip sits before its group's first tab in the DOM, so it
7564
+ // covers the "drop before the group" position. Without a drop
7565
+ // target here, dropping a tab over the chip is a dead zone —
7566
+ // particularly visible when the group is first in the tabs list
7567
+ // and there's no preceding tab whose right zone covers position 0.
7568
+ // The smooth animation path already shifts the chip's margin to
7569
+ // open a gap, so suppress the overlay in that mode.
7570
+ const isVertical = this._ctx.getDirection() === 'vertical';
7571
+ const dropTarget = new Droptarget(chip.element, {
7572
+ acceptedTargetZones: isVertical ? ['top'] : ['left'],
7573
+ overlayModel: {
7574
+ activationSize: { value: 100, type: 'percentage' },
7575
+ },
7576
+ canDisplayOverlay: (event, position) => {
7577
+ var _a;
7578
+ if (this._ctx.group.locked) {
7579
+ return false;
7580
+ }
7581
+ if (this._ctx.accessor.options.disableDnd) {
7582
+ return false;
7583
+ }
7584
+ const data = getPanelData();
7585
+ if (data && this._ctx.accessor.id === data.viewId) {
7586
+ if (((_a = this._ctx.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) ===
7587
+ 'smooth') {
7588
+ return false;
7589
+ }
7590
+ return true;
7591
+ }
7592
+ return this._ctx.group.model.canDisplayOverlay(event, position, 'tab');
7593
+ },
7594
+ });
7595
+ disposables.push(dropTarget, dropTarget.onDrop((event) => {
7596
+ this._callbacks.onChipDrop(tabGroup, event);
7597
+ }));
6510
7598
  const disposable = new CompositeDisposable(...disposables);
6511
- this._chipRenderers.set(tabGroup.id, { chip, disposable });
7599
+ this._chipRenderers.set(tabGroup.id, {
7600
+ chip,
7601
+ html5DragSource,
7602
+ pointerDragSource,
7603
+ disposable,
7604
+ dropTarget,
7605
+ });
6512
7606
  // Group is born collapsed (cross-group drop, layout restore, etc.):
6513
7607
  // its tabs are about to be added without the collapsed class. Skip
6514
7608
  // the animation in the upcoming _updateTabGroupClasses call so they
@@ -6767,6 +7861,7 @@
6767
7861
  for (const tab of this._tabs) {
6768
7862
  tab.value.setDirection(value);
6769
7863
  }
7864
+ this._tabGroupManager.updateDirection();
6770
7865
  }
6771
7866
  constructor(group, accessor, options) {
6772
7867
  super();
@@ -6786,7 +7881,7 @@
6786
7881
  this._voidContainer = null;
6787
7882
  this._voidContainerListeners = null;
6788
7883
  this._extendedDropZone = null;
6789
- this._chipDragCleanup = null;
7884
+ this._pointerInsideTabsList = false;
6790
7885
  this._onTabDragStart = new Emitter();
6791
7886
  this.onTabDragStart = this._onTabDragStart.event;
6792
7887
  this._onDrop = new Emitter();
@@ -6821,13 +7916,40 @@
6821
7916
  onChipDragStart: (tabGroup, chip, event) => {
6822
7917
  this._handleChipDragStart(tabGroup, chip, event);
6823
7918
  },
7919
+ onChipDragEnd: () => {
7920
+ // HTML5 chip dragend (incl. cancels). The Html5DragSource
7921
+ // owns the listener on the chip element, so this fires
7922
+ // even if the chip was detached cross-group — the
7923
+ // element keeps its listeners until the source is
7924
+ // disposed. resetDragAnimation is a no-op after a
7925
+ // successful drop (anim state already null) thanks to
7926
+ // the gating inside it.
7927
+ this.resetDragAnimation();
7928
+ },
7929
+ onChipDrop: (tabGroup, event) => {
7930
+ this._handleChipDrop(tabGroup, event);
7931
+ },
6824
7932
  });
6825
7933
  this.addDisposables(this._onOverflowTabsChange, this._observerDisposable, this._onWillShowOverlay, this._onDrop, this._onTabDragStart, {
6826
7934
  dispose: () => {
6827
7935
  var _a;
6828
7936
  (_a = this._flipTransitionCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
6829
7937
  },
6830
- }, addDisposableListener(this.element, 'pointerdown', (event) => {
7938
+ },
7939
+ // Pointer-side cleanup: when any pointer drag ends, tear
7940
+ // down smooth-reorder anim state the dragover bridge may
7941
+ // have installed. The chip's pointer drag source handles
7942
+ // its own transfer payload + iframe-shield cleanup.
7943
+ PointerDragController.getInstance().onDragEnd(() => {
7944
+ this._pointerInsideTabsList = false;
7945
+ this.resetDragAnimation();
7946
+ }),
7947
+ // Pointer-event mirror of the HTML5 dragover / dragleave handlers
7948
+ // below. Drives smooth-reorder for `dndStrategy: 'pointer'` and
7949
+ // for touch drags in `'auto'`.
7950
+ PointerDragController.getInstance().onDragMove((e) => {
7951
+ this._handlePointerDragMove(e.clientX, e.clientY);
7952
+ }), addDisposableListener(this.element, 'pointerdown', (event) => {
6831
7953
  if (event.defaultPrevented) {
6832
7954
  return;
6833
7955
  }
@@ -6835,135 +7957,60 @@
6835
7957
  if (isLeftClick) {
6836
7958
  this.accessor.doSetGroupActive(this.group);
6837
7959
  }
6838
- }), addDisposableListener(this._tabsList, 'dragover', (event) => {
6839
- var _a, _b, _c, _d;
6840
- if (this.accessor.options.disableDnd) {
7960
+ }),
7961
+ // Trackpad / wheel forwarding. The strip scrolls along its own
7962
+ // axis (x for horizontal headers, y for vertical), so deltaY
7963
+ // from a plain mouse wheel maps onto the strip's axis too —
7964
+ // this gives the VS Code-style "scroll over tab bar to page
7965
+ // through tabs" feel. We only consume the event when the strip
7966
+ // is actually overflowing in the direction the user wheeled in,
7967
+ // so a wheel at the edge of a non-overflowing strip still
7968
+ // bubbles up and scrolls the page. `{ passive: false }` is
7969
+ // required because we call preventDefault().
7970
+ addDisposableListener(this._tabsList, 'wheel', (event) => {
7971
+ const isVertical = this._direction === 'vertical';
7972
+ const primary = isVertical
7973
+ ? event.deltaY || event.deltaX
7974
+ : event.deltaX || event.deltaY;
7975
+ if (primary === 0) {
6841
7976
  return;
6842
7977
  }
6843
- // If _animState exists but belongs to a different
6844
- // drag (stale from a previous operation), replace it
6845
- // so the current drag is handled correctly.
6846
- if (this._animState) {
6847
- const data = getPanelData();
6848
- if ((data === null || data === void 0 ? void 0 : data.tabGroupId) &&
6849
- data.groupId !== this.group.id &&
6850
- this._animState.sourceTabGroupId !== data.tabGroupId) {
6851
- this._animState = null;
6852
- }
6853
- }
6854
- if (!this._animState) {
6855
- const data = getPanelData();
6856
- // In default animation mode, individual tab drops
6857
- // are handled by per-tab Droptargets. But tab group
6858
- // chip drags still need tab-list-level handling so
6859
- // that drops on gaps / void space work.
6860
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) ===
6861
- 'default' &&
6862
- !(data === null || data === void 0 ? void 0 : data.tabGroupId)) {
6863
- return;
6864
- }
6865
- if (data &&
6866
- (data.panelId || data.tabGroupId) &&
6867
- data.groupId !== this.group.id) {
6868
- const avgWidth = this.getAverageTabWidth();
6869
- if (data.tabGroupId) {
6870
- // External group drag — look up the
6871
- // source tab group to size the gap
6872
- const sourceGroup = this.accessor.getPanel(data.groupId);
6873
- const sourceTg = sourceGroup === null || sourceGroup === void 0 ? void 0 : sourceGroup.model.getTabGroups().find((tg) => tg.id === data.tabGroupId);
6874
- const panelCount = (_b = sourceTg === null || sourceTg === void 0 ? void 0 : sourceTg.panelIds.length) !== null && _b !== void 0 ? _b : 1;
6875
- const groupGapWidth = avgWidth * panelCount + avgWidth;
6876
- this._animState = {
6877
- sourceTabId: '',
6878
- sourceIndex: -1,
6879
- tabPositions: this.snapshotTabPositions(),
6880
- chipPositions: this._tabGroupManager.snapshotChipWidths(),
6881
- currentInsertionIndex: null,
6882
- targetTabGroupId: null,
6883
- sourceTabGroupId: data.tabGroupId,
6884
- sourceGroupPanelIds: sourceTg
6885
- ? new Set(sourceTg.panelIds)
6886
- : new Set(),
6887
- sourceChipWidth: avgWidth,
6888
- cursorOffsetFromDragLeft: groupGapWidth / 2,
6889
- sourceGapWidth: groupGapWidth,
6890
- containerLeft: this._tabsList.getBoundingClientRect()
6891
- .left,
6892
- };
6893
- }
6894
- else {
6895
- this._animState = {
6896
- sourceTabId: data.panelId,
6897
- sourceIndex: -1,
6898
- tabPositions: this.snapshotTabPositions(),
6899
- chipPositions: this._tabGroupManager.snapshotChipWidths(),
6900
- currentInsertionIndex: null,
6901
- targetTabGroupId: null,
6902
- sourceTabGroupId: null,
6903
- sourceGroupPanelIds: null,
6904
- sourceChipWidth: 0,
6905
- cursorOffsetFromDragLeft: avgWidth / 2,
6906
- sourceGapWidth: avgWidth,
6907
- containerLeft: this._tabsList.getBoundingClientRect()
6908
- .left,
6909
- };
6910
- }
6911
- }
6912
- else {
6913
- return;
6914
- }
6915
- }
6916
- event.preventDefault(); // allow drop to fire on the container
6917
- // For intra-group drag (sourceIndex >= 0) the gap
6918
- // animation is the sole visual indicator — clear any
6919
- // stale anchor overlay that may have been set while the
6920
- // cursor was over the panel content area or another zone.
6921
- // External drags (sourceIndex === -1) leave the overlay
6922
- // to the individual tab Droptargets so cross-group
6923
- // animation is not disrupted.
6924
- if (this._animState.sourceIndex !== -1) {
6925
- (_d = (_c = this.group.model.dropTargetContainer) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.clear();
6926
- }
6927
- this.handleDragOver(event);
6928
- }, true), addDisposableListener(this._tabsList, 'dragleave', (event) => {
6929
- var _a, _b, _c;
6930
- if (!this._animState) {
7978
+ const max = isVertical
7979
+ ? this._tabsList.scrollHeight -
7980
+ this._tabsList.clientHeight
7981
+ : this._tabsList.scrollWidth -
7982
+ this._tabsList.clientWidth;
7983
+ if (max <= 0) {
6931
7984
  return;
6932
7985
  }
6933
- const related = event.relatedTarget;
6934
- // Ignore moves between children of the tabs list
6935
- if (related && this._tabsList.contains(related)) {
7986
+ const current = isVertical
7987
+ ? this._tabsList.scrollTop
7988
+ : this._tabsList.scrollLeft;
7989
+ // At the edge in the wheel direction: let the page
7990
+ // scroll instead of trapping the gesture.
7991
+ if ((primary < 0 && current <= 0) ||
7992
+ (primary > 0 && current >= max)) {
6936
7993
  return;
6937
7994
  }
6938
- // If moving into the broader drop zone (e.g. void container,
6939
- // left actions), keep _animState alive so the external
6940
- // dragover listeners can continue the gap animation.
6941
- if (related && ((_a = this._extendedDropZone) === null || _a === void 0 ? void 0 : _a.contains(related))) {
6942
- this.resetTabTransforms();
6943
- this._animState.currentInsertionIndex = null;
6944
- return;
7995
+ event.preventDefault();
7996
+ // Custom-scrollbar mode wraps the tabs list and installs
7997
+ // its own wheel listener that rewrites scrollLeft from a
7998
+ // deltaY-only tracker. Without stopPropagation that
7999
+ // handler would clobber our deltaX-aware update.
8000
+ event.stopPropagation();
8001
+ if (isVertical) {
8002
+ this._tabsList.scrollTop = current + primary;
6945
8003
  }
6946
- // When leaving toward the void container (empty header space
6947
- // to the right), keep the animation state so the drop can
6948
- // still land at the end position.
6949
- const rt = event.relatedTarget;
6950
- const isVoid = this._voidContainer &&
6951
- rt &&
6952
- (rt === this._voidContainer ||
6953
- this._voidContainer.contains(rt));
6954
- if (isVoid) {
6955
- return;
8004
+ else {
8005
+ this._tabsList.scrollLeft = current + primary;
6956
8006
  }
6957
- this.resetTabTransforms();
6958
- if (this._animState) {
6959
- if (this._animState.sourceIndex === -1) {
6960
- (_c = (_b = this.group.model.dropTargetContainer) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.clear();
6961
- this._animState = null;
6962
- }
6963
- else {
6964
- this._animState.currentInsertionIndex = null;
6965
- }
8007
+ }, { passive: false }), addDisposableListener(this._tabsList, 'dragover', (event) => {
8008
+ if (this._processDragOver(event.clientX)) {
8009
+ // Allow `drop` to fire on the tabs list container.
8010
+ event.preventDefault();
6966
8011
  }
8012
+ }, true), addDisposableListener(this._tabsList, 'dragleave', (event) => {
8013
+ this._processDragLeave(event.relatedTarget);
6967
8014
  }, true), addDisposableListener(this._tabsList, 'dragend', () => {
6968
8015
  this.resetDragAnimation();
6969
8016
  }), addDisposableListener(this._tabsList, 'drop', (event) => {
@@ -7085,6 +8132,9 @@
7085
8132
  const disposable = new CompositeDisposable(tab.onDragStart((event) => {
7086
8133
  var _a;
7087
8134
  this._onTabDragStart.fire({ nativeEvent: event, panel });
8135
+ // Both HTML5 and pointer drags initialize _animState. Cleanup
8136
+ // is wired in both paths: HTML5 via dragend/drop on _tabsList,
8137
+ // pointer via PointerDragController.onDragEnd subscriptions.
7088
8138
  if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
7089
8139
  const tabWidth = tab.element.getBoundingClientRect().width;
7090
8140
  const sourceIndex = this._tabs.findIndex((x) => x.value === tab);
@@ -7344,6 +8394,7 @@
7344
8394
  for (const tab of this._tabs) {
7345
8395
  tab.value.updateDragAndDropState();
7346
8396
  }
8397
+ this._tabGroupManager.updateDragAndDropState();
7347
8398
  }
7348
8399
  /**
7349
8400
  * Synchronize chip elements and CSS classes for all tab groups
@@ -7355,6 +8406,13 @@
7355
8406
  refreshTabGroupAccent() {
7356
8407
  this._tabGroupManager.refreshAccents();
7357
8408
  }
8409
+ /**
8410
+ * Tabs-list-specific side effects of a chip drag start. The chip's
8411
+ * drag sources (constructed by `TabGroupManager`) own the transfer
8412
+ * payload, iframe shielding, dataTransfer setup, and the HTML5 drag
8413
+ * image. This method just sets up the smooth-reorder anim state and
8414
+ * collapses the source-group tabs in the tabs list.
8415
+ */
7358
8416
  _handleChipDragStart(tabGroup, chip, event) {
7359
8417
  var _a;
7360
8418
  const firstPanelId = tabGroup.panelIds[0];
@@ -7385,89 +8443,79 @@
7385
8443
  sourceGapWidth: groupGapWidth,
7386
8444
  containerLeft: this._tabsList.getBoundingClientRect().left,
7387
8445
  };
7388
- // Set LocalSelectionTransfer so drop targets recognise this as
7389
- // an internal dockview drag. panelId is null (group-level),
7390
- // tabGroupId identifies which tab group is being dragged.
7391
- const panelTransfer = LocalSelectionTransfer.getInstance();
7392
- panelTransfer.setData([
7393
- new PanelTransfer(this.accessor.id, this.group.id, null, tabGroup.id),
7394
- ], PanelTransfer.prototype);
7395
- const iframes = disableIframePointEvents();
7396
- // The dragend listener on `_tabsList` is unreachable for chip
7397
- // drags because cross-group drops detach the chip from the DOM
7398
- // before dragend fires (the source tab group becomes empty, so
7399
- // `_positionChipForGroup` removes the chip element). Without
7400
- // bubbling, the tabsList listener never runs and `_animState`,
7401
- // `_chipDragCleanup`, and the dragging CSS classes leak. Listen
7402
- // directly on the chip element so cleanup happens regardless of
7403
- // whether it's still attached. (Issue #1254.)
7404
- const chipElement = chip.element;
7405
- const onChipDragEnd = () => {
7406
- chipElement.removeEventListener('dragend', onChipDragEnd);
7407
- this.resetDragAnimation();
7408
- };
7409
- chipElement.addEventListener('dragend', onChipDragEnd);
7410
- this._chipDragCleanup = {
7411
- dispose: () => {
7412
- chipElement.removeEventListener('dragend', onChipDragEnd);
7413
- panelTransfer.clearData(PanelTransfer.prototype);
7414
- iframes.release();
7415
- },
7416
- };
7417
- if (event.dataTransfer) {
7418
- event.dataTransfer.effectAllowed = 'move';
7419
- if (event.dataTransfer.items.length === 0) {
7420
- event.dataTransfer.setData('text/plain', '');
7421
- }
7422
- }
7423
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
7424
- // Collapse group tabs + chip after the browser
7425
- // captures the drag image, then open the gap at the
7426
- // source position — all instant (no transitions).
7427
- const groupPanelIds = new Set(tabGroup.panelIds);
7428
- this._pendingCollapse = true;
7429
- requestAnimationFrame(() => {
7430
- var _a;
7431
- var _b;
7432
- this._pendingCollapse = false;
7433
- if (!this._animState) {
7434
- return;
7435
- }
7436
- // Collapse all group tabs instantly
7437
- for (const t of this._tabs) {
7438
- if (groupPanelIds.has(t.value.panel.id)) {
7439
- t.value.element.style.transition = 'none';
7440
- toggleClass(t.value.element, 'dv-tab--dragging', true);
7441
- }
7442
- }
7443
- // Collapse the group chip instantly
7444
- const chipEntry = this._tabGroupManager.chipRenderers.get(tabGroup.id);
7445
- if (chipEntry) {
7446
- chipEntry.chip.element.style.transition = 'none';
7447
- toggleClass(chipEntry.chip.element, 'dv-tab-group-chip--dragging', true);
7448
- }
7449
- // Single reflow for the entire batch
7450
- void this._tabsList.offsetHeight;
7451
- const underline = this._tabGroupManager.groupUnderlines.get(tabGroup.id);
7452
- if (underline) {
7453
- underline.style.display = 'none';
7454
- }
7455
- (_a = (_b = this._animState).currentInsertionIndex) !== null && _a !== void 0 ? _a : (_b.currentInsertionIndex = firstIdx);
7456
- // Apply gap with transitions disabled
7457
- this.applyDragOverTransforms(true);
7458
- // Re-enable transitions for subsequent moves
7459
- for (const t of this._tabs) {
7460
- if (groupPanelIds.has(t.value.panel.id)) {
7461
- t.value.element.style.removeProperty('transition');
7462
- }
8446
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) !== 'smooth') {
8447
+ return;
8448
+ }
8449
+ // Collapse group tabs + chip after the browser captures the drag
8450
+ // image, then open the gap at the source position — all instant
8451
+ // (no transitions).
8452
+ const groupPanelIds = new Set(tabGroup.panelIds);
8453
+ this._pendingCollapse = true;
8454
+ requestAnimationFrame(() => {
8455
+ var _a;
8456
+ var _b;
8457
+ this._pendingCollapse = false;
8458
+ if (!this._animState) {
8459
+ return;
8460
+ }
8461
+ // Collapse all group tabs instantly
8462
+ for (const t of this._tabs) {
8463
+ if (groupPanelIds.has(t.value.panel.id)) {
8464
+ t.value.element.style.transition = 'none';
8465
+ toggleClass(t.value.element, 'dv-tab--dragging', true);
7463
8466
  }
7464
- if (chipEntry) {
7465
- chipEntry.chip.element.style.removeProperty('transition');
8467
+ }
8468
+ // Collapse the group chip instantly
8469
+ const chipEntry = this._tabGroupManager.chipRenderers.get(tabGroup.id);
8470
+ if (chipEntry) {
8471
+ chipEntry.chip.element.style.transition = 'none';
8472
+ toggleClass(chipEntry.chip.element, 'dv-tab-group-chip--dragging', true);
8473
+ }
8474
+ // Single reflow for the entire batch
8475
+ void this._tabsList.offsetHeight;
8476
+ const underline = this._tabGroupManager.groupUnderlines.get(tabGroup.id);
8477
+ if (underline) {
8478
+ underline.style.display = 'none';
8479
+ }
8480
+ (_a = (_b = this._animState).currentInsertionIndex) !== null && _a !== void 0 ? _a : (_b.currentInsertionIndex = firstIdx);
8481
+ this.applyDragOverTransforms(true);
8482
+ for (const t of this._tabs) {
8483
+ if (groupPanelIds.has(t.value.panel.id)) {
8484
+ t.value.element.style.removeProperty('transition');
7466
8485
  }
7467
- });
8486
+ }
8487
+ if (chipEntry) {
8488
+ chipEntry.chip.element.style.removeProperty('transition');
8489
+ }
8490
+ });
8491
+ }
8492
+ /**
8493
+ * A drop on a tab group chip means "insert before this group". Resolve to
8494
+ * the index of the group's first tab, adjusting for same-group removal
8495
+ * (when the source tab is currently to the left of the target slot, its
8496
+ * removal shifts the insertion index down by one). Always clears
8497
+ * `targetTabGroupId` so the dropped tab lands outside the group.
8498
+ */
8499
+ _handleChipDrop(tabGroup, event) {
8500
+ const firstPanelId = tabGroup.panelIds[0];
8501
+ if (!firstPanelId) {
8502
+ return;
8503
+ }
8504
+ const insertionIndex = this._tabs.findIndex((x) => x.value.panel.id === firstPanelId);
8505
+ if (insertionIndex === -1) {
8506
+ return;
7468
8507
  }
7469
- // Build a composite drag image showing chip + group tabs
7470
- this._tabGroupManager.setGroupDragImage(event, tabGroup, chip.element);
8508
+ const data = getPanelData();
8509
+ const sourceIndex = data && data.groupId === this.group.id && data.panelId
8510
+ ? this._tabs.findIndex((x) => x.value.panel.id === data.panelId)
8511
+ : -1;
8512
+ const adjustedIndex = insertionIndex -
8513
+ (sourceIndex !== -1 && sourceIndex < insertionIndex ? 1 : 0);
8514
+ this._onDrop.fire({
8515
+ event: event.nativeEvent,
8516
+ index: adjustedIndex,
8517
+ targetTabGroupId: null,
8518
+ });
7471
8519
  }
7472
8520
  /**
7473
8521
  * Sets the broader container that is part of the same logical drop surface
@@ -7529,6 +8577,164 @@
7529
8577
  }
7530
8578
  return total / this._tabs.length;
7531
8579
  }
8580
+ /**
8581
+ * Pointer-event entry point. The HTML5 path enters via the per-element
8582
+ * `dragover` listener; this one hit-tests the global pointer-drag
8583
+ * position against the tabs list and routes through the same shared
8584
+ * `_processDragOver` / `_processDragLeave` helpers.
8585
+ */
8586
+ _handlePointerDragMove(clientX, clientY) {
8587
+ var _a;
8588
+ const sourceDoc = (_a = this._tabsList.ownerDocument) !== null && _a !== void 0 ? _a : document;
8589
+ const elAtPoint = sourceDoc.elementFromPoint(clientX, clientY);
8590
+ const inside = !!elAtPoint &&
8591
+ (this._tabsList.contains(elAtPoint) ||
8592
+ (!!this._extendedDropZone &&
8593
+ this._extendedDropZone.contains(elAtPoint)));
8594
+ if (!inside) {
8595
+ if (this._pointerInsideTabsList) {
8596
+ this._pointerInsideTabsList = false;
8597
+ this._processDragLeave(elAtPoint);
8598
+ }
8599
+ return;
8600
+ }
8601
+ this._pointerInsideTabsList = true;
8602
+ this._processDragOver(clientX);
8603
+ }
8604
+ /**
8605
+ * Shared body of the dragover entry point. Refreshes stale anim state
8606
+ * for a changed drag identity, initializes anim state for incoming
8607
+ * cross-group drags, and dispatches to the gap-following math in
8608
+ * `handleDragOver`. Returns true when this tabs list has taken
8609
+ * ownership of the drag — HTML5 callers use this to gate
8610
+ * `event.preventDefault()`.
8611
+ */
8612
+ _processDragOver(clientX) {
8613
+ var _a, _b, _c, _d;
8614
+ if (this.accessor.options.disableDnd) {
8615
+ return false;
8616
+ }
8617
+ // Stale-state guard: if a previous drag's anim state is still here
8618
+ // but the current drag is a different identity, drop the stale one
8619
+ // so the new drag starts from a clean slate.
8620
+ if (this._animState) {
8621
+ const data = getPanelData();
8622
+ if ((data === null || data === void 0 ? void 0 : data.tabGroupId) &&
8623
+ data.groupId !== this.group.id &&
8624
+ this._animState.sourceTabGroupId !== data.tabGroupId) {
8625
+ this._animState = null;
8626
+ }
8627
+ }
8628
+ if (!this._animState) {
8629
+ const data = getPanelData();
8630
+ // In default animation mode, individual tab drops are handled
8631
+ // by per-tab Droptargets; only chip drags need tabs-list-level
8632
+ // handling so drops on void space still work.
8633
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'default' &&
8634
+ !(data === null || data === void 0 ? void 0 : data.tabGroupId)) {
8635
+ return false;
8636
+ }
8637
+ if (data &&
8638
+ (data.panelId || data.tabGroupId) &&
8639
+ data.groupId !== this.group.id) {
8640
+ const avgWidth = this.getAverageTabWidth();
8641
+ if (data.tabGroupId) {
8642
+ // External group drag — look up the source group to
8643
+ // size the gap.
8644
+ const sourceGroup = this.accessor.getPanel(data.groupId);
8645
+ const sourceTg = sourceGroup === null || sourceGroup === void 0 ? void 0 : sourceGroup.model.getTabGroups().find((tg) => tg.id === data.tabGroupId);
8646
+ const panelCount = (_b = sourceTg === null || sourceTg === void 0 ? void 0 : sourceTg.panelIds.length) !== null && _b !== void 0 ? _b : 1;
8647
+ const groupGapWidth = avgWidth * panelCount + avgWidth;
8648
+ this._animState = {
8649
+ sourceTabId: '',
8650
+ sourceIndex: -1,
8651
+ tabPositions: this.snapshotTabPositions(),
8652
+ chipPositions: this._tabGroupManager.snapshotChipWidths(),
8653
+ currentInsertionIndex: null,
8654
+ targetTabGroupId: null,
8655
+ sourceTabGroupId: data.tabGroupId,
8656
+ sourceGroupPanelIds: sourceTg
8657
+ ? new Set(sourceTg.panelIds)
8658
+ : new Set(),
8659
+ sourceChipWidth: avgWidth,
8660
+ cursorOffsetFromDragLeft: groupGapWidth / 2,
8661
+ sourceGapWidth: groupGapWidth,
8662
+ containerLeft: this._tabsList.getBoundingClientRect().left,
8663
+ };
8664
+ }
8665
+ else {
8666
+ this._animState = {
8667
+ sourceTabId: data.panelId,
8668
+ sourceIndex: -1,
8669
+ tabPositions: this.snapshotTabPositions(),
8670
+ chipPositions: this._tabGroupManager.snapshotChipWidths(),
8671
+ currentInsertionIndex: null,
8672
+ targetTabGroupId: null,
8673
+ sourceTabGroupId: null,
8674
+ sourceGroupPanelIds: null,
8675
+ sourceChipWidth: 0,
8676
+ cursorOffsetFromDragLeft: avgWidth / 2,
8677
+ sourceGapWidth: avgWidth,
8678
+ containerLeft: this._tabsList.getBoundingClientRect().left,
8679
+ };
8680
+ }
8681
+ }
8682
+ else {
8683
+ return false;
8684
+ }
8685
+ }
8686
+ // For intra-group drag (sourceIndex >= 0) the gap animation is the
8687
+ // sole visual indicator — clear any stale anchor overlay that may
8688
+ // have been set while the cursor was over the panel content area or
8689
+ // another zone. External drags (sourceIndex === -1) leave the
8690
+ // overlay to the individual tab Droptargets so cross-group
8691
+ // animation is not disrupted.
8692
+ if (this._animState.sourceIndex !== -1) {
8693
+ (_d = (_c = this.group.model.dropTargetContainer) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.clear();
8694
+ }
8695
+ this.handleDragOver({ clientX });
8696
+ return true;
8697
+ }
8698
+ /**
8699
+ * Shared body of the dragleave entry point. Preserves anim state when
8700
+ * the drag moves between tabs-list children, into the extended drop
8701
+ * zone, or into the void container; tears it down otherwise.
8702
+ */
8703
+ _processDragLeave(related) {
8704
+ var _a, _b, _c;
8705
+ if (!this._animState) {
8706
+ return;
8707
+ }
8708
+ // Moves between children of the tabs list aren't real leaves.
8709
+ if (related && this._tabsList.contains(related)) {
8710
+ return;
8711
+ }
8712
+ // Moving into the broader drop zone (e.g. void container, left
8713
+ // actions) — keep anim state alive so external listeners can
8714
+ // continue the gap animation.
8715
+ if (related && ((_a = this._extendedDropZone) === null || _a === void 0 ? void 0 : _a.contains(related))) {
8716
+ this.resetTabTransforms();
8717
+ this._animState.currentInsertionIndex = null;
8718
+ return;
8719
+ }
8720
+ // Leaving toward the void container (empty header space to the
8721
+ // right): keep anim state so a drop can still land at the end.
8722
+ const isVoid = this._voidContainer &&
8723
+ related &&
8724
+ (related === this._voidContainer ||
8725
+ this._voidContainer.contains(related));
8726
+ if (isVoid) {
8727
+ return;
8728
+ }
8729
+ this.resetTabTransforms();
8730
+ if (this._animState.sourceIndex === -1) {
8731
+ (_c = (_b = this.group.model.dropTargetContainer) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.clear();
8732
+ this._animState = null;
8733
+ }
8734
+ else {
8735
+ this._animState.currentInsertionIndex = null;
8736
+ }
8737
+ }
7532
8738
  handleDragOver(event) {
7533
8739
  var _a, _b, _c, _d, _e;
7534
8740
  if (!this._animState) {
@@ -7938,20 +9144,23 @@
7938
9144
  * in the model, and run a FLIP animation.
7939
9145
  */
7940
9146
  _commitGroupMove(sourceTabGroupId, insertionIndex) {
7941
- var _a, _b, _c;
7942
- // Read transfer data BEFORE disposing cleanup — disposing
7943
- // _chipDragCleanup clears the global LocalSelectionTransfer
7944
- // singleton which getPanelData() reads from.
9147
+ var _a, _b;
9148
+ // Read transfer data first.
7945
9149
  const data = getPanelData();
7946
- (_a = this._chipDragCleanup) === null || _a === void 0 ? void 0 : _a.dispose();
7947
- this._chipDragCleanup = null;
9150
+ // Synchronously dispose the source chip's drag sources, which
9151
+ // clears the panelTransfer payload + iframe shield. Cross-group
9152
+ // moves dissolve the source chip on a microtask, which is too
9153
+ // late: a synchronous `getPanelData()` after this method (or any
9154
+ // sibling dragover handler firing in the same tick) would
9155
+ // otherwise see stale data still referencing the old tabGroupId.
9156
+ this._tabGroupManager.disposeChipDrag(sourceTabGroupId);
7948
9157
  // Check if the tab group exists in this group (within-group reorder)
7949
9158
  // or in another group (cross-group move).
7950
9159
  const isLocal = this.group.model
7951
9160
  .getTabGroups()
7952
9161
  .some((tg) => tg.id === sourceTabGroupId);
7953
9162
  if (isLocal) {
7954
- if (((_b = this.accessor.options.theme) === null || _b === void 0 ? void 0 : _b.tabAnimation) === 'smooth') {
9163
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
7955
9164
  this._clearGroupDragClasses(sourceTabGroupId);
7956
9165
  const firstPositions = this.snapshotTabPositions();
7957
9166
  this.resetTabTransforms();
@@ -7979,7 +9188,7 @@
7979
9188
  this.accessor.moveGroupOrPanel({
7980
9189
  from: {
7981
9190
  groupId: data.groupId,
7982
- tabGroupId: (_c = data.tabGroupId) !== null && _c !== void 0 ? _c : sourceTabGroupId,
9191
+ tabGroupId: (_b = data.tabGroupId) !== null && _b !== void 0 ? _b : sourceTabGroupId,
7983
9192
  },
7984
9193
  to: {
7985
9194
  group: this.group,
@@ -8007,22 +9216,27 @@
8007
9216
  this._tabGroupManager.skipNextCollapseAnimation = true;
8008
9217
  }
8009
9218
  resetDragAnimation() {
8010
- var _a, _b;
8011
9219
  this._pendingCollapse = false;
8012
- this.resetTabTransforms();
8013
- // Clear drag-collapse classes instantly (no transition)
8014
- if ((_a = this._animState) === null || _a === void 0 ? void 0 : _a.sourceTabGroupId) {
8015
- this._clearGroupDragClasses(this._animState.sourceTabGroupId);
8016
- }
8017
- else {
8018
- this._removeClassInstantlyBatch(this._tabs.map((t) => t.value.element), 'dv-tab--dragging');
8019
- }
8020
- this._animState = null;
8021
- (_b = this._chipDragCleanup) === null || _b === void 0 ? void 0 : _b.dispose();
8022
- this._chipDragCleanup = null;
8023
- // Restore any hidden underlines from group drags
8024
- for (const [, el] of this._tabGroupManager.groupUnderlines) {
8025
- el.style.removeProperty('display');
9220
+ // After a drop, `tab.onDrop` consumes _animState (sets it to null)
9221
+ // and immediately calls `runFlipAnimation`, which sets transforms
9222
+ // and queues an rAF to trigger the CSS transition. dragend fires
9223
+ // synchronously on the source element BEFORE that rAF runs — if
9224
+ // we cleared transforms here we'd clobber the in-flight FLIP, so
9225
+ // gate the cleanup on _animState still being set (i.e. drag was
9226
+ // cancelled rather than dropped).
9227
+ if (this._animState) {
9228
+ this.resetTabTransforms();
9229
+ if (this._animState.sourceTabGroupId) {
9230
+ this._clearGroupDragClasses(this._animState.sourceTabGroupId);
9231
+ }
9232
+ else {
9233
+ this._removeClassInstantlyBatch(this._tabs.map((t) => t.value.element), 'dv-tab--dragging');
9234
+ }
9235
+ this._animState = null;
9236
+ // Restore any hidden underlines from group drags.
9237
+ for (const [, el] of this._tabGroupManager.groupUnderlines) {
9238
+ el.style.removeProperty('display');
9239
+ }
8026
9240
  }
8027
9241
  }
8028
9242
  runFlipAnimation(firstPositions, sourceTabId, isCrossGroup = false, animRange) {
@@ -8522,6 +9736,7 @@
8522
9736
  rootOverlayModel: undefined,
8523
9737
  locked: undefined,
8524
9738
  disableDnd: undefined,
9739
+ dndStrategy: undefined,
8525
9740
  className: undefined,
8526
9741
  noPanelsOverlay: undefined,
8527
9742
  dndEdges: undefined,
@@ -8706,6 +9921,10 @@
8706
9921
  }
8707
9922
 
8708
9923
  class DockviewDidDropEvent extends DockviewEvent {
9924
+ /**
9925
+ * `PointerEvent` for touch drags has no `dataTransfer`; use
9926
+ * `getData()` for the dockview payload regardless of input method.
9927
+ */
8709
9928
  get nativeEvent() {
8710
9929
  return this.options.nativeEvent;
8711
9930
  }
@@ -8810,29 +10029,28 @@
8810
10029
  toggleClass(this.container, 'dv-groupview-floating', false);
8811
10030
  toggleClass(this.container, 'dv-groupview-popout', false);
8812
10031
  toggleClass(this.container, 'dv-groupview-edge', false);
10032
+ // Mouse and touch drop targets must agree on accepted zones.
10033
+ const applyZones = (zones) => {
10034
+ this.contentContainer.dropTarget.setTargetZones(zones);
10035
+ this.contentContainer.pointerDropTarget.setTargetZones(zones);
10036
+ };
8813
10037
  switch (value.type) {
8814
10038
  case 'grid':
8815
- this.contentContainer.dropTarget.setTargetZones([
8816
- 'top',
8817
- 'bottom',
8818
- 'left',
8819
- 'right',
8820
- 'center',
8821
- ]);
10039
+ applyZones(['top', 'bottom', 'left', 'right', 'center']);
8822
10040
  break;
8823
10041
  case 'floating':
8824
- this.contentContainer.dropTarget.setTargetZones(['center']);
8825
- this.contentContainer.dropTarget.setTargetZones(value
10042
+ applyZones(['center']);
10043
+ applyZones(value
8826
10044
  ? ['center']
8827
10045
  : ['top', 'bottom', 'left', 'right', 'center']);
8828
10046
  toggleClass(this.container, 'dv-groupview-floating', true);
8829
10047
  break;
8830
10048
  case 'popout':
8831
- this.contentContainer.dropTarget.setTargetZones(['center']);
10049
+ applyZones(['center']);
8832
10050
  toggleClass(this.container, 'dv-groupview-popout', true);
8833
10051
  break;
8834
10052
  case 'edge':
8835
- this.contentContainer.dropTarget.setTargetZones(['center']);
10053
+ applyZones(['center']);
8836
10054
  toggleClass(this.container, 'dv-groupview-edge', true);
8837
10055
  break;
8838
10056
  }
@@ -8958,6 +10176,8 @@
8958
10176
  // noop
8959
10177
  }), this.contentContainer.dropTarget.onDrop((event) => {
8960
10178
  this.handleDropEvent('content', event.nativeEvent, event.position);
10179
+ }), this.contentContainer.pointerDropTarget.onDrop((event) => {
10180
+ this.handleDropEvent('content', event.nativeEvent, event.position);
8961
10181
  }), this.tabsContainer.onWillShowOverlay((event) => {
8962
10182
  this._onWillShowOverlay.fire(event);
8963
10183
  }), this.contentContainer.dropTarget.onWillShowOverlay((event) => {
@@ -8968,6 +10188,14 @@
8968
10188
  group: this.groupPanel,
8969
10189
  getData: getPanelData,
8970
10190
  }));
10191
+ }), this.contentContainer.pointerDropTarget.onWillShowOverlay((event) => {
10192
+ this._onWillShowOverlay.fire(new DockviewWillShowOverlayLocationEvent(event, {
10193
+ kind: 'content',
10194
+ panel: this.activePanel,
10195
+ api: this._api,
10196
+ group: this.groupPanel,
10197
+ getData: getPanelData,
10198
+ }));
8971
10199
  }), 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(() => {
8972
10200
  this._scheduleTabGroupUpdate();
8973
10201
  }), this._onDidDestroyTabGroup.event(() => {
@@ -10971,7 +12199,12 @@
10971
12199
  this.onDidChange = this._onDidChange.event;
10972
12200
  this._onDidChangeEnd = new Emitter();
10973
12201
  this.onDidChangeEnd = this._onDidChangeEnd.event;
10974
- this.addDisposables(this._onDidChange, this._onDidChangeEnd);
12202
+ this._onDidStartMoving = new Emitter();
12203
+ /** Fires once per drag, the first time the float actually moves. */
12204
+ this.onDidStartMoving = this._onDidStartMoving.event;
12205
+ this._dragMove = new MutableDisposable();
12206
+ this._dragCancelled = false;
12207
+ this.addDisposables(this._onDidChange, this._onDidChangeEnd, this._onDidStartMoving, this._dragMove);
10975
12208
  this._element.className = 'dv-resize-container';
10976
12209
  this._isVisible = true;
10977
12210
  this.setupResize('top');
@@ -11080,16 +12313,60 @@
11080
12313
  result.height = element.height;
11081
12314
  return result;
11082
12315
  }
12316
+ /**
12317
+ * Abort an in-flight move-the-float drag. Used by the void container
12318
+ * when a redock long-press fires after the move started, so the ghost
12319
+ * gesture wins without the float continuing to follow the finger.
12320
+ * Does not emit `onDidChangeEnd` because no change is being committed.
12321
+ */
12322
+ cancelPendingDrag() {
12323
+ if (!this._dragMove.value) {
12324
+ return;
12325
+ }
12326
+ this._dragCancelled = true;
12327
+ toggleClass(this._element, 'dv-resize-container-dragging', false);
12328
+ this._dragMove.value = exports.DockviewDisposable.NONE;
12329
+ }
11083
12330
  setupDrag(dragTarget, options = { inDragMode: false }) {
11084
- const move = new MutableDisposable();
11085
- const track = () => {
12331
+ const track = (captureTarget, pointerId) => {
11086
12332
  let offset = null;
12333
+ let hasMoved = false;
12334
+ this._dragCancelled = false;
11087
12335
  const iframes = disableIframePointEvents();
11088
- move.value = new CompositeDisposable({
12336
+ if (captureTarget &&
12337
+ typeof pointerId === 'number' &&
12338
+ typeof captureTarget.setPointerCapture === 'function') {
12339
+ try {
12340
+ captureTarget.setPointerCapture(pointerId);
12341
+ }
12342
+ catch (_a) {
12343
+ // ignore – non-fatal if the browser refuses capture
12344
+ }
12345
+ }
12346
+ const end = () => {
12347
+ toggleClass(this._element, 'dv-resize-container-dragging', false);
12348
+ this._dragMove.value = exports.DockviewDisposable.NONE;
12349
+ this._onDidChangeEnd.fire();
12350
+ };
12351
+ this._dragMove.value = new CompositeDisposable({
11089
12352
  dispose: () => {
11090
12353
  iframes.release();
12354
+ if (captureTarget &&
12355
+ typeof pointerId === 'number' &&
12356
+ typeof captureTarget.releasePointerCapture ===
12357
+ 'function') {
12358
+ try {
12359
+ captureTarget.releasePointerCapture(pointerId);
12360
+ }
12361
+ catch (_a) {
12362
+ // ignore – pointer may already be released
12363
+ }
12364
+ }
11091
12365
  },
11092
12366
  }, addDisposableListener(window, 'pointermove', (e) => {
12367
+ if (this._dragCancelled) {
12368
+ return;
12369
+ }
11093
12370
  const containerRect = this.options.container.getBoundingClientRect();
11094
12371
  const x = e.clientX - containerRect.left;
11095
12372
  const y = e.clientY - containerRect.top;
@@ -11126,13 +12403,13 @@
11126
12403
  bounds.right = right;
11127
12404
  }
11128
12405
  this.setBounds(bounds);
11129
- }), addDisposableListener(window, 'pointerup', () => {
11130
- toggleClass(this._element, 'dv-resize-container-dragging', false);
11131
- move.dispose();
11132
- this._onDidChangeEnd.fire();
11133
- }));
12406
+ if (!hasMoved) {
12407
+ hasMoved = true;
12408
+ this._onDidStartMoving.fire();
12409
+ }
12410
+ }), addDisposableListener(window, 'pointerup', end), addDisposableListener(window, 'pointercancel', end));
11134
12411
  };
11135
- this.addDisposables(move, addDisposableListener(dragTarget, 'pointerdown', (event) => {
12412
+ this.addDisposables(addDisposableListener(dragTarget, 'pointerdown', (event) => {
11136
12413
  if (event.defaultPrevented) {
11137
12414
  event.preventDefault();
11138
12415
  return;
@@ -11142,7 +12419,7 @@
11142
12419
  if (quasiDefaultPrevented(event)) {
11143
12420
  return;
11144
12421
  }
11145
- track();
12422
+ track(dragTarget, event.pointerId);
11146
12423
  }), addDisposableListener(this.options.content, 'pointerdown', (event) => {
11147
12424
  if (event.defaultPrevented) {
11148
12425
  return;
@@ -11153,7 +12430,7 @@
11153
12430
  return;
11154
12431
  }
11155
12432
  if (event.shiftKey) {
11156
- track();
12433
+ track(this.options.content, event.pointerId);
11157
12434
  }
11158
12435
  }), addDisposableListener(this.options.content, 'pointerdown', () => {
11159
12436
  arialLevelTracker.push(this._element);
@@ -11171,6 +12448,19 @@
11171
12448
  e.preventDefault();
11172
12449
  let startPosition = null;
11173
12450
  const iframes = disableIframePointEvents();
12451
+ const pointerId = e.pointerId;
12452
+ if (typeof resizeHandleElement.setPointerCapture === 'function') {
12453
+ try {
12454
+ resizeHandleElement.setPointerCapture(pointerId);
12455
+ }
12456
+ catch (_a) {
12457
+ // ignore – non-fatal if the browser refuses capture
12458
+ }
12459
+ }
12460
+ const end = () => {
12461
+ move.dispose();
12462
+ this._onDidChangeEnd.fire();
12463
+ };
11174
12464
  move.value = new CompositeDisposable(addDisposableListener(window, 'pointermove', (e) => {
11175
12465
  const containerRect = this.options.container.getBoundingClientRect();
11176
12466
  const overlayRect = this._element.getBoundingClientRect();
@@ -11305,11 +12595,17 @@
11305
12595
  }), {
11306
12596
  dispose: () => {
11307
12597
  iframes.release();
12598
+ if (typeof resizeHandleElement.releasePointerCapture ===
12599
+ 'function') {
12600
+ try {
12601
+ resizeHandleElement.releasePointerCapture(pointerId);
12602
+ }
12603
+ catch (_a) {
12604
+ // ignore – pointer may already be released
12605
+ }
12606
+ }
11308
12607
  },
11309
- }, addDisposableListener(window, 'pointerup', () => {
11310
- move.dispose();
11311
- this._onDidChangeEnd.fire();
11312
- }));
12608
+ }, addDisposableListener(window, 'pointerup', end), addDisposableListener(window, 'pointercancel', end));
11313
12609
  }));
11314
12610
  }
11315
12611
  getMinimumWidth(width) {
@@ -11834,6 +13130,14 @@
11834
13130
  }
11835
13131
  }
11836
13132
 
13133
+ function isCoarsePrimaryInput$1(win) {
13134
+ if (!win.matchMedia) {
13135
+ return false;
13136
+ }
13137
+ const coarse = win.matchMedia('(pointer: coarse)').matches;
13138
+ const fine = win.matchMedia('(pointer: fine)').matches;
13139
+ return coarse && !fine;
13140
+ }
11837
13141
  class PopupService extends CompositeDisposable {
11838
13142
  constructor(root, win = window) {
11839
13143
  super();
@@ -11872,8 +13176,22 @@
11872
13176
  wrapper.style.left = `${position.x - offsetX}px`;
11873
13177
  this._element.appendChild(wrapper);
11874
13178
  this._active = wrapper;
13179
+ // Outside-pointerdown dismissal is suppressed for a short grace
13180
+ // window after opening. Touch long-press callers (chip / tab context
13181
+ // menus) open the popover while the user's finger is still pressing
13182
+ // the source element — Android Chrome can dispatch a follow-up
13183
+ // synthetic pointerdown tied to the gesture, and the release-then-
13184
+ // retap motion can land just outside the wrapper. Either would
13185
+ // dismiss the popover before the user can see or interact with it.
13186
+ // The grace window is short enough that intentional outside taps
13187
+ // still feel responsive.
13188
+ const openedAt = Date.now();
13189
+ const POINTERDOWN_GRACE_MS = 200;
11875
13190
  this._activeDisposable.value = new CompositeDisposable(addDisposableListener(this._window, 'pointerdown', (event) => {
11876
13191
  var _a;
13192
+ if (Date.now() - openedAt < POINTERDOWN_GRACE_MS) {
13193
+ return;
13194
+ }
11877
13195
  const target = event.target;
11878
13196
  if (!(target instanceof HTMLElement)) {
11879
13197
  return;
@@ -11891,6 +13209,18 @@
11891
13209
  this.close();
11892
13210
  }
11893
13211
  }), addDisposableListener(this._window, 'resize', () => {
13212
+ // On touch-primary devices, common interactions resize the
13213
+ // window: on-screen keyboard pop, orientation change, browser
13214
+ // address-bar collapse. None of these mean "the user wants
13215
+ // the popover dismissed". Specifically, focusing the chip
13216
+ // context menu's rename input pops the keyboard, which would
13217
+ // otherwise close the menu the moment the user goes to edit
13218
+ // it. Desktop / hybrid input keeps the existing behaviour —
13219
+ // there a resize genuinely means the user has resized the
13220
+ // window and the popover position is now stale.
13221
+ if (isCoarsePrimaryInput$1(this._window)) {
13222
+ return;
13223
+ }
11894
13224
  this.close();
11895
13225
  }));
11896
13226
  this._window.requestAnimationFrame(() => {
@@ -11947,6 +13277,14 @@
11947
13277
  el.setAttribute('role', 'separator');
11948
13278
  return el;
11949
13279
  }
13280
+ function isCoarsePrimaryInput() {
13281
+ if (typeof window === 'undefined' || !window.matchMedia) {
13282
+ return false;
13283
+ }
13284
+ const coarse = window.matchMedia('(pointer: coarse)').matches;
13285
+ const fine = window.matchMedia('(pointer: fine)').matches;
13286
+ return coarse && !fine;
13287
+ }
11950
13288
  function buildRenameInput(tabGroup) {
11951
13289
  const wrapper = document.createElement('div');
11952
13290
  wrapper.className = 'dv-context-menu-rename';
@@ -11967,10 +13305,17 @@
11967
13305
  e.stopPropagation();
11968
13306
  });
11969
13307
  wrapper.appendChild(input);
11970
- requestAnimationFrame(() => {
11971
- input.focus();
11972
- input.select();
11973
- });
13308
+ // Skip auto-focus on touch-primary devices: focusing the input pops the
13309
+ // on-screen keyboard, which fires `window resize`, which `PopupService`
13310
+ // listens to and uses to dismiss the popover — so the menu opens, the
13311
+ // keyboard appears, and the menu immediately closes before the user can
13312
+ // type. The user can still tap the input to focus it intentionally.
13313
+ if (!isCoarsePrimaryInput()) {
13314
+ requestAnimationFrame(() => {
13315
+ input.focus();
13316
+ input.select();
13317
+ });
13318
+ }
11974
13319
  return wrapper;
11975
13320
  }
11976
13321
  function buildColorPicker(tabGroup, palette) {
@@ -12885,7 +14230,7 @@
12885
14230
  return this._popoutRestorationPromise;
12886
14231
  }
12887
14232
  constructor(container, options) {
12888
- var _a, _b, _c, _d, _e, _f;
14233
+ var _a, _b, _c, _d, _e, _f, _g;
12889
14234
  super(container, {
12890
14235
  proportionalLayout: true,
12891
14236
  orientation: exports.Orientation.HORIZONTAL,
@@ -12984,37 +14329,51 @@
12984
14329
  this._floatingOverlayHost = document.createElement('div');
12985
14330
  this._floatingOverlayHost.className = 'dv-floating-overlay-host';
12986
14331
  this._shellManager.element.appendChild(this._floatingOverlayHost);
12987
- this._rootDropTarget = new Droptarget(this.element, {
12988
- className: 'dv-drop-target-edge',
12989
- canDisplayOverlay: (event, position) => {
12990
- const data = getPanelData();
12991
- if (data) {
12992
- if (data.viewId !== this.id) {
12993
- return false;
12994
- }
12995
- if (position === 'center') {
12996
- // center drop target is only allowed if there are no panels in the grid
12997
- // floating panels are allowed
12998
- return this.gridview.length === 0;
12999
- }
13000
- return true;
13001
- }
13002
- if (position === 'center' && this.gridview.length !== 0) {
13003
- /**
13004
- * for external events only show the four-corner drag overlays, disable
13005
- * the center position so that external drag events can fall through to the group
13006
- * and panel drop target handlers
13007
- */
14332
+ const rootCanDisplayOverlay = (event, position) => {
14333
+ const data = getPanelData();
14334
+ if (data) {
14335
+ if (data.viewId !== this.id) {
13008
14336
  return false;
13009
14337
  }
13010
- const firedEvent = new DockviewUnhandledDragOverEvent(event, 'edge', position, getPanelData);
13011
- this._onUnhandledDragOverEvent.fire(firedEvent);
13012
- return firedEvent.isAccepted;
13013
- },
14338
+ if (position === 'center') {
14339
+ // center drop target is only allowed if there are no panels in the grid
14340
+ // floating panels are allowed
14341
+ return this.gridview.length === 0;
14342
+ }
14343
+ return true;
14344
+ }
14345
+ if (position === 'center' && this.gridview.length !== 0) {
14346
+ /**
14347
+ * for external events only show the four-corner drag overlays, disable
14348
+ * the center position so that external drag events can fall through to the group
14349
+ * and panel drop target handlers
14350
+ */
14351
+ return false;
14352
+ }
14353
+ const firedEvent = new DockviewUnhandledDragOverEvent(event, 'edge', position, getPanelData);
14354
+ this._onUnhandledDragOverEvent.fire(firedEvent);
14355
+ return firedEvent.isAccepted;
14356
+ };
14357
+ this._rootDropTarget = html5Backend.createDropTarget(this.element, {
14358
+ className: 'dv-drop-target-edge',
14359
+ canDisplayOverlay: rootCanDisplayOverlay,
13014
14360
  acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
13015
14361
  overlayModel: (_f = options.rootOverlayModel) !== null && _f !== void 0 ? _f : DEFAULT_ROOT_OVERLAY_MODEL,
13016
14362
  getOverrideTarget: () => { var _a; return (_a = this.rootDropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
13017
14363
  });
14364
+ this._rootPointerDropTarget = pointerBackend.createDropTarget(this.element, {
14365
+ className: 'dv-drop-target-edge',
14366
+ canDisplayOverlay: rootCanDisplayOverlay,
14367
+ acceptedTargetZones: [
14368
+ 'top',
14369
+ 'bottom',
14370
+ 'left',
14371
+ 'right',
14372
+ 'center',
14373
+ ],
14374
+ overlayModel: (_g = options.rootOverlayModel) !== null && _g !== void 0 ? _g : DEFAULT_ROOT_OVERLAY_MODEL,
14375
+ getOverrideTarget: () => { var _a; return (_a = this.rootDropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
14376
+ });
13018
14377
  this.updateDropTargetModel(options);
13019
14378
  toggleClass(this.gridview.element, 'dv-dockview', true);
13020
14379
  toggleClass(this.element, 'dv-debug', !!options.debug);
@@ -13076,7 +14435,7 @@
13076
14435
  d.dispose();
13077
14436
  }
13078
14437
  this._edgeGroupDisposables.clear();
13079
- }), this._rootDropTarget, this._rootDropTarget.onWillShowOverlay((event) => {
14438
+ }), this._rootDropTarget, this._rootPointerDropTarget, exports.DockviewEvent.any(this._rootDropTarget.onWillShowOverlay, this._rootPointerDropTarget.onWillShowOverlay)((event) => {
13080
14439
  if (this.gridview.length > 0 && event.position === 'center') {
13081
14440
  // option only available when no panels in primary grid
13082
14441
  return;
@@ -13088,7 +14447,7 @@
13088
14447
  group: undefined,
13089
14448
  getData: getPanelData,
13090
14449
  }));
13091
- }), this._rootDropTarget.onDrop((event) => {
14450
+ }), exports.DockviewEvent.any(this._rootDropTarget.onDrop, this._rootPointerDropTarget.onDrop)((event) => {
13092
14451
  var _a;
13093
14452
  const willDropEvent = new DockviewWillDropEvent({
13094
14453
  nativeEvent: event.nativeEvent,
@@ -13126,7 +14485,7 @@
13126
14485
  getData: getPanelData,
13127
14486
  }));
13128
14487
  }
13129
- }), this._rootDropTarget);
14488
+ }));
13130
14489
  }
13131
14490
  setVisible(panel, visible) {
13132
14491
  switch (panel.api.location.type) {
@@ -13638,9 +14997,12 @@
13638
14997
  }
13639
14998
  this.updateDropTargetModel(options);
13640
14999
  const oldDisableDnd = this.options.disableDnd;
15000
+ const oldDndStrategy = this.options.dndStrategy;
13641
15001
  this._options = Object.assign(Object.assign({}, this.options), options);
13642
15002
  const newDisableDnd = this.options.disableDnd;
13643
- if (oldDisableDnd !== newDisableDnd) {
15003
+ const newDndStrategy = this.options.dndStrategy;
15004
+ if (oldDisableDnd !== newDisableDnd ||
15005
+ oldDndStrategy !== newDndStrategy) {
13644
15006
  this.updateDragAndDropState();
13645
15007
  }
13646
15008
  if ('theme' in options) {
@@ -15204,15 +16566,18 @@
15204
16566
  }
15205
16567
  updateDropTargetModel(options) {
15206
16568
  if ('dndEdges' in options) {
15207
- this._rootDropTarget.disabled =
15208
- typeof options.dndEdges === 'boolean' &&
15209
- options.dndEdges === false;
16569
+ const disabled = typeof options.dndEdges === 'boolean' &&
16570
+ options.dndEdges === false;
16571
+ this._rootDropTarget.disabled = disabled;
16572
+ this._rootPointerDropTarget.disabled = disabled;
15210
16573
  if (typeof options.dndEdges === 'object' &&
15211
16574
  options.dndEdges !== null) {
15212
16575
  this._rootDropTarget.setOverlayModel(options.dndEdges);
16576
+ this._rootPointerDropTarget.setOverlayModel(options.dndEdges);
15213
16577
  }
15214
16578
  else {
15215
16579
  this._rootDropTarget.setOverlayModel(DEFAULT_ROOT_OVERLAY_MODEL);
16580
+ this._rootPointerDropTarget.setOverlayModel(DEFAULT_ROOT_OVERLAY_MODEL);
15216
16581
  }
15217
16582
  }
15218
16583
  if ('rootOverlayModel' in options) {