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
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * dockview-core
3
- * @version 4.6.0
3
+ * @version 4.7.0
4
4
  * @link https://github.com/mathuo/dockview
5
5
  * @license MIT
6
6
  */
@@ -2135,6 +2135,19 @@ define(['exports'], (function (exports) { 'use strict';
2135
2135
  }
2136
2136
  throw new Error('invalid node');
2137
2137
  }
2138
+ function cloneNode(node, size, orthogonalSize) {
2139
+ if (node instanceof BranchNode) {
2140
+ const result = new BranchNode(node.orientation, node.proportionalLayout, node.styles, size, orthogonalSize, node.disabled, node.margin);
2141
+ for (let i = node.children.length - 1; i >= 0; i--) {
2142
+ const child = node.children[i];
2143
+ result.addChild(cloneNode(child, child.size, child.orthogonalSize), child.size, 0, true);
2144
+ }
2145
+ return result;
2146
+ }
2147
+ else {
2148
+ return new LeafNode(node.view, node.orientation, orthogonalSize);
2149
+ }
2150
+ }
2138
2151
  function flipNode(node, size, orthogonalSize) {
2139
2152
  if (node instanceof BranchNode) {
2140
2153
  const result = new BranchNode(orthogonal(node.orientation), node.proportionalLayout, node.styles, size, orthogonalSize, node.disabled, node.margin);
@@ -2485,6 +2498,29 @@ define(['exports'], (function (exports) { 'use strict';
2485
2498
  this._onDidChange.fire(e);
2486
2499
  });
2487
2500
  }
2501
+ normalize() {
2502
+ if (!this._root) {
2503
+ return;
2504
+ }
2505
+ if (this._root.children.length !== 1) {
2506
+ return;
2507
+ }
2508
+ const oldRoot = this.root;
2509
+ // can remove one level of redundant branching if there is only a single child
2510
+ const childReference = oldRoot.children[0];
2511
+ if (childReference instanceof LeafNode) {
2512
+ return;
2513
+ }
2514
+ oldRoot.element.remove();
2515
+ const child = oldRoot.removeChild(0); // Remove child to prevent double disposal
2516
+ oldRoot.dispose(); // Dispose old root (won't dispose removed child)
2517
+ child.dispose(); // Dispose the removed child
2518
+ this._root = cloneNode(childReference, childReference.size, childReference.orthogonalSize);
2519
+ this.element.appendChild(this._root.element);
2520
+ this.disposable.value = this._root.onDidChange((e) => {
2521
+ this._onDidChange.fire(e);
2522
+ });
2523
+ }
2488
2524
  /**
2489
2525
  * If the root is orientated as a VERTICAL node then nest the existing root within a new HORIZIONTAL root node
2490
2526
  * If the root is orientated as a HORIZONTAL node then nest the existing root within a new VERITCAL root node
@@ -3876,6 +3912,48 @@ define(['exports'], (function (exports) { 'use strict';
3876
3912
  }
3877
3913
  }
3878
3914
 
3915
+ function setGPUOptimizedBounds(element, bounds) {
3916
+ const { top, left, width, height } = bounds;
3917
+ const topPx = `${Math.round(top)}px`;
3918
+ const leftPx = `${Math.round(left)}px`;
3919
+ const widthPx = `${Math.round(width)}px`;
3920
+ const heightPx = `${Math.round(height)}px`;
3921
+ // Use traditional positioning but maintain GPU layer
3922
+ element.style.top = topPx;
3923
+ element.style.left = leftPx;
3924
+ element.style.width = widthPx;
3925
+ element.style.height = heightPx;
3926
+ element.style.visibility = 'visible';
3927
+ // Ensure GPU layer is maintained
3928
+ if (!element.style.transform || element.style.transform === '') {
3929
+ element.style.transform = 'translate3d(0, 0, 0)';
3930
+ }
3931
+ }
3932
+ function setGPUOptimizedBoundsFromStrings(element, bounds) {
3933
+ const { top, left, width, height } = bounds;
3934
+ // Use traditional positioning but maintain GPU layer
3935
+ element.style.top = top;
3936
+ element.style.left = left;
3937
+ element.style.width = width;
3938
+ element.style.height = height;
3939
+ element.style.visibility = 'visible';
3940
+ // Ensure GPU layer is maintained
3941
+ if (!element.style.transform || element.style.transform === '') {
3942
+ element.style.transform = 'translate3d(0, 0, 0)';
3943
+ }
3944
+ }
3945
+ function checkBoundsChanged(element, bounds) {
3946
+ const { top, left, width, height } = bounds;
3947
+ const topPx = `${Math.round(top)}px`;
3948
+ const leftPx = `${Math.round(left)}px`;
3949
+ const widthPx = `${Math.round(width)}px`;
3950
+ const heightPx = `${Math.round(height)}px`;
3951
+ // Check if position or size changed (back to traditional method)
3952
+ return element.style.top !== topPx ||
3953
+ element.style.left !== leftPx ||
3954
+ element.style.width !== widthPx ||
3955
+ element.style.height !== heightPx;
3956
+ }
3879
3957
  class WillShowOverlayEvent extends DockviewEvent {
3880
3958
  get nativeEvent() {
3881
3959
  return this.options.nativeEvent;
@@ -4157,21 +4235,11 @@ define(['exports'], (function (exports) { 'use strict';
4157
4235
  box.left = rootLeft + width - 4;
4158
4236
  box.width = 4;
4159
4237
  }
4160
- const topPx = `${Math.round(box.top)}px`;
4161
- const leftPx = `${Math.round(box.left)}px`;
4162
- const widthPx = `${Math.round(box.width)}px`;
4163
- const heightPx = `${Math.round(box.height)}px`;
4164
- if (overlay.style.top === topPx &&
4165
- overlay.style.left === leftPx &&
4166
- overlay.style.width === widthPx &&
4167
- overlay.style.height === heightPx) {
4238
+ // Use GPU-optimized bounds checking and setting
4239
+ if (!checkBoundsChanged(overlay, box)) {
4168
4240
  return;
4169
4241
  }
4170
- overlay.style.top = topPx;
4171
- overlay.style.left = leftPx;
4172
- overlay.style.width = widthPx;
4173
- overlay.style.height = heightPx;
4174
- overlay.style.visibility = 'visible';
4242
+ setGPUOptimizedBounds(overlay, box);
4175
4243
  overlay.className = `dv-drop-target-anchor${this.options.className ? ` ${this.options.className}` : ''}`;
4176
4244
  toggleClass(overlay, 'dv-drop-target-left', isLeft);
4177
4245
  toggleClass(overlay, 'dv-drop-target-right', isRight);
@@ -4223,10 +4291,7 @@ define(['exports'], (function (exports) { 'use strict';
4223
4291
  box.top = `${100 * (1 - size)}%`;
4224
4292
  box.height = `${100 * size}%`;
4225
4293
  }
4226
- this.overlayElement.style.top = box.top;
4227
- this.overlayElement.style.left = box.left;
4228
- this.overlayElement.style.width = box.width;
4229
- this.overlayElement.style.height = box.height;
4294
+ setGPUOptimizedBoundsFromStrings(this.overlayElement, box);
4230
4295
  toggleClass(this.overlayElement, 'dv-drop-target-small-vertical', isSmallY);
4231
4296
  toggleClass(this.overlayElement, 'dv-drop-target-small-horizontal', isSmallX);
4232
4297
  toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft);
@@ -7669,7 +7734,36 @@ define(['exports'], (function (exports) { 'use strict';
7669
7734
 
7670
7735
  const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
7671
7736
  const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100, width: 300, height: 300 };
7737
+ const DESERIALIZATION_POPOUT_DELAY_MS = 100;
7672
7738
 
7739
+ class PositionCache {
7740
+ constructor() {
7741
+ this.cache = new Map();
7742
+ this.currentFrameId = 0;
7743
+ this.rafId = null;
7744
+ }
7745
+ getPosition(element) {
7746
+ const cached = this.cache.get(element);
7747
+ if (cached && cached.frameId === this.currentFrameId) {
7748
+ return cached.rect;
7749
+ }
7750
+ this.scheduleFrameUpdate();
7751
+ const rect = getDomNodePagePosition(element);
7752
+ this.cache.set(element, { rect, frameId: this.currentFrameId });
7753
+ return rect;
7754
+ }
7755
+ invalidate() {
7756
+ this.currentFrameId++;
7757
+ }
7758
+ scheduleFrameUpdate() {
7759
+ if (this.rafId)
7760
+ return;
7761
+ this.rafId = requestAnimationFrame(() => {
7762
+ this.currentFrameId++;
7763
+ this.rafId = null;
7764
+ });
7765
+ }
7766
+ }
7673
7767
  function createFocusableElement() {
7674
7768
  const element = document.createElement('div');
7675
7769
  element.tabIndex = -1;
@@ -7682,6 +7776,8 @@ define(['exports'], (function (exports) { 'use strict';
7682
7776
  this.accessor = accessor;
7683
7777
  this.map = {};
7684
7778
  this._disposed = false;
7779
+ this.positionCache = new PositionCache();
7780
+ this.pendingUpdates = new Set();
7685
7781
  this.addDisposables(exports.DockviewDisposable.from(() => {
7686
7782
  for (const value of Object.values(this.map)) {
7687
7783
  value.disposable.dispose();
@@ -7690,6 +7786,19 @@ define(['exports'], (function (exports) { 'use strict';
7690
7786
  this._disposed = true;
7691
7787
  }));
7692
7788
  }
7789
+ updateAllPositions() {
7790
+ if (this._disposed) {
7791
+ return;
7792
+ }
7793
+ // Invalidate position cache to force recalculation
7794
+ this.positionCache.invalidate();
7795
+ // Call resize function directly for all visible panels
7796
+ for (const entry of Object.values(this.map)) {
7797
+ if (entry.panel.api.isVisible && entry.resize) {
7798
+ entry.resize();
7799
+ }
7800
+ }
7801
+ }
7693
7802
  detatch(panel) {
7694
7803
  if (this.map[panel.api.id]) {
7695
7804
  const { disposable, destroy } = this.map[panel.api.id];
@@ -7720,17 +7829,33 @@ define(['exports'], (function (exports) { 'use strict';
7720
7829
  this.element.appendChild(focusContainer);
7721
7830
  }
7722
7831
  const resize = () => {
7723
- // TODO propagate position to avoid getDomNodePagePosition calls, possible performance bottleneck?
7724
- const box = getDomNodePagePosition(referenceContainer.element);
7725
- const box2 = getDomNodePagePosition(this.element);
7726
- focusContainer.style.left = `${box.left - box2.left}px`;
7727
- focusContainer.style.top = `${box.top - box2.top}px`;
7728
- focusContainer.style.width = `${box.width}px`;
7729
- focusContainer.style.height = `${box.height}px`;
7730
- toggleClass(focusContainer, 'dv-render-overlay-float', panel.group.api.location.type === 'floating');
7832
+ const panelId = panel.api.id;
7833
+ if (this.pendingUpdates.has(panelId)) {
7834
+ return; // Update already scheduled
7835
+ }
7836
+ this.pendingUpdates.add(panelId);
7837
+ requestAnimationFrame(() => {
7838
+ this.pendingUpdates.delete(panelId);
7839
+ if (this.isDisposed || !this.map[panelId]) {
7840
+ return;
7841
+ }
7842
+ const box = this.positionCache.getPosition(referenceContainer.element);
7843
+ const box2 = this.positionCache.getPosition(this.element);
7844
+ // Use traditional positioning for overlay containers
7845
+ const left = box.left - box2.left;
7846
+ const top = box.top - box2.top;
7847
+ const width = box.width;
7848
+ const height = box.height;
7849
+ focusContainer.style.left = `${left}px`;
7850
+ focusContainer.style.top = `${top}px`;
7851
+ focusContainer.style.width = `${width}px`;
7852
+ focusContainer.style.height = `${height}px`;
7853
+ toggleClass(focusContainer, 'dv-render-overlay-float', panel.group.api.location.type === 'floating');
7854
+ });
7731
7855
  };
7732
7856
  const visibilityChanged = () => {
7733
7857
  if (panel.api.isVisible) {
7858
+ this.positionCache.invalidate();
7734
7859
  resize();
7735
7860
  }
7736
7861
  focusContainer.style.display = panel.api.isVisible ? '' : 'none';
@@ -7825,6 +7950,8 @@ define(['exports'], (function (exports) { 'use strict';
7825
7950
  this.map[panel.api.id].disposable.dispose();
7826
7951
  // and reset the disposable to the active reference-container
7827
7952
  this.map[panel.api.id].disposable = disposable;
7953
+ // store the resize function for direct access
7954
+ this.map[panel.api.id].resize = resize;
7828
7955
  return focusContainer;
7829
7956
  }
7830
7957
  }
@@ -8217,6 +8344,13 @@ define(['exports'], (function (exports) { 'use strict';
8217
8344
  get floatingGroups() {
8218
8345
  return this._floatingGroups;
8219
8346
  }
8347
+ /**
8348
+ * Promise that resolves when all popout groups from the last fromJSON call are restored.
8349
+ * Useful for tests that need to wait for delayed popout creation.
8350
+ */
8351
+ get popoutRestorationPromise() {
8352
+ return this._popoutRestorationPromise;
8353
+ }
8220
8354
  constructor(container, options) {
8221
8355
  var _a, _b, _c;
8222
8356
  super(container, {
@@ -8265,6 +8399,7 @@ define(['exports'], (function (exports) { 'use strict';
8265
8399
  this.onDidMaximizedGroupChange = this._onDidMaximizedGroupChange.event;
8266
8400
  this._floatingGroups = [];
8267
8401
  this._popoutGroups = [];
8402
+ this._popoutRestorationPromise = Promise.resolve();
8268
8403
  this._onDidRemoveGroup = new Emitter();
8269
8404
  this.onDidRemoveGroup = this._onDidRemoveGroup.event;
8270
8405
  this._onDidAddGroup = new Emitter();
@@ -8814,6 +8949,7 @@ define(['exports'], (function (exports) { 'use strict';
8814
8949
  this.updateWatermark();
8815
8950
  }
8816
8951
  orthogonalize(position, options) {
8952
+ this.gridview.normalize();
8817
8953
  switch (position) {
8818
8954
  case 'top':
8819
8955
  case 'bottom':
@@ -9057,18 +9193,30 @@ define(['exports'], (function (exports) { 'use strict';
9057
9193
  });
9058
9194
  }
9059
9195
  const serializedPopoutGroups = (_b = data.popoutGroups) !== null && _b !== void 0 ? _b : [];
9060
- for (const serializedPopoutGroup of serializedPopoutGroups) {
9196
+ // Create a promise that resolves when all popout groups are created
9197
+ const popoutPromises = [];
9198
+ // Queue popup group creation with delays to avoid browser blocking
9199
+ serializedPopoutGroups.forEach((serializedPopoutGroup, index) => {
9061
9200
  const { data, position, gridReferenceGroup, url } = serializedPopoutGroup;
9062
9201
  const group = createGroupFromSerializedState(data);
9063
- this.addPopoutGroup(group, {
9064
- position: position !== null && position !== void 0 ? position : undefined,
9065
- overridePopoutGroup: gridReferenceGroup ? group : undefined,
9066
- referenceGroup: gridReferenceGroup
9067
- ? this.getPanel(gridReferenceGroup)
9068
- : undefined,
9069
- popoutUrl: url,
9202
+ // Add a small delay for each popup after the first to avoid browser popup blocking
9203
+ const popoutPromise = new Promise((resolve) => {
9204
+ setTimeout(() => {
9205
+ this.addPopoutGroup(group, {
9206
+ position: position !== null && position !== void 0 ? position : undefined,
9207
+ overridePopoutGroup: gridReferenceGroup ? group : undefined,
9208
+ referenceGroup: gridReferenceGroup
9209
+ ? this.getPanel(gridReferenceGroup)
9210
+ : undefined,
9211
+ popoutUrl: url,
9212
+ });
9213
+ resolve();
9214
+ }, index * DESERIALIZATION_POPOUT_DELAY_MS); // 100ms delay between each popup
9070
9215
  });
9071
- }
9216
+ popoutPromises.push(popoutPromise);
9217
+ });
9218
+ // Store the promise for tests to wait on
9219
+ this._popoutRestorationPromise = Promise.all(popoutPromises).then(() => void 0);
9072
9220
  for (const floatingGroup of this._floatingGroups) {
9073
9221
  floatingGroup.overlay.setBounds();
9074
9222
  }
@@ -9115,6 +9263,10 @@ define(['exports'], (function (exports) { 'use strict';
9115
9263
  throw err;
9116
9264
  }
9117
9265
  this.updateWatermark();
9266
+ // Force position updates for always visible panels after DOM layout is complete
9267
+ requestAnimationFrame(() => {
9268
+ this.overlayRenderContainer.updateAllPositions();
9269
+ });
9118
9270
  this._onDidLayoutFromJSON.fire();
9119
9271
  }
9120
9272
  clear() {
@@ -9615,7 +9767,6 @@ define(['exports'], (function (exports) { 'use strict';
9615
9767
  const target = options.to.position;
9616
9768
  if (target === 'center') {
9617
9769
  const activePanel = from.activePanel;
9618
- const targetActivePanel = to.activePanel;
9619
9770
  const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, {
9620
9771
  skipSetActive: true,
9621
9772
  })));
@@ -9625,22 +9776,21 @@ define(['exports'], (function (exports) { 'use strict';
9625
9776
  this.movingLock(() => {
9626
9777
  for (const panel of panels) {
9627
9778
  to.model.openPanel(panel, {
9628
- skipSetActive: true, // Always skip setting panels active during move
9779
+ skipSetActive: panel !== activePanel,
9629
9780
  skipSetGroupActive: true,
9630
9781
  });
9631
9782
  }
9632
9783
  });
9633
- if (!options.skipSetActive) {
9634
- // Make the moved panel (from the source group) active
9635
- if (activePanel) {
9636
- this.doSetGroupAndPanelActive(to);
9637
- }
9784
+ // Ensure group becomes active after move
9785
+ if (options.skipSetActive !== true) {
9786
+ // For center moves (merges), we need to ensure the target group is active
9787
+ // unless explicitly told not to (skipSetActive: true)
9788
+ this.doSetGroupAndPanelActive(to);
9638
9789
  }
9639
- else if (targetActivePanel) {
9640
- // Ensure the target group's original active panel remains active
9641
- to.model.openPanel(targetActivePanel, {
9642
- skipSetGroupActive: true
9643
- });
9790
+ else if (!this.activePanel) {
9791
+ // Even with skipSetActive: true, ensure there's an active panel if none exists
9792
+ // This maintains basic functionality while respecting skipSetActive
9793
+ this.doSetGroupAndPanelActive(to);
9644
9794
  }
9645
9795
  }
9646
9796
  else {
@@ -9670,20 +9820,26 @@ define(['exports'], (function (exports) { 'use strict';
9670
9820
  if (selectedPopoutGroup.referenceGroup) {
9671
9821
  const referenceGroup = this.getPanel(selectedPopoutGroup.referenceGroup);
9672
9822
  if (referenceGroup && !referenceGroup.api.isVisible) {
9673
- this.doRemoveGroup(referenceGroup, { skipActive: true });
9823
+ this.doRemoveGroup(referenceGroup, {
9824
+ skipActive: true,
9825
+ });
9674
9826
  }
9675
9827
  }
9676
9828
  // Manually dispose the window without triggering restoration
9677
9829
  selectedPopoutGroup.window.dispose();
9678
9830
  // Update group's location and containers for target
9679
9831
  if (to.api.location.type === 'grid') {
9680
- from.model.renderContainer = this.overlayRenderContainer;
9681
- from.model.dropTargetContainer = this.rootDropTargetContainer;
9832
+ from.model.renderContainer =
9833
+ this.overlayRenderContainer;
9834
+ from.model.dropTargetContainer =
9835
+ this.rootDropTargetContainer;
9682
9836
  from.model.location = { type: 'grid' };
9683
9837
  }
9684
9838
  else if (to.api.location.type === 'floating') {
9685
- from.model.renderContainer = this.overlayRenderContainer;
9686
- from.model.dropTargetContainer = this.rootDropTargetContainer;
9839
+ from.model.renderContainer =
9840
+ this.overlayRenderContainer;
9841
+ from.model.dropTargetContainer =
9842
+ this.rootDropTargetContainer;
9687
9843
  from.model.location = { type: 'floating' };
9688
9844
  }
9689
9845
  break;
@@ -9751,8 +9907,12 @@ define(['exports'], (function (exports) { 'use strict';
9751
9907
  from.panels.forEach((panel) => {
9752
9908
  this._onDidMovePanel.fire({ panel, from });
9753
9909
  });
9754
- if (!options.skipSetActive) {
9755
- this.doSetGroupAndPanelActive(from);
9910
+ // Ensure group becomes active after move
9911
+ if (options.skipSetActive === false) {
9912
+ // Only activate when explicitly requested (skipSetActive: false)
9913
+ // Use 'to' group for non-center moves since 'from' may have been destroyed
9914
+ const targetGroup = to !== null && to !== void 0 ? to : from;
9915
+ this.doSetGroupAndPanelActive(targetGroup);
9756
9916
  }
9757
9917
  }
9758
9918
  doSetGroupActive(group) {