dockview-core 5.1.0 → 5.2.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * dockview-core
3
- * @version 5.1.0
3
+ * @version 5.2.0
4
4
  * @link https://github.com/mathuo/dockview
5
5
  * @license MIT
6
6
  */
@@ -5089,6 +5089,8 @@
5089
5089
  this.onDrop = this._onDropped.event;
5090
5090
  this._onDragStart = new Emitter();
5091
5091
  this.onDragStart = this._onDragStart.event;
5092
+ this._onDragEnd = new Emitter();
5093
+ this.onDragEnd = this._onDragEnd.event;
5092
5094
  this._element = document.createElement('div');
5093
5095
  this._element.className = 'dv-tab';
5094
5096
  this._element.tabIndex = 0;
@@ -5104,6 +5106,10 @@
5104
5106
  }
5105
5107
  const data = getPanelData();
5106
5108
  if (data && this.accessor.id === data.viewId) {
5109
+ if (this.accessor.options.tabAnimation === 'smooth' &&
5110
+ data.groupId === this.group.id) {
5111
+ return false;
5112
+ }
5107
5113
  return true;
5108
5114
  }
5109
5115
  return this.group.model.canDisplayOverlay(event, position, 'tab');
@@ -5111,7 +5117,7 @@
5111
5117
  getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
5112
5118
  });
5113
5119
  this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
5114
- this.addDisposables(this._onPointDown, this._onDropped, this._onDragStart, this.dragHandler.onDragStart((event) => {
5120
+ this.addDisposables(this._onPointDown, this._onDropped, this._onDragStart, this._onDragEnd, this.dragHandler.onDragStart((event) => {
5115
5121
  if (event.dataTransfer) {
5116
5122
  const style = getComputedStyle(this.element);
5117
5123
  const newNode = this.element.cloneNode(true);
@@ -5123,6 +5129,16 @@
5123
5129
  });
5124
5130
  }
5125
5131
  this._onDragStart.fire(event);
5132
+ if (this.accessor.options.tabAnimation === 'smooth') {
5133
+ // Delay collapse to next frame so the browser
5134
+ // captures the full drag image first
5135
+ requestAnimationFrame(() => {
5136
+ toggleClass(this.element, 'dv-tab--dragging', true);
5137
+ });
5138
+ }
5139
+ }), addDisposableListener(this._element, 'dragend', (event) => {
5140
+ toggleClass(this.element, 'dv-tab--dragging', false);
5141
+ this._onDragEnd.fire(event);
5126
5142
  }), this.dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => {
5127
5143
  this._onPointDown.fire(event);
5128
5144
  }), this.dropTarget.onDrop((event) => {
@@ -5476,6 +5492,7 @@
5476
5492
  this.selectedIndex = -1;
5477
5493
  this._showTabsOverflowControl = false;
5478
5494
  this._direction = 'horizontal';
5495
+ this._animState = null;
5479
5496
  this._onTabDragStart = new Emitter();
5480
5497
  this.onTabDragStart = this._onTabDragStart.event;
5481
5498
  this._onDrop = new Emitter();
@@ -5504,7 +5521,88 @@
5504
5521
  if (isLeftClick) {
5505
5522
  this.accessor.doSetGroupActive(this.group);
5506
5523
  }
5507
- }), exports.DockviewDisposable.from(() => {
5524
+ }), addDisposableListener(this._tabsList, 'dragover', (event) => {
5525
+ if (!this._animState) {
5526
+ // Check for external drag from another group
5527
+ if (this.accessor.options.tabAnimation !== 'smooth' ||
5528
+ this.accessor.options.disableDnd) {
5529
+ return;
5530
+ }
5531
+ const data = getPanelData();
5532
+ if (data &&
5533
+ data.panelId &&
5534
+ data.groupId !== this.group.id) {
5535
+ this._animState = {
5536
+ sourceTabId: data.panelId,
5537
+ sourceIndex: -1,
5538
+ tabPositions: this.snapshotTabPositions(),
5539
+ currentInsertionIndex: null,
5540
+ };
5541
+ }
5542
+ else {
5543
+ return;
5544
+ }
5545
+ }
5546
+ this.handleDragOver(event);
5547
+ }, true), addDisposableListener(this._tabsList, 'dragleave', (event) => {
5548
+ if (!this._animState) {
5549
+ return;
5550
+ }
5551
+ // Only handle if leaving the container itself, not moving between children
5552
+ if (event.relatedTarget &&
5553
+ this._tabsList.contains(event.relatedTarget)) {
5554
+ return;
5555
+ }
5556
+ this.resetTabTransforms();
5557
+ if (this._animState) {
5558
+ if (this._animState.sourceIndex === -1) {
5559
+ // External drag left — clear state entirely
5560
+ // (no dragend will fire on this tab list)
5561
+ this._animState = null;
5562
+ }
5563
+ else {
5564
+ this._animState.currentInsertionIndex = null;
5565
+ }
5566
+ }
5567
+ }, true), addDisposableListener(this._tabsList, 'dragend', () => {
5568
+ // Only fires for cancel (not after successful drop, since
5569
+ // source tab is removed from DOM and doesn't bubble)
5570
+ this.resetDragAnimation();
5571
+ }), addDisposableListener(this._tabsList, 'drop', (event) => {
5572
+ if (this.accessor.options.tabAnimation !== 'smooth' ||
5573
+ !this._animState ||
5574
+ this._animState.currentInsertionIndex === null) {
5575
+ return;
5576
+ }
5577
+ event.stopPropagation();
5578
+ event.preventDefault();
5579
+ const animState = this._animState;
5580
+ this._animState = null;
5581
+ const insertionIndex = animState.currentInsertionIndex;
5582
+ const sourceIndex = animState.sourceIndex;
5583
+ // After the source tab is removed, indices after it shift
5584
+ // down by one, so adjust the target index accordingly.
5585
+ const adjustedIndex = insertionIndex -
5586
+ (sourceIndex !== -1 && sourceIndex < insertionIndex
5587
+ ? 1
5588
+ : 0);
5589
+ // No-op: drop at the same position, nothing to animate
5590
+ if (adjustedIndex === sourceIndex) {
5591
+ this.resetTabTransforms();
5592
+ return;
5593
+ }
5594
+ // Snapshot current visual positions (with margins still applied)
5595
+ // before resetting transforms, so FLIP starts from what the
5596
+ // user currently sees — not from a teleported state.
5597
+ const firstPositions = this.snapshotTabPositions();
5598
+ this.resetTabTransforms();
5599
+ this._onDrop.fire({ event, index: adjustedIndex });
5600
+ this.runFlipAnimation(firstPositions, animState.sourceTabId, animState.sourceIndex === -1, {
5601
+ from: Math.min(sourceIndex, adjustedIndex),
5602
+ to: Math.max(sourceIndex, adjustedIndex),
5603
+ });
5604
+ }, true), exports.DockviewDisposable.from(() => {
5605
+ this.resetDragAnimation();
5508
5606
  for (const { value, disposable } of this._tabs) {
5509
5607
  disposable.dispose();
5510
5608
  value.dispose();
@@ -5544,6 +5642,14 @@
5544
5642
  tab.setContent(panel.view.tab);
5545
5643
  const disposable = new CompositeDisposable(tab.onDragStart((event) => {
5546
5644
  this._onTabDragStart.fire({ nativeEvent: event, panel });
5645
+ if (this.accessor.options.tabAnimation === 'smooth') {
5646
+ this._animState = {
5647
+ sourceTabId: panel.id,
5648
+ sourceIndex: this._tabs.findIndex((x) => x.value === tab),
5649
+ tabPositions: this.snapshotTabPositions(),
5650
+ currentInsertionIndex: null,
5651
+ };
5652
+ }
5547
5653
  }), tab.onPointerDown((event) => {
5548
5654
  if (event.defaultPrevented) {
5549
5655
  return;
@@ -5573,10 +5679,29 @@
5573
5679
  break;
5574
5680
  }
5575
5681
  }), tab.onDrop((event) => {
5576
- this._onDrop.fire({
5577
- event: event.nativeEvent,
5578
- index: this._tabs.findIndex((x) => x.value === tab),
5579
- });
5682
+ const animState = this._animState;
5683
+ this._animState = null;
5684
+ const dropIndex = this._tabs.findIndex((x) => x.value === tab);
5685
+ if (animState) {
5686
+ const firstPositions = this.snapshotTabPositions();
5687
+ this.resetTabTransforms();
5688
+ this._onDrop.fire({
5689
+ event: event.nativeEvent,
5690
+ index: dropIndex,
5691
+ });
5692
+ this.runFlipAnimation(firstPositions, animState.sourceTabId, animState.sourceIndex === -1, animState.sourceIndex !== -1
5693
+ ? {
5694
+ from: Math.min(animState.sourceIndex, dropIndex),
5695
+ to: Math.max(animState.sourceIndex, dropIndex),
5696
+ }
5697
+ : undefined);
5698
+ }
5699
+ else {
5700
+ this._onDrop.fire({
5701
+ event: event.nativeEvent,
5702
+ index: dropIndex,
5703
+ });
5704
+ }
5580
5705
  }), tab.onWillShowOverlay((event) => {
5581
5706
  this._onWillShowOverlay.fire(new DockviewWillShowOverlayLocationEvent(event, {
5582
5707
  kind: 'tab',
@@ -5588,14 +5713,29 @@
5588
5713
  }));
5589
5714
  const value = { value: tab, disposable };
5590
5715
  this.addTab(value, index);
5716
+ // If a tab was added during active drag, refresh positions
5717
+ if (this._animState) {
5718
+ this._animState.tabPositions = this.snapshotTabPositions();
5719
+ this.applyDragOverTransforms();
5720
+ }
5591
5721
  }
5592
5722
  delete(id) {
5723
+ var _a;
5724
+ if (((_a = this._animState) === null || _a === void 0 ? void 0 : _a.sourceTabId) === id) {
5725
+ this.resetTabTransforms();
5726
+ this._animState = null;
5727
+ }
5593
5728
  const index = this.indexOf(id);
5594
5729
  const tabToRemove = this._tabs.splice(index, 1)[0];
5595
5730
  const { value, disposable } = tabToRemove;
5596
5731
  disposable.dispose();
5597
5732
  value.dispose();
5598
5733
  value.element.remove();
5734
+ // If a non-source tab was removed during active drag, refresh positions
5735
+ if (this._animState) {
5736
+ this._animState.tabPositions = this.snapshotTabPositions();
5737
+ this.applyDragOverTransforms();
5738
+ }
5599
5739
  }
5600
5740
  addTab(tab, index = this._tabs.length) {
5601
5741
  if (index < 0 || index > this._tabs.length) {
@@ -5624,6 +5764,157 @@
5624
5764
  tab.value.updateDragAndDropState();
5625
5765
  }
5626
5766
  }
5767
+ snapshotTabPositions() {
5768
+ const positions = new Map();
5769
+ for (const tab of this._tabs) {
5770
+ positions.set(tab.value.panel.id, tab.value.element.getBoundingClientRect());
5771
+ }
5772
+ return positions;
5773
+ }
5774
+ getAverageTabWidth() {
5775
+ if (this._tabs.length === 0) {
5776
+ return 0;
5777
+ }
5778
+ let totalWidth = 0;
5779
+ for (const tab of this._tabs) {
5780
+ totalWidth += tab.value.element.getBoundingClientRect().width;
5781
+ }
5782
+ return totalWidth / this._tabs.length;
5783
+ }
5784
+ handleDragOver(event) {
5785
+ if (!this._animState) {
5786
+ return;
5787
+ }
5788
+ const mouseX = event.clientX;
5789
+ let insertionIndex = null;
5790
+ for (let i = 0; i < this._tabs.length; i++) {
5791
+ const tab = this._tabs[i].value;
5792
+ if (tab.panel.id === this._animState.sourceTabId) {
5793
+ continue;
5794
+ }
5795
+ const rect = tab.element.getBoundingClientRect();
5796
+ const midpoint = rect.left + rect.width / 2;
5797
+ if (mouseX < midpoint) {
5798
+ insertionIndex = i;
5799
+ break;
5800
+ }
5801
+ insertionIndex = i + 1;
5802
+ }
5803
+ if (insertionIndex === this._animState.currentInsertionIndex) {
5804
+ return;
5805
+ }
5806
+ this._animState.currentInsertionIndex = insertionIndex;
5807
+ this.applyDragOverTransforms();
5808
+ }
5809
+ applyDragOverTransforms() {
5810
+ if (!this._animState ||
5811
+ this._animState.currentInsertionIndex === null) {
5812
+ this.resetTabTransforms();
5813
+ return;
5814
+ }
5815
+ const insertionIndex = this._animState.currentInsertionIndex;
5816
+ const sourceRect = this._animState.tabPositions.get(this._animState.sourceTabId);
5817
+ const gapWidth = sourceRect
5818
+ ? sourceRect.width
5819
+ : this.getAverageTabWidth();
5820
+ // Find the first non-source tab at insertionIndex to receive the gap margin
5821
+ let gapApplied = false;
5822
+ for (let i = 0; i < this._tabs.length; i++) {
5823
+ const tab = this._tabs[i].value;
5824
+ if (tab.panel.id === this._animState.sourceTabId) {
5825
+ continue;
5826
+ }
5827
+ if (!gapApplied && i >= insertionIndex) {
5828
+ tab.element.style.marginLeft = `${gapWidth}px`;
5829
+ toggleClass(tab.element, 'dv-tab--shifting', true);
5830
+ gapApplied = true;
5831
+ }
5832
+ else {
5833
+ // Keep shifting class while margin animates back to 0,
5834
+ // then remove both once the transition ends
5835
+ if (tab.element.style.marginLeft) {
5836
+ tab.element.style.marginLeft = '0px';
5837
+ toggleClass(tab.element, 'dv-tab--shifting', true);
5838
+ const onEnd = () => {
5839
+ tab.element.style.removeProperty('margin-left');
5840
+ toggleClass(tab.element, 'dv-tab--shifting', false);
5841
+ tab.element.removeEventListener('transitionend', onEnd);
5842
+ };
5843
+ tab.element.addEventListener('transitionend', onEnd);
5844
+ }
5845
+ else {
5846
+ toggleClass(tab.element, 'dv-tab--shifting', false);
5847
+ }
5848
+ }
5849
+ }
5850
+ }
5851
+ resetTabTransforms() {
5852
+ for (const tab of this._tabs) {
5853
+ tab.value.element.style.removeProperty('margin-left');
5854
+ tab.value.element.style.removeProperty('transform');
5855
+ toggleClass(tab.value.element, 'dv-tab--shifting', false);
5856
+ }
5857
+ }
5858
+ resetDragAnimation() {
5859
+ this.resetTabTransforms();
5860
+ this._animState = null;
5861
+ for (const tab of this._tabs) {
5862
+ toggleClass(tab.value.element, 'dv-tab--dragging', false);
5863
+ }
5864
+ }
5865
+ runFlipAnimation(firstPositions, sourceTabId, isCrossGroup = false, animRange) {
5866
+ let hasAnimation = false;
5867
+ for (let i = 0; i < this._tabs.length; i++) {
5868
+ const tab = this._tabs[i];
5869
+ const panelId = tab.value.panel.id;
5870
+ if (panelId === sourceTabId) {
5871
+ if (isCrossGroup) {
5872
+ // Newly inserted tab: slide in from the right
5873
+ const rect = tab.value.element.getBoundingClientRect();
5874
+ tab.value.element.style.transform = `translateX(${rect.width}px)`;
5875
+ toggleClass(tab.value.element, 'dv-tab--shifting', true);
5876
+ hasAnimation = true;
5877
+ }
5878
+ continue;
5879
+ }
5880
+ // Skip tabs outside the affected range (they don't logically move)
5881
+ if (animRange !== undefined &&
5882
+ (i < animRange.from || i > animRange.to)) {
5883
+ continue;
5884
+ }
5885
+ const firstRect = firstPositions.get(panelId);
5886
+ if (!firstRect) {
5887
+ continue;
5888
+ }
5889
+ const lastRect = tab.value.element.getBoundingClientRect();
5890
+ const deltaX = firstRect.left - lastRect.left;
5891
+ if (Math.abs(deltaX) < 1) {
5892
+ continue;
5893
+ }
5894
+ tab.value.element.style.transform = `translateX(${deltaX}px)`;
5895
+ toggleClass(tab.value.element, 'dv-tab--shifting', true);
5896
+ hasAnimation = true;
5897
+ }
5898
+ if (!hasAnimation) {
5899
+ return;
5900
+ }
5901
+ requestAnimationFrame(() => {
5902
+ for (const tab of this._tabs) {
5903
+ if (tab.value.element.style.transform) {
5904
+ tab.value.element.style.transform = '';
5905
+ }
5906
+ }
5907
+ const onTransitionEnd = (event) => {
5908
+ if (event.propertyName === 'transform') {
5909
+ this._tabsList.removeEventListener('transitionend', onTransitionEnd);
5910
+ for (const tab of this._tabs) {
5911
+ toggleClass(tab.value.element, 'dv-tab--shifting', false);
5912
+ }
5913
+ }
5914
+ };
5915
+ this._tabsList.addEventListener('transitionend', onTransitionEnd);
5916
+ });
5917
+ }
5627
5918
  }
5628
5919
 
5629
5920
  const createSvgElementFromPath = (params) => {
@@ -5922,6 +6213,7 @@
5922
6213
  }
5923
6214
  }
5924
6215
 
6216
+ const DEFAULT_TAB_ANIMATION = 'default';
5925
6217
  class DockviewUnhandledDragOverEvent extends AcceptableEvent {
5926
6218
  constructor(nativeEvent, target, position, getData, group) {
5927
6219
  super();
@@ -5956,6 +6248,7 @@
5956
6248
  theme: undefined,
5957
6249
  disableTabsOverflowList: undefined,
5958
6250
  scrollbars: undefined,
6251
+ tabAnimation: undefined,
5959
6252
  };
5960
6253
  return Object.keys(properties);
5961
6254
  })();
@@ -11486,6 +11779,7 @@
11486
11779
 
11487
11780
  exports.BaseGrid = BaseGrid;
11488
11781
  exports.ContentContainer = ContentContainer;
11782
+ exports.DEFAULT_TAB_ANIMATION = DEFAULT_TAB_ANIMATION;
11489
11783
  exports.DefaultDockviewDeserialzier = DefaultDockviewDeserialzier;
11490
11784
  exports.DefaultTab = DefaultTab;
11491
11785
  exports.DockviewApi = DockviewApi;
@@ -19,6 +19,8 @@ export declare class Tab extends CompositeDisposable {
19
19
  readonly onDrop: Event<DroptargetEvent>;
20
20
  private readonly _onDragStart;
21
21
  readonly onDragStart: Event<DragEvent>;
22
+ private readonly _onDragEnd;
23
+ readonly onDragEnd: Event<DragEvent>;
22
24
  readonly onWillShowOverlay: Event<WillShowOverlayEvent>;
23
25
  get element(): HTMLElement;
24
26
  constructor(panel: IDockviewPanel, accessor: DockviewComponent, group: DockviewGroupPanel);
@@ -38,6 +38,8 @@ export class Tab extends CompositeDisposable {
38
38
  this.onDrop = this._onDropped.event;
39
39
  this._onDragStart = new Emitter();
40
40
  this.onDragStart = this._onDragStart.event;
41
+ this._onDragEnd = new Emitter();
42
+ this.onDragEnd = this._onDragEnd.event;
41
43
  this._element = document.createElement('div');
42
44
  this._element.className = 'dv-tab';
43
45
  this._element.tabIndex = 0;
@@ -53,6 +55,10 @@ export class Tab extends CompositeDisposable {
53
55
  }
54
56
  const data = getPanelData();
55
57
  if (data && this.accessor.id === data.viewId) {
58
+ if (this.accessor.options.tabAnimation === 'smooth' &&
59
+ data.groupId === this.group.id) {
60
+ return false;
61
+ }
56
62
  return true;
57
63
  }
58
64
  return this.group.model.canDisplayOverlay(event, position, 'tab');
@@ -60,7 +66,7 @@ export class Tab extends CompositeDisposable {
60
66
  getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
61
67
  });
62
68
  this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
63
- this.addDisposables(this._onPointDown, this._onDropped, this._onDragStart, this.dragHandler.onDragStart((event) => {
69
+ this.addDisposables(this._onPointDown, this._onDropped, this._onDragStart, this._onDragEnd, this.dragHandler.onDragStart((event) => {
64
70
  if (event.dataTransfer) {
65
71
  const style = getComputedStyle(this.element);
66
72
  const newNode = this.element.cloneNode(true);
@@ -72,6 +78,16 @@ export class Tab extends CompositeDisposable {
72
78
  });
73
79
  }
74
80
  this._onDragStart.fire(event);
81
+ if (this.accessor.options.tabAnimation === 'smooth') {
82
+ // Delay collapse to next frame so the browser
83
+ // captures the full drag image first
84
+ requestAnimationFrame(() => {
85
+ toggleClass(this.element, 'dv-tab--dragging', true);
86
+ });
87
+ }
88
+ }), addDisposableListener(this._element, 'dragend', (event) => {
89
+ toggleClass(this.element, 'dv-tab--dragging', false);
90
+ this._onDragEnd.fire(event);
75
91
  }), this.dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => {
76
92
  this._onPointDown.fire(event);
77
93
  }), this.dropTarget.onDrop((event) => {
@@ -18,6 +18,7 @@ export declare class Tabs extends CompositeDisposable {
18
18
  private selectedIndex;
19
19
  private _showTabsOverflowControl;
20
20
  private _direction;
21
+ private _animState;
21
22
  private readonly _onTabDragStart;
22
23
  readonly onTabDragStart: Event<TabDragEvent>;
23
24
  private readonly _onDrop;
@@ -48,4 +49,11 @@ export declare class Tabs extends CompositeDisposable {
48
49
  private addTab;
49
50
  private toggleDropdown;
50
51
  updateDragAndDropState(): void;
52
+ private snapshotTabPositions;
53
+ private getAverageTabWidth;
54
+ private handleDragOver;
55
+ private applyDragOverTransforms;
56
+ private resetTabTransforms;
57
+ private resetDragAnimation;
58
+ private runFlipAnimation;
51
59
  }