dockview-core 6.0.1 → 6.0.5

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,6 +1,6 @@
1
1
  /**
2
2
  * dockview-core
3
- * @version 6.0.1
3
+ * @version 6.0.5
4
4
  * @link https://github.com/mathuo/dockview
5
5
  * @license MIT
6
6
  */
@@ -2830,6 +2830,8 @@
2830
2830
  }
2831
2831
  constructor(parentElement, disableResizing = false) {
2832
2832
  super();
2833
+ this._lastWidth = -1;
2834
+ this._lastHeight = -1;
2833
2835
  this._disableResizing = disableResizing;
2834
2836
  this._element = parentElement;
2835
2837
  this.addDisposables(watchElementResize(this._element, (entry) => {
@@ -2866,7 +2868,16 @@
2866
2868
  */
2867
2869
  return;
2868
2870
  }
2869
- const { width, height } = entry.contentRect;
2871
+ // Round to integers to absorb sub-pixel jitter from
2872
+ // fractional devicePixelRatio (e.g. multi-monitor setups),
2873
+ // which would otherwise re-fire layout in a feedback loop.
2874
+ const width = Math.round(entry.contentRect.width);
2875
+ const height = Math.round(entry.contentRect.height);
2876
+ if (width === this._lastWidth && height === this._lastHeight) {
2877
+ return;
2878
+ }
2879
+ this._lastWidth = width;
2880
+ this._lastHeight = height;
2870
2881
  this.layout(width, height);
2871
2882
  }));
2872
2883
  }
@@ -5448,6 +5459,9 @@
5448
5459
  if (this.group.api.location.type === 'floating' && !_event.shiftKey) {
5449
5460
  return true;
5450
5461
  }
5462
+ if (this.group.api.location.type === 'edge' && this.group.size === 0) {
5463
+ return true;
5464
+ }
5451
5465
  return false;
5452
5466
  }
5453
5467
  getData(dragEvent) {
@@ -11295,8 +11309,14 @@
11295
11309
  };
11296
11310
  }
11297
11311
  const focusContainer = this.map[panel.api.id].element;
11298
- if (panel.view.content.element.parentElement !== focusContainer) {
11299
- focusContainer.appendChild(panel.view.content.element);
11312
+ // Capture the content element now so the destroy disposable below
11313
+ // does not re-query the renderer's `element` getter during teardown.
11314
+ // Some framework adapters (e.g. dockview-angular) tear down their
11315
+ // backing renderer before this disposable fires; reading through the
11316
+ // getter at that point can throw.
11317
+ const contentElement = panel.view.content.element;
11318
+ if (contentElement.parentElement !== focusContainer) {
11319
+ focusContainer.appendChild(contentElement);
11300
11320
  }
11301
11321
  if (focusContainer.parentElement !== this.element) {
11302
11322
  this.element.appendChild(focusContainer);
@@ -11407,8 +11427,8 @@
11407
11427
  }));
11408
11428
  this.map[panel.api.id].destroy = exports.DockviewDisposable.from(() => {
11409
11429
  var _a;
11410
- if (panel.view.content.element.parentElement === focusContainer) {
11411
- focusContainer.removeChild(panel.view.content.element);
11430
+ if (contentElement.parentElement === focusContainer) {
11431
+ focusContainer.removeChild(contentElement);
11412
11432
  }
11413
11433
  (_a = focusContainer.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(focusContainer);
11414
11434
  });
@@ -12263,7 +12283,12 @@
12263
12283
  this._middleIndex = 0;
12264
12284
  this._outerSplitview.addView(this._middleColumn, { type: 'distribute' }, 0);
12265
12285
  this._disposables.addDisposables(watchElementResize(this._shellElement, (entry) => {
12266
- const { width, height } = entry.contentRect;
12286
+ const width = Math.round(entry.contentRect.width);
12287
+ const height = Math.round(entry.contentRect.height);
12288
+ if (width === this._currentWidth &&
12289
+ height === this._currentHeight) {
12290
+ return;
12291
+ }
12267
12292
  this._currentWidth = width;
12268
12293
  this._currentHeight = height;
12269
12294
  this.layout(width, height);
@@ -13333,10 +13358,20 @@
13333
13358
  if (event.isActive) {
13334
13359
  overlay.bringToFront();
13335
13360
  }
13336
- }), watchElementResize(group.element, (entry) => {
13337
- const { width, height } = entry.contentRect;
13338
- group.layout(width, height); // let the group know it's size is changing so it can fire events to the panel
13339
- }));
13361
+ }), (() => {
13362
+ let lastWidth = -1;
13363
+ let lastHeight = -1;
13364
+ return watchElementResize(group.element, (entry) => {
13365
+ const width = Math.round(entry.contentRect.width);
13366
+ const height = Math.round(entry.contentRect.height);
13367
+ if (width === lastWidth && height === lastHeight) {
13368
+ return;
13369
+ }
13370
+ lastWidth = width;
13371
+ lastHeight = height;
13372
+ group.layout(width, height); // let the group know it's size is changing so it can fire events to the panel
13373
+ });
13374
+ })());
13340
13375
  floatingGroupPanel.addDisposables(overlay.onDidChange(() => {
13341
13376
  // this is either a resize or a move
13342
13377
  // to inform the panels .layout(...) the group with it's current size
@@ -14556,6 +14591,10 @@
14556
14591
  const from = options.from.group;
14557
14592
  const to = options.to.group;
14558
14593
  const target = options.to.position;
14594
+ // The group whose panels end up at the target. For non-edge moves
14595
+ // we relocate `from` itself; for edge moves we move panels into a
14596
+ // freshly created group so the edge slot stays anchored.
14597
+ let source = from;
14559
14598
  if (target === 'center') {
14560
14599
  const activePanel = from.activePanel;
14561
14600
  const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, {
@@ -14585,55 +14624,79 @@
14585
14624
  }
14586
14625
  }
14587
14626
  else {
14588
- switch (from.api.location.type) {
14589
- case 'grid':
14590
- this.gridview.removeView(getGridLocation(from.element));
14591
- break;
14592
- case 'floating': {
14593
- const selectedFloatingGroup = this._floatingGroups.find((x) => x.group === from);
14594
- if (!selectedFloatingGroup) {
14595
- throw new Error('dockview: failed to find floating group');
14596
- }
14597
- selectedFloatingGroup.dispose();
14598
- break;
14599
- }
14600
- case 'popout': {
14601
- const selectedPopoutGroup = this._popoutGroups.find((x) => x.popoutGroup === from);
14602
- if (!selectedPopoutGroup) {
14603
- throw new Error('dockview: failed to find popout group');
14604
- }
14605
- // Remove from popout groups list to prevent automatic restoration
14606
- const index = this._popoutGroups.indexOf(selectedPopoutGroup);
14607
- if (index >= 0) {
14608
- this._popoutGroups.splice(index, 1);
14627
+ if (from.api.location.type === 'edge') {
14628
+ /**
14629
+ * Edge groups are permanent structural elements and must
14630
+ * stay anchored in their edge slot. Move the panels into a
14631
+ * new group; the auto-collapse listener registered in
14632
+ * addEdgeGroup will collapse the now-empty edge slot once
14633
+ * the last panel leaves. The placement code below then
14634
+ * positions `source` like any other moved group.
14635
+ */
14636
+ const activePanel = from.activePanel;
14637
+ const movedPanels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, { skipSetActive: true })));
14638
+ source = this.createGroup();
14639
+ this.movingLock(() => {
14640
+ for (const panel of movedPanels) {
14641
+ source.model.openPanel(panel, {
14642
+ skipSetActive: panel !== activePanel,
14643
+ skipSetGroupActive: true,
14644
+ });
14609
14645
  }
14610
- // Clean up the reference group (ghost) if it exists and is hidden
14611
- if (selectedPopoutGroup.referenceGroup) {
14612
- const referenceGroup = this.getPanel(selectedPopoutGroup.referenceGroup);
14613
- if (referenceGroup && !referenceGroup.api.isVisible) {
14614
- this.doRemoveGroup(referenceGroup, {
14615
- skipActive: true,
14616
- });
14646
+ });
14647
+ }
14648
+ else {
14649
+ switch (from.api.location.type) {
14650
+ case 'grid':
14651
+ this.gridview.removeView(getGridLocation(from.element));
14652
+ break;
14653
+ case 'floating': {
14654
+ const selectedFloatingGroup = this._floatingGroups.find((x) => x.group === from);
14655
+ if (!selectedFloatingGroup) {
14656
+ throw new Error('dockview: failed to find floating group');
14617
14657
  }
14658
+ selectedFloatingGroup.dispose();
14659
+ break;
14618
14660
  }
14619
- // Manually dispose the window without triggering restoration
14620
- selectedPopoutGroup.window.dispose();
14621
- // Update group's location and containers for target
14622
- if (to.api.location.type === 'grid') {
14623
- from.model.renderContainer =
14624
- this.overlayRenderContainer;
14625
- from.model.dropTargetContainer =
14626
- this.rootDropTargetContainer;
14627
- from.model.location = { type: 'grid' };
14628
- }
14629
- else if (to.api.location.type === 'floating') {
14630
- from.model.renderContainer =
14631
- this.overlayRenderContainer;
14632
- from.model.dropTargetContainer =
14633
- this.rootDropTargetContainer;
14634
- from.model.location = { type: 'floating' };
14661
+ case 'popout': {
14662
+ const selectedPopoutGroup = this._popoutGroups.find((x) => x.popoutGroup === from);
14663
+ if (!selectedPopoutGroup) {
14664
+ throw new Error('dockview: failed to find popout group');
14665
+ }
14666
+ // Remove from popout groups list to prevent automatic restoration
14667
+ const index = this._popoutGroups.indexOf(selectedPopoutGroup);
14668
+ if (index >= 0) {
14669
+ this._popoutGroups.splice(index, 1);
14670
+ }
14671
+ // Clean up the reference group (ghost) if it exists and is hidden
14672
+ if (selectedPopoutGroup.referenceGroup) {
14673
+ const referenceGroup = this.getPanel(selectedPopoutGroup.referenceGroup);
14674
+ if (referenceGroup &&
14675
+ !referenceGroup.api.isVisible) {
14676
+ this.doRemoveGroup(referenceGroup, {
14677
+ skipActive: true,
14678
+ });
14679
+ }
14680
+ }
14681
+ // Manually dispose the window without triggering restoration
14682
+ selectedPopoutGroup.window.dispose();
14683
+ // Update group's location and containers for target
14684
+ if (to.api.location.type === 'grid') {
14685
+ from.model.renderContainer =
14686
+ this.overlayRenderContainer;
14687
+ from.model.dropTargetContainer =
14688
+ this.rootDropTargetContainer;
14689
+ from.model.location = { type: 'grid' };
14690
+ }
14691
+ else if (to.api.location.type === 'floating') {
14692
+ from.model.renderContainer =
14693
+ this.overlayRenderContainer;
14694
+ from.model.dropTargetContainer =
14695
+ this.rootDropTargetContainer;
14696
+ from.model.location = { type: 'floating' };
14697
+ }
14698
+ break;
14635
14699
  }
14636
- break;
14637
14700
  }
14638
14701
  }
14639
14702
  // For moves to grid locations
@@ -14656,7 +14719,7 @@
14656
14719
  : from.api.width;
14657
14720
  break;
14658
14721
  }
14659
- this.gridview.addView(from, size, dropLocation);
14722
+ this.gridview.addView(source, size, dropLocation);
14660
14723
  }
14661
14724
  else if (to.api.location.type === 'floating') {
14662
14725
  // For moves to floating locations, add as floating group
@@ -14684,7 +14747,7 @@
14684
14747
  else {
14685
14748
  top = 50; // Default fallback
14686
14749
  }
14687
- this.addFloatingGroup(from, {
14750
+ this.addFloatingGroup(source, {
14688
14751
  height: box.height,
14689
14752
  width: box.width,
14690
14753
  position: {
@@ -14695,7 +14758,7 @@
14695
14758
  }
14696
14759
  }
14697
14760
  }
14698
- from.panels.forEach((panel) => {
14761
+ source.panels.forEach((panel) => {
14699
14762
  this._onDidMovePanel.fire({ panel, from });
14700
14763
  });
14701
14764
  this.debouncedUpdateAllPositions();
@@ -14706,6 +14769,11 @@
14706
14769
  const targetGroup = to !== null && to !== void 0 ? to : from;
14707
14770
  this.doSetGroupAndPanelActive(targetGroup);
14708
14771
  }
14772
+ else if (source !== from && options.skipSetActive !== true) {
14773
+ // Edge group moves create a fresh `source` group; activate it
14774
+ // by default so the moved panels receive focus.
14775
+ this.doSetGroupAndPanelActive(source);
14776
+ }
14709
14777
  }
14710
14778
  doSetGroupActive(group) {
14711
14779
  super.doSetGroupActive(group);
@@ -24,6 +24,9 @@ export class GroupDragHandler extends DragHandler {
24
24
  if (this.group.api.location.type === 'floating' && !_event.shiftKey) {
25
25
  return true;
26
26
  }
27
+ if (this.group.api.location.type === 'edge' && this.group.size === 0) {
28
+ return true;
29
+ }
27
30
  return false;
28
31
  }
29
32
  getData(dragEvent) {
@@ -744,10 +744,20 @@ export class DockviewComponent extends BaseGrid {
744
744
  if (event.isActive) {
745
745
  overlay.bringToFront();
746
746
  }
747
- }), watchElementResize(group.element, (entry) => {
748
- const { width, height } = entry.contentRect;
749
- group.layout(width, height); // let the group know it's size is changing so it can fire events to the panel
750
- }));
747
+ }), (() => {
748
+ let lastWidth = -1;
749
+ let lastHeight = -1;
750
+ return watchElementResize(group.element, (entry) => {
751
+ const width = Math.round(entry.contentRect.width);
752
+ const height = Math.round(entry.contentRect.height);
753
+ if (width === lastWidth && height === lastHeight) {
754
+ return;
755
+ }
756
+ lastWidth = width;
757
+ lastHeight = height;
758
+ group.layout(width, height); // let the group know it's size is changing so it can fire events to the panel
759
+ });
760
+ })());
751
761
  floatingGroupPanel.addDisposables(overlay.onDidChange(() => {
752
762
  // this is either a resize or a move
753
763
  // to inform the panels .layout(...) the group with it's current size
@@ -1969,6 +1979,10 @@ export class DockviewComponent extends BaseGrid {
1969
1979
  const from = options.from.group;
1970
1980
  const to = options.to.group;
1971
1981
  const target = options.to.position;
1982
+ // The group whose panels end up at the target. For non-edge moves
1983
+ // we relocate `from` itself; for edge moves we move panels into a
1984
+ // freshly created group so the edge slot stays anchored.
1985
+ let source = from;
1972
1986
  if (target === 'center') {
1973
1987
  const activePanel = from.activePanel;
1974
1988
  const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, {
@@ -1998,55 +2012,79 @@ export class DockviewComponent extends BaseGrid {
1998
2012
  }
1999
2013
  }
2000
2014
  else {
2001
- switch (from.api.location.type) {
2002
- case 'grid':
2003
- this.gridview.removeView(getGridLocation(from.element));
2004
- break;
2005
- case 'floating': {
2006
- const selectedFloatingGroup = this._floatingGroups.find((x) => x.group === from);
2007
- if (!selectedFloatingGroup) {
2008
- throw new Error('dockview: failed to find floating group');
2009
- }
2010
- selectedFloatingGroup.dispose();
2011
- break;
2012
- }
2013
- case 'popout': {
2014
- const selectedPopoutGroup = this._popoutGroups.find((x) => x.popoutGroup === from);
2015
- if (!selectedPopoutGroup) {
2016
- throw new Error('dockview: failed to find popout group');
2017
- }
2018
- // Remove from popout groups list to prevent automatic restoration
2019
- const index = this._popoutGroups.indexOf(selectedPopoutGroup);
2020
- if (index >= 0) {
2021
- this._popoutGroups.splice(index, 1);
2015
+ if (from.api.location.type === 'edge') {
2016
+ /**
2017
+ * Edge groups are permanent structural elements and must
2018
+ * stay anchored in their edge slot. Move the panels into a
2019
+ * new group; the auto-collapse listener registered in
2020
+ * addEdgeGroup will collapse the now-empty edge slot once
2021
+ * the last panel leaves. The placement code below then
2022
+ * positions `source` like any other moved group.
2023
+ */
2024
+ const activePanel = from.activePanel;
2025
+ const movedPanels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, { skipSetActive: true })));
2026
+ source = this.createGroup();
2027
+ this.movingLock(() => {
2028
+ for (const panel of movedPanels) {
2029
+ source.model.openPanel(panel, {
2030
+ skipSetActive: panel !== activePanel,
2031
+ skipSetGroupActive: true,
2032
+ });
2022
2033
  }
2023
- // Clean up the reference group (ghost) if it exists and is hidden
2024
- if (selectedPopoutGroup.referenceGroup) {
2025
- const referenceGroup = this.getPanel(selectedPopoutGroup.referenceGroup);
2026
- if (referenceGroup && !referenceGroup.api.isVisible) {
2027
- this.doRemoveGroup(referenceGroup, {
2028
- skipActive: true,
2029
- });
2034
+ });
2035
+ }
2036
+ else {
2037
+ switch (from.api.location.type) {
2038
+ case 'grid':
2039
+ this.gridview.removeView(getGridLocation(from.element));
2040
+ break;
2041
+ case 'floating': {
2042
+ const selectedFloatingGroup = this._floatingGroups.find((x) => x.group === from);
2043
+ if (!selectedFloatingGroup) {
2044
+ throw new Error('dockview: failed to find floating group');
2030
2045
  }
2046
+ selectedFloatingGroup.dispose();
2047
+ break;
2031
2048
  }
2032
- // Manually dispose the window without triggering restoration
2033
- selectedPopoutGroup.window.dispose();
2034
- // Update group's location and containers for target
2035
- if (to.api.location.type === 'grid') {
2036
- from.model.renderContainer =
2037
- this.overlayRenderContainer;
2038
- from.model.dropTargetContainer =
2039
- this.rootDropTargetContainer;
2040
- from.model.location = { type: 'grid' };
2041
- }
2042
- else if (to.api.location.type === 'floating') {
2043
- from.model.renderContainer =
2044
- this.overlayRenderContainer;
2045
- from.model.dropTargetContainer =
2046
- this.rootDropTargetContainer;
2047
- from.model.location = { type: 'floating' };
2049
+ case 'popout': {
2050
+ const selectedPopoutGroup = this._popoutGroups.find((x) => x.popoutGroup === from);
2051
+ if (!selectedPopoutGroup) {
2052
+ throw new Error('dockview: failed to find popout group');
2053
+ }
2054
+ // Remove from popout groups list to prevent automatic restoration
2055
+ const index = this._popoutGroups.indexOf(selectedPopoutGroup);
2056
+ if (index >= 0) {
2057
+ this._popoutGroups.splice(index, 1);
2058
+ }
2059
+ // Clean up the reference group (ghost) if it exists and is hidden
2060
+ if (selectedPopoutGroup.referenceGroup) {
2061
+ const referenceGroup = this.getPanel(selectedPopoutGroup.referenceGroup);
2062
+ if (referenceGroup &&
2063
+ !referenceGroup.api.isVisible) {
2064
+ this.doRemoveGroup(referenceGroup, {
2065
+ skipActive: true,
2066
+ });
2067
+ }
2068
+ }
2069
+ // Manually dispose the window without triggering restoration
2070
+ selectedPopoutGroup.window.dispose();
2071
+ // Update group's location and containers for target
2072
+ if (to.api.location.type === 'grid') {
2073
+ from.model.renderContainer =
2074
+ this.overlayRenderContainer;
2075
+ from.model.dropTargetContainer =
2076
+ this.rootDropTargetContainer;
2077
+ from.model.location = { type: 'grid' };
2078
+ }
2079
+ else if (to.api.location.type === 'floating') {
2080
+ from.model.renderContainer =
2081
+ this.overlayRenderContainer;
2082
+ from.model.dropTargetContainer =
2083
+ this.rootDropTargetContainer;
2084
+ from.model.location = { type: 'floating' };
2085
+ }
2086
+ break;
2048
2087
  }
2049
- break;
2050
2088
  }
2051
2089
  }
2052
2090
  // For moves to grid locations
@@ -2069,7 +2107,7 @@ export class DockviewComponent extends BaseGrid {
2069
2107
  : from.api.width;
2070
2108
  break;
2071
2109
  }
2072
- this.gridview.addView(from, size, dropLocation);
2110
+ this.gridview.addView(source, size, dropLocation);
2073
2111
  }
2074
2112
  else if (to.api.location.type === 'floating') {
2075
2113
  // For moves to floating locations, add as floating group
@@ -2097,7 +2135,7 @@ export class DockviewComponent extends BaseGrid {
2097
2135
  else {
2098
2136
  top = 50; // Default fallback
2099
2137
  }
2100
- this.addFloatingGroup(from, {
2138
+ this.addFloatingGroup(source, {
2101
2139
  height: box.height,
2102
2140
  width: box.width,
2103
2141
  position: {
@@ -2108,7 +2146,7 @@ export class DockviewComponent extends BaseGrid {
2108
2146
  }
2109
2147
  }
2110
2148
  }
2111
- from.panels.forEach((panel) => {
2149
+ source.panels.forEach((panel) => {
2112
2150
  this._onDidMovePanel.fire({ panel, from });
2113
2151
  });
2114
2152
  this.debouncedUpdateAllPositions();
@@ -2119,6 +2157,11 @@ export class DockviewComponent extends BaseGrid {
2119
2157
  const targetGroup = to !== null && to !== void 0 ? to : from;
2120
2158
  this.doSetGroupAndPanelActive(targetGroup);
2121
2159
  }
2160
+ else if (source !== from && options.skipSetActive !== true) {
2161
+ // Edge group moves create a fresh `source` group; activate it
2162
+ // by default so the moved panels receive focus.
2163
+ this.doSetGroupAndPanelActive(source);
2164
+ }
2122
2165
  }
2123
2166
  doSetGroupActive(group) {
2124
2167
  super.doSetGroupActive(group);
@@ -262,7 +262,12 @@ export class ShellManager {
262
262
  this._middleIndex = 0;
263
263
  this._outerSplitview.addView(this._middleColumn, { type: 'distribute' }, 0);
264
264
  this._disposables.addDisposables(watchElementResize(this._shellElement, (entry) => {
265
- const { width, height } = entry.contentRect;
265
+ const width = Math.round(entry.contentRect.width);
266
+ const height = Math.round(entry.contentRect.height);
267
+ if (width === this._currentWidth &&
268
+ height === this._currentHeight) {
269
+ return;
270
+ }
266
271
  this._currentWidth = width;
267
272
  this._currentHeight = height;
268
273
  this.layout(width, height);
@@ -90,8 +90,14 @@ export class OverlayRenderContainer extends CompositeDisposable {
90
90
  };
91
91
  }
92
92
  const focusContainer = this.map[panel.api.id].element;
93
- if (panel.view.content.element.parentElement !== focusContainer) {
94
- focusContainer.appendChild(panel.view.content.element);
93
+ // Capture the content element now so the destroy disposable below
94
+ // does not re-query the renderer's `element` getter during teardown.
95
+ // Some framework adapters (e.g. dockview-angular) tear down their
96
+ // backing renderer before this disposable fires; reading through the
97
+ // getter at that point can throw.
98
+ const contentElement = panel.view.content.element;
99
+ if (contentElement.parentElement !== focusContainer) {
100
+ focusContainer.appendChild(contentElement);
95
101
  }
96
102
  if (focusContainer.parentElement !== this.element) {
97
103
  this.element.appendChild(focusContainer);
@@ -202,8 +208,8 @@ export class OverlayRenderContainer extends CompositeDisposable {
202
208
  }));
203
209
  this.map[panel.api.id].destroy = Disposable.from(() => {
204
210
  var _a;
205
- if (panel.view.content.element.parentElement === focusContainer) {
206
- focusContainer.removeChild(panel.view.content.element);
211
+ if (contentElement.parentElement === focusContainer) {
212
+ focusContainer.removeChild(contentElement);
207
213
  }
208
214
  (_a = focusContainer.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(focusContainer);
209
215
  });
@@ -2,6 +2,8 @@ import { CompositeDisposable } from './lifecycle';
2
2
  export declare abstract class Resizable extends CompositeDisposable {
3
3
  private readonly _element;
4
4
  private _disableResizing;
5
+ private _lastWidth;
6
+ private _lastHeight;
5
7
  get element(): HTMLElement;
6
8
  get disableResizing(): boolean;
7
9
  set disableResizing(value: boolean);
@@ -12,6 +12,8 @@ export class Resizable extends CompositeDisposable {
12
12
  }
13
13
  constructor(parentElement, disableResizing = false) {
14
14
  super();
15
+ this._lastWidth = -1;
16
+ this._lastHeight = -1;
15
17
  this._disableResizing = disableResizing;
16
18
  this._element = parentElement;
17
19
  this.addDisposables(watchElementResize(this._element, (entry) => {
@@ -48,7 +50,16 @@ export class Resizable extends CompositeDisposable {
48
50
  */
49
51
  return;
50
52
  }
51
- const { width, height } = entry.contentRect;
53
+ // Round to integers to absorb sub-pixel jitter from
54
+ // fractional devicePixelRatio (e.g. multi-monitor setups),
55
+ // which would otherwise re-fire layout in a feedback loop.
56
+ const width = Math.round(entry.contentRect.width);
57
+ const height = Math.round(entry.contentRect.height);
58
+ if (width === this._lastWidth && height === this._lastHeight) {
59
+ return;
60
+ }
61
+ this._lastWidth = width;
62
+ this._lastHeight = height;
52
63
  this.layout(width, height);
53
64
  }));
54
65
  }