dockview-core 6.2.2 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +1 -0
  2. package/dist/cjs/api/dockviewGroupPanelApi.d.ts +10 -1
  3. package/dist/cjs/api/dockviewGroupPanelApi.js +16 -0
  4. package/dist/cjs/dnd/backend.d.ts +70 -0
  5. package/dist/cjs/dnd/backend.js +171 -0
  6. package/dist/cjs/dnd/dropOverlay.d.ts +20 -0
  7. package/dist/cjs/dnd/dropOverlay.js +197 -0
  8. package/dist/cjs/dnd/droptarget.d.ts +20 -6
  9. package/dist/cjs/dnd/droptarget.js +14 -208
  10. package/dist/cjs/dnd/pointer/index.d.ts +11 -0
  11. package/dist/cjs/dnd/pointer/index.js +13 -0
  12. package/dist/cjs/dnd/pointer/longPress.d.ts +32 -0
  13. package/dist/cjs/dnd/pointer/longPress.js +151 -0
  14. package/dist/cjs/dnd/pointer/pointerDragController.d.ts +60 -0
  15. package/dist/cjs/dnd/pointer/pointerDragController.js +241 -0
  16. package/dist/cjs/dnd/pointer/pointerDragSource.d.ts +61 -0
  17. package/dist/cjs/dnd/pointer/pointerDragSource.js +195 -0
  18. package/dist/cjs/dnd/pointer/pointerDropTarget.d.ts +39 -0
  19. package/dist/cjs/dnd/pointer/pointerDropTarget.js +198 -0
  20. package/dist/cjs/dnd/pointer/pointerGhost.d.ts +30 -0
  21. package/dist/cjs/dnd/pointer/pointerGhost.js +44 -0
  22. package/dist/cjs/dnd/pointer/types.d.ts +16 -0
  23. package/dist/cjs/dnd/pointer/types.js +2 -0
  24. package/dist/cjs/dockview/components/panel/content.d.ts +3 -1
  25. package/dist/cjs/dockview/components/panel/content.js +33 -16
  26. package/dist/cjs/dockview/components/popupService.js +34 -0
  27. package/dist/cjs/dockview/components/tab/tab.d.ts +11 -3
  28. package/dist/cjs/dockview/components/tab/tab.js +151 -117
  29. package/dist/cjs/dockview/components/titlebar/tabGroupChip.d.ts +9 -2
  30. package/dist/cjs/dockview/components/titlebar/tabGroupChip.js +15 -6
  31. package/dist/cjs/dockview/components/titlebar/tabGroupIndicator.js +2 -2
  32. package/dist/cjs/dockview/components/titlebar/tabGroups.d.ts +33 -5
  33. package/dist/cjs/dockview/components/titlebar/tabGroups.js +231 -40
  34. package/dist/cjs/dockview/components/titlebar/tabs.d.ts +38 -1
  35. package/dist/cjs/dockview/components/titlebar/tabs.js +381 -253
  36. package/dist/cjs/dockview/components/titlebar/tabsContainer.d.ts +5 -3
  37. package/dist/cjs/dockview/components/titlebar/voidContainer.d.ts +6 -2
  38. package/dist/cjs/dockview/components/titlebar/voidContainer.js +190 -22
  39. package/dist/cjs/dockview/contextMenu.js +19 -4
  40. package/dist/cjs/dockview/dndCapabilities.d.ts +19 -0
  41. package/dist/cjs/dockview/dndCapabilities.js +39 -0
  42. package/dist/cjs/dockview/dockviewComponent.d.ts +2 -0
  43. package/dist/cjs/dockview/dockviewComponent.js +241 -158
  44. package/dist/cjs/dockview/dockviewGroupPanelModel.d.ts +10 -5
  45. package/dist/cjs/dockview/dockviewGroupPanelModel.js +34 -11
  46. package/dist/cjs/dockview/dockviewPanel.js +5 -0
  47. package/dist/cjs/dockview/dockviewPanelModel.d.ts +2 -0
  48. package/dist/cjs/dockview/dockviewPanelModel.js +8 -0
  49. package/dist/cjs/dockview/events.d.ts +2 -1
  50. package/dist/cjs/dockview/events.js +1 -0
  51. package/dist/cjs/dockview/framework.d.ts +8 -0
  52. package/dist/cjs/dockview/options.d.ts +31 -5
  53. package/dist/cjs/dockview/options.js +3 -0
  54. package/dist/cjs/dom.d.ts +5 -1
  55. package/dist/cjs/dom.js +21 -5
  56. package/dist/cjs/index.d.ts +1 -1
  57. package/dist/cjs/overlay/overlay.d.ts +12 -0
  58. package/dist/cjs/overlay/overlay.js +84 -16
  59. package/dist/cjs/paneview/draggablePaneviewPanel.d.ts +3 -1
  60. package/dist/cjs/paneview/draggablePaneviewPanel.js +27 -26
  61. package/dist/cjs/paneview/options.d.ts +4 -3
  62. package/dist/cjs/popoutWindow.d.ts +2 -0
  63. package/dist/cjs/popoutWindow.js +3 -1
  64. package/dist/dockview-core.js +2431 -937
  65. package/dist/dockview-core.min.js +2 -2
  66. package/dist/dockview-core.min.js.map +1 -1
  67. package/dist/dockview-core.min.noStyle.js +2 -2
  68. package/dist/dockview-core.min.noStyle.js.map +1 -1
  69. package/dist/dockview-core.noStyle.js +2430 -936
  70. package/dist/esm/api/dockviewGroupPanelApi.d.ts +10 -1
  71. package/dist/esm/api/dockviewGroupPanelApi.js +12 -0
  72. package/dist/esm/dnd/backend.d.ts +70 -0
  73. package/dist/esm/dnd/backend.js +148 -0
  74. package/dist/esm/dnd/dropOverlay.d.ts +20 -0
  75. package/dist/esm/dnd/dropOverlay.js +192 -0
  76. package/dist/esm/dnd/droptarget.d.ts +20 -6
  77. package/dist/esm/dnd/droptarget.js +16 -210
  78. package/dist/esm/dnd/pointer/index.d.ts +11 -0
  79. package/dist/esm/dnd/pointer/index.js +5 -0
  80. package/dist/esm/dnd/pointer/longPress.d.ts +32 -0
  81. package/dist/esm/dnd/pointer/longPress.js +127 -0
  82. package/dist/esm/dnd/pointer/pointerDragController.d.ts +60 -0
  83. package/dist/esm/dnd/pointer/pointerDragController.js +191 -0
  84. package/dist/esm/dnd/pointer/pointerDragSource.d.ts +61 -0
  85. package/dist/esm/dnd/pointer/pointerDragSource.js +171 -0
  86. package/dist/esm/dnd/pointer/pointerDropTarget.d.ts +39 -0
  87. package/dist/esm/dnd/pointer/pointerDropTarget.js +168 -0
  88. package/dist/esm/dnd/pointer/pointerGhost.d.ts +30 -0
  89. package/dist/esm/dnd/pointer/pointerGhost.js +39 -0
  90. package/dist/esm/dnd/pointer/types.d.ts +16 -0
  91. package/dist/esm/dnd/pointer/types.js +1 -0
  92. package/dist/esm/dockview/components/panel/content.d.ts +3 -1
  93. package/dist/esm/dockview/components/panel/content.js +33 -16
  94. package/dist/esm/dockview/components/popupService.js +34 -0
  95. package/dist/esm/dockview/components/tab/tab.d.ts +11 -3
  96. package/dist/esm/dockview/components/tab/tab.js +139 -114
  97. package/dist/esm/dockview/components/titlebar/tabGroupChip.d.ts +9 -2
  98. package/dist/esm/dockview/components/titlebar/tabGroupChip.js +15 -6
  99. package/dist/esm/dockview/components/titlebar/tabGroupIndicator.js +2 -2
  100. package/dist/esm/dockview/components/titlebar/tabGroups.d.ts +33 -5
  101. package/dist/esm/dockview/components/titlebar/tabGroups.js +177 -12
  102. package/dist/esm/dockview/components/titlebar/tabs.d.ts +38 -1
  103. package/dist/esm/dockview/components/titlebar/tabs.js +360 -229
  104. package/dist/esm/dockview/components/titlebar/tabsContainer.d.ts +5 -3
  105. package/dist/esm/dockview/components/titlebar/voidContainer.d.ts +6 -2
  106. package/dist/esm/dockview/components/titlebar/voidContainer.js +180 -26
  107. package/dist/esm/dockview/contextMenu.js +19 -4
  108. package/dist/esm/dockview/dndCapabilities.d.ts +19 -0
  109. package/dist/esm/dockview/dndCapabilities.js +36 -0
  110. package/dist/esm/dockview/dockviewComponent.d.ts +2 -0
  111. package/dist/esm/dockview/dockviewComponent.js +104 -41
  112. package/dist/esm/dockview/dockviewGroupPanelModel.d.ts +10 -5
  113. package/dist/esm/dockview/dockviewGroupPanelModel.js +33 -11
  114. package/dist/esm/dockview/dockviewPanel.js +5 -0
  115. package/dist/esm/dockview/dockviewPanelModel.d.ts +2 -0
  116. package/dist/esm/dockview/dockviewPanelModel.js +8 -0
  117. package/dist/esm/dockview/events.d.ts +2 -1
  118. package/dist/esm/dockview/events.js +1 -0
  119. package/dist/esm/dockview/framework.d.ts +8 -0
  120. package/dist/esm/dockview/options.d.ts +31 -5
  121. package/dist/esm/dockview/options.js +3 -0
  122. package/dist/esm/dom.d.ts +5 -1
  123. package/dist/esm/dom.js +20 -5
  124. package/dist/esm/index.d.ts +1 -1
  125. package/dist/esm/overlay/overlay.d.ts +12 -0
  126. package/dist/esm/overlay/overlay.js +85 -17
  127. package/dist/esm/paneview/draggablePaneviewPanel.d.ts +3 -1
  128. package/dist/esm/paneview/draggablePaneviewPanel.js +26 -20
  129. package/dist/esm/paneview/options.d.ts +4 -3
  130. package/dist/esm/popoutWindow.d.ts +2 -0
  131. package/dist/esm/popoutWindow.js +3 -1
  132. package/dist/package/main.cjs.js +2430 -936
  133. package/dist/package/main.cjs.min.js +2 -2
  134. package/dist/package/main.esm.min.mjs +2 -2
  135. package/dist/package/main.esm.mjs +2430 -936
  136. package/dist/styles/dockview.css +117 -1
  137. package/package.json +3 -1
  138. package/dist/cjs/dnd/abstractDragHandler.d.ts +0 -14
  139. package/dist/cjs/dnd/abstractDragHandler.js +0 -86
  140. package/dist/cjs/dnd/groupDragHandler.d.ts +0 -12
  141. package/dist/cjs/dnd/groupDragHandler.js +0 -82
  142. package/dist/esm/dnd/abstractDragHandler.d.ts +0 -14
  143. package/dist/esm/dnd/abstractDragHandler.js +0 -63
  144. package/dist/esm/dnd/groupDragHandler.d.ts +0 -12
  145. package/dist/esm/dnd/groupDragHandler.js +0 -59
@@ -1,8 +1,9 @@
1
- import { getPanelData, LocalSelectionTransfer, PanelTransfer, } from '../../../dnd/dataTransfer';
2
- import { addClasses, disableIframePointEvents, isChildEntirelyVisibleWithinParent, OverflowObserver, removeClasses, toggleClass, } from '../../../dom';
1
+ import { getPanelData, } from '../../../dnd/dataTransfer';
2
+ import { addClasses, isChildEntirelyVisibleWithinParent, OverflowObserver, removeClasses, toggleClass, } from '../../../dom';
3
3
  import { addDisposableListener, Emitter } from '../../../events';
4
4
  import { CompositeDisposable, Disposable, MutableDisposable, } from '../../../lifecycle';
5
5
  import { Scrollbar } from '../../../scrollbar';
6
+ import { PointerDragController } from '../../../dnd/pointer/pointerDragController';
6
7
  import { DockviewWillShowOverlayLocationEvent } from '../../events';
7
8
  import { Tab } from '../tab/tab';
8
9
  import { TabGroupManager } from './tabGroups';
@@ -102,6 +103,7 @@ export class Tabs extends CompositeDisposable {
102
103
  for (const tab of this._tabs) {
103
104
  tab.value.setDirection(value);
104
105
  }
106
+ this._tabGroupManager.updateDirection();
105
107
  }
106
108
  constructor(group, accessor, options) {
107
109
  super();
@@ -121,7 +123,7 @@ export class Tabs extends CompositeDisposable {
121
123
  this._voidContainer = null;
122
124
  this._voidContainerListeners = null;
123
125
  this._extendedDropZone = null;
124
- this._chipDragCleanup = null;
126
+ this._pointerInsideTabsList = false;
125
127
  this._onTabDragStart = new Emitter();
126
128
  this.onTabDragStart = this._onTabDragStart.event;
127
129
  this._onDrop = new Emitter();
@@ -156,13 +158,40 @@ export class Tabs extends CompositeDisposable {
156
158
  onChipDragStart: (tabGroup, chip, event) => {
157
159
  this._handleChipDragStart(tabGroup, chip, event);
158
160
  },
161
+ onChipDragEnd: () => {
162
+ // HTML5 chip dragend (incl. cancels). The Html5DragSource
163
+ // owns the listener on the chip element, so this fires
164
+ // even if the chip was detached cross-group — the
165
+ // element keeps its listeners until the source is
166
+ // disposed. resetDragAnimation is a no-op after a
167
+ // successful drop (anim state already null) thanks to
168
+ // the gating inside it.
169
+ this.resetDragAnimation();
170
+ },
171
+ onChipDrop: (tabGroup, event) => {
172
+ this._handleChipDrop(tabGroup, event);
173
+ },
159
174
  });
160
175
  this.addDisposables(this._onOverflowTabsChange, this._observerDisposable, this._onWillShowOverlay, this._onDrop, this._onTabDragStart, {
161
176
  dispose: () => {
162
177
  var _a;
163
178
  (_a = this._flipTransitionCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
164
179
  },
165
- }, addDisposableListener(this.element, 'pointerdown', (event) => {
180
+ },
181
+ // Pointer-side cleanup: when any pointer drag ends, tear
182
+ // down smooth-reorder anim state the dragover bridge may
183
+ // have installed. The chip's pointer drag source handles
184
+ // its own transfer payload + iframe-shield cleanup.
185
+ PointerDragController.getInstance().onDragEnd(() => {
186
+ this._pointerInsideTabsList = false;
187
+ this.resetDragAnimation();
188
+ }),
189
+ // Pointer-event mirror of the HTML5 dragover / dragleave handlers
190
+ // below. Drives smooth-reorder for `dndStrategy: 'pointer'` and
191
+ // for touch drags in `'auto'`.
192
+ PointerDragController.getInstance().onDragMove((e) => {
193
+ this._handlePointerDragMove(e.clientX, e.clientY);
194
+ }), addDisposableListener(this.element, 'pointerdown', (event) => {
166
195
  if (event.defaultPrevented) {
167
196
  return;
168
197
  }
@@ -170,135 +199,60 @@ export class Tabs extends CompositeDisposable {
170
199
  if (isLeftClick) {
171
200
  this.accessor.doSetGroupActive(this.group);
172
201
  }
173
- }), addDisposableListener(this._tabsList, 'dragover', (event) => {
174
- var _a, _b, _c, _d;
175
- if (this.accessor.options.disableDnd) {
202
+ }),
203
+ // Trackpad / wheel forwarding. The strip scrolls along its own
204
+ // axis (x for horizontal headers, y for vertical), so deltaY
205
+ // from a plain mouse wheel maps onto the strip's axis too —
206
+ // this gives the VS Code-style "scroll over tab bar to page
207
+ // through tabs" feel. We only consume the event when the strip
208
+ // is actually overflowing in the direction the user wheeled in,
209
+ // so a wheel at the edge of a non-overflowing strip still
210
+ // bubbles up and scrolls the page. `{ passive: false }` is
211
+ // required because we call preventDefault().
212
+ addDisposableListener(this._tabsList, 'wheel', (event) => {
213
+ const isVertical = this._direction === 'vertical';
214
+ const primary = isVertical
215
+ ? event.deltaY || event.deltaX
216
+ : event.deltaX || event.deltaY;
217
+ if (primary === 0) {
176
218
  return;
177
219
  }
178
- // If _animState exists but belongs to a different
179
- // drag (stale from a previous operation), replace it
180
- // so the current drag is handled correctly.
181
- if (this._animState) {
182
- const data = getPanelData();
183
- if ((data === null || data === void 0 ? void 0 : data.tabGroupId) &&
184
- data.groupId !== this.group.id &&
185
- this._animState.sourceTabGroupId !== data.tabGroupId) {
186
- this._animState = null;
187
- }
188
- }
189
- if (!this._animState) {
190
- const data = getPanelData();
191
- // In default animation mode, individual tab drops
192
- // are handled by per-tab Droptargets. But tab group
193
- // chip drags still need tab-list-level handling so
194
- // that drops on gaps / void space work.
195
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) ===
196
- 'default' &&
197
- !(data === null || data === void 0 ? void 0 : data.tabGroupId)) {
198
- return;
199
- }
200
- if (data &&
201
- (data.panelId || data.tabGroupId) &&
202
- data.groupId !== this.group.id) {
203
- const avgWidth = this.getAverageTabWidth();
204
- if (data.tabGroupId) {
205
- // External group drag — look up the
206
- // source tab group to size the gap
207
- const sourceGroup = this.accessor.getPanel(data.groupId);
208
- const sourceTg = sourceGroup === null || sourceGroup === void 0 ? void 0 : sourceGroup.model.getTabGroups().find((tg) => tg.id === data.tabGroupId);
209
- const panelCount = (_b = sourceTg === null || sourceTg === void 0 ? void 0 : sourceTg.panelIds.length) !== null && _b !== void 0 ? _b : 1;
210
- const groupGapWidth = avgWidth * panelCount + avgWidth;
211
- this._animState = {
212
- sourceTabId: '',
213
- sourceIndex: -1,
214
- tabPositions: this.snapshotTabPositions(),
215
- chipPositions: this._tabGroupManager.snapshotChipWidths(),
216
- currentInsertionIndex: null,
217
- targetTabGroupId: null,
218
- sourceTabGroupId: data.tabGroupId,
219
- sourceGroupPanelIds: sourceTg
220
- ? new Set(sourceTg.panelIds)
221
- : new Set(),
222
- sourceChipWidth: avgWidth,
223
- cursorOffsetFromDragLeft: groupGapWidth / 2,
224
- sourceGapWidth: groupGapWidth,
225
- containerLeft: this._tabsList.getBoundingClientRect()
226
- .left,
227
- };
228
- }
229
- else {
230
- this._animState = {
231
- sourceTabId: data.panelId,
232
- sourceIndex: -1,
233
- tabPositions: this.snapshotTabPositions(),
234
- chipPositions: this._tabGroupManager.snapshotChipWidths(),
235
- currentInsertionIndex: null,
236
- targetTabGroupId: null,
237
- sourceTabGroupId: null,
238
- sourceGroupPanelIds: null,
239
- sourceChipWidth: 0,
240
- cursorOffsetFromDragLeft: avgWidth / 2,
241
- sourceGapWidth: avgWidth,
242
- containerLeft: this._tabsList.getBoundingClientRect()
243
- .left,
244
- };
245
- }
246
- }
247
- else {
248
- return;
249
- }
250
- }
251
- event.preventDefault(); // allow drop to fire on the container
252
- // For intra-group drag (sourceIndex >= 0) the gap
253
- // animation is the sole visual indicator — clear any
254
- // stale anchor overlay that may have been set while the
255
- // cursor was over the panel content area or another zone.
256
- // External drags (sourceIndex === -1) leave the overlay
257
- // to the individual tab Droptargets so cross-group
258
- // animation is not disrupted.
259
- if (this._animState.sourceIndex !== -1) {
260
- (_d = (_c = this.group.model.dropTargetContainer) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.clear();
261
- }
262
- this.handleDragOver(event);
263
- }, true), addDisposableListener(this._tabsList, 'dragleave', (event) => {
264
- var _a, _b, _c;
265
- if (!this._animState) {
220
+ const max = isVertical
221
+ ? this._tabsList.scrollHeight -
222
+ this._tabsList.clientHeight
223
+ : this._tabsList.scrollWidth -
224
+ this._tabsList.clientWidth;
225
+ if (max <= 0) {
266
226
  return;
267
227
  }
268
- const related = event.relatedTarget;
269
- // Ignore moves between children of the tabs list
270
- if (related && this._tabsList.contains(related)) {
228
+ const current = isVertical
229
+ ? this._tabsList.scrollTop
230
+ : this._tabsList.scrollLeft;
231
+ // At the edge in the wheel direction: let the page
232
+ // scroll instead of trapping the gesture.
233
+ if ((primary < 0 && current <= 0) ||
234
+ (primary > 0 && current >= max)) {
271
235
  return;
272
236
  }
273
- // If moving into the broader drop zone (e.g. void container,
274
- // left actions), keep _animState alive so the external
275
- // dragover listeners can continue the gap animation.
276
- if (related && ((_a = this._extendedDropZone) === null || _a === void 0 ? void 0 : _a.contains(related))) {
277
- this.resetTabTransforms();
278
- this._animState.currentInsertionIndex = null;
279
- return;
237
+ event.preventDefault();
238
+ // Custom-scrollbar mode wraps the tabs list and installs
239
+ // its own wheel listener that rewrites scrollLeft from a
240
+ // deltaY-only tracker. Without stopPropagation that
241
+ // handler would clobber our deltaX-aware update.
242
+ event.stopPropagation();
243
+ if (isVertical) {
244
+ this._tabsList.scrollTop = current + primary;
280
245
  }
281
- // When leaving toward the void container (empty header space
282
- // to the right), keep the animation state so the drop can
283
- // still land at the end position.
284
- const rt = event.relatedTarget;
285
- const isVoid = this._voidContainer &&
286
- rt &&
287
- (rt === this._voidContainer ||
288
- this._voidContainer.contains(rt));
289
- if (isVoid) {
290
- return;
246
+ else {
247
+ this._tabsList.scrollLeft = current + primary;
291
248
  }
292
- this.resetTabTransforms();
293
- if (this._animState) {
294
- if (this._animState.sourceIndex === -1) {
295
- (_c = (_b = this.group.model.dropTargetContainer) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.clear();
296
- this._animState = null;
297
- }
298
- else {
299
- this._animState.currentInsertionIndex = null;
300
- }
249
+ }, { passive: false }), addDisposableListener(this._tabsList, 'dragover', (event) => {
250
+ if (this._processDragOver(event.clientX)) {
251
+ // Allow `drop` to fire on the tabs list container.
252
+ event.preventDefault();
301
253
  }
254
+ }, true), addDisposableListener(this._tabsList, 'dragleave', (event) => {
255
+ this._processDragLeave(event.relatedTarget);
302
256
  }, true), addDisposableListener(this._tabsList, 'dragend', () => {
303
257
  this.resetDragAnimation();
304
258
  }), addDisposableListener(this._tabsList, 'drop', (event) => {
@@ -420,6 +374,9 @@ export class Tabs extends CompositeDisposable {
420
374
  const disposable = new CompositeDisposable(tab.onDragStart((event) => {
421
375
  var _a;
422
376
  this._onTabDragStart.fire({ nativeEvent: event, panel });
377
+ // Both HTML5 and pointer drags initialize _animState. Cleanup
378
+ // is wired in both paths: HTML5 via dragend/drop on _tabsList,
379
+ // pointer via PointerDragController.onDragEnd subscriptions.
423
380
  if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
424
381
  const tabWidth = tab.element.getBoundingClientRect().width;
425
382
  const sourceIndex = this._tabs.findIndex((x) => x.value === tab);
@@ -682,6 +639,7 @@ export class Tabs extends CompositeDisposable {
682
639
  for (const tab of this._tabs) {
683
640
  tab.value.updateDragAndDropState();
684
641
  }
642
+ this._tabGroupManager.updateDragAndDropState();
685
643
  }
686
644
  /**
687
645
  * Synchronize chip elements and CSS classes for all tab groups
@@ -693,6 +651,13 @@ export class Tabs extends CompositeDisposable {
693
651
  refreshTabGroupAccent() {
694
652
  this._tabGroupManager.refreshAccents();
695
653
  }
654
+ /**
655
+ * Tabs-list-specific side effects of a chip drag start. The chip's
656
+ * drag sources (constructed by `TabGroupManager`) own the transfer
657
+ * payload, iframe shielding, dataTransfer setup, and the HTML5 drag
658
+ * image. This method just sets up the smooth-reorder anim state and
659
+ * collapses the source-group tabs in the tabs list.
660
+ */
696
661
  _handleChipDragStart(tabGroup, chip, event) {
697
662
  var _a;
698
663
  const firstPanelId = tabGroup.panelIds[0];
@@ -723,89 +688,79 @@ export class Tabs extends CompositeDisposable {
723
688
  sourceGapWidth: groupGapWidth,
724
689
  containerLeft: this._tabsList.getBoundingClientRect().left,
725
690
  };
726
- // Set LocalSelectionTransfer so drop targets recognise this as
727
- // an internal dockview drag. panelId is null (group-level),
728
- // tabGroupId identifies which tab group is being dragged.
729
- const panelTransfer = LocalSelectionTransfer.getInstance();
730
- panelTransfer.setData([
731
- new PanelTransfer(this.accessor.id, this.group.id, null, tabGroup.id),
732
- ], PanelTransfer.prototype);
733
- const iframes = disableIframePointEvents();
734
- // The dragend listener on `_tabsList` is unreachable for chip
735
- // drags because cross-group drops detach the chip from the DOM
736
- // before dragend fires (the source tab group becomes empty, so
737
- // `_positionChipForGroup` removes the chip element). Without
738
- // bubbling, the tabsList listener never runs and `_animState`,
739
- // `_chipDragCleanup`, and the dragging CSS classes leak. Listen
740
- // directly on the chip element so cleanup happens regardless of
741
- // whether it's still attached. (Issue #1254.)
742
- const chipElement = chip.element;
743
- const onChipDragEnd = () => {
744
- chipElement.removeEventListener('dragend', onChipDragEnd);
745
- this.resetDragAnimation();
746
- };
747
- chipElement.addEventListener('dragend', onChipDragEnd);
748
- this._chipDragCleanup = {
749
- dispose: () => {
750
- chipElement.removeEventListener('dragend', onChipDragEnd);
751
- panelTransfer.clearData(PanelTransfer.prototype);
752
- iframes.release();
753
- },
754
- };
755
- if (event.dataTransfer) {
756
- event.dataTransfer.effectAllowed = 'move';
757
- if (event.dataTransfer.items.length === 0) {
758
- event.dataTransfer.setData('text/plain', '');
759
- }
760
- }
761
- if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
762
- // Collapse group tabs + chip after the browser
763
- // captures the drag image, then open the gap at the
764
- // source position — all instant (no transitions).
765
- const groupPanelIds = new Set(tabGroup.panelIds);
766
- this._pendingCollapse = true;
767
- requestAnimationFrame(() => {
768
- var _a;
769
- var _b;
770
- this._pendingCollapse = false;
771
- if (!this._animState) {
772
- return;
773
- }
774
- // Collapse all group tabs instantly
775
- for (const t of this._tabs) {
776
- if (groupPanelIds.has(t.value.panel.id)) {
777
- t.value.element.style.transition = 'none';
778
- toggleClass(t.value.element, 'dv-tab--dragging', true);
779
- }
780
- }
781
- // Collapse the group chip instantly
782
- const chipEntry = this._tabGroupManager.chipRenderers.get(tabGroup.id);
783
- if (chipEntry) {
784
- chipEntry.chip.element.style.transition = 'none';
785
- toggleClass(chipEntry.chip.element, 'dv-tab-group-chip--dragging', true);
786
- }
787
- // Single reflow for the entire batch
788
- void this._tabsList.offsetHeight;
789
- const underline = this._tabGroupManager.groupUnderlines.get(tabGroup.id);
790
- if (underline) {
791
- underline.style.display = 'none';
792
- }
793
- (_a = (_b = this._animState).currentInsertionIndex) !== null && _a !== void 0 ? _a : (_b.currentInsertionIndex = firstIdx);
794
- // Apply gap with transitions disabled
795
- this.applyDragOverTransforms(true);
796
- // Re-enable transitions for subsequent moves
797
- for (const t of this._tabs) {
798
- if (groupPanelIds.has(t.value.panel.id)) {
799
- t.value.element.style.removeProperty('transition');
800
- }
691
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) !== 'smooth') {
692
+ return;
693
+ }
694
+ // Collapse group tabs + chip after the browser captures the drag
695
+ // image, then open the gap at the source position — all instant
696
+ // (no transitions).
697
+ const groupPanelIds = new Set(tabGroup.panelIds);
698
+ this._pendingCollapse = true;
699
+ requestAnimationFrame(() => {
700
+ var _a;
701
+ var _b;
702
+ this._pendingCollapse = false;
703
+ if (!this._animState) {
704
+ return;
705
+ }
706
+ // Collapse all group tabs instantly
707
+ for (const t of this._tabs) {
708
+ if (groupPanelIds.has(t.value.panel.id)) {
709
+ t.value.element.style.transition = 'none';
710
+ toggleClass(t.value.element, 'dv-tab--dragging', true);
801
711
  }
802
- if (chipEntry) {
803
- chipEntry.chip.element.style.removeProperty('transition');
712
+ }
713
+ // Collapse the group chip instantly
714
+ const chipEntry = this._tabGroupManager.chipRenderers.get(tabGroup.id);
715
+ if (chipEntry) {
716
+ chipEntry.chip.element.style.transition = 'none';
717
+ toggleClass(chipEntry.chip.element, 'dv-tab-group-chip--dragging', true);
718
+ }
719
+ // Single reflow for the entire batch
720
+ void this._tabsList.offsetHeight;
721
+ const underline = this._tabGroupManager.groupUnderlines.get(tabGroup.id);
722
+ if (underline) {
723
+ underline.style.display = 'none';
724
+ }
725
+ (_a = (_b = this._animState).currentInsertionIndex) !== null && _a !== void 0 ? _a : (_b.currentInsertionIndex = firstIdx);
726
+ this.applyDragOverTransforms(true);
727
+ for (const t of this._tabs) {
728
+ if (groupPanelIds.has(t.value.panel.id)) {
729
+ t.value.element.style.removeProperty('transition');
804
730
  }
805
- });
731
+ }
732
+ if (chipEntry) {
733
+ chipEntry.chip.element.style.removeProperty('transition');
734
+ }
735
+ });
736
+ }
737
+ /**
738
+ * A drop on a tab group chip means "insert before this group". Resolve to
739
+ * the index of the group's first tab, adjusting for same-group removal
740
+ * (when the source tab is currently to the left of the target slot, its
741
+ * removal shifts the insertion index down by one). Always clears
742
+ * `targetTabGroupId` so the dropped tab lands outside the group.
743
+ */
744
+ _handleChipDrop(tabGroup, event) {
745
+ const firstPanelId = tabGroup.panelIds[0];
746
+ if (!firstPanelId) {
747
+ return;
748
+ }
749
+ const insertionIndex = this._tabs.findIndex((x) => x.value.panel.id === firstPanelId);
750
+ if (insertionIndex === -1) {
751
+ return;
806
752
  }
807
- // Build a composite drag image showing chip + group tabs
808
- this._tabGroupManager.setGroupDragImage(event, tabGroup, chip.element);
753
+ const data = getPanelData();
754
+ const sourceIndex = data && data.groupId === this.group.id && data.panelId
755
+ ? this._tabs.findIndex((x) => x.value.panel.id === data.panelId)
756
+ : -1;
757
+ const adjustedIndex = insertionIndex -
758
+ (sourceIndex !== -1 && sourceIndex < insertionIndex ? 1 : 0);
759
+ this._onDrop.fire({
760
+ event: event.nativeEvent,
761
+ index: adjustedIndex,
762
+ targetTabGroupId: null,
763
+ });
809
764
  }
810
765
  /**
811
766
  * Sets the broader container that is part of the same logical drop surface
@@ -867,6 +822,164 @@ export class Tabs extends CompositeDisposable {
867
822
  }
868
823
  return total / this._tabs.length;
869
824
  }
825
+ /**
826
+ * Pointer-event entry point. The HTML5 path enters via the per-element
827
+ * `dragover` listener; this one hit-tests the global pointer-drag
828
+ * position against the tabs list and routes through the same shared
829
+ * `_processDragOver` / `_processDragLeave` helpers.
830
+ */
831
+ _handlePointerDragMove(clientX, clientY) {
832
+ var _a;
833
+ const sourceDoc = (_a = this._tabsList.ownerDocument) !== null && _a !== void 0 ? _a : document;
834
+ const elAtPoint = sourceDoc.elementFromPoint(clientX, clientY);
835
+ const inside = !!elAtPoint &&
836
+ (this._tabsList.contains(elAtPoint) ||
837
+ (!!this._extendedDropZone &&
838
+ this._extendedDropZone.contains(elAtPoint)));
839
+ if (!inside) {
840
+ if (this._pointerInsideTabsList) {
841
+ this._pointerInsideTabsList = false;
842
+ this._processDragLeave(elAtPoint);
843
+ }
844
+ return;
845
+ }
846
+ this._pointerInsideTabsList = true;
847
+ this._processDragOver(clientX);
848
+ }
849
+ /**
850
+ * Shared body of the dragover entry point. Refreshes stale anim state
851
+ * for a changed drag identity, initializes anim state for incoming
852
+ * cross-group drags, and dispatches to the gap-following math in
853
+ * `handleDragOver`. Returns true when this tabs list has taken
854
+ * ownership of the drag — HTML5 callers use this to gate
855
+ * `event.preventDefault()`.
856
+ */
857
+ _processDragOver(clientX) {
858
+ var _a, _b, _c, _d;
859
+ if (this.accessor.options.disableDnd) {
860
+ return false;
861
+ }
862
+ // Stale-state guard: if a previous drag's anim state is still here
863
+ // but the current drag is a different identity, drop the stale one
864
+ // so the new drag starts from a clean slate.
865
+ if (this._animState) {
866
+ const data = getPanelData();
867
+ if ((data === null || data === void 0 ? void 0 : data.tabGroupId) &&
868
+ data.groupId !== this.group.id &&
869
+ this._animState.sourceTabGroupId !== data.tabGroupId) {
870
+ this._animState = null;
871
+ }
872
+ }
873
+ if (!this._animState) {
874
+ const data = getPanelData();
875
+ // In default animation mode, individual tab drops are handled
876
+ // by per-tab Droptargets; only chip drags need tabs-list-level
877
+ // handling so drops on void space still work.
878
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'default' &&
879
+ !(data === null || data === void 0 ? void 0 : data.tabGroupId)) {
880
+ return false;
881
+ }
882
+ if (data &&
883
+ (data.panelId || data.tabGroupId) &&
884
+ data.groupId !== this.group.id) {
885
+ const avgWidth = this.getAverageTabWidth();
886
+ if (data.tabGroupId) {
887
+ // External group drag — look up the source group to
888
+ // size the gap.
889
+ const sourceGroup = this.accessor.getPanel(data.groupId);
890
+ const sourceTg = sourceGroup === null || sourceGroup === void 0 ? void 0 : sourceGroup.model.getTabGroups().find((tg) => tg.id === data.tabGroupId);
891
+ const panelCount = (_b = sourceTg === null || sourceTg === void 0 ? void 0 : sourceTg.panelIds.length) !== null && _b !== void 0 ? _b : 1;
892
+ const groupGapWidth = avgWidth * panelCount + avgWidth;
893
+ this._animState = {
894
+ sourceTabId: '',
895
+ sourceIndex: -1,
896
+ tabPositions: this.snapshotTabPositions(),
897
+ chipPositions: this._tabGroupManager.snapshotChipWidths(),
898
+ currentInsertionIndex: null,
899
+ targetTabGroupId: null,
900
+ sourceTabGroupId: data.tabGroupId,
901
+ sourceGroupPanelIds: sourceTg
902
+ ? new Set(sourceTg.panelIds)
903
+ : new Set(),
904
+ sourceChipWidth: avgWidth,
905
+ cursorOffsetFromDragLeft: groupGapWidth / 2,
906
+ sourceGapWidth: groupGapWidth,
907
+ containerLeft: this._tabsList.getBoundingClientRect().left,
908
+ };
909
+ }
910
+ else {
911
+ this._animState = {
912
+ sourceTabId: data.panelId,
913
+ sourceIndex: -1,
914
+ tabPositions: this.snapshotTabPositions(),
915
+ chipPositions: this._tabGroupManager.snapshotChipWidths(),
916
+ currentInsertionIndex: null,
917
+ targetTabGroupId: null,
918
+ sourceTabGroupId: null,
919
+ sourceGroupPanelIds: null,
920
+ sourceChipWidth: 0,
921
+ cursorOffsetFromDragLeft: avgWidth / 2,
922
+ sourceGapWidth: avgWidth,
923
+ containerLeft: this._tabsList.getBoundingClientRect().left,
924
+ };
925
+ }
926
+ }
927
+ else {
928
+ return false;
929
+ }
930
+ }
931
+ // For intra-group drag (sourceIndex >= 0) the gap animation is the
932
+ // sole visual indicator — clear any stale anchor overlay that may
933
+ // have been set while the cursor was over the panel content area or
934
+ // another zone. External drags (sourceIndex === -1) leave the
935
+ // overlay to the individual tab Droptargets so cross-group
936
+ // animation is not disrupted.
937
+ if (this._animState.sourceIndex !== -1) {
938
+ (_d = (_c = this.group.model.dropTargetContainer) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.clear();
939
+ }
940
+ this.handleDragOver({ clientX });
941
+ return true;
942
+ }
943
+ /**
944
+ * Shared body of the dragleave entry point. Preserves anim state when
945
+ * the drag moves between tabs-list children, into the extended drop
946
+ * zone, or into the void container; tears it down otherwise.
947
+ */
948
+ _processDragLeave(related) {
949
+ var _a, _b, _c;
950
+ if (!this._animState) {
951
+ return;
952
+ }
953
+ // Moves between children of the tabs list aren't real leaves.
954
+ if (related && this._tabsList.contains(related)) {
955
+ return;
956
+ }
957
+ // Moving into the broader drop zone (e.g. void container, left
958
+ // actions) — keep anim state alive so external listeners can
959
+ // continue the gap animation.
960
+ if (related && ((_a = this._extendedDropZone) === null || _a === void 0 ? void 0 : _a.contains(related))) {
961
+ this.resetTabTransforms();
962
+ this._animState.currentInsertionIndex = null;
963
+ return;
964
+ }
965
+ // Leaving toward the void container (empty header space to the
966
+ // right): keep anim state so a drop can still land at the end.
967
+ const isVoid = this._voidContainer &&
968
+ related &&
969
+ (related === this._voidContainer ||
970
+ this._voidContainer.contains(related));
971
+ if (isVoid) {
972
+ return;
973
+ }
974
+ this.resetTabTransforms();
975
+ if (this._animState.sourceIndex === -1) {
976
+ (_c = (_b = this.group.model.dropTargetContainer) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.clear();
977
+ this._animState = null;
978
+ }
979
+ else {
980
+ this._animState.currentInsertionIndex = null;
981
+ }
982
+ }
870
983
  handleDragOver(event) {
871
984
  var _a, _b, _c, _d, _e;
872
985
  if (!this._animState) {
@@ -987,11 +1100,15 @@ export class Tabs extends CompositeDisposable {
987
1100
  if (!isInsideRange && !isJustBeforeGroup) {
988
1101
  continue;
989
1102
  }
990
- if (isGroupDrag) {
1103
+ if (isGroupDrag && isInsideRange) {
991
1104
  // A group cannot be dropped inside another group.
992
1105
  // Snap the insertion index to just before or just
993
1106
  // after this group based on cursor position relative
994
- // to the group's midpoint.
1107
+ // to the group's midpoint. Only applies when the
1108
+ // insertion would land *inside* the group — for
1109
+ // `isJustBeforeGroup`, the index is already outside
1110
+ // (immediately left of the group) and is a valid
1111
+ // drop position, so leave it untouched (issue #1264).
995
1112
  const groupMid = (firstIdx + lastIdx + 1) / 2;
996
1113
  if (insertionIndex < groupMid) {
997
1114
  insertionIndex = firstIdx;
@@ -1002,6 +1119,12 @@ export class Tabs extends CompositeDisposable {
1002
1119
  // targetTabGroupId stays null
1003
1120
  break;
1004
1121
  }
1122
+ if (isGroupDrag && isJustBeforeGroup) {
1123
+ // Cursor is just before the group — accept this
1124
+ // index as-is. Groups can be dropped at the slot
1125
+ // immediately left of another group's first tab.
1126
+ break;
1127
+ }
1005
1128
  if (isJustBeforeGroup) {
1006
1129
  // Check whether only the source tab (or source group
1007
1130
  // tabs) sits between insertionIndex and firstIdx.
@@ -1266,20 +1389,23 @@ export class Tabs extends CompositeDisposable {
1266
1389
  * in the model, and run a FLIP animation.
1267
1390
  */
1268
1391
  _commitGroupMove(sourceTabGroupId, insertionIndex) {
1269
- var _a, _b, _c;
1270
- // Read transfer data BEFORE disposing cleanup — disposing
1271
- // _chipDragCleanup clears the global LocalSelectionTransfer
1272
- // singleton which getPanelData() reads from.
1392
+ var _a, _b;
1393
+ // Read transfer data first.
1273
1394
  const data = getPanelData();
1274
- (_a = this._chipDragCleanup) === null || _a === void 0 ? void 0 : _a.dispose();
1275
- this._chipDragCleanup = null;
1395
+ // Synchronously dispose the source chip's drag sources, which
1396
+ // clears the panelTransfer payload + iframe shield. Cross-group
1397
+ // moves dissolve the source chip on a microtask, which is too
1398
+ // late: a synchronous `getPanelData()` after this method (or any
1399
+ // sibling dragover handler firing in the same tick) would
1400
+ // otherwise see stale data still referencing the old tabGroupId.
1401
+ this._tabGroupManager.disposeChipDrag(sourceTabGroupId);
1276
1402
  // Check if the tab group exists in this group (within-group reorder)
1277
1403
  // or in another group (cross-group move).
1278
1404
  const isLocal = this.group.model
1279
1405
  .getTabGroups()
1280
1406
  .some((tg) => tg.id === sourceTabGroupId);
1281
1407
  if (isLocal) {
1282
- if (((_b = this.accessor.options.theme) === null || _b === void 0 ? void 0 : _b.tabAnimation) === 'smooth') {
1408
+ if (((_a = this.accessor.options.theme) === null || _a === void 0 ? void 0 : _a.tabAnimation) === 'smooth') {
1283
1409
  this._clearGroupDragClasses(sourceTabGroupId);
1284
1410
  const firstPositions = this.snapshotTabPositions();
1285
1411
  this.resetTabTransforms();
@@ -1307,7 +1433,7 @@ export class Tabs extends CompositeDisposable {
1307
1433
  this.accessor.moveGroupOrPanel({
1308
1434
  from: {
1309
1435
  groupId: data.groupId,
1310
- tabGroupId: (_c = data.tabGroupId) !== null && _c !== void 0 ? _c : sourceTabGroupId,
1436
+ tabGroupId: (_b = data.tabGroupId) !== null && _b !== void 0 ? _b : sourceTabGroupId,
1311
1437
  },
1312
1438
  to: {
1313
1439
  group: this.group,
@@ -1335,22 +1461,27 @@ export class Tabs extends CompositeDisposable {
1335
1461
  this._tabGroupManager.skipNextCollapseAnimation = true;
1336
1462
  }
1337
1463
  resetDragAnimation() {
1338
- var _a, _b;
1339
1464
  this._pendingCollapse = false;
1340
- this.resetTabTransforms();
1341
- // Clear drag-collapse classes instantly (no transition)
1342
- if ((_a = this._animState) === null || _a === void 0 ? void 0 : _a.sourceTabGroupId) {
1343
- this._clearGroupDragClasses(this._animState.sourceTabGroupId);
1344
- }
1345
- else {
1346
- this._removeClassInstantlyBatch(this._tabs.map((t) => t.value.element), 'dv-tab--dragging');
1347
- }
1348
- this._animState = null;
1349
- (_b = this._chipDragCleanup) === null || _b === void 0 ? void 0 : _b.dispose();
1350
- this._chipDragCleanup = null;
1351
- // Restore any hidden underlines from group drags
1352
- for (const [, el] of this._tabGroupManager.groupUnderlines) {
1353
- el.style.removeProperty('display');
1465
+ // After a drop, `tab.onDrop` consumes _animState (sets it to null)
1466
+ // and immediately calls `runFlipAnimation`, which sets transforms
1467
+ // and queues an rAF to trigger the CSS transition. dragend fires
1468
+ // synchronously on the source element BEFORE that rAF runs — if
1469
+ // we cleared transforms here we'd clobber the in-flight FLIP, so
1470
+ // gate the cleanup on _animState still being set (i.e. drag was
1471
+ // cancelled rather than dropped).
1472
+ if (this._animState) {
1473
+ this.resetTabTransforms();
1474
+ if (this._animState.sourceTabGroupId) {
1475
+ this._clearGroupDragClasses(this._animState.sourceTabGroupId);
1476
+ }
1477
+ else {
1478
+ this._removeClassInstantlyBatch(this._tabs.map((t) => t.value.element), 'dv-tab--dragging');
1479
+ }
1480
+ this._animState = null;
1481
+ // Restore any hidden underlines from group drags.
1482
+ for (const [, el] of this._tabGroupManager.groupUnderlines) {
1483
+ el.style.removeProperty('display');
1484
+ }
1354
1485
  }
1355
1486
  }
1356
1487
  runFlipAnimation(firstPositions, sourceTabId, isCrossGroup = false, animRange) {