dockview-core 4.6.0 → 4.7.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.
Files changed (42) hide show
  1. package/dist/cjs/constants.d.ts +1 -0
  2. package/dist/cjs/constants.js +2 -1
  3. package/dist/cjs/dnd/droptarget.js +46 -17
  4. package/dist/cjs/dockview/dockviewComponent.d.ts +6 -0
  5. package/dist/cjs/dockview/dockviewComponent.js +112 -85
  6. package/dist/cjs/gridview/gridview.d.ts +1 -0
  7. package/dist/cjs/gridview/gridview.js +37 -0
  8. package/dist/cjs/overlay/overlayRenderContainer.d.ts +3 -0
  9. package/dist/cjs/overlay/overlayRenderContainer.js +82 -8
  10. package/dist/dockview-core.amd.js +215 -55
  11. package/dist/dockview-core.amd.js.map +1 -1
  12. package/dist/dockview-core.amd.min.js +2 -2
  13. package/dist/dockview-core.amd.min.js.map +1 -1
  14. package/dist/dockview-core.amd.min.noStyle.js +2 -2
  15. package/dist/dockview-core.amd.min.noStyle.js.map +1 -1
  16. package/dist/dockview-core.amd.noStyle.js +214 -54
  17. package/dist/dockview-core.amd.noStyle.js.map +1 -1
  18. package/dist/dockview-core.cjs.js +215 -55
  19. package/dist/dockview-core.cjs.js.map +1 -1
  20. package/dist/dockview-core.esm.js +215 -55
  21. package/dist/dockview-core.esm.js.map +1 -1
  22. package/dist/dockview-core.esm.min.js +2 -2
  23. package/dist/dockview-core.esm.min.js.map +1 -1
  24. package/dist/dockview-core.js +215 -55
  25. package/dist/dockview-core.js.map +1 -1
  26. package/dist/dockview-core.min.js +2 -2
  27. package/dist/dockview-core.min.js.map +1 -1
  28. package/dist/dockview-core.min.noStyle.js +2 -2
  29. package/dist/dockview-core.min.noStyle.js.map +1 -1
  30. package/dist/dockview-core.noStyle.js +214 -54
  31. package/dist/dockview-core.noStyle.js.map +1 -1
  32. package/dist/esm/constants.d.ts +1 -0
  33. package/dist/esm/constants.js +1 -0
  34. package/dist/esm/dnd/droptarget.js +46 -17
  35. package/dist/esm/dockview/dockviewComponent.d.ts +6 -0
  36. package/dist/esm/dockview/dockviewComponent.js +62 -29
  37. package/dist/esm/gridview/gridview.d.ts +1 -0
  38. package/dist/esm/gridview/gridview.js +36 -0
  39. package/dist/esm/overlay/overlayRenderContainer.d.ts +3 -0
  40. package/dist/esm/overlay/overlayRenderContainer.js +69 -8
  41. package/dist/styles/dockview.css +37 -5
  42. 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;
@@ -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);
@@ -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() {
@@ -1474,7 +1499,6 @@ export class DockviewComponent extends BaseGrid {
1474
1499
  const target = options.to.position;
1475
1500
  if (target === 'center') {
1476
1501
  const activePanel = from.activePanel;
1477
- const targetActivePanel = to.activePanel;
1478
1502
  const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, {
1479
1503
  skipSetActive: true,
1480
1504
  })));
@@ -1484,22 +1508,21 @@ export class DockviewComponent extends BaseGrid {
1484
1508
  this.movingLock(() => {
1485
1509
  for (const panel of panels) {
1486
1510
  to.model.openPanel(panel, {
1487
- skipSetActive: true, // Always skip setting panels active during move
1511
+ skipSetActive: panel !== activePanel,
1488
1512
  skipSetGroupActive: true,
1489
1513
  });
1490
1514
  }
1491
1515
  });
1492
- if (!options.skipSetActive) {
1493
- // Make the moved panel (from the source group) active
1494
- if (activePanel) {
1495
- this.doSetGroupAndPanelActive(to);
1496
- }
1516
+ // Ensure group becomes active after move
1517
+ if (options.skipSetActive !== true) {
1518
+ // For center moves (merges), we need to ensure the target group is active
1519
+ // unless explicitly told not to (skipSetActive: true)
1520
+ this.doSetGroupAndPanelActive(to);
1497
1521
  }
1498
- else if (targetActivePanel) {
1499
- // Ensure the target group's original active panel remains active
1500
- to.model.openPanel(targetActivePanel, {
1501
- skipSetGroupActive: true
1502
- });
1522
+ else if (!this.activePanel) {
1523
+ // Even with skipSetActive: true, ensure there's an active panel if none exists
1524
+ // This maintains basic functionality while respecting skipSetActive
1525
+ this.doSetGroupAndPanelActive(to);
1503
1526
  }
1504
1527
  }
1505
1528
  else {
@@ -1529,20 +1552,26 @@ export class DockviewComponent extends BaseGrid {
1529
1552
  if (selectedPopoutGroup.referenceGroup) {
1530
1553
  const referenceGroup = this.getPanel(selectedPopoutGroup.referenceGroup);
1531
1554
  if (referenceGroup && !referenceGroup.api.isVisible) {
1532
- this.doRemoveGroup(referenceGroup, { skipActive: true });
1555
+ this.doRemoveGroup(referenceGroup, {
1556
+ skipActive: true,
1557
+ });
1533
1558
  }
1534
1559
  }
1535
1560
  // Manually dispose the window without triggering restoration
1536
1561
  selectedPopoutGroup.window.dispose();
1537
1562
  // Update group's location and containers for target
1538
1563
  if (to.api.location.type === 'grid') {
1539
- from.model.renderContainer = this.overlayRenderContainer;
1540
- from.model.dropTargetContainer = this.rootDropTargetContainer;
1564
+ from.model.renderContainer =
1565
+ this.overlayRenderContainer;
1566
+ from.model.dropTargetContainer =
1567
+ this.rootDropTargetContainer;
1541
1568
  from.model.location = { type: 'grid' };
1542
1569
  }
1543
1570
  else if (to.api.location.type === 'floating') {
1544
- from.model.renderContainer = this.overlayRenderContainer;
1545
- from.model.dropTargetContainer = this.rootDropTargetContainer;
1571
+ from.model.renderContainer =
1572
+ this.overlayRenderContainer;
1573
+ from.model.dropTargetContainer =
1574
+ this.rootDropTargetContainer;
1546
1575
  from.model.location = { type: 'floating' };
1547
1576
  }
1548
1577
  break;
@@ -1610,8 +1639,12 @@ export class DockviewComponent extends BaseGrid {
1610
1639
  from.panels.forEach((panel) => {
1611
1640
  this._onDidMovePanel.fire({ panel, from });
1612
1641
  });
1613
- if (!options.skipSetActive) {
1614
- this.doSetGroupAndPanelActive(from);
1642
+ // Ensure group becomes active after move
1643
+ if (options.skipSetActive === false) {
1644
+ // Only activate when explicitly requested (skipSetActive: false)
1645
+ // Use 'to' group for non-center moves since 'from' may have been destroyed
1646
+ const targetGroup = to !== null && to !== void 0 ? to : from;
1647
+ this.doSetGroupAndPanelActive(targetGroup);
1615
1648
  }
1616
1649
  }
1617
1650
  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
  }
@@ -9,6 +9,10 @@
9
9
  height: 4px;
10
10
  border-radius: 2px;
11
11
  background-color: transparent;
12
+ /* GPU optimizations */
13
+ will-change: background-color, transform;
14
+ transform: translate3d(0, 0, 0);
15
+ backface-visibility: hidden;
12
16
  transition-property: background-color;
13
17
  transition-timing-function: ease-in-out;
14
18
  transition-duration: 1s;
@@ -593,9 +597,14 @@
593
597
  .dv-drop-target-container .dv-drop-target-anchor {
594
598
  position: relative;
595
599
  border: var(--dv-drag-over-border);
596
- transition: opacity var(--dv-transition-duration) ease-in, top var(--dv-transition-duration) ease-out, left var(--dv-transition-duration) ease-out, width var(--dv-transition-duration) ease-out, height var(--dv-transition-duration) ease-out;
597
600
  background-color: var(--dv-drag-over-background-color);
598
601
  opacity: 1;
602
+ /* GPU optimizations */
603
+ will-change: transform, opacity;
604
+ transform: translate3d(0, 0, 0);
605
+ backface-visibility: hidden;
606
+ contain: layout paint;
607
+ transition: opacity var(--dv-transition-duration) ease-in, transform var(--dv-transition-duration) ease-out;
599
608
  }
600
609
  .dv-drop-target {
601
610
  position: relative;
@@ -636,6 +645,7 @@
636
645
  .dv-dockview {
637
646
  position: relative;
638
647
  background-color: var(--dv-group-view-background-color);
648
+ contain: layout;
639
649
  }
640
650
  .dv-dockview .dv-watermark-container {
641
651
  position: absolute;
@@ -723,12 +733,18 @@
723
733
  z-index: calc(var(--dv-overlay-z-index) - 2);
724
734
  border: 1px solid var(--dv-tab-divider-color);
725
735
  box-shadow: var(--dv-floating-box-shadow);
736
+ /* GPU optimizations for floating group movement */
737
+ will-change: transform, opacity;
738
+ transform: translate3d(0, 0, 0);
739
+ backface-visibility: hidden;
726
740
  }
727
741
  .dv-resize-container.dv-hidden {
728
742
  display: none;
729
743
  }
730
744
  .dv-resize-container.dv-resize-container-dragging {
731
745
  opacity: 0.5;
746
+ /* Enhanced GPU acceleration during drag */
747
+ will-change: transform, opacity;
732
748
  }
733
749
  .dv-resize-container .dv-resize-handle-top {
734
750
  height: 4px;
@@ -806,7 +822,14 @@
806
822
  --dv-overlay-z-index: var(--dv-overlay-z-index, 999);
807
823
  position: absolute;
808
824
  z-index: 1;
825
+ width: 100%;
809
826
  height: 100%;
827
+ contain: layout paint;
828
+ isolation: isolate;
829
+ /* GPU optimizations */
830
+ will-change: transform;
831
+ transform: translate3d(0, 0, 0);
832
+ backface-visibility: hidden;
810
833
  }
811
834
  .dv-render-overlay.dv-render-overlay-float {
812
835
  z-index: calc(var(--dv-overlay-z-index) - 1);
@@ -821,8 +844,11 @@
821
844
  width: 100%;
822
845
  }
823
846
  .dv-pane-container.dv-animated .dv-view {
824
- transition-duration: 0.15s;
825
- transition-timing-function: ease-out;
847
+ /* GPU optimizations for smooth pane animations */
848
+ will-change: transform;
849
+ transform: translate3d(0, 0, 0);
850
+ backface-visibility: hidden;
851
+ transition: transform 0.15s ease-out;
826
852
  }
827
853
  .dv-pane-container .dv-view {
828
854
  overflow: hidden;
@@ -931,8 +957,11 @@
931
957
  }
932
958
  .dv-split-view-container.dv-animation .dv-view,
933
959
  .dv-split-view-container.dv-animation .dv-sash {
934
- transition-duration: 0.15s;
935
- transition-timing-function: ease-out;
960
+ /* GPU optimizations for smooth animations */
961
+ will-change: transform;
962
+ transform: translate3d(0, 0, 0);
963
+ backface-visibility: hidden;
964
+ transition: transform 0.15s ease-out;
936
965
  }
937
966
  .dv-split-view-container.dv-horizontal {
938
967
  height: 100%;
@@ -1104,6 +1133,9 @@
1104
1133
  height: 100%;
1105
1134
  overflow: auto;
1106
1135
  scrollbar-width: thin;
1136
+ /* GPU optimizations for smooth scrolling */
1137
+ will-change: scroll-position;
1138
+ transform: translate3d(0, 0, 0);
1107
1139
  /* Track */
1108
1140
  /* Handle */
1109
1141
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dockview-core",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "Zero dependency layout manager supporting tabs, grids and splitviews",
5
5
  "keywords": [
6
6
  "splitview",