dockview-core 4.6.2 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/cjs/constants.d.ts +1 -0
  2. package/dist/cjs/constants.js +2 -1
  3. package/dist/cjs/dnd/abstractDragHandler.d.ts +3 -1
  4. package/dist/cjs/dnd/abstractDragHandler.js +6 -2
  5. package/dist/cjs/dnd/droptarget.js +46 -17
  6. package/dist/cjs/dnd/groupDragHandler.d.ts +1 -1
  7. package/dist/cjs/dnd/groupDragHandler.js +2 -2
  8. package/dist/cjs/dockview/components/tab/tab.d.ts +1 -0
  9. package/dist/cjs/dockview/components/tab/tab.js +6 -5
  10. package/dist/cjs/dockview/components/titlebar/voidContainer.d.ts +1 -0
  11. package/dist/cjs/dockview/components/titlebar/voidContainer.js +3 -2
  12. package/dist/cjs/dockview/dockviewComponent.d.ts +6 -0
  13. package/dist/cjs/dockview/dockviewComponent.js +115 -86
  14. package/dist/cjs/gridview/gridview.d.ts +1 -0
  15. package/dist/cjs/gridview/gridview.js +37 -0
  16. package/dist/cjs/overlay/overlayRenderContainer.d.ts +3 -0
  17. package/dist/cjs/overlay/overlayRenderContainer.js +82 -8
  18. package/dist/dockview-core.amd.js +235 -67
  19. package/dist/dockview-core.amd.js.map +1 -1
  20. package/dist/dockview-core.amd.min.js +2 -2
  21. package/dist/dockview-core.amd.min.js.map +1 -1
  22. package/dist/dockview-core.amd.min.noStyle.js +2 -2
  23. package/dist/dockview-core.amd.min.noStyle.js.map +1 -1
  24. package/dist/dockview-core.amd.noStyle.js +234 -66
  25. package/dist/dockview-core.amd.noStyle.js.map +1 -1
  26. package/dist/dockview-core.cjs.js +235 -67
  27. package/dist/dockview-core.cjs.js.map +1 -1
  28. package/dist/dockview-core.esm.js +235 -67
  29. package/dist/dockview-core.esm.js.map +1 -1
  30. package/dist/dockview-core.esm.min.js +2 -2
  31. package/dist/dockview-core.esm.min.js.map +1 -1
  32. package/dist/dockview-core.js +235 -67
  33. package/dist/dockview-core.js.map +1 -1
  34. package/dist/dockview-core.min.js +2 -2
  35. package/dist/dockview-core.min.js.map +1 -1
  36. package/dist/dockview-core.min.noStyle.js +2 -2
  37. package/dist/dockview-core.min.noStyle.js.map +1 -1
  38. package/dist/dockview-core.noStyle.js +234 -66
  39. package/dist/dockview-core.noStyle.js.map +1 -1
  40. package/dist/esm/constants.d.ts +1 -0
  41. package/dist/esm/constants.js +1 -0
  42. package/dist/esm/dnd/abstractDragHandler.d.ts +3 -1
  43. package/dist/esm/dnd/abstractDragHandler.js +6 -2
  44. package/dist/esm/dnd/droptarget.js +46 -17
  45. package/dist/esm/dnd/groupDragHandler.d.ts +1 -1
  46. package/dist/esm/dnd/groupDragHandler.js +2 -2
  47. package/dist/esm/dockview/components/tab/tab.d.ts +1 -0
  48. package/dist/esm/dockview/components/tab/tab.js +6 -5
  49. package/dist/esm/dockview/components/titlebar/voidContainer.d.ts +1 -0
  50. package/dist/esm/dockview/components/titlebar/voidContainer.js +3 -2
  51. package/dist/esm/dockview/dockviewComponent.d.ts +6 -0
  52. package/dist/esm/dockview/dockviewComponent.js +65 -30
  53. package/dist/esm/gridview/gridview.d.ts +1 -0
  54. package/dist/esm/gridview/gridview.js +36 -0
  55. package/dist/esm/overlay/overlayRenderContainer.d.ts +3 -0
  56. package/dist/esm/overlay/overlayRenderContainer.js +69 -8
  57. package/dist/styles/dockview.css +37 -5
  58. package/package.json +1 -1
@@ -5,3 +5,4 @@ export declare const DEFAULT_FLOATING_GROUP_POSITION: {
5
5
  width: number;
6
6
  height: number;
7
7
  };
8
+ export declare const DESERIALIZATION_POPOUT_DELAY_MS = 100;
@@ -1,2 +1,3 @@
1
1
  export const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
2
2
  export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100, width: 300, height: 300 };
3
+ export const DESERIALIZATION_POPOUT_DELAY_MS = 100;
@@ -1,11 +1,13 @@
1
1
  import { CompositeDisposable, IDisposable } from '../lifecycle';
2
2
  export declare abstract class DragHandler extends CompositeDisposable {
3
3
  protected readonly el: HTMLElement;
4
+ private disabled?;
4
5
  private readonly dataDisposable;
5
6
  private readonly pointerEventsDisposable;
6
7
  private readonly _onDragStart;
7
8
  readonly onDragStart: import("../events").Event<DragEvent>;
8
- constructor(el: HTMLElement);
9
+ constructor(el: HTMLElement, disabled?: boolean | undefined);
10
+ setDisabled(disabled: boolean): void;
9
11
  abstract getData(event: DragEvent): IDisposable;
10
12
  protected isCancelled(_event: DragEvent): boolean;
11
13
  private configure;
@@ -2,9 +2,10 @@ import { disableIframePointEvents } from '../dom';
2
2
  import { addDisposableListener, Emitter } from '../events';
3
3
  import { CompositeDisposable, MutableDisposable, } from '../lifecycle';
4
4
  export class DragHandler extends CompositeDisposable {
5
- constructor(el) {
5
+ constructor(el, disabled) {
6
6
  super();
7
7
  this.el = el;
8
+ this.disabled = disabled;
8
9
  this.dataDisposable = new MutableDisposable();
9
10
  this.pointerEventsDisposable = new MutableDisposable();
10
11
  this._onDragStart = new Emitter();
@@ -12,12 +13,15 @@ export class DragHandler extends CompositeDisposable {
12
13
  this.addDisposables(this._onDragStart, this.dataDisposable, this.pointerEventsDisposable);
13
14
  this.configure();
14
15
  }
16
+ setDisabled(disabled) {
17
+ this.disabled = disabled;
18
+ }
15
19
  isCancelled(_event) {
16
20
  return false;
17
21
  }
18
22
  configure() {
19
23
  this.addDisposables(this._onDragStart, addDisposableListener(this.el, 'dragstart', (event) => {
20
- if (event.defaultPrevented || this.isCancelled(event)) {
24
+ if (event.defaultPrevented || this.isCancelled(event) || this.disabled) {
21
25
  event.preventDefault();
22
26
  return;
23
27
  }
@@ -3,6 +3,48 @@ import { DockviewEvent, Emitter } from '../events';
3
3
  import { CompositeDisposable } from '../lifecycle';
4
4
  import { DragAndDropObserver } from './dnd';
5
5
  import { clamp } from '../math';
6
+ function setGPUOptimizedBounds(element, bounds) {
7
+ const { top, left, width, height } = bounds;
8
+ const topPx = `${Math.round(top)}px`;
9
+ const leftPx = `${Math.round(left)}px`;
10
+ const widthPx = `${Math.round(width)}px`;
11
+ const heightPx = `${Math.round(height)}px`;
12
+ // Use traditional positioning but maintain GPU layer
13
+ element.style.top = topPx;
14
+ element.style.left = leftPx;
15
+ element.style.width = widthPx;
16
+ element.style.height = heightPx;
17
+ element.style.visibility = 'visible';
18
+ // Ensure GPU layer is maintained
19
+ if (!element.style.transform || element.style.transform === '') {
20
+ element.style.transform = 'translate3d(0, 0, 0)';
21
+ }
22
+ }
23
+ function setGPUOptimizedBoundsFromStrings(element, bounds) {
24
+ const { top, left, width, height } = bounds;
25
+ // Use traditional positioning but maintain GPU layer
26
+ element.style.top = top;
27
+ element.style.left = left;
28
+ element.style.width = width;
29
+ element.style.height = height;
30
+ element.style.visibility = 'visible';
31
+ // Ensure GPU layer is maintained
32
+ if (!element.style.transform || element.style.transform === '') {
33
+ element.style.transform = 'translate3d(0, 0, 0)';
34
+ }
35
+ }
36
+ function checkBoundsChanged(element, bounds) {
37
+ const { top, left, width, height } = bounds;
38
+ const topPx = `${Math.round(top)}px`;
39
+ const leftPx = `${Math.round(left)}px`;
40
+ const widthPx = `${Math.round(width)}px`;
41
+ const heightPx = `${Math.round(height)}px`;
42
+ // Check if position or size changed (back to traditional method)
43
+ return element.style.top !== topPx ||
44
+ element.style.left !== leftPx ||
45
+ element.style.width !== widthPx ||
46
+ element.style.height !== heightPx;
47
+ }
6
48
  export class WillShowOverlayEvent extends DockviewEvent {
7
49
  get nativeEvent() {
8
50
  return this.options.nativeEvent;
@@ -286,21 +328,11 @@ export class Droptarget extends CompositeDisposable {
286
328
  box.left = rootLeft + width - 4;
287
329
  box.width = 4;
288
330
  }
289
- const topPx = `${Math.round(box.top)}px`;
290
- const leftPx = `${Math.round(box.left)}px`;
291
- const widthPx = `${Math.round(box.width)}px`;
292
- const heightPx = `${Math.round(box.height)}px`;
293
- if (overlay.style.top === topPx &&
294
- overlay.style.left === leftPx &&
295
- overlay.style.width === widthPx &&
296
- overlay.style.height === heightPx) {
331
+ // Use GPU-optimized bounds checking and setting
332
+ if (!checkBoundsChanged(overlay, box)) {
297
333
  return;
298
334
  }
299
- overlay.style.top = topPx;
300
- overlay.style.left = leftPx;
301
- overlay.style.width = widthPx;
302
- overlay.style.height = heightPx;
303
- overlay.style.visibility = 'visible';
335
+ setGPUOptimizedBounds(overlay, box);
304
336
  overlay.className = `dv-drop-target-anchor${this.options.className ? ` ${this.options.className}` : ''}`;
305
337
  toggleClass(overlay, 'dv-drop-target-left', isLeft);
306
338
  toggleClass(overlay, 'dv-drop-target-right', isRight);
@@ -352,10 +384,7 @@ export class Droptarget extends CompositeDisposable {
352
384
  box.top = `${100 * (1 - size)}%`;
353
385
  box.height = `${100 * size}%`;
354
386
  }
355
- this.overlayElement.style.top = box.top;
356
- this.overlayElement.style.left = box.left;
357
- this.overlayElement.style.width = box.width;
358
- this.overlayElement.style.height = box.height;
387
+ setGPUOptimizedBoundsFromStrings(this.overlayElement, box);
359
388
  toggleClass(this.overlayElement, 'dv-drop-target-small-vertical', isSmallY);
360
389
  toggleClass(this.overlayElement, 'dv-drop-target-small-horizontal', isSmallX);
361
390
  toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft);
@@ -6,7 +6,7 @@ export declare class GroupDragHandler extends DragHandler {
6
6
  private readonly accessor;
7
7
  private readonly group;
8
8
  private readonly panelTransfer;
9
- constructor(element: HTMLElement, accessor: DockviewComponent, group: DockviewGroupPanel);
9
+ constructor(element: HTMLElement, accessor: DockviewComponent, group: DockviewGroupPanel, disabled?: boolean);
10
10
  isCancelled(_event: DragEvent): boolean;
11
11
  getData(dragEvent: DragEvent): IDisposable;
12
12
  }
@@ -4,8 +4,8 @@ import { DragHandler } from './abstractDragHandler';
4
4
  import { LocalSelectionTransfer, PanelTransfer } from './dataTransfer';
5
5
  import { addGhostImage } from './ghost';
6
6
  export class GroupDragHandler extends DragHandler {
7
- constructor(element, accessor, group) {
8
- super(element);
7
+ constructor(element, accessor, group, disabled) {
8
+ super(element, disabled);
9
9
  this.accessor = accessor;
10
10
  this.group = group;
11
11
  this.panelTransfer = LocalSelectionTransfer.getInstance();
@@ -12,6 +12,7 @@ export declare class Tab extends CompositeDisposable {
12
12
  private readonly _element;
13
13
  private readonly dropTarget;
14
14
  private content;
15
+ private readonly dragHandler;
15
16
  private readonly _onPointDown;
16
17
  readonly onPointerDown: Event<MouseEvent>;
17
18
  private readonly _onDropped;
@@ -6,8 +6,8 @@ import { Droptarget, } from '../../../dnd/droptarget';
6
6
  import { DragHandler } from '../../../dnd/abstractDragHandler';
7
7
  import { addGhostImage } from '../../../dnd/ghost';
8
8
  class TabDragHandler extends DragHandler {
9
- constructor(element, accessor, group, panel) {
10
- super(element);
9
+ constructor(element, accessor, group, panel, disabled) {
10
+ super(element, disabled);
11
11
  this.accessor = accessor;
12
12
  this.group = group;
13
13
  this.panel = panel;
@@ -43,7 +43,7 @@ export class Tab extends CompositeDisposable {
43
43
  this._element.tabIndex = 0;
44
44
  this._element.draggable = !this.accessor.options.disableDnd;
45
45
  toggleClass(this.element, 'dv-inactive-tab', true);
46
- const dragHandler = new TabDragHandler(this._element, this.accessor, this.group, this.panel);
46
+ this.dragHandler = new TabDragHandler(this._element, this.accessor, this.group, this.panel, !!this.accessor.options.disableDnd);
47
47
  this.dropTarget = new Droptarget(this._element, {
48
48
  acceptedTargetZones: ['left', 'right'],
49
49
  overlayModel: { activationSize: { value: 50, type: 'percentage' } },
@@ -60,7 +60,7 @@ export class Tab extends CompositeDisposable {
60
60
  getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
61
61
  });
62
62
  this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
63
- this.addDisposables(this._onPointDown, this._onDropped, this._onDragStart, dragHandler.onDragStart((event) => {
63
+ this.addDisposables(this._onPointDown, this._onDropped, this._onDragStart, this.dragHandler.onDragStart((event) => {
64
64
  if (event.dataTransfer) {
65
65
  const style = getComputedStyle(this.element);
66
66
  const newNode = this.element.cloneNode(true);
@@ -72,7 +72,7 @@ export class Tab extends CompositeDisposable {
72
72
  });
73
73
  }
74
74
  this._onDragStart.fire(event);
75
- }), dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => {
75
+ }), this.dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => {
76
76
  this._onPointDown.fire(event);
77
77
  }), this.dropTarget.onDrop((event) => {
78
78
  this._onDropped.fire(event);
@@ -91,6 +91,7 @@ export class Tab extends CompositeDisposable {
91
91
  }
92
92
  updateDragAndDropState() {
93
93
  this._element.draggable = !this.accessor.options.disableDnd;
94
+ this.dragHandler.setDisabled(!!this.accessor.options.disableDnd);
94
95
  }
95
96
  dispose() {
96
97
  super.dispose();
@@ -8,6 +8,7 @@ export declare class VoidContainer extends CompositeDisposable {
8
8
  private readonly group;
9
9
  private readonly _element;
10
10
  private readonly dropTarget;
11
+ private readonly handler;
11
12
  private readonly _onDrop;
12
13
  readonly onDrop: Event<DroptargetEvent>;
13
14
  private readonly _onDragStart;
@@ -23,7 +23,7 @@ export class VoidContainer extends CompositeDisposable {
23
23
  this.addDisposables(this._onDrop, this._onDragStart, addDisposableListener(this._element, 'pointerdown', () => {
24
24
  this.accessor.doSetGroupActive(this.group);
25
25
  }));
26
- const handler = new GroupDragHandler(this._element, accessor, group);
26
+ this.handler = new GroupDragHandler(this._element, accessor, group, !!this.accessor.options.disableDnd);
27
27
  this.dropTarget = new Droptarget(this._element, {
28
28
  acceptedTargetZones: ['center'],
29
29
  canDisplayOverlay: (event, position) => {
@@ -36,7 +36,7 @@ export class VoidContainer extends CompositeDisposable {
36
36
  getOverrideTarget: () => { var _a; return (_a = group.model.dropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
37
37
  });
38
38
  this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
39
- this.addDisposables(handler, handler.onDragStart((event) => {
39
+ this.addDisposables(this.handler, this.handler.onDragStart((event) => {
40
40
  this._onDragStart.fire(event);
41
41
  }), this.dropTarget.onDrop((event) => {
42
42
  this._onDrop.fire(event);
@@ -45,5 +45,6 @@ export class VoidContainer extends CompositeDisposable {
45
45
  updateDragAndDropState() {
46
46
  this._element.draggable = !this.accessor.options.disableDnd;
47
47
  toggleClass(this._element, 'dv-draggable', !this.accessor.options.disableDnd);
48
+ this.handler.setDisabled(!!this.accessor.options.disableDnd);
48
49
  }
49
50
  }
@@ -219,6 +219,7 @@ export declare class DockviewComponent extends BaseGrid<DockviewGroupPanel> impl
219
219
  private readonly _floatingGroups;
220
220
  private readonly _popoutGroups;
221
221
  private readonly _rootDropTarget;
222
+ private _popoutRestorationPromise;
222
223
  private readonly _onDidRemoveGroup;
223
224
  readonly onDidRemoveGroup: Event<DockviewGroupPanel>;
224
225
  protected readonly _onDidAddGroup: Emitter<DockviewGroupPanel>;
@@ -235,6 +236,11 @@ export declare class DockviewComponent extends BaseGrid<DockviewGroupPanel> impl
235
236
  get renderer(): DockviewPanelRenderer;
236
237
  get api(): DockviewApi;
237
238
  get floatingGroups(): DockviewFloatingGroupPanel[];
239
+ /**
240
+ * Promise that resolves when all popout groups from the last fromJSON call are restored.
241
+ * Useful for tests that need to wait for delayed popout creation.
242
+ */
243
+ get popoutRestorationPromise(): Promise<void>;
238
244
  constructor(container: HTMLElement, options: DockviewComponentOptions);
239
245
  setVisible(panel: DockviewGroupPanel, visible: boolean): void;
240
246
  addPopoutGroup(itemToPopout: DockviewPanel | DockviewGroupPanel, options?: DockviewPopoutGroupOptions): Promise<boolean>;
@@ -19,7 +19,7 @@ import { getPanelData } from '../dnd/dataTransfer';
19
19
  import { Overlay } from '../overlay/overlay';
20
20
  import { addTestId, Classnames, getDockviewTheme, onDidWindowResizeEnd, onDidWindowMoveEnd, toggleClass, watchElementResize, } from '../dom';
21
21
  import { DockviewFloatingGroupPanel } from './dockviewFloatingGroupPanel';
22
- import { DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE, DEFAULT_FLOATING_GROUP_POSITION, } from '../constants';
22
+ import { DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE, DEFAULT_FLOATING_GROUP_POSITION, DESERIALIZATION_POPOUT_DELAY_MS, } from '../constants';
23
23
  import { OverlayRenderContainer, } from '../overlay/overlayRenderContainer';
24
24
  import { PopoutWindow } from '../popoutWindow';
25
25
  import { StrictEventsSequencing } from './strictEventsSequencing';
@@ -74,6 +74,13 @@ export class DockviewComponent extends BaseGrid {
74
74
  get floatingGroups() {
75
75
  return this._floatingGroups;
76
76
  }
77
+ /**
78
+ * Promise that resolves when all popout groups from the last fromJSON call are restored.
79
+ * Useful for tests that need to wait for delayed popout creation.
80
+ */
81
+ get popoutRestorationPromise() {
82
+ return this._popoutRestorationPromise;
83
+ }
77
84
  constructor(container, options) {
78
85
  var _a, _b, _c;
79
86
  super(container, {
@@ -122,6 +129,7 @@ export class DockviewComponent extends BaseGrid {
122
129
  this.onDidMaximizedGroupChange = this._onDidMaximizedGroupChange.event;
123
130
  this._floatingGroups = [];
124
131
  this._popoutGroups = [];
132
+ this._popoutRestorationPromise = Promise.resolve();
125
133
  this._onDidRemoveGroup = new Emitter();
126
134
  this.onDidRemoveGroup = this._onDidRemoveGroup.event;
127
135
  this._onDidAddGroup = new Emitter();
@@ -671,6 +679,7 @@ export class DockviewComponent extends BaseGrid {
671
679
  this.updateWatermark();
672
680
  }
673
681
  orthogonalize(position, options) {
682
+ this.gridview.normalize();
674
683
  switch (position) {
675
684
  case 'top':
676
685
  case 'bottom':
@@ -916,18 +925,30 @@ export class DockviewComponent extends BaseGrid {
916
925
  });
917
926
  }
918
927
  const serializedPopoutGroups = (_b = data.popoutGroups) !== null && _b !== void 0 ? _b : [];
919
- for (const serializedPopoutGroup of serializedPopoutGroups) {
928
+ // Create a promise that resolves when all popout groups are created
929
+ const popoutPromises = [];
930
+ // Queue popup group creation with delays to avoid browser blocking
931
+ serializedPopoutGroups.forEach((serializedPopoutGroup, index) => {
920
932
  const { data, position, gridReferenceGroup, url } = serializedPopoutGroup;
921
933
  const group = createGroupFromSerializedState(data);
922
- this.addPopoutGroup(group, {
923
- position: position !== null && position !== void 0 ? position : undefined,
924
- overridePopoutGroup: gridReferenceGroup ? group : undefined,
925
- referenceGroup: gridReferenceGroup
926
- ? this.getPanel(gridReferenceGroup)
927
- : undefined,
928
- popoutUrl: url,
934
+ // Add a small delay for each popup after the first to avoid browser popup blocking
935
+ const popoutPromise = new Promise((resolve) => {
936
+ setTimeout(() => {
937
+ this.addPopoutGroup(group, {
938
+ position: position !== null && position !== void 0 ? position : undefined,
939
+ overridePopoutGroup: gridReferenceGroup ? group : undefined,
940
+ referenceGroup: gridReferenceGroup
941
+ ? this.getPanel(gridReferenceGroup)
942
+ : undefined,
943
+ popoutUrl: url,
944
+ });
945
+ resolve();
946
+ }, index * DESERIALIZATION_POPOUT_DELAY_MS); // 100ms delay between each popup
929
947
  });
930
- }
948
+ popoutPromises.push(popoutPromise);
949
+ });
950
+ // Store the promise for tests to wait on
951
+ this._popoutRestorationPromise = Promise.all(popoutPromises).then(() => void 0);
931
952
  for (const floatingGroup of this._floatingGroups) {
932
953
  floatingGroup.overlay.setBounds();
933
954
  }
@@ -974,6 +995,10 @@ export class DockviewComponent extends BaseGrid {
974
995
  throw err;
975
996
  }
976
997
  this.updateWatermark();
998
+ // Force position updates for always visible panels after DOM layout is complete
999
+ requestAnimationFrame(() => {
1000
+ this.overlayRenderContainer.updateAllPositions();
1001
+ });
977
1002
  this._onDidLayoutFromJSON.fire();
978
1003
  }
979
1004
  clear() {
@@ -1361,11 +1386,13 @@ export class DockviewComponent extends BaseGrid {
1361
1386
  // remove the group and do not set a new group as active
1362
1387
  this.doRemoveGroup(sourceGroup, { skipActive: true });
1363
1388
  }
1389
+ // Check if destination group is empty - if so, force render the component
1390
+ const isDestinationGroupEmpty = destinationGroup.model.size === 0;
1364
1391
  this.movingLock(() => {
1365
1392
  var _a;
1366
1393
  return destinationGroup.model.openPanel(removedPanel, {
1367
1394
  index: destinationIndex,
1368
- skipSetActive: (_a = options.skipSetActive) !== null && _a !== void 0 ? _a : false,
1395
+ skipSetActive: ((_a = options.skipSetActive) !== null && _a !== void 0 ? _a : false) && !isDestinationGroupEmpty,
1369
1396
  skipSetGroupActive: true,
1370
1397
  });
1371
1398
  });
@@ -1474,7 +1501,6 @@ export class DockviewComponent extends BaseGrid {
1474
1501
  const target = options.to.position;
1475
1502
  if (target === 'center') {
1476
1503
  const activePanel = from.activePanel;
1477
- const targetActivePanel = to.activePanel;
1478
1504
  const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, {
1479
1505
  skipSetActive: true,
1480
1506
  })));
@@ -1484,22 +1510,21 @@ export class DockviewComponent extends BaseGrid {
1484
1510
  this.movingLock(() => {
1485
1511
  for (const panel of panels) {
1486
1512
  to.model.openPanel(panel, {
1487
- skipSetActive: true, // Always skip setting panels active during move
1513
+ skipSetActive: panel !== activePanel,
1488
1514
  skipSetGroupActive: true,
1489
1515
  });
1490
1516
  }
1491
1517
  });
1492
- if (!options.skipSetActive) {
1493
- // Make the moved panel (from the source group) active
1494
- if (activePanel) {
1495
- this.doSetGroupAndPanelActive(to);
1496
- }
1518
+ // Ensure group becomes active after move
1519
+ if (options.skipSetActive !== true) {
1520
+ // For center moves (merges), we need to ensure the target group is active
1521
+ // unless explicitly told not to (skipSetActive: true)
1522
+ this.doSetGroupAndPanelActive(to);
1497
1523
  }
1498
- else if (targetActivePanel) {
1499
- // Ensure the target group's original active panel remains active
1500
- to.model.openPanel(targetActivePanel, {
1501
- skipSetGroupActive: true
1502
- });
1524
+ else if (!this.activePanel) {
1525
+ // Even with skipSetActive: true, ensure there's an active panel if none exists
1526
+ // This maintains basic functionality while respecting skipSetActive
1527
+ this.doSetGroupAndPanelActive(to);
1503
1528
  }
1504
1529
  }
1505
1530
  else {
@@ -1529,20 +1554,26 @@ export class DockviewComponent extends BaseGrid {
1529
1554
  if (selectedPopoutGroup.referenceGroup) {
1530
1555
  const referenceGroup = this.getPanel(selectedPopoutGroup.referenceGroup);
1531
1556
  if (referenceGroup && !referenceGroup.api.isVisible) {
1532
- this.doRemoveGroup(referenceGroup, { skipActive: true });
1557
+ this.doRemoveGroup(referenceGroup, {
1558
+ skipActive: true,
1559
+ });
1533
1560
  }
1534
1561
  }
1535
1562
  // Manually dispose the window without triggering restoration
1536
1563
  selectedPopoutGroup.window.dispose();
1537
1564
  // Update group's location and containers for target
1538
1565
  if (to.api.location.type === 'grid') {
1539
- from.model.renderContainer = this.overlayRenderContainer;
1540
- from.model.dropTargetContainer = this.rootDropTargetContainer;
1566
+ from.model.renderContainer =
1567
+ this.overlayRenderContainer;
1568
+ from.model.dropTargetContainer =
1569
+ this.rootDropTargetContainer;
1541
1570
  from.model.location = { type: 'grid' };
1542
1571
  }
1543
1572
  else if (to.api.location.type === 'floating') {
1544
- from.model.renderContainer = this.overlayRenderContainer;
1545
- from.model.dropTargetContainer = this.rootDropTargetContainer;
1573
+ from.model.renderContainer =
1574
+ this.overlayRenderContainer;
1575
+ from.model.dropTargetContainer =
1576
+ this.rootDropTargetContainer;
1546
1577
  from.model.location = { type: 'floating' };
1547
1578
  }
1548
1579
  break;
@@ -1610,8 +1641,12 @@ export class DockviewComponent extends BaseGrid {
1610
1641
  from.panels.forEach((panel) => {
1611
1642
  this._onDidMovePanel.fire({ panel, from });
1612
1643
  });
1613
- if (!options.skipSetActive) {
1614
- this.doSetGroupAndPanelActive(from);
1644
+ // Ensure group becomes active after move
1645
+ if (options.skipSetActive === false) {
1646
+ // Only activate when explicitly requested (skipSetActive: false)
1647
+ // Use 'to' group for non-center moves since 'from' may have been destroyed
1648
+ const targetGroup = to !== null && to !== void 0 ? to : from;
1649
+ this.doSetGroupAndPanelActive(targetGroup);
1615
1650
  }
1616
1651
  }
1617
1652
  doSetGroupActive(group) {
@@ -134,6 +134,7 @@ export declare class Gridview implements IDisposable {
134
134
  private _deserializeNode;
135
135
  private get root();
136
136
  private set root(value);
137
+ normalize(): void;
137
138
  /**
138
139
  * If the root is orientated as a VERTICAL node then nest the existing root within a new HORIZIONTAL root node
139
140
  * If the root is orientated as a HORIZONTAL node then nest the existing root within a new VERITCAL root node
@@ -17,6 +17,19 @@ function findLeaf(candiateNode, last) {
17
17
  }
18
18
  throw new Error('invalid node');
19
19
  }
20
+ function cloneNode(node, size, orthogonalSize) {
21
+ if (node instanceof BranchNode) {
22
+ const result = new BranchNode(node.orientation, node.proportionalLayout, node.styles, size, orthogonalSize, node.disabled, node.margin);
23
+ for (let i = node.children.length - 1; i >= 0; i--) {
24
+ const child = node.children[i];
25
+ result.addChild(cloneNode(child, child.size, child.orthogonalSize), child.size, 0, true);
26
+ }
27
+ return result;
28
+ }
29
+ else {
30
+ return new LeafNode(node.view, node.orientation, orthogonalSize);
31
+ }
32
+ }
20
33
  function flipNode(node, size, orthogonalSize) {
21
34
  if (node instanceof BranchNode) {
22
35
  const result = new BranchNode(orthogonal(node.orientation), node.proportionalLayout, node.styles, size, orthogonalSize, node.disabled, node.margin);
@@ -367,6 +380,29 @@ export class Gridview {
367
380
  this._onDidChange.fire(e);
368
381
  });
369
382
  }
383
+ normalize() {
384
+ if (!this._root) {
385
+ return;
386
+ }
387
+ if (this._root.children.length !== 1) {
388
+ return;
389
+ }
390
+ const oldRoot = this.root;
391
+ // can remove one level of redundant branching if there is only a single child
392
+ const childReference = oldRoot.children[0];
393
+ if (childReference instanceof LeafNode) {
394
+ return;
395
+ }
396
+ oldRoot.element.remove();
397
+ const child = oldRoot.removeChild(0); // Remove child to prevent double disposal
398
+ oldRoot.dispose(); // Dispose old root (won't dispose removed child)
399
+ child.dispose(); // Dispose the removed child
400
+ this._root = cloneNode(childReference, childReference.size, childReference.orthogonalSize);
401
+ this.element.appendChild(this._root.element);
402
+ this.disposable.value = this._root.onDidChange((e) => {
403
+ this._onDidChange.fire(e);
404
+ });
405
+ }
370
406
  /**
371
407
  * If the root is orientated as a VERTICAL node then nest the existing root within a new HORIZIONTAL root node
372
408
  * If the root is orientated as a HORIZONTAL node then nest the existing root within a new VERITCAL root node
@@ -12,7 +12,10 @@ export declare class OverlayRenderContainer extends CompositeDisposable {
12
12
  readonly accessor: DockviewComponent;
13
13
  private readonly map;
14
14
  private _disposed;
15
+ private readonly positionCache;
16
+ private readonly pendingUpdates;
15
17
  constructor(element: HTMLElement, accessor: DockviewComponent);
18
+ updateAllPositions(): void;
16
19
  detatch(panel: IDockviewPanel): boolean;
17
20
  attach(options: {
18
21
  panel: IDockviewPanel;
@@ -1,6 +1,34 @@
1
1
  import { DragAndDropObserver } from '../dnd/dnd';
2
2
  import { getDomNodePagePosition, toggleClass } from '../dom';
3
3
  import { CompositeDisposable, Disposable, MutableDisposable, } from '../lifecycle';
4
+ class PositionCache {
5
+ constructor() {
6
+ this.cache = new Map();
7
+ this.currentFrameId = 0;
8
+ this.rafId = null;
9
+ }
10
+ getPosition(element) {
11
+ const cached = this.cache.get(element);
12
+ if (cached && cached.frameId === this.currentFrameId) {
13
+ return cached.rect;
14
+ }
15
+ this.scheduleFrameUpdate();
16
+ const rect = getDomNodePagePosition(element);
17
+ this.cache.set(element, { rect, frameId: this.currentFrameId });
18
+ return rect;
19
+ }
20
+ invalidate() {
21
+ this.currentFrameId++;
22
+ }
23
+ scheduleFrameUpdate() {
24
+ if (this.rafId)
25
+ return;
26
+ this.rafId = requestAnimationFrame(() => {
27
+ this.currentFrameId++;
28
+ this.rafId = null;
29
+ });
30
+ }
31
+ }
4
32
  function createFocusableElement() {
5
33
  const element = document.createElement('div');
6
34
  element.tabIndex = -1;
@@ -13,6 +41,8 @@ export class OverlayRenderContainer extends CompositeDisposable {
13
41
  this.accessor = accessor;
14
42
  this.map = {};
15
43
  this._disposed = false;
44
+ this.positionCache = new PositionCache();
45
+ this.pendingUpdates = new Set();
16
46
  this.addDisposables(Disposable.from(() => {
17
47
  for (const value of Object.values(this.map)) {
18
48
  value.disposable.dispose();
@@ -21,6 +51,19 @@ export class OverlayRenderContainer extends CompositeDisposable {
21
51
  this._disposed = true;
22
52
  }));
23
53
  }
54
+ updateAllPositions() {
55
+ if (this._disposed) {
56
+ return;
57
+ }
58
+ // Invalidate position cache to force recalculation
59
+ this.positionCache.invalidate();
60
+ // Call resize function directly for all visible panels
61
+ for (const entry of Object.values(this.map)) {
62
+ if (entry.panel.api.isVisible && entry.resize) {
63
+ entry.resize();
64
+ }
65
+ }
66
+ }
24
67
  detatch(panel) {
25
68
  if (this.map[panel.api.id]) {
26
69
  const { disposable, destroy } = this.map[panel.api.id];
@@ -51,17 +94,33 @@ export class OverlayRenderContainer extends CompositeDisposable {
51
94
  this.element.appendChild(focusContainer);
52
95
  }
53
96
  const resize = () => {
54
- // TODO propagate position to avoid getDomNodePagePosition calls, possible performance bottleneck?
55
- const box = getDomNodePagePosition(referenceContainer.element);
56
- const box2 = getDomNodePagePosition(this.element);
57
- focusContainer.style.left = `${box.left - box2.left}px`;
58
- focusContainer.style.top = `${box.top - box2.top}px`;
59
- focusContainer.style.width = `${box.width}px`;
60
- focusContainer.style.height = `${box.height}px`;
61
- toggleClass(focusContainer, 'dv-render-overlay-float', panel.group.api.location.type === 'floating');
97
+ const panelId = panel.api.id;
98
+ if (this.pendingUpdates.has(panelId)) {
99
+ return; // Update already scheduled
100
+ }
101
+ this.pendingUpdates.add(panelId);
102
+ requestAnimationFrame(() => {
103
+ this.pendingUpdates.delete(panelId);
104
+ if (this.isDisposed || !this.map[panelId]) {
105
+ return;
106
+ }
107
+ const box = this.positionCache.getPosition(referenceContainer.element);
108
+ const box2 = this.positionCache.getPosition(this.element);
109
+ // Use traditional positioning for overlay containers
110
+ const left = box.left - box2.left;
111
+ const top = box.top - box2.top;
112
+ const width = box.width;
113
+ const height = box.height;
114
+ focusContainer.style.left = `${left}px`;
115
+ focusContainer.style.top = `${top}px`;
116
+ focusContainer.style.width = `${width}px`;
117
+ focusContainer.style.height = `${height}px`;
118
+ toggleClass(focusContainer, 'dv-render-overlay-float', panel.group.api.location.type === 'floating');
119
+ });
62
120
  };
63
121
  const visibilityChanged = () => {
64
122
  if (panel.api.isVisible) {
123
+ this.positionCache.invalidate();
65
124
  resize();
66
125
  }
67
126
  focusContainer.style.display = panel.api.isVisible ? '' : 'none';
@@ -156,6 +215,8 @@ export class OverlayRenderContainer extends CompositeDisposable {
156
215
  this.map[panel.api.id].disposable.dispose();
157
216
  // and reset the disposable to the active reference-container
158
217
  this.map[panel.api.id].disposable = disposable;
218
+ // store the resize function for direct access
219
+ this.map[panel.api.id].resize = resize;
159
220
  return focusContainer;
160
221
  }
161
222
  }