dockview-core 6.0.5 → 6.0.6

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 6.0.5
3
+ * @version 6.0.6
4
4
  * @link https://github.com/mathuo/dockview
5
5
  * @license MIT
6
6
  */
@@ -7205,10 +7205,12 @@
7205
7205
  const index = this.indexOf(id);
7206
7206
  const tabToRemove = this._tabs.splice(index, 1)[0];
7207
7207
  this._tabMap.delete(id);
7208
- const { value, disposable } = tabToRemove;
7209
- disposable.dispose();
7210
- value.dispose();
7211
- value.element.remove();
7208
+ if (tabToRemove) {
7209
+ const { value, disposable } = tabToRemove;
7210
+ disposable.dispose();
7211
+ value.dispose();
7212
+ value.element.remove();
7213
+ }
7212
7214
  // If a non-source tab was removed during active drag, refresh positions
7213
7215
  if (this._animState) {
7214
7216
  this._animState.tabPositions = this.snapshotTabPositions();
@@ -7880,6 +7882,14 @@
7880
7882
  // handles panel transfer and tab group recreation.
7881
7883
  // Use the REAL tab group ID from transfer data, not the
7882
7884
  // potentially stale one from _animState.
7885
+ //
7886
+ // Clear any inline gap margin / shifting class applied to
7887
+ // destination tabs during dragover. Cross-group moves don't
7888
+ // run the FLIP path, and `moveGroupOrPanel` only inserts new
7889
+ // panels — it doesn't recreate existing destination tabs, so
7890
+ // their inline `margin-left` would otherwise persist as a
7891
+ // visible gap (issue #1243).
7892
+ this.resetTabTransforms();
7883
7893
  this.accessor.moveGroupOrPanel({
7884
7894
  from: {
7885
7895
  groupId: data.groupId,
@@ -9660,15 +9670,17 @@
9660
9670
  if (position === 'center') {
9661
9671
  return;
9662
9672
  }
9663
- if (data.panelId === null) {
9664
- // don't allow group move to drop anywhere on self
9673
+ if (data.panelId === null && !data.tabGroupId) {
9674
+ // Full-group drops on self are a no-op.
9675
+ // Tab-group drags are partial moves: an edge drop
9676
+ // splits the layout and creates a new group.
9665
9677
  return;
9666
9678
  }
9667
9679
  }
9668
9680
  }
9669
9681
  if (type === 'header') {
9670
9682
  if (data.groupId === this.id) {
9671
- if (data.panelId === null) {
9683
+ if (data.panelId === null && !data.tabGroupId) {
9672
9684
  return;
9673
9685
  }
9674
9686
  }
@@ -13536,7 +13548,7 @@
13536
13548
  // Collapse when the group becomes empty
13537
13549
  const autoCollapseDisposable = group.model.onDidRemovePanel(() => {
13538
13550
  if (group.model.isEmpty) {
13539
- this._shellManager.setEdgeGroupCollapsed(position, true);
13551
+ this.setEdgeGroupCollapsed(group, true);
13540
13552
  }
13541
13553
  });
13542
13554
  this._edgeGroupDisposables.set(position, autoCollapseDisposable);
@@ -13580,6 +13592,13 @@
13580
13592
  setEdgeGroupCollapsed(group, collapsed) {
13581
13593
  for (const [position, edgeGroup] of this._edgeGroups) {
13582
13594
  if (edgeGroup === group) {
13595
+ if (this._shellManager.isEdgeGroupCollapsed(position) ===
13596
+ collapsed) {
13597
+ // Skip the splitview resize on a no-op: with non-zero
13598
+ // theme gap, redundant resizeView calls accumulate
13599
+ // rounding drift that gradually shrinks the group.
13600
+ return;
13601
+ }
13583
13602
  this._shellManager.setEdgeGroupCollapsed(position, collapsed);
13584
13603
  edgeGroup.api._onDidCollapsedChange.fire({
13585
13604
  isCollapsed: collapsed,
@@ -14532,7 +14551,14 @@
14532
14551
  const label = tabGroup.label;
14533
14552
  const color = tabGroup.color;
14534
14553
  const collapsed = tabGroup.collapsed;
14554
+ const componentParams = tabGroup.componentParams;
14535
14555
  const panelIds = [...tabGroup.panelIds];
14556
+ // Capture the destination's grid location BEFORE potentially
14557
+ // removing the source group, in case source === destination and
14558
+ // the source becomes empty after panel removal.
14559
+ const referenceLocation = destinationTarget && destinationTarget !== 'center'
14560
+ ? getGridLocation(destinationGroup.element)
14561
+ : undefined;
14536
14562
  // Remove panels from the source group
14537
14563
  const removedPanels = this.movingLock(() => panelIds
14538
14564
  .map((pid) => sourceGroup.model.removePanel(pid, {
@@ -14543,11 +14569,6 @@
14543
14569
  if (removedPanels.length === 0) {
14544
14570
  return;
14545
14571
  }
14546
- if (!options.keepEmptyGroups &&
14547
- sourceGroup.model.size === 0 &&
14548
- sourceGroup !== destinationGroup) {
14549
- this.doRemoveGroup(sourceGroup, { skipActive: true });
14550
- }
14551
14572
  const addPanelsToGroup = (targetGroup) => {
14552
14573
  this.movingLock(() => {
14553
14574
  for (const panel of removedPanels) {
@@ -14563,6 +14584,7 @@
14563
14584
  label,
14564
14585
  color,
14565
14586
  collapsed,
14587
+ componentParams,
14566
14588
  });
14567
14589
  for (const panel of removedPanels) {
14568
14590
  targetGroup.model.addPanelToTabGroup(newTabGroup.id, panel.id);
@@ -14577,15 +14599,27 @@
14577
14599
  });
14578
14600
  }
14579
14601
  };
14580
- if (!destinationTarget || destinationTarget === 'center') {
14581
- addPanelsToGroup(destinationGroup);
14602
+ let targetGroup;
14603
+ if (!destinationTarget ||
14604
+ destinationTarget === 'center' ||
14605
+ !referenceLocation) {
14606
+ targetGroup = destinationGroup;
14582
14607
  }
14583
14608
  else {
14584
- const referenceLocation = getGridLocation(destinationGroup.element);
14585
14609
  const dropLocation = getRelativeLocation(this.gridview.orientation, referenceLocation, destinationTarget);
14586
- const newGroup = this.createGroupAtLocation(dropLocation);
14587
- addPanelsToGroup(newGroup);
14610
+ targetGroup = this.createGroupAtLocation(dropLocation);
14588
14611
  }
14612
+ // Remove the source group if it became empty. We compare against
14613
+ // the actual targetGroup (which is a freshly-created group for
14614
+ // edge drops) rather than the originally-passed destinationGroup,
14615
+ // so a tab-group drag onto its own group's edge still cleans up
14616
+ // the now-empty source.
14617
+ if (!options.keepEmptyGroups &&
14618
+ sourceGroup.model.size === 0 &&
14619
+ sourceGroup !== targetGroup) {
14620
+ this.doRemoveGroup(sourceGroup, { skipActive: true });
14621
+ }
14622
+ addPanelsToGroup(targetGroup);
14589
14623
  }
14590
14624
  moveGroup(options) {
14591
14625
  const from = options.from.group;
@@ -14597,6 +14631,16 @@
14597
14631
  let source = from;
14598
14632
  if (target === 'center') {
14599
14633
  const activePanel = from.activePanel;
14634
+ // Snapshot tab group metadata before removing panels so we
14635
+ // can recreate the tab groups in the destination after the
14636
+ // panels are merged in.
14637
+ const tabGroupSnapshots = from.model.getTabGroups().map((tg) => ({
14638
+ label: tg.label,
14639
+ color: tg.color,
14640
+ collapsed: tg.collapsed,
14641
+ componentParams: tg.componentParams,
14642
+ panelIds: [...tg.panelIds],
14643
+ }));
14600
14644
  const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, {
14601
14645
  skipSetActive: true,
14602
14646
  })));
@@ -14611,6 +14655,17 @@
14611
14655
  });
14612
14656
  }
14613
14657
  });
14658
+ for (const snapshot of tabGroupSnapshots) {
14659
+ const newTabGroup = to.model.createTabGroup({
14660
+ label: snapshot.label,
14661
+ color: snapshot.color,
14662
+ collapsed: snapshot.collapsed,
14663
+ componentParams: snapshot.componentParams,
14664
+ });
14665
+ for (const panelId of snapshot.panelIds) {
14666
+ to.model.addPanelToTabGroup(newTabGroup.id, panelId);
14667
+ }
14668
+ }
14614
14669
  // Ensure group becomes active after move
14615
14670
  if (options.skipSetActive !== true) {
14616
14671
  // For center moves (merges), we need to ensure the target group is active
@@ -14634,6 +14689,17 @@
14634
14689
  * positions `source` like any other moved group.
14635
14690
  */
14636
14691
  const activePanel = from.activePanel;
14692
+ // Snapshot tab group metadata so the new group inherits
14693
+ // the tab grouping from the edge slot.
14694
+ const tabGroupSnapshots = from.model
14695
+ .getTabGroups()
14696
+ .map((tg) => ({
14697
+ label: tg.label,
14698
+ color: tg.color,
14699
+ collapsed: tg.collapsed,
14700
+ componentParams: tg.componentParams,
14701
+ panelIds: [...tg.panelIds],
14702
+ }));
14637
14703
  const movedPanels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, { skipSetActive: true })));
14638
14704
  source = this.createGroup();
14639
14705
  this.movingLock(() => {
@@ -14644,6 +14710,17 @@
14644
14710
  });
14645
14711
  }
14646
14712
  });
14713
+ for (const snapshot of tabGroupSnapshots) {
14714
+ const newTabGroup = source.model.createTabGroup({
14715
+ label: snapshot.label,
14716
+ color: snapshot.color,
14717
+ collapsed: snapshot.collapsed,
14718
+ componentParams: snapshot.componentParams,
14719
+ });
14720
+ for (const panelId of snapshot.panelIds) {
14721
+ source.model.addPanelToTabGroup(newTabGroup.id, panelId);
14722
+ }
14723
+ }
14647
14724
  }
14648
14725
  else {
14649
14726
  switch (from.api.location.type) {
@@ -604,10 +604,12 @@ export class Tabs extends CompositeDisposable {
604
604
  const index = this.indexOf(id);
605
605
  const tabToRemove = this._tabs.splice(index, 1)[0];
606
606
  this._tabMap.delete(id);
607
- const { value, disposable } = tabToRemove;
608
- disposable.dispose();
609
- value.dispose();
610
- value.element.remove();
607
+ if (tabToRemove) {
608
+ const { value, disposable } = tabToRemove;
609
+ disposable.dispose();
610
+ value.dispose();
611
+ value.element.remove();
612
+ }
611
613
  // If a non-source tab was removed during active drag, refresh positions
612
614
  if (this._animState) {
613
615
  this._animState.tabPositions = this.snapshotTabPositions();
@@ -1279,6 +1281,14 @@ export class Tabs extends CompositeDisposable {
1279
1281
  // handles panel transfer and tab group recreation.
1280
1282
  // Use the REAL tab group ID from transfer data, not the
1281
1283
  // potentially stale one from _animState.
1284
+ //
1285
+ // Clear any inline gap margin / shifting class applied to
1286
+ // destination tabs during dragover. Cross-group moves don't
1287
+ // run the FLIP path, and `moveGroupOrPanel` only inserts new
1288
+ // panels — it doesn't recreate existing destination tabs, so
1289
+ // their inline `margin-left` would otherwise persist as a
1290
+ // visible gap (issue #1243).
1291
+ this.resetTabTransforms();
1282
1292
  this.accessor.moveGroupOrPanel({
1283
1293
  from: {
1284
1294
  groupId: data.groupId,
@@ -924,7 +924,7 @@ export class DockviewComponent extends BaseGrid {
924
924
  // Collapse when the group becomes empty
925
925
  const autoCollapseDisposable = group.model.onDidRemovePanel(() => {
926
926
  if (group.model.isEmpty) {
927
- this._shellManager.setEdgeGroupCollapsed(position, true);
927
+ this.setEdgeGroupCollapsed(group, true);
928
928
  }
929
929
  });
930
930
  this._edgeGroupDisposables.set(position, autoCollapseDisposable);
@@ -968,6 +968,13 @@ export class DockviewComponent extends BaseGrid {
968
968
  setEdgeGroupCollapsed(group, collapsed) {
969
969
  for (const [position, edgeGroup] of this._edgeGroups) {
970
970
  if (edgeGroup === group) {
971
+ if (this._shellManager.isEdgeGroupCollapsed(position) ===
972
+ collapsed) {
973
+ // Skip the splitview resize on a no-op: with non-zero
974
+ // theme gap, redundant resizeView calls accumulate
975
+ // rounding drift that gradually shrinks the group.
976
+ return;
977
+ }
971
978
  this._shellManager.setEdgeGroupCollapsed(position, collapsed);
972
979
  edgeGroup.api._onDidCollapsedChange.fire({
973
980
  isCollapsed: collapsed,
@@ -1920,7 +1927,14 @@ export class DockviewComponent extends BaseGrid {
1920
1927
  const label = tabGroup.label;
1921
1928
  const color = tabGroup.color;
1922
1929
  const collapsed = tabGroup.collapsed;
1930
+ const componentParams = tabGroup.componentParams;
1923
1931
  const panelIds = [...tabGroup.panelIds];
1932
+ // Capture the destination's grid location BEFORE potentially
1933
+ // removing the source group, in case source === destination and
1934
+ // the source becomes empty after panel removal.
1935
+ const referenceLocation = destinationTarget && destinationTarget !== 'center'
1936
+ ? getGridLocation(destinationGroup.element)
1937
+ : undefined;
1924
1938
  // Remove panels from the source group
1925
1939
  const removedPanels = this.movingLock(() => panelIds
1926
1940
  .map((pid) => sourceGroup.model.removePanel(pid, {
@@ -1931,11 +1945,6 @@ export class DockviewComponent extends BaseGrid {
1931
1945
  if (removedPanels.length === 0) {
1932
1946
  return;
1933
1947
  }
1934
- if (!options.keepEmptyGroups &&
1935
- sourceGroup.model.size === 0 &&
1936
- sourceGroup !== destinationGroup) {
1937
- this.doRemoveGroup(sourceGroup, { skipActive: true });
1938
- }
1939
1948
  const addPanelsToGroup = (targetGroup) => {
1940
1949
  this.movingLock(() => {
1941
1950
  for (const panel of removedPanels) {
@@ -1951,6 +1960,7 @@ export class DockviewComponent extends BaseGrid {
1951
1960
  label,
1952
1961
  color,
1953
1962
  collapsed,
1963
+ componentParams,
1954
1964
  });
1955
1965
  for (const panel of removedPanels) {
1956
1966
  targetGroup.model.addPanelToTabGroup(newTabGroup.id, panel.id);
@@ -1965,15 +1975,27 @@ export class DockviewComponent extends BaseGrid {
1965
1975
  });
1966
1976
  }
1967
1977
  };
1968
- if (!destinationTarget || destinationTarget === 'center') {
1969
- addPanelsToGroup(destinationGroup);
1978
+ let targetGroup;
1979
+ if (!destinationTarget ||
1980
+ destinationTarget === 'center' ||
1981
+ !referenceLocation) {
1982
+ targetGroup = destinationGroup;
1970
1983
  }
1971
1984
  else {
1972
- const referenceLocation = getGridLocation(destinationGroup.element);
1973
1985
  const dropLocation = getRelativeLocation(this.gridview.orientation, referenceLocation, destinationTarget);
1974
- const newGroup = this.createGroupAtLocation(dropLocation);
1975
- addPanelsToGroup(newGroup);
1986
+ targetGroup = this.createGroupAtLocation(dropLocation);
1976
1987
  }
1988
+ // Remove the source group if it became empty. We compare against
1989
+ // the actual targetGroup (which is a freshly-created group for
1990
+ // edge drops) rather than the originally-passed destinationGroup,
1991
+ // so a tab-group drag onto its own group's edge still cleans up
1992
+ // the now-empty source.
1993
+ if (!options.keepEmptyGroups &&
1994
+ sourceGroup.model.size === 0 &&
1995
+ sourceGroup !== targetGroup) {
1996
+ this.doRemoveGroup(sourceGroup, { skipActive: true });
1997
+ }
1998
+ addPanelsToGroup(targetGroup);
1977
1999
  }
1978
2000
  moveGroup(options) {
1979
2001
  const from = options.from.group;
@@ -1985,6 +2007,16 @@ export class DockviewComponent extends BaseGrid {
1985
2007
  let source = from;
1986
2008
  if (target === 'center') {
1987
2009
  const activePanel = from.activePanel;
2010
+ // Snapshot tab group metadata before removing panels so we
2011
+ // can recreate the tab groups in the destination after the
2012
+ // panels are merged in.
2013
+ const tabGroupSnapshots = from.model.getTabGroups().map((tg) => ({
2014
+ label: tg.label,
2015
+ color: tg.color,
2016
+ collapsed: tg.collapsed,
2017
+ componentParams: tg.componentParams,
2018
+ panelIds: [...tg.panelIds],
2019
+ }));
1988
2020
  const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, {
1989
2021
  skipSetActive: true,
1990
2022
  })));
@@ -1999,6 +2031,17 @@ export class DockviewComponent extends BaseGrid {
1999
2031
  });
2000
2032
  }
2001
2033
  });
2034
+ for (const snapshot of tabGroupSnapshots) {
2035
+ const newTabGroup = to.model.createTabGroup({
2036
+ label: snapshot.label,
2037
+ color: snapshot.color,
2038
+ collapsed: snapshot.collapsed,
2039
+ componentParams: snapshot.componentParams,
2040
+ });
2041
+ for (const panelId of snapshot.panelIds) {
2042
+ to.model.addPanelToTabGroup(newTabGroup.id, panelId);
2043
+ }
2044
+ }
2002
2045
  // Ensure group becomes active after move
2003
2046
  if (options.skipSetActive !== true) {
2004
2047
  // For center moves (merges), we need to ensure the target group is active
@@ -2022,6 +2065,17 @@ export class DockviewComponent extends BaseGrid {
2022
2065
  * positions `source` like any other moved group.
2023
2066
  */
2024
2067
  const activePanel = from.activePanel;
2068
+ // Snapshot tab group metadata so the new group inherits
2069
+ // the tab grouping from the edge slot.
2070
+ const tabGroupSnapshots = from.model
2071
+ .getTabGroups()
2072
+ .map((tg) => ({
2073
+ label: tg.label,
2074
+ color: tg.color,
2075
+ collapsed: tg.collapsed,
2076
+ componentParams: tg.componentParams,
2077
+ panelIds: [...tg.panelIds],
2078
+ }));
2025
2079
  const movedPanels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, { skipSetActive: true })));
2026
2080
  source = this.createGroup();
2027
2081
  this.movingLock(() => {
@@ -2032,6 +2086,17 @@ export class DockviewComponent extends BaseGrid {
2032
2086
  });
2033
2087
  }
2034
2088
  });
2089
+ for (const snapshot of tabGroupSnapshots) {
2090
+ const newTabGroup = source.model.createTabGroup({
2091
+ label: snapshot.label,
2092
+ color: snapshot.color,
2093
+ collapsed: snapshot.collapsed,
2094
+ componentParams: snapshot.componentParams,
2095
+ });
2096
+ for (const panelId of snapshot.panelIds) {
2097
+ source.model.addPanelToTabGroup(newTabGroup.id, panelId);
2098
+ }
2099
+ }
2035
2100
  }
2036
2101
  else {
2037
2102
  switch (from.api.location.type) {
@@ -1061,15 +1061,17 @@ export class DockviewGroupPanelModel extends CompositeDisposable {
1061
1061
  if (position === 'center') {
1062
1062
  return;
1063
1063
  }
1064
- if (data.panelId === null) {
1065
- // don't allow group move to drop anywhere on self
1064
+ if (data.panelId === null && !data.tabGroupId) {
1065
+ // Full-group drops on self are a no-op.
1066
+ // Tab-group drags are partial moves: an edge drop
1067
+ // splits the layout and creates a new group.
1066
1068
  return;
1067
1069
  }
1068
1070
  }
1069
1071
  }
1070
1072
  if (type === 'header') {
1071
1073
  if (data.groupId === this.id) {
1072
- if (data.panelId === null) {
1074
+ if (data.panelId === null && !data.tabGroupId) {
1073
1075
  return;
1074
1076
  }
1075
1077
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * dockview-core
3
- * @version 6.0.5
3
+ * @version 6.0.6
4
4
  * @link https://github.com/mathuo/dockview
5
5
  * @license MIT
6
6
  */
@@ -7201,10 +7201,12 @@ class Tabs extends CompositeDisposable {
7201
7201
  const index = this.indexOf(id);
7202
7202
  const tabToRemove = this._tabs.splice(index, 1)[0];
7203
7203
  this._tabMap.delete(id);
7204
- const { value, disposable } = tabToRemove;
7205
- disposable.dispose();
7206
- value.dispose();
7207
- value.element.remove();
7204
+ if (tabToRemove) {
7205
+ const { value, disposable } = tabToRemove;
7206
+ disposable.dispose();
7207
+ value.dispose();
7208
+ value.element.remove();
7209
+ }
7208
7210
  // If a non-source tab was removed during active drag, refresh positions
7209
7211
  if (this._animState) {
7210
7212
  this._animState.tabPositions = this.snapshotTabPositions();
@@ -7876,6 +7878,14 @@ class Tabs extends CompositeDisposable {
7876
7878
  // handles panel transfer and tab group recreation.
7877
7879
  // Use the REAL tab group ID from transfer data, not the
7878
7880
  // potentially stale one from _animState.
7881
+ //
7882
+ // Clear any inline gap margin / shifting class applied to
7883
+ // destination tabs during dragover. Cross-group moves don't
7884
+ // run the FLIP path, and `moveGroupOrPanel` only inserts new
7885
+ // panels — it doesn't recreate existing destination tabs, so
7886
+ // their inline `margin-left` would otherwise persist as a
7887
+ // visible gap (issue #1243).
7888
+ this.resetTabTransforms();
7879
7889
  this.accessor.moveGroupOrPanel({
7880
7890
  from: {
7881
7891
  groupId: data.groupId,
@@ -9656,15 +9666,17 @@ class DockviewGroupPanelModel extends CompositeDisposable {
9656
9666
  if (position === 'center') {
9657
9667
  return;
9658
9668
  }
9659
- if (data.panelId === null) {
9660
- // don't allow group move to drop anywhere on self
9669
+ if (data.panelId === null && !data.tabGroupId) {
9670
+ // Full-group drops on self are a no-op.
9671
+ // Tab-group drags are partial moves: an edge drop
9672
+ // splits the layout and creates a new group.
9661
9673
  return;
9662
9674
  }
9663
9675
  }
9664
9676
  }
9665
9677
  if (type === 'header') {
9666
9678
  if (data.groupId === this.id) {
9667
- if (data.panelId === null) {
9679
+ if (data.panelId === null && !data.tabGroupId) {
9668
9680
  return;
9669
9681
  }
9670
9682
  }
@@ -13532,7 +13544,7 @@ class DockviewComponent extends BaseGrid {
13532
13544
  // Collapse when the group becomes empty
13533
13545
  const autoCollapseDisposable = group.model.onDidRemovePanel(() => {
13534
13546
  if (group.model.isEmpty) {
13535
- this._shellManager.setEdgeGroupCollapsed(position, true);
13547
+ this.setEdgeGroupCollapsed(group, true);
13536
13548
  }
13537
13549
  });
13538
13550
  this._edgeGroupDisposables.set(position, autoCollapseDisposable);
@@ -13576,6 +13588,13 @@ class DockviewComponent extends BaseGrid {
13576
13588
  setEdgeGroupCollapsed(group, collapsed) {
13577
13589
  for (const [position, edgeGroup] of this._edgeGroups) {
13578
13590
  if (edgeGroup === group) {
13591
+ if (this._shellManager.isEdgeGroupCollapsed(position) ===
13592
+ collapsed) {
13593
+ // Skip the splitview resize on a no-op: with non-zero
13594
+ // theme gap, redundant resizeView calls accumulate
13595
+ // rounding drift that gradually shrinks the group.
13596
+ return;
13597
+ }
13579
13598
  this._shellManager.setEdgeGroupCollapsed(position, collapsed);
13580
13599
  edgeGroup.api._onDidCollapsedChange.fire({
13581
13600
  isCollapsed: collapsed,
@@ -14528,7 +14547,14 @@ class DockviewComponent extends BaseGrid {
14528
14547
  const label = tabGroup.label;
14529
14548
  const color = tabGroup.color;
14530
14549
  const collapsed = tabGroup.collapsed;
14550
+ const componentParams = tabGroup.componentParams;
14531
14551
  const panelIds = [...tabGroup.panelIds];
14552
+ // Capture the destination's grid location BEFORE potentially
14553
+ // removing the source group, in case source === destination and
14554
+ // the source becomes empty after panel removal.
14555
+ const referenceLocation = destinationTarget && destinationTarget !== 'center'
14556
+ ? getGridLocation(destinationGroup.element)
14557
+ : undefined;
14532
14558
  // Remove panels from the source group
14533
14559
  const removedPanels = this.movingLock(() => panelIds
14534
14560
  .map((pid) => sourceGroup.model.removePanel(pid, {
@@ -14539,11 +14565,6 @@ class DockviewComponent extends BaseGrid {
14539
14565
  if (removedPanels.length === 0) {
14540
14566
  return;
14541
14567
  }
14542
- if (!options.keepEmptyGroups &&
14543
- sourceGroup.model.size === 0 &&
14544
- sourceGroup !== destinationGroup) {
14545
- this.doRemoveGroup(sourceGroup, { skipActive: true });
14546
- }
14547
14568
  const addPanelsToGroup = (targetGroup) => {
14548
14569
  this.movingLock(() => {
14549
14570
  for (const panel of removedPanels) {
@@ -14559,6 +14580,7 @@ class DockviewComponent extends BaseGrid {
14559
14580
  label,
14560
14581
  color,
14561
14582
  collapsed,
14583
+ componentParams,
14562
14584
  });
14563
14585
  for (const panel of removedPanels) {
14564
14586
  targetGroup.model.addPanelToTabGroup(newTabGroup.id, panel.id);
@@ -14573,15 +14595,27 @@ class DockviewComponent extends BaseGrid {
14573
14595
  });
14574
14596
  }
14575
14597
  };
14576
- if (!destinationTarget || destinationTarget === 'center') {
14577
- addPanelsToGroup(destinationGroup);
14598
+ let targetGroup;
14599
+ if (!destinationTarget ||
14600
+ destinationTarget === 'center' ||
14601
+ !referenceLocation) {
14602
+ targetGroup = destinationGroup;
14578
14603
  }
14579
14604
  else {
14580
- const referenceLocation = getGridLocation(destinationGroup.element);
14581
14605
  const dropLocation = getRelativeLocation(this.gridview.orientation, referenceLocation, destinationTarget);
14582
- const newGroup = this.createGroupAtLocation(dropLocation);
14583
- addPanelsToGroup(newGroup);
14606
+ targetGroup = this.createGroupAtLocation(dropLocation);
14584
14607
  }
14608
+ // Remove the source group if it became empty. We compare against
14609
+ // the actual targetGroup (which is a freshly-created group for
14610
+ // edge drops) rather than the originally-passed destinationGroup,
14611
+ // so a tab-group drag onto its own group's edge still cleans up
14612
+ // the now-empty source.
14613
+ if (!options.keepEmptyGroups &&
14614
+ sourceGroup.model.size === 0 &&
14615
+ sourceGroup !== targetGroup) {
14616
+ this.doRemoveGroup(sourceGroup, { skipActive: true });
14617
+ }
14618
+ addPanelsToGroup(targetGroup);
14585
14619
  }
14586
14620
  moveGroup(options) {
14587
14621
  const from = options.from.group;
@@ -14593,6 +14627,16 @@ class DockviewComponent extends BaseGrid {
14593
14627
  let source = from;
14594
14628
  if (target === 'center') {
14595
14629
  const activePanel = from.activePanel;
14630
+ // Snapshot tab group metadata before removing panels so we
14631
+ // can recreate the tab groups in the destination after the
14632
+ // panels are merged in.
14633
+ const tabGroupSnapshots = from.model.getTabGroups().map((tg) => ({
14634
+ label: tg.label,
14635
+ color: tg.color,
14636
+ collapsed: tg.collapsed,
14637
+ componentParams: tg.componentParams,
14638
+ panelIds: [...tg.panelIds],
14639
+ }));
14596
14640
  const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, {
14597
14641
  skipSetActive: true,
14598
14642
  })));
@@ -14607,6 +14651,17 @@ class DockviewComponent extends BaseGrid {
14607
14651
  });
14608
14652
  }
14609
14653
  });
14654
+ for (const snapshot of tabGroupSnapshots) {
14655
+ const newTabGroup = to.model.createTabGroup({
14656
+ label: snapshot.label,
14657
+ color: snapshot.color,
14658
+ collapsed: snapshot.collapsed,
14659
+ componentParams: snapshot.componentParams,
14660
+ });
14661
+ for (const panelId of snapshot.panelIds) {
14662
+ to.model.addPanelToTabGroup(newTabGroup.id, panelId);
14663
+ }
14664
+ }
14610
14665
  // Ensure group becomes active after move
14611
14666
  if (options.skipSetActive !== true) {
14612
14667
  // For center moves (merges), we need to ensure the target group is active
@@ -14630,6 +14685,17 @@ class DockviewComponent extends BaseGrid {
14630
14685
  * positions `source` like any other moved group.
14631
14686
  */
14632
14687
  const activePanel = from.activePanel;
14688
+ // Snapshot tab group metadata so the new group inherits
14689
+ // the tab grouping from the edge slot.
14690
+ const tabGroupSnapshots = from.model
14691
+ .getTabGroups()
14692
+ .map((tg) => ({
14693
+ label: tg.label,
14694
+ color: tg.color,
14695
+ collapsed: tg.collapsed,
14696
+ componentParams: tg.componentParams,
14697
+ panelIds: [...tg.panelIds],
14698
+ }));
14633
14699
  const movedPanels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, { skipSetActive: true })));
14634
14700
  source = this.createGroup();
14635
14701
  this.movingLock(() => {
@@ -14640,6 +14706,17 @@ class DockviewComponent extends BaseGrid {
14640
14706
  });
14641
14707
  }
14642
14708
  });
14709
+ for (const snapshot of tabGroupSnapshots) {
14710
+ const newTabGroup = source.model.createTabGroup({
14711
+ label: snapshot.label,
14712
+ color: snapshot.color,
14713
+ collapsed: snapshot.collapsed,
14714
+ componentParams: snapshot.componentParams,
14715
+ });
14716
+ for (const panelId of snapshot.panelIds) {
14717
+ source.model.addPanelToTabGroup(newTabGroup.id, panelId);
14718
+ }
14719
+ }
14643
14720
  }
14644
14721
  else {
14645
14722
  switch (from.api.location.type) {