dockview-core 6.0.5 → 6.0.7

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.
@@ -725,10 +725,12 @@ var Tabs = /** @class */ (function (_super) {
725
725
  var index = this.indexOf(id);
726
726
  var tabToRemove = this._tabs.splice(index, 1)[0];
727
727
  this._tabMap.delete(id);
728
- var value = tabToRemove.value, disposable = tabToRemove.disposable;
729
- disposable.dispose();
730
- value.dispose();
731
- value.element.remove();
728
+ if (tabToRemove) {
729
+ var value = tabToRemove.value, disposable = tabToRemove.disposable;
730
+ disposable.dispose();
731
+ value.dispose();
732
+ value.element.remove();
733
+ }
732
734
  // If a non-source tab was removed during active drag, refresh positions
733
735
  if (this._animState) {
734
736
  this._animState.tabPositions = this.snapshotTabPositions();
@@ -896,8 +898,23 @@ var Tabs = /** @class */ (function (_super) {
896
898
  new dataTransfer_1.PanelTransfer(this.accessor.id, this.group.id, null, tabGroup.id),
897
899
  ], dataTransfer_1.PanelTransfer.prototype);
898
900
  var iframes = (0, dom_1.disableIframePointEvents)();
901
+ // The dragend listener on `_tabsList` is unreachable for chip
902
+ // drags because cross-group drops detach the chip from the DOM
903
+ // before dragend fires (the source tab group becomes empty, so
904
+ // `_positionChipForGroup` removes the chip element). Without
905
+ // bubbling, the tabsList listener never runs and `_animState`,
906
+ // `_chipDragCleanup`, and the dragging CSS classes leak. Listen
907
+ // directly on the chip element so cleanup happens regardless of
908
+ // whether it's still attached. (Issue #1254.)
909
+ var chipElement = chip.element;
910
+ var onChipDragEnd = function () {
911
+ chipElement.removeEventListener('dragend', onChipDragEnd);
912
+ _this.resetDragAnimation();
913
+ };
914
+ chipElement.addEventListener('dragend', onChipDragEnd);
899
915
  this._chipDragCleanup = {
900
916
  dispose: function () {
917
+ chipElement.removeEventListener('dragend', onChipDragEnd);
901
918
  panelTransfer.clearData(dataTransfer_1.PanelTransfer.prototype);
902
919
  iframes.release();
903
920
  },
@@ -1605,6 +1622,14 @@ var Tabs = /** @class */ (function (_super) {
1605
1622
  // handles panel transfer and tab group recreation.
1606
1623
  // Use the REAL tab group ID from transfer data, not the
1607
1624
  // potentially stale one from _animState.
1625
+ //
1626
+ // Clear any inline gap margin / shifting class applied to
1627
+ // destination tabs during dragover. Cross-group moves don't
1628
+ // run the FLIP path, and `moveGroupOrPanel` only inserts new
1629
+ // panels — it doesn't recreate existing destination tabs, so
1630
+ // their inline `margin-left` would otherwise persist as a
1631
+ // visible gap (issue #1243).
1632
+ this.resetTabTransforms();
1608
1633
  this.accessor.moveGroupOrPanel({
1609
1634
  from: {
1610
1635
  groupId: data.groupId,
@@ -1125,7 +1125,7 @@ var DockviewComponent = /** @class */ (function (_super) {
1125
1125
  // Collapse when the group becomes empty
1126
1126
  var autoCollapseDisposable = group.model.onDidRemovePanel(function () {
1127
1127
  if (group.model.isEmpty) {
1128
- _this._shellManager.setEdgeGroupCollapsed(position, true);
1128
+ _this.setEdgeGroupCollapsed(group, true);
1129
1129
  }
1130
1130
  });
1131
1131
  this._edgeGroupDisposables.set(position, autoCollapseDisposable);
@@ -1183,6 +1183,13 @@ var DockviewComponent = /** @class */ (function (_super) {
1183
1183
  for (var _b = __values(this._edgeGroups), _c = _b.next(); !_c.done; _c = _b.next()) {
1184
1184
  var _d = __read(_c.value, 2), position = _d[0], edgeGroup = _d[1];
1185
1185
  if (edgeGroup === group) {
1186
+ if (this._shellManager.isEdgeGroupCollapsed(position) ===
1187
+ collapsed) {
1188
+ // Skip the splitview resize on a no-op: with non-zero
1189
+ // theme gap, redundant resizeView calls accumulate
1190
+ // rounding drift that gradually shrinks the group.
1191
+ return;
1192
+ }
1186
1193
  this._shellManager.setEdgeGroupCollapsed(position, collapsed);
1187
1194
  edgeGroup.api._onDidCollapsedChange.fire({
1188
1195
  isCollapsed: collapsed,
@@ -2360,7 +2367,14 @@ var DockviewComponent = /** @class */ (function (_super) {
2360
2367
  var label = tabGroup.label;
2361
2368
  var color = tabGroup.color;
2362
2369
  var collapsed = tabGroup.collapsed;
2370
+ var componentParams = tabGroup.componentParams;
2363
2371
  var panelIds = __spreadArray([], __read(tabGroup.panelIds), false);
2372
+ // Capture the destination's grid location BEFORE potentially
2373
+ // removing the source group, in case source === destination and
2374
+ // the source becomes empty after panel removal.
2375
+ var referenceLocation = destinationTarget && destinationTarget !== 'center'
2376
+ ? (0, gridview_1.getGridLocation)(destinationGroup.element)
2377
+ : undefined;
2364
2378
  // Remove panels from the source group
2365
2379
  var removedPanels = this.movingLock(function () {
2366
2380
  return panelIds
@@ -2375,11 +2389,6 @@ var DockviewComponent = /** @class */ (function (_super) {
2375
2389
  if (removedPanels.length === 0) {
2376
2390
  return;
2377
2391
  }
2378
- if (!options.keepEmptyGroups &&
2379
- sourceGroup.model.size === 0 &&
2380
- sourceGroup !== destinationGroup) {
2381
- this.doRemoveGroup(sourceGroup, { skipActive: true });
2382
- }
2383
2392
  var addPanelsToGroup = function (targetGroup) {
2384
2393
  var e_28, _a, e_29, _b;
2385
2394
  _this.movingLock(function () {
@@ -2407,6 +2416,7 @@ var DockviewComponent = /** @class */ (function (_super) {
2407
2416
  label: label,
2408
2417
  color: color,
2409
2418
  collapsed: collapsed,
2419
+ componentParams: componentParams,
2410
2420
  });
2411
2421
  try {
2412
2422
  for (var removedPanels_1 = __values(removedPanels), removedPanels_1_1 = removedPanels_1.next(); !removedPanels_1_1.done; removedPanels_1_1 = removedPanels_1.next()) {
@@ -2441,17 +2451,30 @@ var DockviewComponent = /** @class */ (function (_super) {
2441
2451
  finally { if (e_29) throw e_29.error; }
2442
2452
  }
2443
2453
  };
2444
- if (!destinationTarget || destinationTarget === 'center') {
2445
- addPanelsToGroup(destinationGroup);
2454
+ var targetGroup;
2455
+ if (!destinationTarget ||
2456
+ destinationTarget === 'center' ||
2457
+ !referenceLocation) {
2458
+ targetGroup = destinationGroup;
2446
2459
  }
2447
2460
  else {
2448
- var referenceLocation = (0, gridview_1.getGridLocation)(destinationGroup.element);
2449
2461
  var dropLocation = (0, gridview_1.getRelativeLocation)(this.gridview.orientation, referenceLocation, destinationTarget);
2450
- var newGroup = this.createGroupAtLocation(dropLocation);
2451
- addPanelsToGroup(newGroup);
2462
+ targetGroup = this.createGroupAtLocation(dropLocation);
2452
2463
  }
2464
+ // Remove the source group if it became empty. We compare against
2465
+ // the actual targetGroup (which is a freshly-created group for
2466
+ // edge drops) rather than the originally-passed destinationGroup,
2467
+ // so a tab-group drag onto its own group's edge still cleans up
2468
+ // the now-empty source.
2469
+ if (!options.keepEmptyGroups &&
2470
+ sourceGroup.model.size === 0 &&
2471
+ sourceGroup !== targetGroup) {
2472
+ this.doRemoveGroup(sourceGroup, { skipActive: true });
2473
+ }
2474
+ addPanelsToGroup(targetGroup);
2453
2475
  };
2454
2476
  DockviewComponent.prototype.moveGroup = function (options) {
2477
+ var e_31, _a, e_32, _b, e_33, _c, e_34, _d;
2455
2478
  var _this = this;
2456
2479
  var from = options.from.group;
2457
2480
  var to = options.to.group;
@@ -2462,6 +2485,16 @@ var DockviewComponent = /** @class */ (function (_super) {
2462
2485
  var source = from;
2463
2486
  if (target === 'center') {
2464
2487
  var activePanel_1 = from.activePanel;
2488
+ // Snapshot tab group metadata before removing panels so we
2489
+ // can recreate the tab groups in the destination after the
2490
+ // panels are merged in.
2491
+ var tabGroupSnapshots = from.model.getTabGroups().map(function (tg) { return ({
2492
+ label: tg.label,
2493
+ color: tg.color,
2494
+ collapsed: tg.collapsed,
2495
+ componentParams: tg.componentParams,
2496
+ panelIds: __spreadArray([], __read(tg.panelIds), false),
2497
+ }); });
2465
2498
  var panels_3 = this.movingLock(function () {
2466
2499
  return __spreadArray([], __read(from.panels), false).map(function (p) {
2467
2500
  return from.model.removePanel(p.id, {
@@ -2473,7 +2506,7 @@ var DockviewComponent = /** @class */ (function (_super) {
2473
2506
  this.doRemoveGroup(from, { skipActive: true });
2474
2507
  }
2475
2508
  this.movingLock(function () {
2476
- var e_31, _a;
2509
+ var e_35, _a;
2477
2510
  try {
2478
2511
  for (var panels_4 = __values(panels_3), panels_4_1 = panels_4.next(); !panels_4_1.done; panels_4_1 = panels_4.next()) {
2479
2512
  var panel = panels_4_1.value;
@@ -2483,14 +2516,45 @@ var DockviewComponent = /** @class */ (function (_super) {
2483
2516
  });
2484
2517
  }
2485
2518
  }
2486
- catch (e_31_1) { e_31 = { error: e_31_1 }; }
2519
+ catch (e_35_1) { e_35 = { error: e_35_1 }; }
2487
2520
  finally {
2488
2521
  try {
2489
2522
  if (panels_4_1 && !panels_4_1.done && (_a = panels_4.return)) _a.call(panels_4);
2490
2523
  }
2491
- finally { if (e_31) throw e_31.error; }
2524
+ finally { if (e_35) throw e_35.error; }
2492
2525
  }
2493
2526
  });
2527
+ try {
2528
+ for (var tabGroupSnapshots_1 = __values(tabGroupSnapshots), tabGroupSnapshots_1_1 = tabGroupSnapshots_1.next(); !tabGroupSnapshots_1_1.done; tabGroupSnapshots_1_1 = tabGroupSnapshots_1.next()) {
2529
+ var snapshot = tabGroupSnapshots_1_1.value;
2530
+ var newTabGroup = to.model.createTabGroup({
2531
+ label: snapshot.label,
2532
+ color: snapshot.color,
2533
+ collapsed: snapshot.collapsed,
2534
+ componentParams: snapshot.componentParams,
2535
+ });
2536
+ try {
2537
+ for (var _e = (e_32 = void 0, __values(snapshot.panelIds)), _f = _e.next(); !_f.done; _f = _e.next()) {
2538
+ var panelId = _f.value;
2539
+ to.model.addPanelToTabGroup(newTabGroup.id, panelId);
2540
+ }
2541
+ }
2542
+ catch (e_32_1) { e_32 = { error: e_32_1 }; }
2543
+ finally {
2544
+ try {
2545
+ if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
2546
+ }
2547
+ finally { if (e_32) throw e_32.error; }
2548
+ }
2549
+ }
2550
+ }
2551
+ catch (e_31_1) { e_31 = { error: e_31_1 }; }
2552
+ finally {
2553
+ try {
2554
+ if (tabGroupSnapshots_1_1 && !tabGroupSnapshots_1_1.done && (_a = tabGroupSnapshots_1.return)) _a.call(tabGroupSnapshots_1);
2555
+ }
2556
+ finally { if (e_31) throw e_31.error; }
2557
+ }
2494
2558
  // Ensure group becomes active after move
2495
2559
  if (options.skipSetActive !== true) {
2496
2560
  // For center moves (merges), we need to ensure the target group is active
@@ -2514,6 +2578,17 @@ var DockviewComponent = /** @class */ (function (_super) {
2514
2578
  * positions `source` like any other moved group.
2515
2579
  */
2516
2580
  var activePanel_2 = from.activePanel;
2581
+ // Snapshot tab group metadata so the new group inherits
2582
+ // the tab grouping from the edge slot.
2583
+ var tabGroupSnapshots = from.model
2584
+ .getTabGroups()
2585
+ .map(function (tg) { return ({
2586
+ label: tg.label,
2587
+ color: tg.color,
2588
+ collapsed: tg.collapsed,
2589
+ componentParams: tg.componentParams,
2590
+ panelIds: __spreadArray([], __read(tg.panelIds), false),
2591
+ }); });
2517
2592
  var movedPanels_1 = this.movingLock(function () {
2518
2593
  return __spreadArray([], __read(from.panels), false).map(function (p) {
2519
2594
  return from.model.removePanel(p.id, { skipSetActive: true });
@@ -2521,7 +2596,7 @@ var DockviewComponent = /** @class */ (function (_super) {
2521
2596
  });
2522
2597
  source = this.createGroup();
2523
2598
  this.movingLock(function () {
2524
- var e_32, _a;
2599
+ var e_36, _a;
2525
2600
  try {
2526
2601
  for (var movedPanels_2 = __values(movedPanels_1), movedPanels_2_1 = movedPanels_2.next(); !movedPanels_2_1.done; movedPanels_2_1 = movedPanels_2.next()) {
2527
2602
  var panel = movedPanels_2_1.value;
@@ -2531,14 +2606,45 @@ var DockviewComponent = /** @class */ (function (_super) {
2531
2606
  });
2532
2607
  }
2533
2608
  }
2534
- catch (e_32_1) { e_32 = { error: e_32_1 }; }
2609
+ catch (e_36_1) { e_36 = { error: e_36_1 }; }
2535
2610
  finally {
2536
2611
  try {
2537
2612
  if (movedPanels_2_1 && !movedPanels_2_1.done && (_a = movedPanels_2.return)) _a.call(movedPanels_2);
2538
2613
  }
2539
- finally { if (e_32) throw e_32.error; }
2614
+ finally { if (e_36) throw e_36.error; }
2540
2615
  }
2541
2616
  });
2617
+ try {
2618
+ for (var tabGroupSnapshots_2 = __values(tabGroupSnapshots), tabGroupSnapshots_2_1 = tabGroupSnapshots_2.next(); !tabGroupSnapshots_2_1.done; tabGroupSnapshots_2_1 = tabGroupSnapshots_2.next()) {
2619
+ var snapshot = tabGroupSnapshots_2_1.value;
2620
+ var newTabGroup = source.model.createTabGroup({
2621
+ label: snapshot.label,
2622
+ color: snapshot.color,
2623
+ collapsed: snapshot.collapsed,
2624
+ componentParams: snapshot.componentParams,
2625
+ });
2626
+ try {
2627
+ for (var _g = (e_34 = void 0, __values(snapshot.panelIds)), _h = _g.next(); !_h.done; _h = _g.next()) {
2628
+ var panelId = _h.value;
2629
+ source.model.addPanelToTabGroup(newTabGroup.id, panelId);
2630
+ }
2631
+ }
2632
+ catch (e_34_1) { e_34 = { error: e_34_1 }; }
2633
+ finally {
2634
+ try {
2635
+ if (_h && !_h.done && (_d = _g.return)) _d.call(_g);
2636
+ }
2637
+ finally { if (e_34) throw e_34.error; }
2638
+ }
2639
+ }
2640
+ }
2641
+ catch (e_33_1) { e_33 = { error: e_33_1 }; }
2642
+ finally {
2643
+ try {
2644
+ if (tabGroupSnapshots_2_1 && !tabGroupSnapshots_2_1.done && (_c = tabGroupSnapshots_2.return)) _c.call(tabGroupSnapshots_2);
2645
+ }
2646
+ finally { if (e_33) throw e_33.error; }
2647
+ }
2542
2648
  }
2543
2649
  else {
2544
2650
  switch (from.api.location.type) {
@@ -2841,7 +2947,7 @@ var DockviewComponent = /** @class */ (function (_super) {
2841
2947
  }
2842
2948
  };
2843
2949
  DockviewComponent.prototype.updateTheme = function () {
2844
- var e_33, _a;
2950
+ var e_37, _a;
2845
2951
  var _b, _c, _d, _e, _f, _g, _h, _j, _k;
2846
2952
  var theme = (_b = this._options.theme) !== null && _b !== void 0 ? _b : theme_1.themeAbyss;
2847
2953
  // Apply the theme class only to the shell so edge groups and the
@@ -2882,12 +2988,12 @@ var DockviewComponent = /** @class */ (function (_super) {
2882
2988
  group.model.updateTabGroups();
2883
2989
  }
2884
2990
  }
2885
- catch (e_33_1) { e_33 = { error: e_33_1 }; }
2991
+ catch (e_37_1) { e_37 = { error: e_37_1 }; }
2886
2992
  finally {
2887
2993
  try {
2888
2994
  if (_m && !_m.done && (_a = _l.return)) _a.call(_l);
2889
2995
  }
2890
- finally { if (e_33) throw e_33.error; }
2996
+ finally { if (e_37) throw e_37.error; }
2891
2997
  }
2892
2998
  };
2893
2999
  return DockviewComponent;
@@ -1335,15 +1335,17 @@ var DockviewGroupPanelModel = /** @class */ (function (_super) {
1335
1335
  if (position === 'center') {
1336
1336
  return;
1337
1337
  }
1338
- if (data.panelId === null) {
1339
- // don't allow group move to drop anywhere on self
1338
+ if (data.panelId === null && !data.tabGroupId) {
1339
+ // Full-group drops on self are a no-op.
1340
+ // Tab-group drags are partial moves: an edge drop
1341
+ // splits the layout and creates a new group.
1340
1342
  return;
1341
1343
  }
1342
1344
  }
1343
1345
  }
1344
1346
  if (type === 'header') {
1345
1347
  if (data.groupId === this.id) {
1346
- if (data.panelId === null) {
1348
+ if (data.panelId === null && !data.tabGroupId) {
1347
1349
  return;
1348
1350
  }
1349
1351
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * dockview-core
3
- * @version 6.0.5
3
+ * @version 6.0.7
4
4
  * @link https://github.com/mathuo/dockview
5
5
  * @license MIT
6
6
  */
@@ -7235,10 +7235,12 @@
7235
7235
  const index = this.indexOf(id);
7236
7236
  const tabToRemove = this._tabs.splice(index, 1)[0];
7237
7237
  this._tabMap.delete(id);
7238
- const { value, disposable } = tabToRemove;
7239
- disposable.dispose();
7240
- value.dispose();
7241
- value.element.remove();
7238
+ if (tabToRemove) {
7239
+ const { value, disposable } = tabToRemove;
7240
+ disposable.dispose();
7241
+ value.dispose();
7242
+ value.element.remove();
7243
+ }
7242
7244
  // If a non-source tab was removed during active drag, refresh positions
7243
7245
  if (this._animState) {
7244
7246
  this._animState.tabPositions = this.snapshotTabPositions();
@@ -7360,8 +7362,23 @@
7360
7362
  new PanelTransfer(this.accessor.id, this.group.id, null, tabGroup.id),
7361
7363
  ], PanelTransfer.prototype);
7362
7364
  const iframes = disableIframePointEvents();
7365
+ // The dragend listener on `_tabsList` is unreachable for chip
7366
+ // drags because cross-group drops detach the chip from the DOM
7367
+ // before dragend fires (the source tab group becomes empty, so
7368
+ // `_positionChipForGroup` removes the chip element). Without
7369
+ // bubbling, the tabsList listener never runs and `_animState`,
7370
+ // `_chipDragCleanup`, and the dragging CSS classes leak. Listen
7371
+ // directly on the chip element so cleanup happens regardless of
7372
+ // whether it's still attached. (Issue #1254.)
7373
+ const chipElement = chip.element;
7374
+ const onChipDragEnd = () => {
7375
+ chipElement.removeEventListener('dragend', onChipDragEnd);
7376
+ this.resetDragAnimation();
7377
+ };
7378
+ chipElement.addEventListener('dragend', onChipDragEnd);
7363
7379
  this._chipDragCleanup = {
7364
7380
  dispose: () => {
7381
+ chipElement.removeEventListener('dragend', onChipDragEnd);
7365
7382
  panelTransfer.clearData(PanelTransfer.prototype);
7366
7383
  iframes.release();
7367
7384
  },
@@ -7910,6 +7927,14 @@
7910
7927
  // handles panel transfer and tab group recreation.
7911
7928
  // Use the REAL tab group ID from transfer data, not the
7912
7929
  // potentially stale one from _animState.
7930
+ //
7931
+ // Clear any inline gap margin / shifting class applied to
7932
+ // destination tabs during dragover. Cross-group moves don't
7933
+ // run the FLIP path, and `moveGroupOrPanel` only inserts new
7934
+ // panels — it doesn't recreate existing destination tabs, so
7935
+ // their inline `margin-left` would otherwise persist as a
7936
+ // visible gap (issue #1243).
7937
+ this.resetTabTransforms();
7913
7938
  this.accessor.moveGroupOrPanel({
7914
7939
  from: {
7915
7940
  groupId: data.groupId,
@@ -9690,15 +9715,17 @@
9690
9715
  if (position === 'center') {
9691
9716
  return;
9692
9717
  }
9693
- if (data.panelId === null) {
9694
- // don't allow group move to drop anywhere on self
9718
+ if (data.panelId === null && !data.tabGroupId) {
9719
+ // Full-group drops on self are a no-op.
9720
+ // Tab-group drags are partial moves: an edge drop
9721
+ // splits the layout and creates a new group.
9695
9722
  return;
9696
9723
  }
9697
9724
  }
9698
9725
  }
9699
9726
  if (type === 'header') {
9700
9727
  if (data.groupId === this.id) {
9701
- if (data.panelId === null) {
9728
+ if (data.panelId === null && !data.tabGroupId) {
9702
9729
  return;
9703
9730
  }
9704
9731
  }
@@ -13566,7 +13593,7 @@
13566
13593
  // Collapse when the group becomes empty
13567
13594
  const autoCollapseDisposable = group.model.onDidRemovePanel(() => {
13568
13595
  if (group.model.isEmpty) {
13569
- this._shellManager.setEdgeGroupCollapsed(position, true);
13596
+ this.setEdgeGroupCollapsed(group, true);
13570
13597
  }
13571
13598
  });
13572
13599
  this._edgeGroupDisposables.set(position, autoCollapseDisposable);
@@ -13610,6 +13637,13 @@
13610
13637
  setEdgeGroupCollapsed(group, collapsed) {
13611
13638
  for (const [position, edgeGroup] of this._edgeGroups) {
13612
13639
  if (edgeGroup === group) {
13640
+ if (this._shellManager.isEdgeGroupCollapsed(position) ===
13641
+ collapsed) {
13642
+ // Skip the splitview resize on a no-op: with non-zero
13643
+ // theme gap, redundant resizeView calls accumulate
13644
+ // rounding drift that gradually shrinks the group.
13645
+ return;
13646
+ }
13613
13647
  this._shellManager.setEdgeGroupCollapsed(position, collapsed);
13614
13648
  edgeGroup.api._onDidCollapsedChange.fire({
13615
13649
  isCollapsed: collapsed,
@@ -14562,7 +14596,14 @@
14562
14596
  const label = tabGroup.label;
14563
14597
  const color = tabGroup.color;
14564
14598
  const collapsed = tabGroup.collapsed;
14599
+ const componentParams = tabGroup.componentParams;
14565
14600
  const panelIds = [...tabGroup.panelIds];
14601
+ // Capture the destination's grid location BEFORE potentially
14602
+ // removing the source group, in case source === destination and
14603
+ // the source becomes empty after panel removal.
14604
+ const referenceLocation = destinationTarget && destinationTarget !== 'center'
14605
+ ? getGridLocation(destinationGroup.element)
14606
+ : undefined;
14566
14607
  // Remove panels from the source group
14567
14608
  const removedPanels = this.movingLock(() => panelIds
14568
14609
  .map((pid) => sourceGroup.model.removePanel(pid, {
@@ -14573,11 +14614,6 @@
14573
14614
  if (removedPanels.length === 0) {
14574
14615
  return;
14575
14616
  }
14576
- if (!options.keepEmptyGroups &&
14577
- sourceGroup.model.size === 0 &&
14578
- sourceGroup !== destinationGroup) {
14579
- this.doRemoveGroup(sourceGroup, { skipActive: true });
14580
- }
14581
14617
  const addPanelsToGroup = (targetGroup) => {
14582
14618
  this.movingLock(() => {
14583
14619
  for (const panel of removedPanels) {
@@ -14593,6 +14629,7 @@
14593
14629
  label,
14594
14630
  color,
14595
14631
  collapsed,
14632
+ componentParams,
14596
14633
  });
14597
14634
  for (const panel of removedPanels) {
14598
14635
  targetGroup.model.addPanelToTabGroup(newTabGroup.id, panel.id);
@@ -14607,15 +14644,27 @@
14607
14644
  });
14608
14645
  }
14609
14646
  };
14610
- if (!destinationTarget || destinationTarget === 'center') {
14611
- addPanelsToGroup(destinationGroup);
14647
+ let targetGroup;
14648
+ if (!destinationTarget ||
14649
+ destinationTarget === 'center' ||
14650
+ !referenceLocation) {
14651
+ targetGroup = destinationGroup;
14612
14652
  }
14613
14653
  else {
14614
- const referenceLocation = getGridLocation(destinationGroup.element);
14615
14654
  const dropLocation = getRelativeLocation(this.gridview.orientation, referenceLocation, destinationTarget);
14616
- const newGroup = this.createGroupAtLocation(dropLocation);
14617
- addPanelsToGroup(newGroup);
14655
+ targetGroup = this.createGroupAtLocation(dropLocation);
14618
14656
  }
14657
+ // Remove the source group if it became empty. We compare against
14658
+ // the actual targetGroup (which is a freshly-created group for
14659
+ // edge drops) rather than the originally-passed destinationGroup,
14660
+ // so a tab-group drag onto its own group's edge still cleans up
14661
+ // the now-empty source.
14662
+ if (!options.keepEmptyGroups &&
14663
+ sourceGroup.model.size === 0 &&
14664
+ sourceGroup !== targetGroup) {
14665
+ this.doRemoveGroup(sourceGroup, { skipActive: true });
14666
+ }
14667
+ addPanelsToGroup(targetGroup);
14619
14668
  }
14620
14669
  moveGroup(options) {
14621
14670
  const from = options.from.group;
@@ -14627,6 +14676,16 @@
14627
14676
  let source = from;
14628
14677
  if (target === 'center') {
14629
14678
  const activePanel = from.activePanel;
14679
+ // Snapshot tab group metadata before removing panels so we
14680
+ // can recreate the tab groups in the destination after the
14681
+ // panels are merged in.
14682
+ const tabGroupSnapshots = from.model.getTabGroups().map((tg) => ({
14683
+ label: tg.label,
14684
+ color: tg.color,
14685
+ collapsed: tg.collapsed,
14686
+ componentParams: tg.componentParams,
14687
+ panelIds: [...tg.panelIds],
14688
+ }));
14630
14689
  const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, {
14631
14690
  skipSetActive: true,
14632
14691
  })));
@@ -14641,6 +14700,17 @@
14641
14700
  });
14642
14701
  }
14643
14702
  });
14703
+ for (const snapshot of tabGroupSnapshots) {
14704
+ const newTabGroup = to.model.createTabGroup({
14705
+ label: snapshot.label,
14706
+ color: snapshot.color,
14707
+ collapsed: snapshot.collapsed,
14708
+ componentParams: snapshot.componentParams,
14709
+ });
14710
+ for (const panelId of snapshot.panelIds) {
14711
+ to.model.addPanelToTabGroup(newTabGroup.id, panelId);
14712
+ }
14713
+ }
14644
14714
  // Ensure group becomes active after move
14645
14715
  if (options.skipSetActive !== true) {
14646
14716
  // For center moves (merges), we need to ensure the target group is active
@@ -14664,6 +14734,17 @@
14664
14734
  * positions `source` like any other moved group.
14665
14735
  */
14666
14736
  const activePanel = from.activePanel;
14737
+ // Snapshot tab group metadata so the new group inherits
14738
+ // the tab grouping from the edge slot.
14739
+ const tabGroupSnapshots = from.model
14740
+ .getTabGroups()
14741
+ .map((tg) => ({
14742
+ label: tg.label,
14743
+ color: tg.color,
14744
+ collapsed: tg.collapsed,
14745
+ componentParams: tg.componentParams,
14746
+ panelIds: [...tg.panelIds],
14747
+ }));
14667
14748
  const movedPanels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, { skipSetActive: true })));
14668
14749
  source = this.createGroup();
14669
14750
  this.movingLock(() => {
@@ -14674,6 +14755,17 @@
14674
14755
  });
14675
14756
  }
14676
14757
  });
14758
+ for (const snapshot of tabGroupSnapshots) {
14759
+ const newTabGroup = source.model.createTabGroup({
14760
+ label: snapshot.label,
14761
+ color: snapshot.color,
14762
+ collapsed: snapshot.collapsed,
14763
+ componentParams: snapshot.componentParams,
14764
+ });
14765
+ for (const panelId of snapshot.panelIds) {
14766
+ source.model.addPanelToTabGroup(newTabGroup.id, panelId);
14767
+ }
14768
+ }
14677
14769
  }
14678
14770
  else {
14679
14771
  switch (from.api.location.type) {