dockview-core 8.0.0 → 8.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,12 +1,14 @@
1
1
  /**
2
2
  * dockview-core
3
- * @version 8.0.0
3
+ * @version 8.2.0
4
4
  * @link https://github.com/dockview/dockview
5
5
  * @license MIT
6
6
  */
7
7
  //#region src/dnd/dataTransfer.ts
8
8
  var _LocalSelectionTransfer;
9
- var TransferObject = class {};
9
+ var TransferObject = class {
10
+ constructor() {}
11
+ };
10
12
  var PanelTransfer = class extends TransferObject {
11
13
  constructor(viewId, groupId, panelId, tabGroupId) {
12
14
  super();
@@ -199,7 +201,6 @@ var Emitter = class Emitter {
199
201
  this.options = options;
200
202
  this._listeners = [];
201
203
  this._disposed = false;
202
- this._pauseTokens = /* @__PURE__ */ new Set();
203
204
  }
204
205
  get event() {
205
206
  if (!this._event) {
@@ -220,14 +221,26 @@ var Emitter = class Emitter {
220
221
  }
221
222
  fire(e) {
222
223
  var _this$options2;
223
- if (this._pauseTokens.size > 0) return;
224
+ if (this._pauseTokens !== void 0 && this._pauseTokens.size > 0) return;
224
225
  if ((_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.replay) this._last = e;
225
- for (const listener of this._listeners.slice()) listener.callback(e);
226
+ const listeners = this._listeners;
227
+ const length = listeners.length;
228
+ if (length === 0) return;
229
+ if (length === 1) {
230
+ listeners[0].callback(e);
231
+ return;
232
+ }
233
+ for (const listener of listeners.slice()) listener.callback(e);
226
234
  }
227
235
  pause() {
236
+ var _this$_pauseTokens;
228
237
  const token = {};
238
+ (_this$_pauseTokens = this._pauseTokens) !== null && _this$_pauseTokens !== void 0 || (this._pauseTokens = /* @__PURE__ */ new Set());
229
239
  this._pauseTokens.add(token);
230
- return Disposable.from(() => this._pauseTokens.delete(token));
240
+ return Disposable.from(() => {
241
+ var _this$_pauseTokens2;
242
+ return (_this$_pauseTokens2 = this._pauseTokens) === null || _this$_pauseTokens2 === void 0 ? void 0 : _this$_pauseTokens2.delete(token);
243
+ });
231
244
  }
232
245
  dispose() {
233
246
  if (!this._disposed) {
@@ -530,8 +543,10 @@ function onDidWindowMoveEnd(window) {
530
543
  let previousScreenX = window.screenX;
531
544
  let previousScreenY = window.screenY;
532
545
  let timeout;
546
+ let rafHandle = 0;
547
+ let stopped = false;
533
548
  const checkMovement = () => {
534
- if (window.closed) return;
549
+ if (stopped || window.closed) return;
535
550
  const currentScreenX = window.screenX;
536
551
  const currentScreenY = window.screenY;
537
552
  if (currentScreenX !== previousScreenX || currentScreenY !== previousScreenY) {
@@ -542,9 +557,18 @@ function onDidWindowMoveEnd(window) {
542
557
  previousScreenX = currentScreenX;
543
558
  previousScreenY = currentScreenY;
544
559
  }
545
- requestAnimationFrame(checkMovement);
560
+ rafHandle = requestAnimationFrame(checkMovement);
546
561
  };
547
562
  checkMovement();
563
+ const disposeEmitter = emitter.dispose.bind(emitter);
564
+ emitter.dispose = () => {
565
+ if (!stopped) {
566
+ stopped = true;
567
+ cancelAnimationFrame(rafHandle);
568
+ clearTimeout(timeout);
569
+ }
570
+ disposeEmitter();
571
+ };
548
572
  return emitter;
549
573
  }
550
574
  function onDidWindowResizeEnd(element, cb) {
@@ -680,6 +704,11 @@ var ViewItem = class {
680
704
  set size(size) {
681
705
  this._size = size;
682
706
  }
707
+ setContainerGeometry(prop, value) {
708
+ if (this._appliedStyles[prop] === value) return;
709
+ this._appliedStyles[prop] = value;
710
+ this.container.style[prop] = value;
711
+ }
683
712
  get size() {
684
713
  return this._size;
685
714
  }
@@ -715,6 +744,7 @@ var ViewItem = class {
715
744
  this.view = view;
716
745
  this.disposable = disposable;
717
746
  this._cachedVisibleSize = void 0;
747
+ this._appliedStyles = {};
718
748
  if (typeof size === "number") {
719
749
  this._size = size;
720
750
  this._cachedVisibleSize = void 0;
@@ -763,6 +793,16 @@ let LayoutPriority = /* @__PURE__ */ function(LayoutPriority) {
763
793
  LayoutPriority["Normal"] = "normal";
764
794
  return LayoutPriority;
765
795
  }({});
796
+ function setSashPosition(sash, left, top) {
797
+ if (sash.appliedLeft !== left) {
798
+ sash.appliedLeft = left;
799
+ sash.container.style.left = left;
800
+ }
801
+ if (sash.appliedTop !== top) {
802
+ sash.appliedTop = top;
803
+ sash.container.style.top = top;
804
+ }
805
+ }
766
806
  let Sizing;
767
807
  (function(_Sizing) {
768
808
  _Sizing.Distribute = { type: "distribute" };
@@ -881,10 +921,6 @@ var Splitview = class {
881
921
  pushToEnd(upIndexes, i);
882
922
  pushToEnd(downIndexes, i);
883
923
  }
884
- const upItems = upIndexes.map((i) => this.viewItems[i]);
885
- const upSizes = upIndexes.map((i) => sizes[i]);
886
- const downItems = downIndexes.map((i) => this.viewItems[i]);
887
- const downSizes = downIndexes.map((i) => sizes[i]);
888
924
  const minDeltaUp = upIndexes.reduce((_, i) => _ + this.viewItems[i].minimumSize - sizes[i], 0);
889
925
  const maxDeltaUp = upIndexes.reduce((_, i) => _ + this.viewItems[i].maximumSize - sizes[i], 0);
890
926
  const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((_, i) => _ + sizes[i] - this.viewItems[i].minimumSize, 0);
@@ -908,19 +944,21 @@ var Splitview = class {
908
944
  const tentativeDelta = clamp(delta, minDelta, maxDelta);
909
945
  let actualDelta = 0;
910
946
  let deltaUp = tentativeDelta;
911
- for (let i = 0; i < upItems.length; i++) {
912
- const item = upItems[i];
913
- const size = clamp(upSizes[i] + deltaUp, item.minimumSize, item.maximumSize);
914
- const viewDelta = size - upSizes[i];
947
+ for (const upIndex of upIndexes) {
948
+ const item = this.viewItems[upIndex];
949
+ const priorSize = sizes[upIndex];
950
+ const size = clamp(priorSize + deltaUp, item.minimumSize, item.maximumSize);
951
+ const viewDelta = size - priorSize;
915
952
  actualDelta += viewDelta;
916
953
  deltaUp -= viewDelta;
917
954
  item.size = size;
918
955
  }
919
956
  let deltaDown = actualDelta;
920
- for (let i = 0; i < downItems.length; i++) {
921
- const item = downItems[i];
922
- const size = clamp(downSizes[i] - deltaDown, item.minimumSize, item.maximumSize);
923
- const viewDelta = size - downSizes[i];
957
+ for (const downIndex of downIndexes) {
958
+ const item = this.viewItems[downIndex];
959
+ const priorSize = sizes[downIndex];
960
+ const size = clamp(priorSize - deltaDown, item.minimumSize, item.maximumSize);
961
+ const viewDelta = size - priorSize;
924
962
  deltaDown += viewDelta;
925
963
  item.size = size;
926
964
  }
@@ -1168,13 +1206,22 @@ var Splitview = class {
1168
1206
  this.saveProportions();
1169
1207
  }
1170
1208
  distributeEmptySpace(lowPriorityIndex) {
1171
- const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
1209
+ let contentSize = 0;
1210
+ for (const item of this.viewItems) contentSize += item.size;
1172
1211
  let emptyDelta = this.size - contentSize;
1212
+ if (emptyDelta === 0) return;
1173
1213
  const indexes = range(this.viewItems.length - 1, -1);
1174
- const lowPriorityIndexes = indexes.filter((i) => this.viewItems[i].priority === "low");
1175
- const highPriorityIndexes = indexes.filter((i) => this.viewItems[i].priority === "high");
1176
- for (const index of highPriorityIndexes) pushToStart(indexes, index);
1177
- for (const index of lowPriorityIndexes) pushToEnd(indexes, index);
1214
+ let hasPriority = false;
1215
+ for (const item of this.viewItems) if (item.priority === "low" || item.priority === "high") {
1216
+ hasPriority = true;
1217
+ break;
1218
+ }
1219
+ if (hasPriority) {
1220
+ const lowPriorityIndexes = indexes.filter((i) => this.viewItems[i].priority === "low");
1221
+ const highPriorityIndexes = indexes.filter((i) => this.viewItems[i].priority === "high");
1222
+ for (const index of highPriorityIndexes) pushToStart(indexes, index);
1223
+ for (const index of lowPriorityIndexes) pushToEnd(indexes, index);
1224
+ }
1178
1225
  if (typeof lowPriorityIndex === "number") pushToEnd(indexes, lowPriorityIndex);
1179
1226
  for (let i = 0; emptyDelta !== 0 && i < indexes.length; i++) {
1180
1227
  const item = this.viewItems[indexes[i]];
@@ -1197,49 +1244,45 @@ var Splitview = class {
1197
1244
  * For each view `i` the offet must be adjusted by `m * i/(n - 1)`.
1198
1245
  */
1199
1246
  layoutViews() {
1200
- this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
1247
+ let contentSize = 0;
1248
+ let visibleViewCount = 0;
1249
+ for (const item of this.viewItems) {
1250
+ contentSize += item.size;
1251
+ if (item.visible) visibleViewCount++;
1252
+ }
1253
+ this._contentSize = contentSize;
1201
1254
  this.updateSashEnablement();
1202
1255
  if (this.viewItems.length === 0) return;
1203
- const visibleViewItems = this.viewItems.filter((i) => i.visible);
1204
- const sashCount = Math.max(0, visibleViewItems.length - 1);
1205
- const marginReducedSize = this.margin * sashCount / Math.max(1, visibleViewItems.length);
1256
+ const sashCount = Math.max(0, visibleViewCount - 1);
1257
+ const marginReducedSize = this.margin * sashCount / Math.max(1, visibleViewCount);
1206
1258
  let totalLeftOffset = 0;
1207
1259
  const viewLeftOffsets = [];
1208
1260
  const sashWidth = 4;
1209
- const runningVisiblePanelCount = this.viewItems.reduce((arr, viewItem, i) => {
1210
- const flag = viewItem.visible ? 1 : 0;
1211
- if (i === 0) arr.push(flag);
1212
- else arr.push(arr[i - 1] + flag);
1213
- return arr;
1214
- }, []);
1261
+ let runningVisiblePanelCount = 0;
1215
1262
  this.viewItems.forEach((view, i) => {
1216
1263
  totalLeftOffset += this.viewItems[i].size;
1217
1264
  viewLeftOffsets.push(totalLeftOffset);
1265
+ runningVisiblePanelCount += view.visible ? 1 : 0;
1218
1266
  const size = view.visible ? view.size - marginReducedSize : 0;
1219
- const visiblePanelsBeforeThisView = Math.max(0, runningVisiblePanelCount[i] - 1);
1267
+ const visiblePanelsBeforeThisView = Math.max(0, runningVisiblePanelCount - 1);
1220
1268
  const offset = i === 0 || visiblePanelsBeforeThisView === 0 ? 0 : viewLeftOffsets[i - 1] + visiblePanelsBeforeThisView / sashCount * marginReducedSize;
1221
1269
  if (i < this.viewItems.length - 1) {
1222
1270
  const newSize = view.visible ? offset + size - sashWidth / 2 + this.margin / 2 : offset;
1223
- if (this._orientation === "HORIZONTAL") {
1224
- this.sashes[i].container.style.left = `${newSize}px`;
1225
- this.sashes[i].container.style.top = `0px`;
1226
- }
1227
- if (this._orientation === "VERTICAL") {
1228
- this.sashes[i].container.style.left = `0px`;
1229
- this.sashes[i].container.style.top = `${newSize}px`;
1230
- }
1271
+ const sash = this.sashes[i];
1272
+ if (this._orientation === "HORIZONTAL") setSashPosition(sash, `${newSize}px`, `0px`);
1273
+ if (this._orientation === "VERTICAL") setSashPosition(sash, `0px`, `${newSize}px`);
1231
1274
  }
1232
1275
  if (this._orientation === "HORIZONTAL") {
1233
- view.container.style.width = `${size}px`;
1234
- view.container.style.left = `${offset}px`;
1235
- view.container.style.top = "";
1236
- view.container.style.height = "";
1276
+ view.setContainerGeometry("width", `${size}px`);
1277
+ view.setContainerGeometry("left", `${offset}px`);
1278
+ view.setContainerGeometry("top", "");
1279
+ view.setContainerGeometry("height", "");
1237
1280
  }
1238
1281
  if (this._orientation === "VERTICAL") {
1239
- view.container.style.height = `${size}px`;
1240
- view.container.style.top = `${offset}px`;
1241
- view.container.style.width = "";
1242
- view.container.style.left = "";
1282
+ view.setContainerGeometry("height", `${size}px`);
1283
+ view.setContainerGeometry("top", `${offset}px`);
1284
+ view.setContainerGeometry("width", "");
1285
+ view.setContainerGeometry("left", "");
1243
1286
  }
1244
1287
  view.view.layout(view.size - marginReducedSize, this._orthogonalSize);
1245
1288
  });
@@ -1257,6 +1300,7 @@ var Splitview = class {
1257
1300
  }
1258
1301
  }
1259
1302
  updateSashEnablement() {
1303
+ if (this.sashes.length === 0) return;
1260
1304
  let previous = false;
1261
1305
  const collapsesDown = this.viewItems.map((i) => previous = i.size - i.minimumSize > 0 || previous);
1262
1306
  previous = false;
@@ -1563,10 +1607,20 @@ var BranchNode = class BranchNode extends CompositeDisposable {
1563
1607
  return this.orientation === "HORIZONTAL" ? this.orthogonalSize : this.size;
1564
1608
  }
1565
1609
  get minimumSize() {
1566
- return this.children.length === 0 ? 0 : Math.max(...this.children.map((c, index) => this.splitview.isViewVisible(index) ? c.minimumOrthogonalSize : 0));
1610
+ if (this._cachedMinimumSize === void 0) {
1611
+ let value = 0;
1612
+ for (let index = 0; index < this.children.length; index++) if (this.splitview.isViewVisible(index)) value = Math.max(value, this.children[index].minimumOrthogonalSize);
1613
+ this._cachedMinimumSize = value;
1614
+ }
1615
+ return this._cachedMinimumSize;
1567
1616
  }
1568
1617
  get maximumSize() {
1569
- return Math.min(...this.children.map((c, index) => this.splitview.isViewVisible(index) ? c.maximumOrthogonalSize : Number.POSITIVE_INFINITY));
1618
+ if (this._cachedMaximumSize === void 0) {
1619
+ let value = Number.POSITIVE_INFINITY;
1620
+ for (let index = 0; index < this.children.length; index++) if (this.splitview.isViewVisible(index)) value = Math.min(value, this.children[index].maximumOrthogonalSize);
1621
+ this._cachedMaximumSize = value;
1622
+ }
1623
+ return this._cachedMaximumSize;
1570
1624
  }
1571
1625
  get minimumOrthogonalSize() {
1572
1626
  return this.splitview.minimumSize;
@@ -1593,15 +1647,29 @@ var BranchNode = class BranchNode extends CompositeDisposable {
1593
1647
  return this.orientation === "HORIZONTAL" ? this.maximumSize : this.maximumOrthogonalSize;
1594
1648
  }
1595
1649
  get priority() {
1650
+ var _this$_cachedPriority;
1651
+ (_this$_cachedPriority = this._cachedPriority) !== null && _this$_cachedPriority !== void 0 || (this._cachedPriority = this.computePriority());
1652
+ return this._cachedPriority;
1653
+ }
1654
+ computePriority() {
1596
1655
  if (this.children.length === 0) return "normal";
1597
- const priorities = new Set(this.children.map((c) => {
1598
- var _c$priority;
1599
- return (_c$priority = c.priority) !== null && _c$priority !== void 0 ? _c$priority : "normal";
1600
- }));
1601
- if (priorities.has("high")) return "high";
1602
- else if (priorities.has("low")) return "low";
1656
+ let hasHigh = false;
1657
+ let hasLow = false;
1658
+ for (const child of this.children) {
1659
+ var _child$priority;
1660
+ const priority = (_child$priority = child.priority) !== null && _child$priority !== void 0 ? _child$priority : "normal";
1661
+ if (priority === "high") hasHigh = true;
1662
+ else if (priority === "low") hasLow = true;
1663
+ }
1664
+ if (hasHigh) return "high";
1665
+ else if (hasLow) return "low";
1603
1666
  return "normal";
1604
1667
  }
1668
+ invalidateCachedSizes() {
1669
+ this._cachedMinimumSize = void 0;
1670
+ this._cachedMaximumSize = void 0;
1671
+ this._cachedPriority = void 0;
1672
+ }
1605
1673
  get disabled() {
1606
1674
  return this.splitview.disabled;
1607
1675
  }
@@ -1677,6 +1745,7 @@ var BranchNode = class BranchNode extends CompositeDisposable {
1677
1745
  if (this.splitview.isViewVisible(index) === visible) return;
1678
1746
  const wereAllChildrenHidden = this.splitview.contentSize === 0;
1679
1747
  this.splitview.setViewVisible(index, visible);
1748
+ this.invalidateCachedSizes();
1680
1749
  const areAllChildrenHidden = this.splitview.contentSize === 0;
1681
1750
  if (visible && wereAllChildrenHidden || !visible && areAllChildrenHidden) this._onDidVisibilityChange.fire({ visible });
1682
1751
  }
@@ -1725,12 +1794,14 @@ var BranchNode = class BranchNode extends CompositeDisposable {
1725
1794
  return child;
1726
1795
  }
1727
1796
  setupChildrenEvents() {
1797
+ this.invalidateCachedSizes();
1728
1798
  this._childrenDisposable.dispose();
1729
1799
  this._childrenDisposable = new CompositeDisposable(Event.any(...this.children.map((c) => c.onDidChange))((e) => {
1730
1800
  /**
1731
1801
  * indicate a change has occured to allows any re-rendering but don't bubble
1732
1802
  * event because that was specific to this branch
1733
1803
  */
1804
+ this.invalidateCachedSizes();
1734
1805
  this._onDidChange.fire({ size: e.orthogonalSize });
1735
1806
  }), ...this.children.map((c, i) => {
1736
1807
  if (c instanceof BranchNode) return c.onDidVisibilityChange(({ visible }) => {
@@ -3407,7 +3478,7 @@ var DockviewApi = class {
3407
3478
  * Add a group and return the created object.
3408
3479
  */
3409
3480
  addGroup(options) {
3410
- return this.component.addGroup(options);
3481
+ return this.component.withOrigin("api", () => this.component.addGroup(options));
3411
3482
  }
3412
3483
  /**
3413
3484
  * Close all groups and panels.
@@ -3545,13 +3616,13 @@ var DockviewApi = class {
3545
3616
  this.activatePrevious(options);
3546
3617
  }
3547
3618
  maximizeGroup(panel) {
3548
- this.component.maximizeGroup(panel.group);
3619
+ this.component.withOrigin("api", () => this.component.maximizeGroup(panel.group));
3549
3620
  }
3550
3621
  hasMaximizedGroup() {
3551
3622
  return this.component.hasMaximizedGroup();
3552
3623
  }
3553
3624
  exitMaximizedGroup() {
3554
- this.component.exitMaximizedGroup();
3625
+ this.component.withOrigin("api", () => this.component.exitMaximizedGroup());
3555
3626
  }
3556
3627
  get onDidMaximizedGroupChange() {
3557
3628
  return this.component.onDidMaximizedGroupChange;
@@ -5506,9 +5577,6 @@ var ContentContainer = class extends CompositeDisposable {
5506
5577
  const doRender = ((_options$asActive = options === null || options === void 0 ? void 0 : options.asActive) !== null && _options$asActive !== void 0 ? _options$asActive : true) || this.panel && this.group.isPanelActive(this.panel);
5507
5578
  if (((_this$panel = this.panel) === null || _this$panel === void 0 ? void 0 : _this$panel.view.content.element.parentElement) === this._element) {
5508
5579
  var _this$panel$view$cont, _this$panel$view$cont2;
5509
- /**
5510
- * If the currently attached panel is mounted directly to the content then remove it
5511
- */
5512
5580
  this.panel.view.content.element.remove();
5513
5581
  (_this$panel$view$cont = (_this$panel$view$cont2 = this.panel.view.content).onHide) === null || _this$panel$view$cont === void 0 || _this$panel$view$cont.call(_this$panel$view$cont2);
5514
5582
  }
@@ -6249,7 +6317,7 @@ var Scrollbar = class Scrollbar extends CompositeDisposable {
6249
6317
  this.element.appendChild(this._scrollbar);
6250
6318
  this.addDisposables(addDisposableListener(this.element, "wheel", (event) => {
6251
6319
  this._scrollOffset += event.deltaY * Scrollbar.MouseWheelSpeed;
6252
- this.calculateScrollbarStyles();
6320
+ this.scheduleStyleUpdate();
6253
6321
  }), addDisposableListener(this._scrollbar, "pointerdown", (event) => {
6254
6322
  var _this$_scrollbar$owne;
6255
6323
  event.preventDefault();
@@ -6261,7 +6329,7 @@ var Scrollbar = class Scrollbar extends CompositeDisposable {
6261
6329
  const delta = this._orientation === "horizontal" ? event.clientX - originalClient : event.clientY - originalClient;
6262
6330
  const p = (this._orientation === "horizontal" ? this.element.clientWidth : this.element.clientHeight) / (this._orientation === "horizontal" ? this.scrollableElement.scrollWidth : this.scrollableElement.scrollHeight);
6263
6331
  this._scrollOffset = originalScrollOffset + delta / p;
6264
- this.calculateScrollbarStyles();
6332
+ this.scheduleStyleUpdate();
6265
6333
  };
6266
6334
  const onEnd = () => {
6267
6335
  toggleClass(this.element, "dv-scrollable-scrolling", false);
@@ -6273,10 +6341,10 @@ var Scrollbar = class Scrollbar extends CompositeDisposable {
6273
6341
  doc.addEventListener("pointerup", onEnd);
6274
6342
  doc.addEventListener("pointercancel", onEnd);
6275
6343
  }), addDisposableListener(this.element, "scroll", () => {
6276
- this.calculateScrollbarStyles();
6344
+ this.scheduleStyleUpdate();
6277
6345
  }), addDisposableListener(this.scrollableElement, "scroll", () => {
6278
6346
  this._scrollOffset = this._orientation === "horizontal" ? this.scrollableElement.scrollLeft : this.scrollableElement.scrollTop;
6279
- this.calculateScrollbarStyles();
6347
+ this.scheduleStyleUpdate();
6280
6348
  }), watchElementResize(this.element, () => {
6281
6349
  toggleClass(this.element, "dv-scrollable-resizing", true);
6282
6350
  if (this._animationTimer) clearTimeout(this._animationTimer);
@@ -6284,9 +6352,29 @@ var Scrollbar = class Scrollbar extends CompositeDisposable {
6284
6352
  clearTimeout(this._animationTimer);
6285
6353
  toggleClass(this.element, "dv-scrollable-resizing", false);
6286
6354
  }, 500);
6287
- this.calculateScrollbarStyles();
6355
+ this.scheduleStyleUpdate();
6356
+ }), Disposable.from(() => {
6357
+ if (this._pendingStyleFrame !== void 0) {
6358
+ cancelAnimationFrame(this._pendingStyleFrame);
6359
+ this._pendingStyleFrame = void 0;
6360
+ }
6288
6361
  }));
6289
6362
  }
6363
+ /**
6364
+ * Coalesce the scrollbar restyle to one pass per animation frame. The
6365
+ * wheel / scroll / pointermove handlers can each fire many times per frame,
6366
+ * and `calculateScrollbarStyles` both reads layout (`clientWidth`/
6367
+ * `scrollWidth`) and writes styles + `scrollLeft`/`scrollTop`; running it
6368
+ * synchronously per event caused repeated read→write→read reflow. Batching
6369
+ * to a frame keeps a burst of events to a single measure+write.
6370
+ */
6371
+ scheduleStyleUpdate() {
6372
+ if (this._pendingStyleFrame !== void 0) return;
6373
+ this._pendingStyleFrame = requestAnimationFrame(() => {
6374
+ this._pendingStyleFrame = void 0;
6375
+ this.calculateScrollbarStyles();
6376
+ });
6377
+ }
6290
6378
  calculateScrollbarStyles() {
6291
6379
  const clientSize = this._orientation === "horizontal" ? this.element.clientWidth : this.element.clientHeight;
6292
6380
  const scrollSize = this._orientation === "horizontal" ? this.scrollableElement.scrollWidth : this.scrollableElement.scrollHeight;
@@ -8747,8 +8835,8 @@ var Tabs = class extends CompositeDisposable {
8747
8835
  }, true), addDisposableListener(this._tabsList, "dragend", () => {
8748
8836
  this._reorder.resetDragAnimation();
8749
8837
  }), addDisposableListener(this._tabsList, "drop", (event) => {
8750
- var _this$accessor$option, _this$group$model$dro;
8751
- if (!this._animState || this._animState.currentInsertionIndex === null) return;
8838
+ var _this$_animState3, _this$accessor$option, _this$group$model$dro;
8839
+ if (((_this$_animState3 = this._animState) === null || _this$_animState3 === void 0 ? void 0 : _this$_animState3.currentInsertionIndex) == null) return;
8752
8840
  if (((_this$accessor$option = this.accessor.options.theme) === null || _this$accessor$option === void 0 ? void 0 : _this$accessor$option.tabAnimation) !== "smooth" && !this._animState.sourceTabGroupId) return;
8753
8841
  event.stopPropagation();
8754
8842
  event.preventDefault();
@@ -8953,7 +9041,7 @@ var Tabs = class extends CompositeDisposable {
8953
9041
  };
8954
9042
  this._pendingCollapse = true;
8955
9043
  requestAnimationFrame(() => {
8956
- var _this$_animState3, _this$_animState3$cur;
9044
+ var _this$_animState4, _this$_animState4$cur;
8957
9045
  this._pendingCollapse = false;
8958
9046
  if (!this._animState) return;
8959
9047
  if (!this._tabsList.classList.contains("dv-tabs-container--wrap")) {
@@ -8961,7 +9049,7 @@ var Tabs = class extends CompositeDisposable {
8961
9049
  toggleClass(tab.element, "dv-tab--dragging", true);
8962
9050
  tab.element.offsetHeight;
8963
9051
  }
8964
- (_this$_animState3$cur = (_this$_animState3 = this._animState).currentInsertionIndex) !== null && _this$_animState3$cur !== void 0 || (_this$_animState3.currentInsertionIndex = sourceIndex);
9052
+ (_this$_animState4$cur = (_this$_animState4 = this._animState).currentInsertionIndex) !== null && _this$_animState4$cur !== void 0 || (_this$_animState4.currentInsertionIndex = sourceIndex);
8965
9053
  this.applyDragOverTransforms(true);
8966
9054
  tab.element.style.removeProperty("transition");
8967
9055
  });
@@ -9050,8 +9138,8 @@ var Tabs = class extends CompositeDisposable {
9050
9138
  }
9051
9139
  }
9052
9140
  delete(id) {
9053
- var _this$_animState4;
9054
- if (((_this$_animState4 = this._animState) === null || _this$_animState4 === void 0 ? void 0 : _this$_animState4.sourceTabId) === id) {
9141
+ var _this$_animState5;
9142
+ if (((_this$_animState5 = this._animState) === null || _this$_animState5 === void 0 ? void 0 : _this$_animState5.sourceTabId) === id) {
9055
9143
  this.resetTabTransforms();
9056
9144
  this._animState = null;
9057
9145
  }
@@ -9168,7 +9256,7 @@ var Tabs = class extends CompositeDisposable {
9168
9256
  const groupPanelIds = new Set(tabGroup.panelIds);
9169
9257
  this._pendingCollapse = true;
9170
9258
  requestAnimationFrame(() => {
9171
- var _this$_animState5, _this$_animState5$cur;
9259
+ var _this$_animState6, _this$_animState6$cur;
9172
9260
  this._pendingCollapse = false;
9173
9261
  if (!this._animState) return;
9174
9262
  for (const t of this._tabs) if (groupPanelIds.has(t.value.panel.id)) {
@@ -9183,7 +9271,7 @@ var Tabs = class extends CompositeDisposable {
9183
9271
  this._tabsList.offsetHeight;
9184
9272
  const underline = this._tabGroupManager.groupUnderlines.get(tabGroup.id);
9185
9273
  if (underline) underline.style.display = "none";
9186
- (_this$_animState5$cur = (_this$_animState5 = this._animState).currentInsertionIndex) !== null && _this$_animState5$cur !== void 0 || (_this$_animState5.currentInsertionIndex = firstIdx);
9274
+ (_this$_animState6$cur = (_this$_animState6 = this._animState).currentInsertionIndex) !== null && _this$_animState6$cur !== void 0 || (_this$_animState6.currentInsertionIndex = firstIdx);
9187
9275
  this.applyDragOverTransforms(true);
9188
9276
  for (const t of this._tabs) if (groupPanelIds.has(t.value.panel.id)) t.value.element.style.removeProperty("transition");
9189
9277
  if (chipEntry) chipEntry.chip.element.style.removeProperty("transition");
@@ -9918,6 +10006,7 @@ var DockviewGroupPanelModel = class extends CompositeDisposable {
9918
10006
  set headerPosition(value) {
9919
10007
  var _this$_activePanel;
9920
10008
  this._headerPosition = value;
10009
+ this.invalidateHeaderSize();
9921
10010
  removeClasses(this.container, "dv-groupview-header-top", "dv-groupview-header-bottom", "dv-groupview-header-left", "dv-groupview-header-right");
9922
10011
  addClasses(this.container, `dv-groupview-header-${value}`);
9923
10012
  const direction = value === "top" || value === "bottom" ? "horizontal" : "vertical";
@@ -10059,7 +10148,14 @@ var DockviewGroupPanelModel = class extends CompositeDisposable {
10059
10148
  this.header.hidden = !!options.hideHeader;
10060
10149
  this.locked = (_options$locked = options.locked) !== null && _options$locked !== void 0 ? _options$locked : false;
10061
10150
  this.headerPosition = (_options$headerPositi = options.headerPosition) !== null && _options$headerPositi !== void 0 ? _options$headerPositi : accessor.defaultHeaderPosition;
10062
- this.addDisposables(this._onDidPanelTitleChange.event(() => this.updateAccessibleLabel()), this._onTabDragStart, this._onGroupDragStart, this._onWillShowOverlay, this.tabsContainer.onTabDragStart((event) => {
10151
+ this.addDisposables(watchElementResize(this.tabsContainer.element, () => {
10152
+ if (this.isDisposed) return;
10153
+ const size = this.measureHeaderSize();
10154
+ if (size !== this._cachedHeaderSize) {
10155
+ this._cachedHeaderSize = size;
10156
+ this.layout(this._width, this._height);
10157
+ }
10158
+ }), this._onDidPanelTitleChange.event(() => this.updateAccessibleLabel()), this._onTabDragStart, this._onGroupDragStart, this._onWillShowOverlay, this.tabsContainer.onTabDragStart((event) => {
10063
10159
  this._onTabDragStart.fire(event);
10064
10160
  }), this.tabsContainer.onGroupDragStart((event) => {
10065
10161
  this._onGroupDragStart.fire(event);
@@ -10424,7 +10520,11 @@ var DockviewGroupPanelModel = class extends CompositeDisposable {
10424
10520
  this.panels.forEach((panel) => {
10425
10521
  this.rerender(panel);
10426
10522
  });
10427
- if (this._activePanel) this.contentContainer.renderPanel(this._activePanel, { asActive: true });
10523
+ if (this._activePanel) {
10524
+ this.contentContainer.renderPanel(this._activePanel, { asActive: true });
10525
+ const { width, height } = this.contentDimensions();
10526
+ this._activePanel.layout(width, height);
10527
+ }
10428
10528
  }
10429
10529
  get renderContainer() {
10430
10530
  var _this$_overwriteRende;
@@ -10516,16 +10616,11 @@ var DockviewGroupPanelModel = class extends CompositeDisposable {
10516
10616
  (_this$_activePanel3 = this._activePanel) === null || _this$_activePanel3 === void 0 || _this$_activePanel3.focus();
10517
10617
  }
10518
10618
  openPanel(panel, options = {}) {
10519
- /**
10520
- * set the panel group
10521
- * add the panel
10522
- * check if group active
10523
- * check if panel active
10524
- */
10525
10619
  if (typeof options.index !== "number" || options.index > this.panels.length) options.index = this.panels.length;
10526
10620
  const skipSetActive = !!options.skipSetActive;
10527
10621
  panel.updateParentGroup(this.groupPanel, { skipSetActive: options.skipSetActive });
10528
10622
  this.doAddPanel(panel, options.index, { skipSetActive });
10623
+ this.invalidateHeaderSize();
10529
10624
  if (this._activePanel === panel) {
10530
10625
  this.contentContainer.renderPanel(panel, { asActive: true });
10531
10626
  return;
@@ -10588,6 +10683,7 @@ var DockviewGroupPanelModel = class extends CompositeDisposable {
10588
10683
  * changing) down to the content + active panel.
10589
10684
  */
10590
10685
  relayout() {
10686
+ this.invalidateHeaderSize();
10591
10687
  this.layout(this._width, this._height);
10592
10688
  }
10593
10689
  /**
@@ -10597,17 +10693,32 @@ var DockviewGroupPanelModel = class extends CompositeDisposable {
10597
10693
  * width); a hidden header has `display:none` so its `offset*` is 0 and
10598
10694
  * nothing is subtracted.
10599
10695
  */
10600
- contentDimensions() {
10696
+ /**
10697
+ * Measure the header's extent along its occupied axis. This is the single
10698
+ * forced-reflow read; it now runs only on a cache miss (first layout after
10699
+ * a header change) and inside the header's ResizeObserver — not on every
10700
+ * layout frame.
10701
+ */
10702
+ measureHeaderSize() {
10601
10703
  const headerEl = this.tabsContainer.element;
10704
+ return this.headerPosition === "top" || this.headerPosition === "bottom" ? headerEl.offsetHeight : headerEl.offsetWidth;
10705
+ }
10706
+ invalidateHeaderSize() {
10707
+ this._cachedHeaderSize = void 0;
10708
+ }
10709
+ contentDimensions() {
10710
+ var _this$_cachedHeaderSi;
10602
10711
  const horizontal = this.headerPosition === "top" || this.headerPosition === "bottom";
10712
+ const headerSize = (_this$_cachedHeaderSi = this._cachedHeaderSize) !== null && _this$_cachedHeaderSi !== void 0 ? _this$_cachedHeaderSi : this._cachedHeaderSize = this.measureHeaderSize();
10603
10713
  return {
10604
- width: horizontal ? this._width : Math.max(0, this._width - headerEl.offsetWidth),
10605
- height: horizontal ? Math.max(0, this._height - headerEl.offsetHeight) : this._height
10714
+ width: horizontal ? this._width : Math.max(0, this._width - headerSize),
10715
+ height: horizontal ? Math.max(0, this._height - headerSize) : this._height
10606
10716
  };
10607
10717
  }
10608
10718
  _removePanel(panel, options) {
10609
10719
  const isActivePanel = this._activePanel === panel;
10610
10720
  this.doRemovePanel(panel);
10721
+ this.invalidateHeaderSize();
10611
10722
  if (isActivePanel && this.panels.length > 0) {
10612
10723
  const nextPanel = this.mostRecentlyUsed[0];
10613
10724
  this.openPanel(nextPanel, {
@@ -10933,6 +11044,7 @@ var GridviewPanel = class extends BasePanelView {
10933
11044
  if (typeof event.minimumHeight === "number" || typeof event.minimumHeight === "function") this._minimumHeight = event.minimumHeight;
10934
11045
  if (typeof event.maximumWidth === "number" || typeof event.maximumWidth === "function") this._maximumWidth = event.maximumWidth;
10935
11046
  if (typeof event.maximumHeight === "number" || typeof event.maximumHeight === "function") this._maximumHeight = event.maximumHeight;
11047
+ this._onDidChange.fire(void 0);
10936
11048
  }), this.api.onDidSizeChange((event) => {
10937
11049
  this._onDidChange.fire({
10938
11050
  height: event.height,
@@ -11052,20 +11164,22 @@ var DockviewGroupPanelApiImpl = class extends GridviewPanelApiImpl {
11052
11164
  return this._group.model.headerPosition;
11053
11165
  }
11054
11166
  moveTo(options) {
11055
- var _options$group, _options$position, _options$skipSetActiv, _options$position2;
11056
11167
  if (!this._group) throw new Error(NOT_INITIALIZED_MESSAGE);
11057
- const group = (_options$group = options.group) !== null && _options$group !== void 0 ? _options$group : this.accessor.addGroup({
11058
- direction: positionToDirection((_options$position = options.position) !== null && _options$position !== void 0 ? _options$position : "right"),
11059
- skipSetActive: (_options$skipSetActiv = options.skipSetActive) !== null && _options$skipSetActiv !== void 0 ? _options$skipSetActiv : false
11060
- });
11061
- this.accessor.moveGroupOrPanel({
11062
- from: { groupId: this._group.id },
11063
- to: {
11064
- group,
11065
- position: options.group ? (_options$position2 = options.position) !== null && _options$position2 !== void 0 ? _options$position2 : "center" : "center",
11066
- index: options.index
11067
- },
11068
- skipSetActive: options.skipSetActive
11168
+ this.accessor.withOrigin("api", () => {
11169
+ var _options$group, _options$position, _options$skipSetActiv, _options$position2;
11170
+ const group = (_options$group = options.group) !== null && _options$group !== void 0 ? _options$group : this.accessor.addGroup({
11171
+ direction: positionToDirection((_options$position = options.position) !== null && _options$position !== void 0 ? _options$position : "right"),
11172
+ skipSetActive: (_options$skipSetActiv = options.skipSetActive) !== null && _options$skipSetActiv !== void 0 ? _options$skipSetActiv : false
11173
+ });
11174
+ this.accessor.moveGroupOrPanel({
11175
+ from: { groupId: this._group.id },
11176
+ to: {
11177
+ group,
11178
+ position: options.group ? (_options$position2 = options.position) !== null && _options$position2 !== void 0 ? _options$position2 : "center" : "center",
11179
+ index: options.index
11180
+ },
11181
+ skipSetActive: options.skipSetActive
11182
+ });
11069
11183
  });
11070
11184
  }
11071
11185
  maximize() {
@@ -11375,7 +11489,7 @@ var DockviewPanelApiImpl = class extends GridviewPanelApiImpl {
11375
11489
  this._group = value;
11376
11490
  this._onDidGroupChange.fire({});
11377
11491
  this.setupGroupEventListeners(oldGroup);
11378
- this._onDidLocationChange.fire({ location: this.group.api.location });
11492
+ this.fireLocationChange();
11379
11493
  }
11380
11494
  }
11381
11495
  get group() {
@@ -11414,18 +11528,20 @@ var DockviewPanelApiImpl = class extends GridviewPanelApiImpl {
11414
11528
  this.accessor.withOrigin("api", () => super.setActive());
11415
11529
  }
11416
11530
  moveTo(options) {
11417
- var _options$group, _options$position;
11418
- this.accessor.moveGroupOrPanel({
11419
- from: {
11420
- groupId: this._group.id,
11421
- panelId: this.panel.id
11422
- },
11423
- to: {
11424
- group: (_options$group = options.group) !== null && _options$group !== void 0 ? _options$group : this._group,
11425
- position: options.group ? (_options$position = options.position) !== null && _options$position !== void 0 ? _options$position : "center" : "center",
11426
- index: options.index
11427
- },
11428
- skipSetActive: options.skipSetActive
11531
+ this.accessor.withOrigin("api", () => {
11532
+ var _options$group, _options$position;
11533
+ return this.accessor.moveGroupOrPanel({
11534
+ from: {
11535
+ groupId: this._group.id,
11536
+ panelId: this.panel.id
11537
+ },
11538
+ to: {
11539
+ group: (_options$group = options.group) !== null && _options$group !== void 0 ? _options$group : this._group,
11540
+ position: options.group ? (_options$position = options.position) !== null && _options$position !== void 0 ? _options$position : "center" : "center",
11541
+ index: options.index
11542
+ },
11543
+ skipSetActive: options.skipSetActive
11544
+ });
11429
11545
  });
11430
11546
  }
11431
11547
  setTitle(title) {
@@ -11449,6 +11565,23 @@ var DockviewPanelApiImpl = class extends GridviewPanelApiImpl {
11449
11565
  exitMaximized() {
11450
11566
  this.group.api.exitMaximized();
11451
11567
  }
11568
+ /**
11569
+ * Report that this panel's location may have moved.
11570
+ *
11571
+ * A relocation touches the location more than once - the panel is
11572
+ * reparented into the destination group, then that group is tagged with
11573
+ * the location it ends up at - so the event is coalesced to the end of the
11574
+ * enclosing layout mutation and reports where the panel actually settled.
11575
+ * Firing each signal as it happened surfaced an intermediate `grid`
11576
+ * location the panel was never in, at a point where it was in neither
11577
+ * group's panel list. Outside a mutation this fires straight away.
11578
+ */
11579
+ fireLocationChange() {
11580
+ this.accessor.deferLocationChange(this, () => {
11581
+ if (this.isDisposed) return;
11582
+ this._onDidLocationChange.fire({ location: this.location });
11583
+ });
11584
+ }
11452
11585
  setupGroupEventListeners(previousGroup) {
11453
11586
  var _previousGroup$isActi;
11454
11587
  let _trackGroupActive = (_previousGroup$isActi = previousGroup === null || previousGroup === void 0 ? void 0 : previousGroup.isActive) !== null && _previousGroup$isActi !== void 0 ? _previousGroup$isActi : false;
@@ -11457,9 +11590,9 @@ var DockviewPanelApiImpl = class extends GridviewPanelApiImpl {
11457
11590
  const hasBecomeVisible = event.isVisible && !this.isVisible;
11458
11591
  const isActivePanel = this.group.model.isPanelActive(this.panel);
11459
11592
  if (hasBecomeHidden || hasBecomeVisible && isActivePanel) this._onDidVisibilityChange.fire(event);
11460
- }), this.group.api.onDidLocationChange((event) => {
11593
+ }), this.group.api.onDidLocationChange(() => {
11461
11594
  if (this.group !== this.panel.group) return;
11462
- this._onDidLocationChange.fire(event);
11595
+ this.fireLocationChange();
11463
11596
  }), this.group.api.onDidActiveChange(() => {
11464
11597
  if (this.group !== this.panel.group) return;
11465
11598
  if (_trackGroupActive !== this.isGroupActive) {
@@ -12535,12 +12668,6 @@ const OPTION_MODULE_RULES = [
12535
12668
  moduleName: "ContextMenu",
12536
12669
  when: (o) => o.getTabGroupChipContextMenuItems != null
12537
12670
  },
12538
- {
12539
- optionKey: "createContextMenuItemComponent",
12540
- reason: "createContextMenuItemComponent",
12541
- moduleName: "ContextMenu",
12542
- when: (o) => o.createContextMenuItemComponent != null
12543
- },
12544
12671
  {
12545
12672
  optionKey: "dndCompass",
12546
12673
  reason: "dndCompass",
@@ -13269,7 +13396,18 @@ var LiveRegionService = class extends CompositeDisposable {
13269
13396
  prev = next;
13270
13397
  const panel = group.activePanel;
13271
13398
  if (!panel) return;
13272
- const kind = next === "floating" ? "float" : next === "popout" ? "popout" : "dock";
13399
+ let kind;
13400
+ switch (next) {
13401
+ case "floating":
13402
+ kind = "float";
13403
+ break;
13404
+ case "popout":
13405
+ kind = "popout";
13406
+ break;
13407
+ default:
13408
+ kind = "dock";
13409
+ break;
13410
+ }
13273
13411
  this._announce(panel, kind);
13274
13412
  });
13275
13413
  this._locationSubs.set(group.id, sub);
@@ -13321,29 +13459,14 @@ const LiveRegionModule = defineModule({
13321
13459
 
13322
13460
  //#endregion
13323
13461
  //#region src/dockview/advancedDnDService.ts
13324
- /** Cursor offset of the group drag ghost, matched to the long-shipped default. */
13462
+ /** Cursor offset of the group drag ghost. */
13325
13463
  const GROUP_DRAG_GHOST_OFFSET_X = 30;
13326
13464
  const GROUP_DRAG_GHOST_OFFSET_Y = -10;
13327
13465
  /**
13328
- * The narrow surface the {@link AdvancedDnDService} needs from the host
13329
- * (the `DockviewComponent`).
13330
- *
13331
- * The `onWill*` emitters stay on the component so the public event shape is
13332
- * unchanged whether or not this module is registered; the service is only the
13333
- * dispatch point those fires are routed through. Engine policy (e.g. the
13334
- * `disableDnd` guard) stays on the component, ahead of the dispatch.
13335
- */
13336
- /**
13337
13466
  * Owns the dispatch of the advanced drag-and-drop hooks: `onWillDragPanel`,
13338
- * `onWillDragGroup`, `onWillDrop` and `onWillShowOverlay`.
13339
- *
13340
- * At this stage the service is a thin dispatcher that forwards to the host's
13341
- * emitters; the customisation surface it gates (custom drop overlays, custom
13342
- * group drag ghosts, hook veto/transform behaviour) is currently inlined in
13343
- * core and is extracted into this service in later phases. Routing the
13344
- * dispatch through a module slot is what lets that customisation layer move
13345
- * into a separately-distributed package in a future major version without
13346
- * changing the public event surface.
13467
+ * `onWillDragGroup`, `onWillDrop` and `onWillShowOverlay`, forwarding each to
13468
+ * the host's emitters so the public event shape is unchanged whether or not
13469
+ * this module is registered.
13347
13470
  *
13348
13471
  * The service holds no drag state of its own. The gesture is driven by the
13349
13472
  * DnD backends, and the per-group subscriptions live on the component's group
@@ -13469,7 +13592,7 @@ const AllModules = [
13469
13592
  //#region src/overlay/overlayRenderContainer.ts
13470
13593
  var PositionCache = class {
13471
13594
  constructor() {
13472
- this.cache = /* @__PURE__ */ new Map();
13595
+ this.cache = /* @__PURE__ */ new WeakMap();
13473
13596
  this.currentFrameId = 0;
13474
13597
  this.rafId = null;
13475
13598
  }
@@ -13495,6 +13618,13 @@ var PositionCache = class {
13495
13618
  });
13496
13619
  }
13497
13620
  };
13621
+ /**
13622
+ * Placeholder handle marking "a reposition frame has been requested but the
13623
+ * real handle is not known yet". Never passed to `cancelAnimationFrame` in
13624
+ * practice — it is replaced within the same synchronous block — and harmless
13625
+ * if it were, since no frame carries this id.
13626
+ */
13627
+ const SCHEDULED = -1;
13498
13628
  function createFocusableElement() {
13499
13629
  const element = document.createElement("div");
13500
13630
  element.tabIndex = -1;
@@ -13507,12 +13637,13 @@ var OverlayRenderContainer = class extends CompositeDisposable {
13507
13637
  this.accessor = accessor;
13508
13638
  this.map = {};
13509
13639
  this._disposed = false;
13640
+ this._generation = 0;
13510
13641
  this.positionCache = new PositionCache();
13511
- this.pendingUpdates = /* @__PURE__ */ new Set();
13512
13642
  this.addDisposables(Disposable.from(() => {
13513
13643
  for (const value of Object.values(this.map)) {
13514
13644
  value.disposable.dispose();
13515
13645
  value.destroy.dispose();
13646
+ this.cancelPendingUpdate(value);
13516
13647
  }
13517
13648
  this._disposed = true;
13518
13649
  }));
@@ -13539,11 +13670,24 @@ var OverlayRenderContainer = class extends CompositeDisposable {
13539
13670
  this.positionCache.invalidate();
13540
13671
  (_entry$resize = entry.resize) === null || _entry$resize === void 0 || _entry$resize.call(entry);
13541
13672
  }
13673
+ /**
13674
+ * Drop the reposition frame queued for a panel, if any. A queued frame is
13675
+ * bound to the reference container that was current when it was scheduled,
13676
+ * so it must be discarded whenever that container stops being the one the
13677
+ * overlay should track.
13678
+ */
13679
+ cancelPendingUpdate(entry) {
13680
+ if (entry.pendingUpdate !== void 0) {
13681
+ cancelAnimationFrame(entry.pendingUpdate);
13682
+ entry.pendingUpdate = void 0;
13683
+ }
13684
+ }
13542
13685
  detatch(panel) {
13543
13686
  if (this.map[panel.api.id]) {
13544
- const { disposable, destroy } = this.map[panel.api.id];
13545
- disposable.dispose();
13546
- destroy.dispose();
13687
+ const entry = this.map[panel.api.id];
13688
+ entry.disposable.dispose();
13689
+ entry.destroy.dispose();
13690
+ this.cancelPendingUpdate(entry);
13547
13691
  delete this.map[panel.api.id];
13548
13692
  return true;
13549
13693
  }
@@ -13559,22 +13703,73 @@ var OverlayRenderContainer = class extends CompositeDisposable {
13559
13703
  panel,
13560
13704
  disposable: Disposable.NONE,
13561
13705
  destroy: Disposable.NONE,
13562
- element
13706
+ element,
13707
+ retainPreviousGeometry: false,
13708
+ positioned: false,
13709
+ generation: ++this._generation
13563
13710
  };
13564
13711
  }
13565
- const focusContainer = this.map[panel.api.id].element;
13712
+ const mapEntry = this.map[panel.api.id];
13713
+ /**
13714
+ * A *change* of reference container supersedes the previous `attach`:
13715
+ * take a fresh generation so its `resize` closure can no longer run,
13716
+ * and drop the frame it queued against the old container. During
13717
+ * `fromJSON({ reuseExistingPanels: true })` that old container is a
13718
+ * detached staging group measuring 0x0, so leaving its frame in flight
13719
+ * both wastes the update and delays the reposition against the real one.
13720
+ *
13721
+ * Re-attaching over the *same* container (re-open, active panel change)
13722
+ * deliberately leaves scheduled work alone: `repositionPanelOverlay`
13723
+ * (the auto-hide peek) schedules a frame carrying the sticky
13724
+ * `forceVisible`/`clip` state which `attach` does not re-apply, and a
13725
+ * peeked panel's `api.isVisible` is false — so discarding that frame
13726
+ * leaves `visibilityChanged` to hide the overlay and the peek renders
13727
+ * nothing. A newly created entry already holds a fresh generation, so
13728
+ * the stale closures a `detatch` left behind are fenced off either way.
13729
+ */
13730
+ if (mapEntry.referenceContainer !== referenceContainer) {
13731
+ mapEntry.generation = ++this._generation;
13732
+ this.cancelPendingUpdate(mapEntry);
13733
+ mapEntry.referenceContainer = referenceContainer;
13734
+ /**
13735
+ * Arm the retain flag only here: it means "a replacement reference
13736
+ * container is awaiting layout", so the 0x0 early-return below
13737
+ * holds the last good box rather than blanking the panel mid-move.
13738
+ * Arming it on every re-attach instead would make a container that
13739
+ * genuinely collapses to zero keep its stale geometry forever.
13740
+ *
13741
+ * Gated on `positioned` because an overlay that has never been
13742
+ * positioned has no left/top/width/height, and `.dv-render-overlay`
13743
+ * defaults to 100%/100% — retaining "previous" geometry there would
13744
+ * pin the content over the whole container.
13745
+ */
13746
+ mapEntry.retainPreviousGeometry = mapEntry.positioned;
13747
+ }
13748
+ const generation = mapEntry.generation;
13749
+ const focusContainer = mapEntry.element;
13566
13750
  const contentElement = panel.view.content.element;
13567
13751
  if (contentElement.parentElement !== focusContainer) focusContainer.appendChild(contentElement);
13568
13752
  if (focusContainer.parentElement !== this.element) this.element.appendChild(focusContainer);
13569
13753
  const resize = () => {
13570
13754
  const panelId = panel.api.id;
13571
- if (this.pendingUpdates.has(panelId)) return;
13572
- this.pendingUpdates.add(panelId);
13573
- requestAnimationFrame(() => {
13755
+ const scheduling = this.map[panelId];
13756
+ if ((scheduling === null || scheduling === void 0 ? void 0 : scheduling.generation) !== generation) return;
13757
+ if (scheduling.pendingUpdate !== void 0) return;
13758
+ /**
13759
+ * Claim the slot *before* scheduling. `requestAnimationFrame` is
13760
+ * shimmed to run inline by some hosts (and by several suites here),
13761
+ * in which case the callback below clears `pendingUpdate` while
13762
+ * this call is still on the stack — assigning the handle afterwards
13763
+ * would resurrect a slot that is already spent and block every
13764
+ * later reposition. The sentinel is replaced by the real handle
13765
+ * only if the frame has not already run.
13766
+ */
13767
+ scheduling.pendingUpdate = SCHEDULED;
13768
+ const handle = requestAnimationFrame(() => {
13574
13769
  var _entry$forceVisible;
13575
- this.pendingUpdates.delete(panelId);
13576
13770
  const entry = this.map[panelId];
13577
- if (this.isDisposed || !entry) return;
13771
+ if (entry) entry.pendingUpdate = void 0;
13772
+ if (this.isDisposed || (entry === null || entry === void 0 ? void 0 : entry.generation) !== generation) return;
13578
13773
  const forceVisible = (_entry$forceVisible = entry.forceVisible) !== null && _entry$forceVisible !== void 0 ? _entry$forceVisible : false;
13579
13774
  const clip = entry.clip;
13580
13775
  const box = this.positionCache.getPosition(referenceContainer.element);
@@ -13583,6 +13778,15 @@ var OverlayRenderContainer = class extends CompositeDisposable {
13583
13778
  const top = box.top - box2.top;
13584
13779
  const width = box.width;
13585
13780
  const height = box.height;
13781
+ if (entry.retainPreviousGeometry && (width === 0 || height === 0)) {
13782
+ if (!panel.api.isVisible && !forceVisible) {
13783
+ focusContainer.style.visibility = "hidden";
13784
+ focusContainer.style.pointerEvents = "none";
13785
+ }
13786
+ return;
13787
+ }
13788
+ entry.retainPreviousGeometry = false;
13789
+ entry.positioned = true;
13586
13790
  focusContainer.style.left = `${left}px`;
13587
13791
  focusContainer.style.top = `${top}px`;
13588
13792
  focusContainer.style.width = `${width}px`;
@@ -13611,11 +13815,30 @@ var OverlayRenderContainer = class extends CompositeDisposable {
13611
13815
  } else focusContainer.style.clipPath = "";
13612
13816
  toggleClass(focusContainer, "dv-render-overlay-float", panel.group.api.location.type === "floating");
13613
13817
  });
13818
+ if (scheduling.pendingUpdate === SCHEDULED) scheduling.pendingUpdate = handle;
13614
13819
  };
13615
13820
  const visibilityChanged = () => {
13616
13821
  if (panel.api.isVisible) {
13822
+ var _this$map$panel$api$i;
13617
13823
  this.positionCache.invalidate();
13618
13824
  resize();
13825
+ /**
13826
+ * Existing geometry is safe to show while its replacement lays
13827
+ * out. `retainPreviousGeometry` is only set when geometry has
13828
+ * actually been written (see `attach`), so this can never
13829
+ * un-hide an overlay whose left/top/width/height are unset —
13830
+ * `.dv-render-overlay` is 100%/100% by default and would
13831
+ * otherwise cover the whole dock.
13832
+ *
13833
+ * The retained geometry is the panel's position in the previous
13834
+ * layout, so restoring a layout that moves the panel does paint
13835
+ * it at stale coordinates until the reposition lands. That is
13836
+ * bounded to the next frame now that a superseded `attach` can
13837
+ * no longer swallow the reposition (see `attach`); showing
13838
+ * stale-but-real geometry for a frame is the deliberate
13839
+ * trade-off against blanking the panel for the whole rebuild.
13840
+ */
13841
+ if ((_this$map$panel$api$i = this.map[panel.api.id]) === null || _this$map$panel$api$i === void 0 ? void 0 : _this$map$panel$api$i.retainPreviousGeometry) focusContainer.style.visibility = "";
13619
13842
  focusContainer.style.pointerEvents = "";
13620
13843
  } else {
13621
13844
  focusContainer.style.visibility = "hidden";
@@ -13838,7 +14061,17 @@ var PopoutWindow = class extends CompositeDisposable {
13838
14061
  window: externalWindow
13839
14062
  });
13840
14063
  return new Promise((resolve, reject) => {
13841
- externalWindow.addEventListener("unload", (e) => {});
14064
+ externalWindow.addEventListener("unload", () => {});
14065
+ /**
14066
+ * `load` is the only event that resolves this promise with a
14067
+ * container, so a window that goes away first would leave the
14068
+ * caller awaiting a promise that never settles - and
14069
+ * `addPopoutGroup` holds its layout transaction open until it does.
14070
+ * Settle with `null` on close, the same signal a blocked popup
14071
+ * gives and one the caller already handles. `resolve` after the
14072
+ * fact is a no-op, so a `load` that arrived first still wins.
14073
+ */
14074
+ disposable.addDisposables(_this.onWillClose(() => resolve(null)));
13842
14075
  externalWindow.addEventListener("load", () => {
13843
14076
  /**
13844
14077
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
@@ -15138,7 +15371,22 @@ var DockviewComponent = class extends BaseGrid {
15138
15371
  const rect = candidate.element.getBoundingClientRect();
15139
15372
  const dx = rect.left + rect.width / 2 - fromX;
15140
15373
  const dy = rect.top + rect.height / 2 - fromY;
15141
- if (!(direction === "left" ? dx < 0 && Math.abs(dx) >= Math.abs(dy) : direction === "right" ? dx > 0 && Math.abs(dx) >= Math.abs(dy) : direction === "up" ? dy < 0 && Math.abs(dy) >= Math.abs(dx) : dy > 0 && Math.abs(dy) >= Math.abs(dx))) continue;
15374
+ let inDirection;
15375
+ switch (direction) {
15376
+ case "left":
15377
+ inDirection = dx < 0 && Math.abs(dx) >= Math.abs(dy);
15378
+ break;
15379
+ case "right":
15380
+ inDirection = dx > 0 && Math.abs(dx) >= Math.abs(dy);
15381
+ break;
15382
+ case "up":
15383
+ inDirection = dy < 0 && Math.abs(dy) >= Math.abs(dx);
15384
+ break;
15385
+ default:
15386
+ inDirection = dy > 0 && Math.abs(dy) >= Math.abs(dx);
15387
+ break;
15388
+ }
15389
+ if (!inDirection) continue;
15142
15390
  const distance = dx * dx + dy * dy;
15143
15391
  if (distance < bestDistance) {
15144
15392
  bestDistance = distance;
@@ -15241,6 +15489,7 @@ var DockviewComponent = class extends BaseGrid {
15241
15489
  this._onWillDrop = new Emitter();
15242
15490
  this.onWillDrop = this._onWillDrop.event;
15243
15491
  this._mutationDepth = 0;
15492
+ this._pendingLocationChanges = /* @__PURE__ */ new Map();
15244
15493
  this._origin = "user";
15245
15494
  this._originDepth = 0;
15246
15495
  this._onWillMutateLayout = new Emitter();
@@ -15338,7 +15587,7 @@ var DockviewComponent = class extends BaseGrid {
15338
15587
  toggleClass(this.element, "dv-debug", !!options.debug);
15339
15588
  this.updateTheme();
15340
15589
  if (options.debug) this.addDisposables(new StrictEventsSequencing(this));
15341
- this.addDisposables(this.rootDropTargetContainer, this.floatingDropTargetContainer, addDisposableListener(this._shellManager.element, "dragend", () => {
15590
+ this.addDisposables(Disposable.from(() => this._pendingLocationChanges.clear()), this.rootDropTargetContainer, this.floatingDropTargetContainer, addDisposableListener(this._shellManager.element, "dragend", () => {
15342
15591
  var _this$rootDropTargetC2, _this$floatingDropTar;
15343
15592
  (_this$rootDropTargetC2 = this.rootDropTargetContainer.model) === null || _this$rootDropTargetC2 === void 0 || _this$rootDropTargetC2.clear();
15344
15593
  (_this$floatingDropTar = this.floatingDropTargetContainer.model) === null || _this$floatingDropTar === void 0 || _this$floatingDropTar.clear();
@@ -15412,7 +15661,7 @@ var DockviewComponent = class extends BaseGrid {
15412
15661
  return (_this$_popoutWindowSe3 = (_this$_popoutWindowSe4 = this._popoutWindowService) === null || _this$_popoutWindowSe4 === void 0 || (_this$_popoutWindowSe4 = _this$_popoutWindowSe4.findByGroup(group)) === null || _this$_popoutWindowSe4 === void 0 ? void 0 : _this$_popoutWindowSe4.popupService) !== null && _this$_popoutWindowSe3 !== void 0 ? _this$_popoutWindowSe3 : this.popupService;
15413
15662
  }
15414
15663
  addPopoutGroup(itemToPopout, options) {
15415
- return this.mutation("popout", () => this._doAddPopoutGroup(itemToPopout, options));
15664
+ return this.mutationAsync("popout", () => this._doAddPopoutGroup(itemToPopout, options));
15416
15665
  }
15417
15666
  /** Enumerate the popout groups currently open in their own windows. */
15418
15667
  getPopouts() {
@@ -15509,15 +15758,32 @@ var DockviewComponent = class extends BaseGrid {
15509
15758
  }
15510
15759
  };
15511
15760
  let floatingBox;
15512
- if (!(options === null || options === void 0 ? void 0 : options.overridePopoutGroup) && !(options === null || options === void 0 ? void 0 : options.overridePopoutGridview) && isGroupAddedToDom) if (itemToPopout instanceof DockviewPanel) this.movingLock(() => {
15513
- const panel = referenceGroup.model.removePanel(itemToPopout);
15514
- group.model.openPanel(panel);
15515
- });
15516
- else {
15761
+ /**
15762
+ * Popping out rehomes panels into `group`, so like the floating
15763
+ * case it owes the caller an `onDidMovePanel` per panel. The
15764
+ * events are deferred until the window is wired up so listeners
15765
+ * observe the panels' final `popout` location.
15766
+ */
15767
+ let movedPanels = [];
15768
+ if (!(options === null || options === void 0 ? void 0 : options.overridePopoutGroup) && !(options === null || options === void 0 ? void 0 : options.overridePopoutGridview) && isGroupAddedToDom) if (itemToPopout instanceof DockviewPanel) {
15769
+ const sourceGroup = itemToPopout.group;
15770
+ this.movingLock(() => {
15771
+ const panel = referenceGroup.model.removePanel(itemToPopout);
15772
+ group.model.openPanel(panel);
15773
+ });
15774
+ movedPanels = [{
15775
+ panel: itemToPopout,
15776
+ from: sourceGroup
15777
+ }];
15778
+ } else {
15517
15779
  this.movingLock(() => moveGroupWithoutDestroying({
15518
15780
  from: referenceGroup,
15519
15781
  to: group
15520
15782
  }));
15783
+ movedPanels = group.panels.map((panel) => ({
15784
+ panel,
15785
+ from: referenceGroup
15786
+ }));
15521
15787
  switch (referenceLocation) {
15522
15788
  case "grid":
15523
15789
  referenceGroup.api.setVisible(false);
@@ -15626,6 +15892,7 @@ var DockviewComponent = class extends BaseGrid {
15626
15892
  group: value.popoutGroup,
15627
15893
  window: value.getWindow()
15628
15894
  });
15895
+ for (const { panel, from } of movedPanels) this.fireDidMovePanel(panel, from);
15629
15896
  return true;
15630
15897
  }).catch((err) => {
15631
15898
  console.error("dockview: failed to create popout.", err);
@@ -15703,6 +15970,16 @@ var DockviewComponent = class extends BaseGrid {
15703
15970
  const members = this.groups.filter((candidate) => popoutGridview.element.contains(candidate.element));
15704
15971
  const anchorPresent = members.includes(group);
15705
15972
  const anchorIsSoleMember = anchorPresent && members.length === 1;
15973
+ /**
15974
+ * Popping a group out fires `onDidMovePanel` per panel, so closing the
15975
+ * window owes the caller the same: every panel in it is rehomed, into
15976
+ * the reference group the popout left behind or into a fresh grid slot.
15977
+ * `movingLock` suppresses onDidAddPanel / onDidRemovePanel on these
15978
+ * paths, so `onDidMovePanel` is the only event that can carry it. The
15979
+ * events are deferred to the end so listeners observe the panels at
15980
+ * their final group and location.
15981
+ */
15982
+ const movedPanels = [];
15706
15983
  if (genuineClose) for (const member of members) {
15707
15984
  if (member === group) continue;
15708
15985
  this.movingLock(() => {
@@ -15713,12 +15990,21 @@ var DockviewComponent = class extends BaseGrid {
15713
15990
  });
15714
15991
  this.redockGroupToMainGrid(member);
15715
15992
  });
15993
+ for (const panel of member.panels) movedPanels.push({
15994
+ panel,
15995
+ from: member
15996
+ });
15716
15997
  }
15717
15998
  if (anchorPresent && isGroupAddedToDom && this.getPanel(referenceGroup.id)) {
15999
+ const rehomed = [...group.panels];
15718
16000
  this.movingLock(() => moveGroupWithoutDestroying({
15719
16001
  from: group,
15720
16002
  to: referenceGroup
15721
16003
  }));
16004
+ for (const panel of rehomed) movedPanels.push({
16005
+ panel,
16006
+ from: group
16007
+ });
15722
16008
  if (!referenceGroup.api.isVisible) referenceGroup.api.setVisible(true);
15723
16009
  if (this.getPanel(group.id)) this.doRemoveGroup(group, { skipPopoutAssociated: true });
15724
16010
  } else if (anchorPresent && this.getPanel(group.id)) {
@@ -15749,10 +16035,15 @@ var DockviewComponent = class extends BaseGrid {
15749
16035
  this.movingLock(() => {
15750
16036
  this.doAddGroup(group, [0]);
15751
16037
  });
16038
+ for (const panel of group.panels) movedPanels.push({
16039
+ panel,
16040
+ from: group
16041
+ });
15752
16042
  }
15753
16043
  this.doSetGroupAndPanelActive(group);
15754
16044
  }
15755
16045
  disposePopoutGridview();
16046
+ for (const { panel, from } of movedPanels) this.fireDidMovePanel(panel, from);
15756
16047
  }
15757
16048
  addFloatingGroup(item, options) {
15758
16049
  this.mutation("float", () => this._doAddFloatingGroup(item, options));
@@ -15761,7 +16052,15 @@ var DockviewComponent = class extends BaseGrid {
15761
16052
  if (!assertModule(this._floatingGroupService, "FloatingGroup", "api.addFloatingGroup")) return;
15762
16053
  if (item instanceof DockviewGroupPanel && item.model.location.type === "edge") return;
15763
16054
  let group;
16055
+ /**
16056
+ * Floating relocates panels exactly like a drop onto the grid does, so
16057
+ * it owes the caller an `onDidMovePanel` per panel. The events are
16058
+ * deferred until the window is mounted below: only then do the panels
16059
+ * report their final `floating` location.
16060
+ */
16061
+ let movedPanels = [];
15764
16062
  if (item instanceof DockviewPanel) {
16063
+ const sourceGroup = item.group;
15765
16064
  group = this.createGroup();
15766
16065
  this._onDidAddGroup.fire(group);
15767
16066
  this.movingLock(() => this.removePanel(item, {
@@ -15770,6 +16069,10 @@ var DockviewComponent = class extends BaseGrid {
15770
16069
  skipSetActiveGroup: true
15771
16070
  }));
15772
16071
  this.movingLock(() => group.model.openPanel(item, { skipSetGroupActive: true }));
16072
+ movedPanels = [{
16073
+ panel: item,
16074
+ from: sourceGroup
16075
+ }];
15773
16076
  } else {
15774
16077
  var _this$_popoutWindowSe8;
15775
16078
  group = item;
@@ -15786,11 +16089,21 @@ var DockviewComponent = class extends BaseGrid {
15786
16089
  });
15787
16090
  this.doRemoveGroup(popoutReferenceGroup, { skipDispose: true });
15788
16091
  group = popoutReferenceGroup;
15789
- } else this.doRemoveGroup(item, {
15790
- skipDispose: true,
15791
- skipPopoutReturn: true,
15792
- skipPopoutAssociated: false
15793
- });
16092
+ movedPanels = group.panels.map((panel) => ({
16093
+ panel,
16094
+ from: item
16095
+ }));
16096
+ } else {
16097
+ this.doRemoveGroup(item, {
16098
+ skipDispose: true,
16099
+ skipPopoutReturn: true,
16100
+ skipPopoutAssociated: false
16101
+ });
16102
+ movedPanels = group.panels.map((panel) => ({
16103
+ panel,
16104
+ from: group
16105
+ }));
16106
+ }
15794
16107
  }
15795
16108
  function getAnchoredBox() {
15796
16109
  if (options === null || options === void 0 ? void 0 : options.position) {
@@ -15823,6 +16136,7 @@ var DockviewComponent = class extends BaseGrid {
15823
16136
  skipActiveGroup: options === null || options === void 0 ? void 0 : options.skipActiveGroup,
15824
16137
  disableSmartGuides: options === null || options === void 0 ? void 0 : options.disableSmartGuides
15825
16138
  });
16139
+ for (const { panel, from } of movedPanels) this.fireDidMovePanel(panel, from);
15826
16140
  }
15827
16141
  /**
15828
16142
  * Build an empty gridview configured to match the main grid's styling, for
@@ -15986,11 +16300,15 @@ var DockviewComponent = class extends BaseGrid {
15986
16300
  /**
15987
16301
  * Reveal (create-or-fill) the edge group at `position` and move the dragged
15988
16302
  * item described by `data` into it. A newly created edge group is created
15989
- * collapsed and flagged `autoReveal` so it tears down to zero footprint when
15990
- * later emptied. If an edge group already exists there it is reused: the
15991
- * panel is added to its tabs and its collapsed/toggled state is left as-is
15992
- * (never re-created; `addEdgeGroup` throws on a duplicate position). No-op if
15993
- * the EdgeGroup module is absent.
16303
+ * collapsed, flagged `autoReveal` so it tears down to zero footprint when
16304
+ * later emptied, and takes its auto-hide state from `options.autoHide`. If an
16305
+ * edge group already exists there it is reused: the panel is added to its
16306
+ * tabs and its collapsed/toggled *and auto-hide* state are left as-is (never
16307
+ * re-created; `addEdgeGroup` throws on a duplicate position). This keeps a
16308
+ * drag-reveal from silently converting a static edge group into an
16309
+ * auto-hiding one; to change an existing group's auto-hide, call
16310
+ * `api.getEdgeGroup(position)?.setAutoHide(...)` directly. No-op if the
16311
+ * EdgeGroup module is absent.
15994
16312
  *
15995
16313
  * This is the primitive behind the dock-to-edge groups: the two-band
15996
16314
  * drag-reveal affordance routes its outer-band drops here.
@@ -16009,7 +16327,7 @@ var DockviewComponent = class extends BaseGrid {
16009
16327
  collapsed: true
16010
16328
  });
16011
16329
  group = service.get(position);
16012
- } else if ((options === null || options === void 0 ? void 0 : options.autoHide) !== void 0) this.setEdgeGroupAutoHide(group, options.autoHide);
16330
+ }
16013
16331
  if (!group) return;
16014
16332
  this.moveGroupOrPanel({
16015
16333
  from: {
@@ -16250,35 +16568,119 @@ var DockviewComponent = class extends BaseGrid {
16250
16568
  if (typeof data !== "object" || data === null) throw new Error("dockview: serialized layout must be a non-null object");
16251
16569
  if (!data.grid || ((_data$grid$root = data.grid.root) === null || _data$grid$root === void 0 ? void 0 : _data$grid$root.type) !== "branch" || !Array.isArray(data.grid.root.data)) throw new Error("dockview: root must be of type branch");
16252
16570
  const existingPanels = /* @__PURE__ */ new Map();
16253
- let tempGroup;
16571
+ const temporaryGroups = /* @__PURE__ */ new Map();
16572
+ const stagedPanels = [];
16573
+ const temporaryGroupDisposables = [];
16574
+ /**
16575
+ * The staging groups are detached from `_groups` and never enter the
16576
+ * grid, so nothing else will ever tear them down.
16577
+ *
16578
+ * `dispose()` reaches consumer `IContentRenderer.dispose()`, so one
16579
+ * throwing renderer must not abort the rest of the cleanup — that would
16580
+ * re-introduce the very leak this reclaims — nor replace the
16581
+ * deserialization error the caller is being given. Draining the list
16582
+ * also makes this safe to call more than once.
16583
+ */
16584
+ const disposeTemporaryGroups = () => {
16585
+ for (const disposable of temporaryGroupDisposables) try {
16586
+ disposable.dispose();
16587
+ } catch (err) {
16588
+ console.error("dockview: failed to dispose a temporary group created for reuseExistingPanels", err);
16589
+ }
16590
+ temporaryGroupDisposables.length = 0;
16591
+ };
16254
16592
  if (options === null || options === void 0 ? void 0 : options.reuseExistingPanels) {
16255
16593
  /**
16256
- * What are we doing here?
16257
- *
16258
- * 1. Create a temporary group to hold any panels that currently exist and that also exist in the new layout
16259
- * 2. Remove that temporary group from the group mapping so that it doesn't get cleared when we clear the layout
16594
+ * Visible, always-rendered panels need individual staging groups
16595
+ * to remain active. Other reused panels can share a staging group.
16596
+ * The staging groups are excluded from the layout clear below.
16260
16597
  */
16261
- tempGroup = this.createGroup();
16262
- this._groups.delete(tempGroup.api.id);
16263
16598
  const newPanels = Object.keys(data.panels);
16264
- for (const panel of this.panels) if (newPanels.includes(panel.api.id)) existingPanels.set(panel.api.id, panel);
16265
- this.movingLock(() => {
16266
- Array.from(existingPanels.values()).forEach((panel) => {
16267
- this.moveGroupOrPanel({
16268
- from: {
16269
- groupId: panel.api.group.api.id,
16270
- panelId: panel.api.id
16271
- },
16272
- to: {
16273
- group: tempGroup,
16274
- position: "center"
16275
- },
16276
- keepEmptyGroups: true
16599
+ let sharedTemporaryGroup;
16600
+ const createTemporaryGroup = () => {
16601
+ const temporaryGroup = this.createGroup();
16602
+ /**
16603
+ * Removing the group from `_groups` also drops the record's
16604
+ * `CompositeDisposable`, so capture it first and tear both it
16605
+ * and the group down in the `finally` below. Left undisposed a
16606
+ * staging group leaks its `ResizeObserver`, its
16607
+ * `onDidOptionsChange` subscription (retained by this
16608
+ * component's emitter) and the watermark its model mounts when
16609
+ * the group empties — once per reused panel, per `fromJSON`.
16610
+ */
16611
+ const record = this._groups.get(temporaryGroup.api.id);
16612
+ this._groups.delete(temporaryGroup.api.id);
16613
+ temporaryGroupDisposables.push(Disposable.from(() => {
16614
+ /**
16615
+ * Reclaim the group's own resources, never its panels.
16616
+ * Staging is driven by the ids in `data.panels` while
16617
+ * reclaiming is driven by the group `views` that
16618
+ * reference them, so a panel whose state is present but
16619
+ * unreferenced is still staged here — and `dispose()`
16620
+ * on a non-empty group reaches the consumer's
16621
+ * `IContentRenderer.dispose()`. Emptying the group
16622
+ * first leaves those panels exactly as they were before
16623
+ * this teardown existed, which also keeps this safe
16624
+ * against any future path that rebuilds a panel under
16625
+ * an id that is live in the new layout.
16626
+ */
16627
+ this.movingLock(() => {
16628
+ for (const panel of temporaryGroup.panels.slice()) temporaryGroup.model.removePanel(panel);
16629
+ });
16630
+ record === null || record === void 0 || record.disposable.dispose();
16631
+ temporaryGroup.dispose();
16632
+ }));
16633
+ return temporaryGroup;
16634
+ };
16635
+ /**
16636
+ * Staging and the clear below run before the deserialization
16637
+ * `try`, so a throw here (a consumer `onDidRemovePanel` handler, a
16638
+ * renderer teardown during `clear`) would escape without reclaiming
16639
+ * the staging groups already created.
16640
+ *
16641
+ * The creation loop is inside the guard too, not just the moves and
16642
+ * the clear: `createGroup()` reaches consumer code of its own —
16643
+ * `initialize()` mounts the watermark through
16644
+ * `createWatermarkComponent()` — so a throw on the n-th panel would
16645
+ * otherwise strand the n-1 staging groups already built.
16646
+ */
16647
+ try {
16648
+ for (const panel of this.panels) if (newPanels.includes(panel.api.id)) {
16649
+ existingPanels.set(panel.api.id, panel);
16650
+ let temporaryGroup;
16651
+ if (panel.api.renderer === "always" && panel.api.isVisible) temporaryGroup = createTemporaryGroup();
16652
+ else {
16653
+ var _sharedTemporaryGroup;
16654
+ (_sharedTemporaryGroup = sharedTemporaryGroup) !== null && _sharedTemporaryGroup !== void 0 || (sharedTemporaryGroup = createTemporaryGroup());
16655
+ temporaryGroup = sharedTemporaryGroup;
16656
+ }
16657
+ temporaryGroups.set(panel.api.id, temporaryGroup);
16658
+ stagedPanels.push({
16659
+ panel,
16660
+ temporaryGroup
16661
+ });
16662
+ }
16663
+ this.movingLock(() => {
16664
+ stagedPanels.forEach(({ panel, temporaryGroup }) => {
16665
+ this.moveGroupOrPanel({
16666
+ from: {
16667
+ groupId: panel.api.group.api.id,
16668
+ panelId: panel.api.id
16669
+ },
16670
+ to: {
16671
+ group: temporaryGroup,
16672
+ position: "center"
16673
+ },
16674
+ keepEmptyGroups: true
16675
+ });
16277
16676
  });
16278
16677
  });
16279
- });
16280
- }
16281
- this.clear();
16678
+ this.clear();
16679
+ } catch (err) {
16680
+ disposeTemporaryGroups();
16681
+ throw err;
16682
+ }
16683
+ } else this.clear();
16282
16684
  const { grid, panels, activeGroup } = data;
16283
16685
  try {
16284
16686
  var _data$floatingGroups, _data$popoutGroups, _this$_popoutWindowSe12, _this$_floatingGroupS5, _this$_watermarkServi2;
@@ -16310,9 +16712,10 @@ var DockviewComponent = class extends BaseGrid {
16310
16712
  * due to a corruption of input data.
16311
16713
  */
16312
16714
  const existingPanel = existingPanels.get(child);
16313
- if (tempGroup && existingPanel) {
16715
+ const temporaryGroup = temporaryGroups.get(child);
16716
+ if (temporaryGroup && existingPanel) {
16314
16717
  this.movingLock(() => {
16315
- tempGroup.model.removePanel(existingPanel);
16718
+ temporaryGroup.model.removePanel(existingPanel);
16316
16719
  });
16317
16720
  createdPanels.push(existingPanel);
16318
16721
  existingPanel.updateFromStateModel(panels[child]);
@@ -16342,7 +16745,7 @@ var DockviewComponent = class extends BaseGrid {
16342
16745
  return createGroupFromSerializedState(node.data);
16343
16746
  } });
16344
16747
  this._layoutFromShell(width, height);
16345
- if (data.edgeGroups) this.deserializeEdgeGroups(data.edgeGroups, panels);
16748
+ if (data.edgeGroups) this.deserializeEdgeGroups(data.edgeGroups, panels, existingPanels, temporaryGroups);
16346
16749
  this.deserializeFloatingWindows((_data$floatingGroups = data.floatingGroups) !== null && _data$floatingGroups !== void 0 ? _data$floatingGroups : [], createGroupFromSerializedState);
16347
16750
  const popoutPromises = this.deserializePopoutWindows((_data$popoutGroups = data.popoutGroups) !== null && _data$popoutGroups !== void 0 ? _data$popoutGroups : [], createGroupFromSerializedState);
16348
16751
  (_this$_popoutWindowSe12 = this._popoutWindowService) === null || _this$_popoutWindowSe12 === void 0 || _this$_popoutWindowSe12.finishRestoration(popoutPromises);
@@ -16379,6 +16782,8 @@ var DockviewComponent = class extends BaseGrid {
16379
16782
  * expect trying to load a corrupted layout to result in an error and not silently fail...
16380
16783
  */
16381
16784
  throw err;
16785
+ } finally {
16786
+ disposeTemporaryGroups();
16382
16787
  }
16383
16788
  this.debouncedUpdateAllPositions();
16384
16789
  this._onDidLayoutFromJSON.fire();
@@ -16401,7 +16806,7 @@ var DockviewComponent = class extends BaseGrid {
16401
16806
  members
16402
16807
  };
16403
16808
  }
16404
- deserializeEdgeGroups(edgeGroups, panels) {
16809
+ deserializeEdgeGroups(edgeGroups, panels, existingPanels, temporaryGroups) {
16405
16810
  const edgeService = assertModule(this._edgeGroupService, "EdgeGroup", "fromJSON edge restoration");
16406
16811
  if (!edgeService) return;
16407
16812
  for (const _position of [
@@ -16431,13 +16836,40 @@ var DockviewComponent = class extends BaseGrid {
16431
16836
  if (groupState) {
16432
16837
  const { views, activeView } = groupState;
16433
16838
  const createdPanels = [];
16434
- for (const panelId of views) if (panels[panelId]) {
16435
- const panel = this._deserializer.fromJSON(panels[panelId], edgeGroup);
16436
- createdPanels.push(panel);
16839
+ for (const panelId of views) {
16840
+ if (!panels[panelId]) continue;
16841
+ /**
16842
+ * Reclaim a staged panel rather than rebuilding it, the
16843
+ * same way the grid path does. Deserializing here instead
16844
+ * would honour `reuseExistingPanels` everywhere except edge
16845
+ * groups: the live panel keeps sitting in its staging group
16846
+ * while a second panel is built under the same id, so the
16847
+ * consumer's renderer for the original is orphaned — never
16848
+ * disposed, and its element left inside the id-keyed
16849
+ * overlay entry the replacement now shares.
16850
+ */
16851
+ const existingPanel = existingPanels.get(panelId);
16852
+ const temporaryGroup = temporaryGroups.get(panelId);
16853
+ if (temporaryGroup && existingPanel) {
16854
+ this.movingLock(() => {
16855
+ temporaryGroup.model.removePanel(existingPanel);
16856
+ });
16857
+ createdPanels.push(existingPanel);
16858
+ existingPanel.updateFromStateModel(panels[panelId]);
16859
+ } else {
16860
+ const panel = this._deserializer.fromJSON(panels[panelId], edgeGroup);
16861
+ createdPanels.push(panel);
16862
+ }
16437
16863
  }
16438
16864
  for (const panel of createdPanels) {
16439
16865
  const isActive = activeView === panel.id;
16440
- edgeGroup.model.openPanel(panel, {
16866
+ if (existingPanels.has(panel.api.id)) this.movingLock(() => {
16867
+ edgeGroup.model.openPanel(panel, {
16868
+ skipSetActive: !isActive,
16869
+ skipSetGroupActive: true
16870
+ });
16871
+ });
16872
+ else edgeGroup.model.openPanel(panel, {
16441
16873
  skipSetActive: !isActive,
16442
16874
  skipSetGroupActive: true
16443
16875
  });
@@ -16540,6 +16972,7 @@ var DockviewComponent = class extends BaseGrid {
16540
16972
  height: options.initialHeight
16541
16973
  };
16542
16974
  let index;
16975
+ const shouldSkipSetActive = (group) => !!options.inactive && group.model.size > 0;
16543
16976
  if (options.position) if (isPanelOptionsWithPanel(options.position)) {
16544
16977
  const referencePanel = typeof options.position.referencePanel === "string" ? this.getGroupPanel(options.position.referencePanel) : options.position.referencePanel;
16545
16978
  index = options.position.index;
@@ -16562,7 +16995,7 @@ var DockviewComponent = class extends BaseGrid {
16562
16995
  const group = this.orthogonalize(directionToPosition(options.position.direction));
16563
16996
  const panel = this.createPanel(options, group);
16564
16997
  group.model.openPanel(panel, {
16565
- skipSetActive: options.inactive,
16998
+ skipSetActive: shouldSkipSetActive(group),
16566
16999
  skipSetGroupActive: options.inactive,
16567
17000
  index
16568
17001
  });
@@ -16589,14 +17022,14 @@ var DockviewComponent = class extends BaseGrid {
16589
17022
  }));
16590
17023
  panel = this.createPanel(options, group);
16591
17024
  group.model.openPanel(panel, {
16592
- skipSetActive: options.inactive,
17025
+ skipSetActive: shouldSkipSetActive(group),
16593
17026
  skipSetGroupActive: options.inactive,
16594
17027
  index
16595
17028
  });
16596
17029
  } else if (referenceGroup.api.location.type === "floating" || referenceGroup.api.location.type === "edge" || target === "center") {
16597
17030
  panel = this.createPanel(options, referenceGroup);
16598
17031
  referenceGroup.model.openPanel(panel, {
16599
- skipSetActive: options.inactive,
17032
+ skipSetActive: shouldSkipSetActive(referenceGroup),
16600
17033
  skipSetGroupActive: options.inactive,
16601
17034
  index
16602
17035
  });
@@ -16611,7 +17044,7 @@ var DockviewComponent = class extends BaseGrid {
16611
17044
  const group = this.createGroupAtLocation(relativeLocation, this.orientationAtLocation(relativeLocation) === "VERTICAL" ? initial === null || initial === void 0 ? void 0 : initial.height : initial === null || initial === void 0 ? void 0 : initial.width);
16612
17045
  panel = this.createPanel(options, group);
16613
17046
  group.model.openPanel(panel, {
16614
- skipSetActive: options.inactive,
17047
+ skipSetActive: shouldSkipSetActive(group),
16615
17048
  skipSetGroupActive: options.inactive,
16616
17049
  index
16617
17050
  });
@@ -16628,7 +17061,7 @@ var DockviewComponent = class extends BaseGrid {
16628
17061
  }));
16629
17062
  panel = this.createPanel(options, group);
16630
17063
  group.model.openPanel(panel, {
16631
- skipSetActive: options.inactive,
17064
+ skipSetActive: shouldSkipSetActive(group),
16632
17065
  skipSetGroupActive: options.inactive,
16633
17066
  index
16634
17067
  });
@@ -16636,7 +17069,7 @@ var DockviewComponent = class extends BaseGrid {
16636
17069
  const group = this.createGroupAtLocation([0], this.gridview.orientation === "VERTICAL" ? initial === null || initial === void 0 ? void 0 : initial.height : initial === null || initial === void 0 ? void 0 : initial.width);
16637
17070
  panel = this.createPanel(options, group);
16638
17071
  group.model.openPanel(panel, {
16639
- skipSetActive: options.inactive,
17072
+ skipSetActive: shouldSkipSetActive(group),
16640
17073
  skipSetGroupActive: options.inactive,
16641
17074
  index
16642
17075
  });
@@ -16860,22 +17293,100 @@ var DockviewComponent = class extends BaseGrid {
16860
17293
  * outermost mutation.
16861
17294
  */
16862
17295
  mutation(kind, func) {
16863
- const outer = this._mutationDepth === 0;
17296
+ const close = this.openMutation(kind);
17297
+ try {
17298
+ return func();
17299
+ } finally {
17300
+ close();
17301
+ }
17302
+ }
17303
+ /**
17304
+ * `mutation()` for an operation whose work continues after the synchronous
17305
+ * call returns. The transaction stays open until the returned promise
17306
+ * settles, so everything the operation does - the group it adds, the panels
17307
+ * it relocates - lands inside the bracket, matching the synchronous
17308
+ * move / float paths.
17309
+ */
17310
+ mutationAsync(kind, func) {
17311
+ var _this = this;
17312
+ return _asyncToGenerator(function* () {
17313
+ const close = _this.openMutation(kind);
17314
+ try {
17315
+ return yield func();
17316
+ } finally {
17317
+ close();
17318
+ }
17319
+ })();
17320
+ }
17321
+ /**
17322
+ * Open a transaction and return the function that closes it. Shared by the
17323
+ * synchronous and asynchronous brackets; the returned function must be
17324
+ * called exactly once.
17325
+ *
17326
+ * Both ends key off the depth counter reaching zero rather than off which
17327
+ * bracket opened first. For synchronous nesting the two are the same thing
17328
+ * - brackets close in the order they opened - but asynchronous transactions
17329
+ * can *overlap* rather than nest (two `addPopoutGroup` calls in flight at
17330
+ * once, or a restore staggering several), and there the first to open is
17331
+ * not the last to close. Reporting on the opener would fire `didMutate`
17332
+ * while the other transaction was still doing structural work, which is the
17333
+ * very thing the async bracket exists to prevent. Overlapping transactions
17334
+ * therefore report as one, tagged with the kind of the last to finish.
17335
+ */
17336
+ openMutation(kind) {
16864
17337
  const origin = this._origin;
16865
- if (outer) this._onWillMutateLayout.fire({
17338
+ if (this._mutationDepth === 0) this._onWillMutateLayout.fire({
16866
17339
  kind,
16867
17340
  origin
16868
17341
  });
16869
17342
  this._mutationDepth++;
16870
- try {
16871
- return func();
16872
- } finally {
17343
+ return () => {
16873
17344
  this._mutationDepth--;
16874
- if (outer) this._onDidMutateLayout.fire({
16875
- kind,
16876
- origin
16877
- });
17345
+ if (this._mutationDepth === 0) {
17346
+ this.flushLocationChanges();
17347
+ this._onDidMutateLayout.fire({
17348
+ kind,
17349
+ origin
17350
+ });
17351
+ }
17352
+ };
17353
+ }
17354
+ /**
17355
+ * Coalesce a panel's `onDidLocationChange` to the end of the enclosing
17356
+ * transaction, or fire it immediately when no transaction is in flight.
17357
+ *
17358
+ * Relocating a panel touches its location twice: once as it is reparented
17359
+ * into the destination group - which, when that group has just been created
17360
+ * for a floating or popout window, has not been told where it lives yet -
17361
+ * and once as the group is tagged with its final location. Reporting each
17362
+ * signal as it happens therefore leaks an intermediate `grid` location the
17363
+ * panel was never in, at a moment when it belongs to neither group's panel
17364
+ * list and so is missing from `api.panels`.
17365
+ *
17366
+ * Deferring collapses those signals into a single event carrying the
17367
+ * settled location. Note the signals are deliberately *not* compared
17368
+ * against the panel's previous location: a panel can move between two
17369
+ * floating windows without its location type changing, and this event is
17370
+ * the only signal `OverlayRenderContainer` has to re-resolve the panel's
17371
+ * z-index against its new host window.
17372
+ */
17373
+ deferLocationChange(key, fire) {
17374
+ if (this._mutationDepth === 0) {
17375
+ fire();
17376
+ return;
16878
17377
  }
17378
+ this._pendingLocationChanges.set(key, fire);
17379
+ }
17380
+ /**
17381
+ * Report every panel whose location moved during the transaction that has
17382
+ * just closed. Each callback reads the panel's location at this point, so
17383
+ * what it reports is where the panel ended up.
17384
+ */
17385
+ flushLocationChanges() {
17386
+ if (this._pendingLocationChanges.size === 0) return;
17387
+ const pending = Array.from(this._pendingLocationChanges.values());
17388
+ this._pendingLocationChanges.clear();
17389
+ for (const fire of pending) fire();
16879
17390
  }
16880
17391
  /**
16881
17392
  * The origin of the operation currently in progress (`'user'` by default).
@@ -16916,6 +17427,18 @@ var DockviewComponent = class extends BaseGrid {
16916
17427
  origin: this._origin
16917
17428
  });
16918
17429
  }
17430
+ /**
17431
+ * Announce that `panel` has finished relocating. Must be called once the
17432
+ * move has settled: `to` is read from the panel's current group, so an
17433
+ * early call would report the source group as the destination.
17434
+ */
17435
+ fireDidMovePanel(panel, from) {
17436
+ this._onDidMovePanel.fire({
17437
+ panel,
17438
+ from,
17439
+ to: panel.group
17440
+ });
17441
+ }
16919
17442
  moveGroupOrPanel(options) {
16920
17443
  this.mutation("move", () => this._doMoveGroupOrPanel(options));
16921
17444
  }
@@ -16976,10 +17499,7 @@ var DockviewComponent = class extends BaseGrid {
16976
17499
  });
16977
17500
  });
16978
17501
  if (!options.skipSetActive) this.doSetGroupAndPanelActive(destinationGroup);
16979
- this._onDidMovePanel.fire({
16980
- panel: removedPanel,
16981
- from: sourceGroup
16982
- });
17502
+ this.fireDidMovePanel(removedPanel, sourceGroup);
16983
17503
  } else {
16984
17504
  /**
16985
17505
  * Dropping a panel to the extremities of a group which will place that panel
@@ -16998,10 +17518,7 @@ var DockviewComponent = class extends BaseGrid {
16998
17518
  const [sourceParentLocation, from] = tail(getGridLocation(sourceGroup.element));
16999
17519
  if (sequenceEquals(sourceParentLocation, targetParentLocation)) {
17000
17520
  this.gridview.moveView(sourceParentLocation, from, to);
17001
- this._onDidMovePanel.fire({
17002
- panel: this.getGroupPanel(sourceItemId),
17003
- from: sourceGroup
17004
- });
17521
+ this.fireDidMovePanel(this.getGroupPanel(sourceItemId), sourceGroup);
17005
17522
  return;
17006
17523
  }
17007
17524
  }
@@ -17032,10 +17549,7 @@ var DockviewComponent = class extends BaseGrid {
17032
17549
  const newGroup = this.createGroupAtLocation(updatedTargetLocation, void 0, void 0, destinationGridview);
17033
17550
  this.movingLock(() => newGroup.model.openPanel(removedPanel, { skipSetActive: true }));
17034
17551
  this.doSetGroupAndPanelActive(newGroup);
17035
- this._onDidMovePanel.fire({
17036
- panel: this.getGroupPanel(sourceItemId),
17037
- from: sourceGroup
17038
- });
17552
+ this.fireDidMovePanel(this.getGroupPanel(sourceItemId), sourceGroup);
17039
17553
  return;
17040
17554
  }
17041
17555
  if (sourceGroup.api.location.type === "edge") {
@@ -17052,10 +17566,7 @@ var DockviewComponent = class extends BaseGrid {
17052
17566
  const newGroup = this.createGroupAtLocation(targetLocation, void 0, void 0, destinationGridview);
17053
17567
  this.movingLock(() => newGroup.model.openPanel(removedPanel, { skipSetGroupActive: true }));
17054
17568
  this.doSetGroupAndPanelActive(newGroup);
17055
- this._onDidMovePanel.fire({
17056
- panel: removedPanel,
17057
- from: sourceGroup
17058
- });
17569
+ this.fireDidMovePanel(removedPanel, sourceGroup);
17059
17570
  return;
17060
17571
  }
17061
17572
  const targetGroup = this.movingLock(() => this.doRemoveGroup(sourceGroup, {
@@ -17067,10 +17578,7 @@ var DockviewComponent = class extends BaseGrid {
17067
17578
  this.movingLock(() => this.doAddGroup(targetGroup, location, void 0, destinationGridview));
17068
17579
  this.setGroupLocationForRoot(targetGroup, destinationGridview);
17069
17580
  this.doSetGroupAndPanelActive(targetGroup);
17070
- this._onDidMovePanel.fire({
17071
- panel: this.getGroupPanel(sourceItemId),
17072
- from: sourceGroup
17073
- });
17581
+ this.fireDidMovePanel(this.getGroupPanel(sourceItemId), sourceGroup);
17074
17582
  } else {
17075
17583
  /**
17076
17584
  * The group we are removing from has many panels, we need to remove the panels we are moving,
@@ -17085,10 +17593,7 @@ var DockviewComponent = class extends BaseGrid {
17085
17593
  const group = this.createGroupAtLocation(dropLocation, void 0, void 0, destinationGridview);
17086
17594
  this.movingLock(() => group.model.openPanel(removedPanel, { skipSetGroupActive: true }));
17087
17595
  this.doSetGroupAndPanelActive(group);
17088
- this._onDidMovePanel.fire({
17089
- panel: removedPanel,
17090
- from: sourceGroup
17091
- });
17596
+ this.fireDidMovePanel(removedPanel, sourceGroup);
17092
17597
  }
17093
17598
  }
17094
17599
  }
@@ -17123,10 +17628,7 @@ var DockviewComponent = class extends BaseGrid {
17123
17628
  });
17124
17629
  for (const panel of removedPanels) targetGroup.model.addPanelToTabGroup(newTabGroup.id, panel.id);
17125
17630
  if (!options.skipSetActive) this.doSetGroupAndPanelActive(targetGroup);
17126
- for (const panel of removedPanels) this._onDidMovePanel.fire({
17127
- panel,
17128
- from: sourceGroup
17129
- });
17631
+ for (const panel of removedPanels) this.fireDidMovePanel(panel, sourceGroup);
17130
17632
  };
17131
17633
  let targetGroup;
17132
17634
  if (!destinationTarget || destinationTarget === "center" || !referenceLocation) targetGroup = destinationGroup;
@@ -17147,10 +17649,12 @@ var DockviewComponent = class extends BaseGrid {
17147
17649
  this.mutation("maximize", () => super.exitMaximizedGroup());
17148
17650
  }
17149
17651
  _doMoveGroup(options) {
17652
+ var _mergedPanels;
17150
17653
  const from = options.from.group;
17151
17654
  const to = options.to.group;
17152
17655
  const target = options.to.position;
17153
17656
  let source = from;
17657
+ let mergedPanels;
17154
17658
  if (target === "center") {
17155
17659
  const activePanel = from.activePanel;
17156
17660
  const tabGroupSnapshots = from.model.getTabGroups().map((tg) => ({
@@ -17168,6 +17672,7 @@ var DockviewComponent = class extends BaseGrid {
17168
17672
  skipSetGroupActive: true
17169
17673
  });
17170
17674
  });
17675
+ mergedPanels = panels;
17171
17676
  for (const snapshot of tabGroupSnapshots) {
17172
17677
  const newTabGroup = to.model.createTabGroup({
17173
17678
  label: snapshot.label,
@@ -17257,11 +17762,8 @@ var DockviewComponent = class extends BaseGrid {
17257
17762
  this.setGroupLocationForRoot(source, destGridview);
17258
17763
  }
17259
17764
  }
17260
- source.panels.forEach((panel) => {
17261
- this._onDidMovePanel.fire({
17262
- panel,
17263
- from
17264
- });
17765
+ ((_mergedPanels = mergedPanels) !== null && _mergedPanels !== void 0 ? _mergedPanels : source.panels).forEach((panel) => {
17766
+ this.fireDidMovePanel(panel, from);
17265
17767
  });
17266
17768
  this.debouncedUpdateAllPositions();
17267
17769
  if (options.skipSetActive === false) {