dockview-core 6.6.0 → 7.0.2

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 (153) hide show
  1. package/README.md +8 -1
  2. package/dist/cjs/api/component.api.d.ts +42 -21
  3. package/dist/cjs/api/component.api.js +111 -20
  4. package/dist/cjs/api/dockviewGroupPanelApi.d.ts +23 -8
  5. package/dist/cjs/api/dockviewGroupPanelApi.js +23 -0
  6. package/dist/cjs/api/dockviewPanelApi.d.ts +4 -3
  7. package/dist/cjs/api/dockviewPanelApi.js +8 -0
  8. package/dist/cjs/dnd/droptarget.d.ts +8 -0
  9. package/dist/cjs/dnd/droptarget.js +28 -0
  10. package/dist/cjs/dockview/accessibilityMessages.d.ts +32 -0
  11. package/dist/cjs/dockview/accessibilityMessages.js +51 -0
  12. package/dist/cjs/dockview/allModules.d.ts +8 -0
  13. package/dist/cjs/dockview/allModules.js +25 -0
  14. package/dist/cjs/dockview/components/panel/content.d.ts +2 -0
  15. package/dist/cjs/dockview/components/panel/content.js +35 -4
  16. package/dist/cjs/dockview/components/tab/tab.js +33 -5
  17. package/dist/cjs/dockview/components/titlebar/floatingTitleBar.d.ts +35 -0
  18. package/dist/cjs/dockview/components/titlebar/floatingTitleBar.js +95 -0
  19. package/dist/cjs/dockview/components/titlebar/groupDragSource.d.ts +52 -0
  20. package/dist/cjs/dockview/components/titlebar/groupDragSource.js +218 -0
  21. package/dist/cjs/dockview/components/titlebar/tabGroupIndicator.d.ts +2 -1
  22. package/dist/cjs/dockview/components/titlebar/tabGroupIndicator.js +31 -24
  23. package/dist/cjs/dockview/components/titlebar/tabGroups.js +1 -0
  24. package/dist/cjs/dockview/components/titlebar/tabs.d.ts +12 -0
  25. package/dist/cjs/dockview/components/titlebar/tabs.js +105 -2
  26. package/dist/cjs/dockview/components/titlebar/tabsContainer.d.ts +4 -0
  27. package/dist/cjs/dockview/components/titlebar/tabsContainer.js +13 -3
  28. package/dist/cjs/dockview/components/titlebar/voidContainer.d.ts +1 -4
  29. package/dist/cjs/dockview/components/titlebar/voidContainer.js +31 -155
  30. package/dist/cjs/dockview/dockviewComponent.d.ts +299 -44
  31. package/dist/cjs/dockview/dockviewComponent.js +1787 -993
  32. package/dist/cjs/dockview/dockviewFloatingGroupPanel.d.ts +33 -2
  33. package/dist/cjs/dockview/dockviewFloatingGroupPanel.js +39 -3
  34. package/dist/cjs/dockview/dockviewGroupPanel.d.ts +0 -1
  35. package/dist/cjs/dockview/dockviewGroupPanelModel.d.ts +36 -14
  36. package/dist/cjs/dockview/dockviewGroupPanelModel.js +133 -101
  37. package/dist/cjs/dockview/dockviewPanel.d.ts +2 -2
  38. package/dist/cjs/dockview/edgeGroupService.d.ts +38 -0
  39. package/dist/cjs/dockview/edgeGroupService.js +128 -0
  40. package/dist/cjs/dockview/floatingGroupService.d.ts +37 -0
  41. package/dist/cjs/dockview/floatingGroupService.js +231 -0
  42. package/dist/cjs/dockview/headerActionsService.d.ts +32 -0
  43. package/dist/cjs/dockview/headerActionsService.js +149 -0
  44. package/dist/cjs/dockview/liveRegionService.d.ts +53 -0
  45. package/dist/cjs/dockview/liveRegionService.js +185 -0
  46. package/dist/cjs/dockview/moduleContracts.d.ts +119 -0
  47. package/dist/cjs/dockview/moduleContracts.js +2 -0
  48. package/dist/cjs/dockview/modules.d.ts +110 -0
  49. package/dist/cjs/dockview/modules.js +304 -0
  50. package/dist/cjs/dockview/options.d.ts +159 -6
  51. package/dist/cjs/dockview/options.js +8 -1
  52. package/dist/cjs/dockview/popoutWindowService.d.ts +95 -0
  53. package/dist/cjs/dockview/popoutWindowService.js +261 -0
  54. package/dist/cjs/dockview/rootDropTargetService.d.ts +35 -0
  55. package/dist/cjs/dockview/rootDropTargetService.js +87 -0
  56. package/dist/cjs/dockview/watermarkService.d.ts +30 -0
  57. package/dist/cjs/dockview/watermarkService.js +61 -0
  58. package/dist/cjs/gridview/baseComponentGridview.d.ts +1 -1
  59. package/dist/cjs/gridview/baseComponentGridview.js +3 -2
  60. package/dist/cjs/gridview/gridviewComponent.d.ts +3 -3
  61. package/dist/cjs/gridview/gridviewPanel.d.ts +1 -1
  62. package/dist/cjs/index.d.ts +11 -4
  63. package/dist/cjs/index.js +14 -1
  64. package/dist/cjs/overlay/overlay.d.ts +43 -1
  65. package/dist/cjs/overlay/overlay.js +57 -8
  66. package/dist/cjs/paneview/draggablePaneviewPanel.d.ts +2 -2
  67. package/dist/cjs/paneview/draggablePaneviewPanel.js +4 -4
  68. package/dist/cjs/paneview/paneviewComponent.d.ts +3 -3
  69. package/dist/cjs/paneview/paneviewComponent.js +5 -5
  70. package/dist/dockview-core.js +3199 -1251
  71. package/dist/dockview-core.min.js +2 -2
  72. package/dist/dockview-core.min.js.map +1 -1
  73. package/dist/dockview-core.min.noStyle.js +2 -2
  74. package/dist/dockview-core.min.noStyle.js.map +1 -1
  75. package/dist/dockview-core.noStyle.js +3198 -1250
  76. package/dist/esm/api/component.api.d.ts +42 -21
  77. package/dist/esm/api/component.api.js +63 -18
  78. package/dist/esm/api/dockviewGroupPanelApi.d.ts +23 -8
  79. package/dist/esm/api/dockviewGroupPanelApi.js +19 -0
  80. package/dist/esm/api/dockviewPanelApi.d.ts +4 -3
  81. package/dist/esm/api/dockviewPanelApi.js +7 -0
  82. package/dist/esm/dnd/droptarget.d.ts +8 -0
  83. package/dist/esm/dnd/droptarget.js +28 -0
  84. package/dist/esm/dockview/accessibilityMessages.d.ts +32 -0
  85. package/dist/esm/dockview/accessibilityMessages.js +30 -0
  86. package/dist/esm/dockview/allModules.d.ts +8 -0
  87. package/dist/esm/dockview/allModules.js +22 -0
  88. package/dist/esm/dockview/components/panel/content.d.ts +2 -0
  89. package/dist/esm/dockview/components/panel/content.js +36 -5
  90. package/dist/esm/dockview/components/tab/tab.js +33 -5
  91. package/dist/esm/dockview/components/titlebar/floatingTitleBar.d.ts +35 -0
  92. package/dist/esm/dockview/components/titlebar/floatingTitleBar.js +65 -0
  93. package/dist/esm/dockview/components/titlebar/groupDragSource.d.ts +52 -0
  94. package/dist/esm/dockview/components/titlebar/groupDragSource.js +178 -0
  95. package/dist/esm/dockview/components/titlebar/tabGroupIndicator.d.ts +2 -1
  96. package/dist/esm/dockview/components/titlebar/tabGroupIndicator.js +31 -24
  97. package/dist/esm/dockview/components/titlebar/tabGroups.js +1 -0
  98. package/dist/esm/dockview/components/titlebar/tabs.d.ts +12 -0
  99. package/dist/esm/dockview/components/titlebar/tabs.js +102 -2
  100. package/dist/esm/dockview/components/titlebar/tabsContainer.d.ts +4 -0
  101. package/dist/esm/dockview/components/titlebar/tabsContainer.js +8 -2
  102. package/dist/esm/dockview/components/titlebar/voidContainer.d.ts +1 -4
  103. package/dist/esm/dockview/components/titlebar/voidContainer.js +33 -145
  104. package/dist/esm/dockview/dockviewComponent.d.ts +299 -44
  105. package/dist/esm/dockview/dockviewComponent.js +1421 -717
  106. package/dist/esm/dockview/dockviewFloatingGroupPanel.d.ts +33 -2
  107. package/dist/esm/dockview/dockviewFloatingGroupPanel.js +35 -3
  108. package/dist/esm/dockview/dockviewGroupPanel.d.ts +0 -1
  109. package/dist/esm/dockview/dockviewGroupPanelModel.d.ts +36 -14
  110. package/dist/esm/dockview/dockviewGroupPanelModel.js +109 -93
  111. package/dist/esm/dockview/dockviewPanel.d.ts +2 -2
  112. package/dist/esm/dockview/edgeGroupService.d.ts +38 -0
  113. package/dist/esm/dockview/edgeGroupService.js +63 -0
  114. package/dist/esm/dockview/floatingGroupService.d.ts +37 -0
  115. package/dist/esm/dockview/floatingGroupService.js +150 -0
  116. package/dist/esm/dockview/headerActionsService.d.ts +32 -0
  117. package/dist/esm/dockview/headerActionsService.js +86 -0
  118. package/dist/esm/dockview/liveRegionService.d.ts +53 -0
  119. package/dist/esm/dockview/liveRegionService.js +159 -0
  120. package/dist/esm/dockview/moduleContracts.d.ts +119 -0
  121. package/dist/esm/dockview/moduleContracts.js +1 -0
  122. package/dist/esm/dockview/modules.d.ts +110 -0
  123. package/dist/esm/dockview/modules.js +170 -0
  124. package/dist/esm/dockview/options.d.ts +159 -6
  125. package/dist/esm/dockview/options.js +8 -1
  126. package/dist/esm/dockview/popoutWindowService.d.ts +95 -0
  127. package/dist/esm/dockview/popoutWindowService.js +175 -0
  128. package/dist/esm/dockview/rootDropTargetService.d.ts +35 -0
  129. package/dist/esm/dockview/rootDropTargetService.js +82 -0
  130. package/dist/esm/dockview/watermarkService.d.ts +30 -0
  131. package/dist/esm/dockview/watermarkService.js +56 -0
  132. package/dist/esm/gridview/baseComponentGridview.d.ts +1 -1
  133. package/dist/esm/gridview/baseComponentGridview.js +2 -2
  134. package/dist/esm/gridview/gridviewComponent.d.ts +3 -3
  135. package/dist/esm/gridview/gridviewPanel.d.ts +1 -1
  136. package/dist/esm/index.d.ts +11 -4
  137. package/dist/esm/index.js +4 -0
  138. package/dist/esm/overlay/overlay.d.ts +43 -1
  139. package/dist/esm/overlay/overlay.js +53 -8
  140. package/dist/esm/paneview/draggablePaneviewPanel.d.ts +2 -2
  141. package/dist/esm/paneview/draggablePaneviewPanel.js +4 -4
  142. package/dist/esm/paneview/paneviewComponent.d.ts +3 -3
  143. package/dist/esm/paneview/paneviewComponent.js +5 -5
  144. package/dist/package/main.cjs.js +3234 -1286
  145. package/dist/package/main.cjs.min.js +2 -2
  146. package/dist/package/main.esm.min.mjs +2 -2
  147. package/dist/package/main.esm.mjs +3189 -1252
  148. package/dist/styles/dockview.css +275 -13
  149. package/package.json +10 -1
  150. package/dist/cjs/dockview/contextMenu.d.ts +0 -10
  151. package/dist/cjs/dockview/contextMenu.js +0 -313
  152. package/dist/esm/dockview/contextMenu.d.ts +0 -10
  153. package/dist/esm/dockview/contextMenu.js +0 -228
@@ -1,7 +1,6 @@
1
- import { getRelativeLocation, getGridLocation, orthogonal, } from '../gridview/gridview';
1
+ import { getRelativeLocation, getGridLocation, orthogonal, Gridview, } from '../gridview/gridview';
2
2
  import { directionToPosition, } from '../dnd/droptarget';
3
- import { html5Backend, pointerBackend } from '../dnd/backend';
4
- import { tail, sequenceEquals, remove } from '../array';
3
+ import { tail, sequenceEquals } from '../array';
5
4
  import { DockviewPanel } from './dockviewPanel';
6
5
  import { CompositeDisposable, Disposable } from '../lifecycle';
7
6
  import { Event, Emitter, addDisposableListener } from '../events';
@@ -11,29 +10,26 @@ import { DefaultDockviewDeserialzier } from './deserializer';
11
10
  import { DockviewUnhandledDragOverEvent, isGroupOptionsWithGroup, isGroupOptionsWithPanel, isPanelOptionsWithGroup, isPanelOptionsWithPanel, } from './options';
12
11
  import { BaseGrid, toTarget, } from '../gridview/baseComponentGridview';
13
12
  import { DockviewApi } from '../api/component.api';
14
- import { Orientation } from '../splitview/splitview';
13
+ import { Orientation, Sizing } from '../splitview/splitview';
15
14
  import { DockviewDidDropEvent, DockviewWillDropEvent, } from './dockviewGroupPanelModel';
16
15
  import { DockviewWillShowOverlayLocationEvent, } from './events';
17
16
  import { DockviewGroupPanel } from './dockviewGroupPanel';
18
17
  import { DockviewPanelModel } from './dockviewPanelModel';
19
18
  import { getPanelData } from '../dnd/dataTransfer';
20
19
  import { Overlay } from '../overlay/overlay';
21
- import { addTestId, Classnames, getDockviewTheme, onDidWindowResizeEnd, onDidWindowMoveEnd, toggleClass, watchElementResize, } from '../dom';
22
- import { DockviewFloatingGroupPanel } from './dockviewFloatingGroupPanel';
20
+ import { Classnames, getDockviewTheme, onDidWindowResizeEnd, onDidWindowMoveEnd, toggleClass, } from '../dom';
21
+ import { FloatingTitleBar } from './components/titlebar/floatingTitleBar';
22
+ import { assertModule, getRegisteredModules, isDockviewPackageLoaded, ModuleRegistry, } from './modules';
23
+ import { AllModules } from './allModules';
23
24
  import { DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE, DEFAULT_FLOATING_GROUP_POSITION, DESERIALIZATION_POPOUT_DELAY_MS, } from '../constants';
24
25
  import { OverlayRenderContainer, } from '../overlay/overlayRenderContainer';
25
26
  import { PopoutWindow } from '../popoutWindow';
26
27
  import { StrictEventsSequencing } from './strictEventsSequencing';
27
28
  import { PopupService } from './components/popupService';
28
- import { ContextMenuController } from './contextMenu';
29
29
  import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer';
30
30
  import { themeAbyss } from './theme';
31
31
  import { ShellManager, } from './dockviewShell';
32
32
  import { DEFAULT_TAB_GROUP_COLORS, TabGroupColorPalette, } from './tabGroupAccent';
33
- const DEFAULT_ROOT_OVERLAY_MODEL = {
34
- activationSize: { type: 'pixels', value: 10 },
35
- size: { type: 'pixels', value: 20 },
36
- };
37
33
  function buildTabGroupColorPalette(options) {
38
34
  var _a;
39
35
  const entries = (_a = options.tabGroupColors) !== null && _a !== void 0 ? _a : DEFAULT_TAB_GROUP_COLORS;
@@ -54,7 +50,51 @@ function moveGroupWithoutDestroying(options) {
54
50
  });
55
51
  });
56
52
  }
53
+ let _hasWarnedUsingCoreDirectly = false;
54
+ /**
55
+ * `dockview-core` is an internal package. The public `dockview` package calls
56
+ * `markDockviewPackageLoaded()` on import; if that marker is absent the consumer
57
+ * is using `dockview-core` directly, so emit a one-time console warning
58
+ * steering them to `dockview`.
59
+ *
60
+ * Suppressed in production builds: it is a development-time nudge and most
61
+ * bundlers inline `process.env.NODE_ENV` so the branch is dropped entirely. The
62
+ * `typeof process` guard keeps this safe in plain browser/UMD contexts where
63
+ * `process` is undefined.
64
+ */
65
+ function warnIfUsingCoreDirectly() {
66
+ if (typeof process !== 'undefined' &&
67
+ process.env &&
68
+ process.env.NODE_ENV === 'production') {
69
+ return;
70
+ }
71
+ if (_hasWarnedUsingCoreDirectly || isDockviewPackageLoaded()) {
72
+ return;
73
+ }
74
+ _hasWarnedUsingCoreDirectly = true;
75
+ console.warn('dockview: do not use "dockview-core" directly — it is an internal ' +
76
+ 'package. Use the "dockview" package, the JavaScript version of ' +
77
+ 'dockview, instead. This notice is shown once.');
78
+ }
57
79
  export class DockviewComponent extends BaseGrid {
80
+ fireDidCreateTabGroup(event) {
81
+ this._onDidCreateTabGroup.fire(event);
82
+ }
83
+ fireDidDestroyTabGroup(event) {
84
+ this._onDidDestroyTabGroup.fire(event);
85
+ }
86
+ fireDidAddPanelToTabGroup(event) {
87
+ this._onDidAddPanelToTabGroup.fire(event);
88
+ }
89
+ fireDidRemovePanelFromTabGroup(event) {
90
+ this._onDidRemovePanelFromTabGroup.fire(event);
91
+ }
92
+ fireDidTabGroupChange(event) {
93
+ this._onDidTabGroupChange.fire(event);
94
+ }
95
+ fireDidTabGroupCollapsedChange(event) {
96
+ this._onDidTabGroupCollapsedChange.fire(event);
97
+ }
58
98
  get orientation() {
59
99
  return this.gridview.orientation;
60
100
  }
@@ -89,17 +129,210 @@ export class DockviewComponent extends BaseGrid {
89
129
  return this._api;
90
130
  }
91
131
  get floatingGroups() {
92
- return this._floatingGroups;
132
+ var _a, _b, _c;
133
+ return ((_c = (_b = (_a = this._moduleRegistry) === null || _a === void 0 ? void 0 : _a.services.floatingGroupService) === null || _b === void 0 ? void 0 : _b.floatingGroups) !== null && _c !== void 0 ? _c : []);
134
+ }
135
+ /**
136
+ * Boxes of the floating groups other than `exclude`, in coordinates
137
+ * relative to the floating overlay container. Supplied to a
138
+ * `transformFloatingGroupDrag` callback as `context.others` so it can
139
+ * align the dragged float against its siblings.
140
+ */
141
+ _gatherFloatingGroupBoxes(exclude) {
142
+ var _a;
143
+ const container = (_a = this._floatingOverlayHost) !== null && _a !== void 0 ? _a : this.gridview.element;
144
+ const containerRect = container.getBoundingClientRect();
145
+ return this.floatingGroups
146
+ .filter((floating) => floating.group !== exclude)
147
+ .map((floating) => {
148
+ const rect = floating.overlay.element.getBoundingClientRect();
149
+ return {
150
+ left: rect.left - containerRect.left,
151
+ top: rect.top - containerRect.top,
152
+ width: rect.width,
153
+ height: rect.height,
154
+ };
155
+ });
156
+ }
157
+ get _floatingGroupService() {
158
+ return this._moduleRegistry.services.floatingGroupService;
159
+ }
160
+ get _popoutWindowService() {
161
+ return this._moduleRegistry.services.popoutWindowService;
162
+ }
163
+ get _watermarkService() {
164
+ // Tier 1 module — optional. Callers must `?.`-guard so the module
165
+ // can be removed from AllModules without crashing the component.
166
+ return this._moduleRegistry.services.watermarkService;
167
+ }
168
+ get _edgeGroupService() {
169
+ return this._moduleRegistry.services.edgeGroupService;
170
+ }
171
+ get _rootDropTargetService() {
172
+ // Optional like every other module service — RootDropTargetModule can be
173
+ // removed from the registered set without crashing the component.
174
+ return this._moduleRegistry.services.rootDropTargetService;
175
+ }
176
+ get _advancedDnDService() {
177
+ // Optional — callers `?.`-guard so the module can be removed from
178
+ // AllModules. Absent ⇒ the onWill* hooks simply don't fire (≡ no
179
+ // subscriber), which is invisible to apps not customising DnD.
180
+ return this._moduleRegistry.services.advancedDnDService;
181
+ }
182
+ get headerActionsService() {
183
+ return this._moduleRegistry.services.headerActionsService;
184
+ }
185
+ isGridEmpty() {
186
+ return this.gridview.length === 0;
187
+ }
188
+ rootDropTargetOverrideTarget() {
189
+ var _a;
190
+ return (_a = this.rootDropTargetContainer) === null || _a === void 0 ? void 0 : _a.model;
191
+ }
192
+ dispatchUnhandledDragOver(nativeEvent, position) {
193
+ const event = new DockviewUnhandledDragOverEvent(nativeEvent, 'edge', position, getPanelData);
194
+ this._onUnhandledDragOver.fire(event);
195
+ return event.isAccepted;
196
+ }
197
+ // IAdvancedDnDHost — the emitters stay here so the public onWill* event
198
+ // shape is unchanged; AdvancedDnDService routes the per-group fires
199
+ // through these. Engine guards (e.g. disableDnd) run on the component
200
+ // ahead of the dispatch.
201
+ fireWillDragPanel(event) {
202
+ this._onWillDragPanel.fire(event);
203
+ }
204
+ fireWillDragGroup(event) {
205
+ this._onWillDragGroup.fire(event);
206
+ }
207
+ fireWillDrop(event) {
208
+ this._onWillDrop.fire(event);
209
+ }
210
+ fireWillShowOverlay(event) {
211
+ this._onWillShowOverlay.fire(event);
212
+ }
213
+ /**
214
+ * Resolve the custom group drag ghost (via the AdvancedDnD module), or
215
+ * `undefined` to fall back to the default chip. Returns `undefined` when
216
+ * the module is absent — the default ghost then renders.
217
+ */
218
+ buildGroupDragGhost(group) {
219
+ var _a;
220
+ return (_a = this._advancedDnDService) === null || _a === void 0 ? void 0 : _a.buildGroupDragGhost(group);
221
+ }
222
+ /**
223
+ * Resolve the app-supplied drop overlay model (via the AdvancedDnD module)
224
+ * for a group drop target, or `undefined` to keep the target's default.
225
+ */
226
+ resolveDropOverlayModel(location, group) {
227
+ var _a;
228
+ return (_a = this._advancedDnDService) === null || _a === void 0 ? void 0 : _a.resolveOverlayModel(location, group);
229
+ }
230
+ // IAccessibilityHost — keyboard docking reaches the AdvancedDnD preview +
231
+ // LiveRegion announcer through these so the service stays decoupled.
232
+ /** Outermost element — the shell (incl. edge groups) once built, else the gridview. */
233
+ get rootElement() {
234
+ var _a, _b;
235
+ return (_b = (_a = this._shellManager) === null || _a === void 0 ? void 0 : _a.element) !== null && _b !== void 0 ? _b : this.element;
236
+ }
237
+ /**
238
+ * The next / previous group in gridview (spatial) order, wrapping round.
239
+ * The keyboard accessibility module's focus navigation is built on this
240
+ * primitive — the only piece that needs the grid internals; the rest of
241
+ * the navigation logic lives in the AccessibilityService.
242
+ */
243
+ adjacentGroup(group, reverse) {
244
+ var _a;
245
+ // gridview traversal only covers grid groups; a floating/popout group
246
+ // isn't in the grid, so there's no adjacent grid group to step to.
247
+ if (group.api.location.type !== 'grid') {
248
+ return undefined;
249
+ }
250
+ const location = getGridLocation(group.element);
251
+ return ((_a = (reverse
252
+ ? this.gridview.previous(location)
253
+ : this.gridview.next(location))) === null || _a === void 0 ? void 0 : _a.view);
254
+ }
255
+ /**
256
+ * The nearest grid group in a spatial direction from `group`, by
257
+ * comparing group centre points. Floating and popout groups sit outside
258
+ * the grid's geometry and are ignored. Returns `undefined` when there is
259
+ * no group in that direction.
260
+ */
261
+ adjacentGroupInDirection(group, direction) {
262
+ if (group.api.location.type !== 'grid') {
263
+ return undefined;
264
+ }
265
+ const from = group.element.getBoundingClientRect();
266
+ const fromX = from.left + from.width / 2;
267
+ const fromY = from.top + from.height / 2;
268
+ let best;
269
+ let bestDistance = Number.POSITIVE_INFINITY;
270
+ for (const candidate of this.groups) {
271
+ if (candidate === group || candidate.api.location.type !== 'grid') {
272
+ continue;
273
+ }
274
+ const rect = candidate.element.getBoundingClientRect();
275
+ const dx = rect.left + rect.width / 2 - fromX;
276
+ const dy = rect.top + rect.height / 2 - fromY;
277
+ // Require the candidate to sit predominantly in the asked-for
278
+ // direction (dominant axis), so 'left' ignores a group that's
279
+ // mostly above/below.
280
+ const inDirection = direction === 'left'
281
+ ? dx < 0 && Math.abs(dx) >= Math.abs(dy)
282
+ : direction === 'right'
283
+ ? dx > 0 && Math.abs(dx) >= Math.abs(dy)
284
+ : direction === 'up'
285
+ ? dy < 0 && Math.abs(dy) >= Math.abs(dx)
286
+ : dy > 0 && Math.abs(dy) >= Math.abs(dx);
287
+ if (!inDirection) {
288
+ continue;
289
+ }
290
+ const distance = dx * dx + dy * dy;
291
+ if (distance < bestDistance) {
292
+ bestDistance = distance;
293
+ best = candidate;
294
+ }
295
+ }
296
+ return best;
297
+ }
298
+ showDropPreview(group, position) {
299
+ var _a, _b;
300
+ return ((_b = (_a = this._advancedDnDService) === null || _a === void 0 ? void 0 : _a.showPreviewOverlay(group, position)) !== null && _b !== void 0 ? _b : Disposable.NONE);
301
+ }
302
+ announce(message) {
303
+ var _a;
304
+ (_a = this._moduleRegistry.services.liveRegionService) === null || _a === void 0 ? void 0 : _a.announce(message);
305
+ }
306
+ dockPanel(panel, group, position) {
307
+ this.moveGroupOrPanel({
308
+ from: { groupId: panel.group.id, panelId: panel.id },
309
+ to: { group, position },
310
+ });
311
+ }
312
+ get contextMenuService() {
313
+ // Owned by ContextMenuModule — undefined when the module is not
314
+ // registered, so callers must `?.`-guard.
315
+ return this._moduleRegistry.services.contextMenuService;
316
+ }
317
+ get mountElement() {
318
+ return this.gridview.element;
319
+ }
320
+ hasVisibleGridGroup() {
321
+ return this.groups.some((group) => group.api.location.type === 'grid' && group.api.isVisible);
322
+ }
323
+ fireLayoutChange() {
324
+ this._bufferOnDidLayoutChange.fire();
93
325
  }
94
326
  /**
95
327
  * Promise that resolves when all popout groups from the last fromJSON call are restored.
96
328
  * Useful for tests that need to wait for delayed popout creation.
97
329
  */
98
330
  get popoutRestorationPromise() {
99
- return this._popoutRestorationPromise;
331
+ var _a, _b;
332
+ return ((_b = (_a = this._popoutWindowService) === null || _a === void 0 ? void 0 : _a.restorationPromise) !== null && _b !== void 0 ? _b : Promise.resolve());
100
333
  }
101
334
  constructor(container, options) {
102
- var _a, _b, _c, _d, _e, _f, _g;
335
+ var _a, _b, _c, _d, _e;
103
336
  super(container, {
104
337
  proportionalLayout: true,
105
338
  orientation: Orientation.HORIZONTAL,
@@ -113,8 +346,7 @@ export class DockviewComponent extends BaseGrid {
113
346
  });
114
347
  this.nextGroupId = sequentialNumberGenerator();
115
348
  this._deserializer = new DefaultDockviewDeserialzier(this);
116
- this._watermark = null;
117
- this._popoutPopupServices = new Map();
349
+ this._moduleRegistry = new ModuleRegistry();
118
350
  this._onWillDragPanel = new Emitter();
119
351
  this.onWillDragPanel = this._onWillDragPanel.event;
120
352
  this._onWillDragGroup = new Emitter();
@@ -123,10 +355,25 @@ export class DockviewComponent extends BaseGrid {
123
355
  this.onDidDrop = this._onDidDrop.event;
124
356
  this._onWillDrop = new Emitter();
125
357
  this.onWillDrop = this._onWillDrop.event;
358
+ // Transaction boundary bracketing each top-level structural mutation.
359
+ // Compound operations (e.g. a drag that relocates a panel) nest via the
360
+ // depth counter and bracket as a single transaction. See `mutation()`.
361
+ this._mutationDepth = 0;
362
+ // Current operation origin. Defaults to `'user'`; the DockviewApi boundary
363
+ // flips it to `'api'` for the duration of a programmatic call via
364
+ // `withOrigin`. Nested operations inherit the outermost origin (tracked by
365
+ // `_originDepth`, independent of mutation bracketing so it also covers
366
+ // active-panel changes that are not structural mutations).
367
+ this._origin = 'user';
368
+ this._originDepth = 0;
369
+ this._onWillMutateLayout = new Emitter();
370
+ this.onWillMutateLayout = this._onWillMutateLayout.event;
371
+ this._onDidMutateLayout = new Emitter();
372
+ this.onDidMutateLayout = this._onDidMutateLayout.event;
126
373
  this._onWillShowOverlay = new Emitter();
127
374
  this.onWillShowOverlay = this._onWillShowOverlay.event;
128
- this._onUnhandledDragOverEvent = new Emitter();
129
- this.onUnhandledDragOverEvent = this._onUnhandledDragOverEvent.event;
375
+ this._onUnhandledDragOver = new Emitter();
376
+ this.onUnhandledDragOver = this._onUnhandledDragOver.event;
130
377
  this._onDidRemovePanel = new Emitter();
131
378
  this.onDidRemovePanel = this._onDidRemovePanel.event;
132
379
  this._onDidAddPanel = new Emitter();
@@ -135,6 +382,10 @@ export class DockviewComponent extends BaseGrid {
135
382
  this.onDidPopoutGroupSizeChange = this._onDidPopoutGroupSizeChange.event;
136
383
  this._onDidPopoutGroupPositionChange = new Emitter();
137
384
  this.onDidPopoutGroupPositionChange = this._onDidPopoutGroupPositionChange.event;
385
+ this._onDidAddPopoutGroup = new Emitter();
386
+ this.onDidAddPopoutGroup = this._onDidAddPopoutGroup.event;
387
+ this._onDidRemovePopoutGroup = new Emitter();
388
+ this.onDidRemovePopoutGroup = this._onDidRemovePopoutGroup.event;
138
389
  this._onDidOpenPopoutWindowFail = new Emitter();
139
390
  this.onDidOpenPopoutWindowFail = this._onDidOpenPopoutWindowFail.event;
140
391
  this._onDidLayoutFromJSON = new Emitter();
@@ -158,12 +409,6 @@ export class DockviewComponent extends BaseGrid {
158
409
  this._onDidMaximizedGroupChange = new Emitter();
159
410
  this.onDidMaximizedGroupChange = this._onDidMaximizedGroupChange.event;
160
411
  this._inShellLayout = false;
161
- this._edgeGroups = new Map();
162
- this._edgeGroupDisposables = new Map();
163
- this._floatingGroups = [];
164
- this._popoutGroups = [];
165
- this._popoutRestorationPromise = Promise.resolve();
166
- this._popoutRestorationCleanups = new Set();
167
412
  this._onDidRemoveGroup = new Emitter();
168
413
  this.onDidRemoveGroup = this._onDidRemoveGroup.event;
169
414
  this._onDidAddGroup = new Emitter();
@@ -175,8 +420,38 @@ export class DockviewComponent extends BaseGrid {
175
420
  this._moving = false;
176
421
  this._options = options;
177
422
  this._tabGroupColorPalette = buildTabGroupColorPalette(options);
423
+ // Internal seam: defaults to the full set, but tests can supply a
424
+ // subset to assert every module is independently removable (and the
425
+ // opt-in module API will build on this later). Not part of the public
426
+ // options surface.
427
+ const explicitModules = options
428
+ .modules;
429
+ const modules = explicitModules !== null && explicitModules !== void 0 ? explicitModules : [
430
+ ...AllModules,
431
+ ...getRegisteredModules(),
432
+ ];
433
+ for (const module of modules) {
434
+ this._moduleRegistry.register(module);
435
+ }
436
+ this._moduleRegistry.initialize(this);
437
+ // Surface popout removal symmetrically with `onDidAddPopoutGroup`. The
438
+ // service is the single point every removal path funnels through — a
439
+ // genuine window close and an explicit removal alike — and it suppresses
440
+ // the event during component teardown.
441
+ const popoutWindowService = this._popoutWindowService;
442
+ if (popoutWindowService) {
443
+ this.addDisposables(popoutWindowService.onDidRemove((entry) => {
444
+ this._onDidRemovePopoutGroup.fire({
445
+ id: entry.popoutGroup.id,
446
+ group: entry.popoutGroup,
447
+ window: entry.getWindow(),
448
+ });
449
+ }));
450
+ }
451
+ // Purely a developer warning (no functional effect): nudge anyone using
452
+ // the internal `dockview-core` package directly towards `dockview`.
453
+ warnIfUsingCoreDirectly();
178
454
  this.popupService = new PopupService(this.element);
179
- this.contextMenuController = new ContextMenuController(this);
180
455
  this._api = new DockviewApi(this);
181
456
  // The shell always wraps the dockview element so edge groups can be
182
457
  // added at any time via addEdgeGroup() without re-parenting the DOM.
@@ -198,69 +473,23 @@ export class DockviewComponent extends BaseGrid {
198
473
  this._floatingOverlayHost = document.createElement('div');
199
474
  this._floatingOverlayHost.className = 'dv-floating-overlay-host';
200
475
  this._shellManager.element.appendChild(this._floatingOverlayHost);
201
- const rootCanDisplayOverlay = (event, position) => {
202
- const data = getPanelData();
203
- if (data) {
204
- if (data.viewId !== this.id) {
205
- return false;
206
- }
207
- if (position === 'center') {
208
- // center drop target is only allowed if there are no panels in the grid
209
- // floating panels are allowed
210
- return this.gridview.length === 0;
211
- }
212
- return true;
213
- }
214
- if (position === 'center' && this.gridview.length !== 0) {
215
- /**
216
- * for external events only show the four-corner drag overlays, disable
217
- * the center position so that external drag events can fall through to the group
218
- * and panel drop target handlers
219
- */
220
- return false;
221
- }
222
- const firedEvent = new DockviewUnhandledDragOverEvent(event, 'edge', position, getPanelData);
223
- this._onUnhandledDragOverEvent.fire(firedEvent);
224
- return firedEvent.isAccepted;
225
- };
226
- this._rootDropTarget = html5Backend.createDropTarget(this.element, {
227
- className: 'dv-drop-target-edge',
228
- canDisplayOverlay: rootCanDisplayOverlay,
229
- acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
230
- overlayModel: (_f = options.rootOverlayModel) !== null && _f !== void 0 ? _f : DEFAULT_ROOT_OVERLAY_MODEL,
231
- getOverrideTarget: () => { var _a; return (_a = this.rootDropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
232
- });
233
- this._rootPointerDropTarget = pointerBackend.createDropTarget(this.element, {
234
- className: 'dv-drop-target-edge',
235
- canDisplayOverlay: rootCanDisplayOverlay,
236
- acceptedTargetZones: [
237
- 'top',
238
- 'bottom',
239
- 'left',
240
- 'right',
241
- 'center',
242
- ],
243
- overlayModel: (_g = options.rootOverlayModel) !== null && _g !== void 0 ? _g : DEFAULT_ROOT_OVERLAY_MODEL,
244
- getOverrideTarget: () => { var _a; return (_a = this.rootDropTargetContainer) === null || _a === void 0 ? void 0 : _a.model; },
245
- });
246
- this.updateDropTargetModel(options);
247
476
  toggleClass(this.gridview.element, 'dv-dockview', true);
248
477
  toggleClass(this.element, 'dv-debug', !!options.debug);
249
478
  this.updateTheme();
250
- this.updateWatermark();
251
479
  if (options.debug) {
252
480
  this.addDisposables(new StrictEventsSequencing(this));
253
481
  }
254
- this.addDisposables(this.rootDropTargetContainer, this.overlayRenderContainer, this._onWillDragPanel, this._onWillDragGroup, this._onWillShowOverlay, this._onDidActivePanelChange, this._onDidAddPanel, this._onDidRemovePanel, this._onDidLayoutFromJSON, this._onDidDrop, this._onWillDrop, this._onDidMovePanel, this._onDidMovePanel.event(() => {
482
+ this.addDisposables(this.rootDropTargetContainer, this.overlayRenderContainer, this._onWillDragPanel, this._onWillDragGroup, this._onWillShowOverlay, this._onDidActivePanelChange, this._onDidAddPanel, this._onDidRemovePanel, this._onDidLayoutFromJSON, this._onDidDrop, this._onWillDrop, this._onWillMutateLayout, this._onDidMutateLayout, this._onDidMovePanel, this._onDidMovePanel.event(() => {
255
483
  /**
256
484
  * Update overlay positions after DOM layout completes to prevent 0×0 dimensions.
257
485
  * With defaultRenderer="always" this results in panel content not showing after move operations.
258
486
  * Debounced to avoid multiple calls when moving groups with multiple panels.
259
487
  */
260
488
  this.debouncedUpdateAllPositions();
261
- }), this._onDidAddGroup, this._onDidRemoveGroup, this._onDidActiveGroupChange, this._onUnhandledDragOverEvent, this._onDidMaximizedGroupChange, this._onDidOptionsChange, this._onDidPopoutGroupSizeChange, this._onDidPopoutGroupPositionChange, this._onDidOpenPopoutWindowFail, this._onDidCreateTabGroup, this._onDidDestroyTabGroup, this._onDidAddPanelToTabGroup, this._onDidRemovePanelFromTabGroup, this._onDidTabGroupChange, this._onDidTabGroupCollapsedChange, this.onDidViewVisibilityChangeMicroTaskQueue(() => {
262
- this.updateWatermark();
263
- }), this.onDidAdd((event) => {
489
+ }), this._onDidAddGroup, this._onDidRemoveGroup, this._onDidActiveGroupChange, this._onUnhandledDragOver, this._onDidMaximizedGroupChange, this._onDidPopoutGroupSizeChange, this._onDidPopoutGroupPositionChange, this._onDidAddPopoutGroup, this._onDidRemovePopoutGroup, this._onDidOpenPopoutWindowFail, this._onDidCreateTabGroup, this._onDidDestroyTabGroup, this._onDidAddPanelToTabGroup, this._onDidRemovePanelFromTabGroup, this._onDidTabGroupChange, this._onDidTabGroupCollapsedChange, Event.any(this.onDidPopoutGroupSizeChange, this.onDidPopoutGroupPositionChange, this.onDidCreateTabGroup, this.onDidDestroyTabGroup, this.onDidAddPanelToTabGroup, this.onDidRemovePanelFromTabGroup, this.onDidTabGroupChange, this.onDidTabGroupCollapsedChange)(() => {
490
+ // Popout size/position and tab-group mutations persist as layout changes.
491
+ this.fireLayoutChange();
492
+ }), this._onDidOptionsChange, this.onDidAdd((event) => {
264
493
  if (!this._moving) {
265
494
  this._onDidAddGroup.fire(event);
266
495
  }
@@ -277,84 +506,80 @@ export class DockviewComponent extends BaseGrid {
277
506
  group: event.panel,
278
507
  isMaximized: event.isMaximized,
279
508
  });
280
- }), Event.any(this.onDidAdd, this.onDidRemove)(() => {
281
- this.updateWatermark();
282
- }), Event.any(this.onDidAddPanel, this.onDidRemovePanel, this.onDidAddGroup, this.onDidRemove, this.onDidRemoveGroup, this.onDidMovePanel, this.onDidActivePanelChange, this.onDidPopoutGroupPositionChange, this.onDidPopoutGroupSizeChange, this.onDidCreateTabGroup, this.onDidDestroyTabGroup, this.onDidAddPanelToTabGroup, this.onDidRemovePanelFromTabGroup, this.onDidTabGroupChange, this.onDidTabGroupCollapsedChange)(() => {
509
+ }), Event.any(this.onDidAddPanel, this.onDidRemovePanel, this.onDidAddGroup, this.onDidRemove, this.onDidRemoveGroup, this.onDidMovePanel, this.onDidActivePanelChange)(() => {
283
510
  this._bufferOnDidLayoutChange.fire();
284
511
  }), Disposable.from(() => {
285
512
  var _a;
286
- // Cancel any pending popout-restoration timers scheduled by
287
- // fromJSON so they don't open new browser windows after
288
- // dispose, and resolve their promises so callers awaiting
289
- // popoutRestorationPromise don't hang. See issue #851.
290
- for (const cleanup of [...this._popoutRestorationCleanups]) {
291
- cleanup();
292
- }
293
- this._popoutRestorationCleanups.clear();
294
- // iterate over a copy of the array since .dispose() mutates the original array
295
- for (const group of [...this._floatingGroups]) {
296
- group.dispose();
297
- }
298
- // iterate over a copy of the array since .dispose() mutates the original array
299
- for (const group of [...this._popoutGroups]) {
300
- group.disposable.dispose();
301
- }
513
+ // Registry disposes init() subscriptions then every module
514
+ // service that implements IDisposable. The order matters so
515
+ // init handlers stop firing into services about to be torn
516
+ // down. Includes popout-restoration timer cancellation that
517
+ // resolves promises so awaiters of popoutRestorationPromise
518
+ // don't hang. See issue #851.
519
+ this._moduleRegistry.dispose();
302
520
  (_a = this._shellManager) === null || _a === void 0 ? void 0 : _a.dispose();
303
- for (const d of this._edgeGroupDisposables.values()) {
304
- d.dispose();
305
- }
306
- this._edgeGroupDisposables.clear();
307
- }), this._rootDropTarget, this._rootPointerDropTarget, Event.any(this._rootDropTarget.onWillShowOverlay, this._rootPointerDropTarget.onWillShowOverlay)((event) => {
308
- if (this.gridview.length > 0 && event.position === 'center') {
309
- // option only available when no panels in primary grid
310
- return;
311
- }
312
- this._onWillShowOverlay.fire(new DockviewWillShowOverlayLocationEvent(event, {
313
- kind: 'edge',
314
- panel: undefined,
315
- api: this._api,
316
- group: undefined,
317
- getData: getPanelData,
318
- }));
319
- }), Event.any(this._rootDropTarget.onDrop, this._rootPointerDropTarget.onDrop)((event) => {
320
- var _a;
321
- const willDropEvent = new DockviewWillDropEvent({
322
- nativeEvent: event.nativeEvent,
323
- position: event.position,
324
- panel: undefined,
325
- api: this._api,
326
- group: undefined,
327
- getData: getPanelData,
328
- kind: 'edge',
329
- });
330
- this._onWillDrop.fire(willDropEvent);
331
- if (willDropEvent.defaultPrevented) {
332
- return;
333
- }
334
- const data = getPanelData();
335
- if (data) {
336
- this.moveGroupOrPanel({
337
- from: {
338
- groupId: data.groupId,
339
- panelId: (_a = data.panelId) !== null && _a !== void 0 ? _a : undefined,
340
- },
341
- to: {
342
- group: this.orthogonalize(event.position),
343
- position: 'center',
344
- },
345
- });
346
- }
347
- else {
348
- this._onDidDrop.fire(new DockviewDidDropEvent({
521
+ }));
522
+ // Root edge-drop wiring lives with its (optional) module — guard it so
523
+ // the module is independently removable.
524
+ const rootDropTarget = this._rootDropTargetService;
525
+ if (rootDropTarget) {
526
+ this.addDisposables(rootDropTarget.onWillShowOverlay((event) => {
527
+ if (this.gridview.length > 0 &&
528
+ event.position === 'center') {
529
+ // option only available when no panels in primary grid
530
+ return;
531
+ }
532
+ this._onWillShowOverlay.fire(new DockviewWillShowOverlayLocationEvent(event, {
533
+ kind: 'edge',
534
+ panel: undefined,
535
+ api: this._api,
536
+ group: undefined,
537
+ getData: getPanelData,
538
+ }));
539
+ }), rootDropTarget.onDrop((event) => {
540
+ var _a;
541
+ const willDropEvent = new DockviewWillDropEvent({
349
542
  nativeEvent: event.nativeEvent,
350
543
  position: event.position,
351
544
  panel: undefined,
352
545
  api: this._api,
353
546
  group: undefined,
354
547
  getData: getPanelData,
355
- }));
356
- }
357
- }));
548
+ kind: 'edge',
549
+ });
550
+ this._onWillDrop.fire(willDropEvent);
551
+ if (willDropEvent.defaultPrevented) {
552
+ return;
553
+ }
554
+ const data = getPanelData();
555
+ if (data) {
556
+ this.moveGroupOrPanel({
557
+ from: {
558
+ groupId: data.groupId,
559
+ panelId: (_a = data.panelId) !== null && _a !== void 0 ? _a : undefined,
560
+ },
561
+ to: {
562
+ group: this.orthogonalize(event.position),
563
+ position: 'center',
564
+ },
565
+ });
566
+ }
567
+ else {
568
+ this._onDidDrop.fire(new DockviewDidDropEvent({
569
+ nativeEvent: event.nativeEvent,
570
+ position: event.position,
571
+ panel: undefined,
572
+ api: this._api,
573
+ group: undefined,
574
+ getData: getPanelData,
575
+ }));
576
+ }
577
+ }));
578
+ }
579
+ // Final module wiring: runs after the host is fully constructed.
580
+ // Modules subscribe to host events here so the component doesn't
581
+ // need to manually invoke them at scattered call sites.
582
+ this._moduleRegistry.postConstruct(this);
358
583
  }
359
584
  setVisible(panel, visible) {
360
585
  switch (panel.api.location.type) {
@@ -386,11 +611,29 @@ export class DockviewComponent extends BaseGrid {
386
611
  * and dismisses on events from that window.
387
612
  */
388
613
  getPopupServiceForGroup(group) {
389
- var _a;
390
- return (_a = this._popoutPopupServices.get(group.id)) !== null && _a !== void 0 ? _a : this.popupService;
614
+ var _a, _b;
615
+ return ((_b = (_a = this._popoutWindowService) === null || _a === void 0 ? void 0 : _a.getPopupService(group.id)) !== null && _b !== void 0 ? _b : this.popupService);
391
616
  }
392
617
  addPopoutGroup(itemToPopout, options) {
393
- var _a, _b, _c, _d, _e, _f;
618
+ // The transaction brackets the synchronous structural change; the
619
+ // popout window opens asynchronously after it resolves.
620
+ return this.mutation('popout', () => this._doAddPopoutGroup(itemToPopout, options));
621
+ }
622
+ /** Enumerate the popout groups currently open in their own windows. */
623
+ getPopouts() {
624
+ var _a, _b;
625
+ return ((_b = (_a = this._popoutWindowService) === null || _a === void 0 ? void 0 : _a.entries.map((entry) => ({
626
+ id: entry.popoutGroup.id,
627
+ group: entry.popoutGroup,
628
+ window: entry.getWindow(),
629
+ }))) !== null && _b !== void 0 ? _b : []);
630
+ }
631
+ _doAddPopoutGroup(itemToPopout, options) {
632
+ var _a, _b, _c, _d, _e;
633
+ const service = assertModule(this._popoutWindowService, 'PopoutWindow', 'api.addPopoutGroup');
634
+ if (!service) {
635
+ return Promise.resolve(false);
636
+ }
394
637
  if (itemToPopout instanceof DockviewGroupPanel &&
395
638
  itemToPopout.model.location.type === 'edge') {
396
639
  // edge groups are permanent structural elements and cannot be popped out
@@ -416,16 +659,21 @@ export class DockviewComponent extends BaseGrid {
416
659
  }
417
660
  const box = getBox();
418
661
  const groupId = (_b = (_a = options === null || options === void 0 ? void 0 : options.overridePopoutGroup) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : this.getNextGroupId();
662
+ // Resolve the configured popout URL once (per-call override → global
663
+ // option). Recorded on the entry / group locations so it survives
664
+ // serialization; the '/popout.html' default is applied only when
665
+ // actually opening the window, not baked into saved layouts.
666
+ const resolvedPopoutUrl = (_c = options === null || options === void 0 ? void 0 : options.popoutUrl) !== null && _c !== void 0 ? _c : (_d = this.options) === null || _d === void 0 ? void 0 : _d.popoutUrl;
419
667
  const _window = new PopoutWindow(`${this.id}-${groupId}`, // unique id
420
668
  theme !== null && theme !== void 0 ? theme : '', {
421
- url: (_e = (_c = options === null || options === void 0 ? void 0 : options.popoutUrl) !== null && _c !== void 0 ? _c : (_d = this.options) === null || _d === void 0 ? void 0 : _d.popoutUrl) !== null && _e !== void 0 ? _e : '/popout.html',
669
+ url: resolvedPopoutUrl !== null && resolvedPopoutUrl !== void 0 ? resolvedPopoutUrl : '/popout.html',
422
670
  left: window.screenX + box.left,
423
671
  top: window.screenY + box.top,
424
672
  width: box.width,
425
673
  height: box.height,
426
674
  onDidOpen: options === null || options === void 0 ? void 0 : options.onDidOpen,
427
675
  onWillClose: options === null || options === void 0 ? void 0 : options.onWillClose,
428
- nonce: (_f = this.options) === null || _f === void 0 ? void 0 : _f.nonce,
676
+ nonce: (_e = this.options) === null || _e === void 0 ? void 0 : _e.nonce,
429
677
  });
430
678
  const popoutWindowDisposable = new CompositeDisposable(_window, _window.onDidClose(() => {
431
679
  popoutWindowDisposable.dispose();
@@ -433,7 +681,7 @@ export class DockviewComponent extends BaseGrid {
433
681
  return _window
434
682
  .open()
435
683
  .then((popoutContainer) => {
436
- var _a;
684
+ var _a, _b, _c;
437
685
  if (_window.isDisposed) {
438
686
  return false;
439
687
  }
@@ -449,7 +697,12 @@ export class DockviewComponent extends BaseGrid {
449
697
  */
450
698
  const isGroupAddedToDom = referenceGroup.element.parentElement !== null;
451
699
  let group;
452
- if (!isGroupAddedToDom) {
700
+ if (options === null || options === void 0 ? void 0 : options.overridePopoutGridview) {
701
+ // Restoring a multi-group window: the anchor group is
702
+ // already built inside the supplied gridview.
703
+ group = (_a = options.overridePopoutGroup) !== null && _a !== void 0 ? _a : referenceGroup;
704
+ }
705
+ else if (!isGroupAddedToDom) {
453
706
  group = referenceGroup;
454
707
  }
455
708
  else if (options === null || options === void 0 ? void 0 : options.overridePopoutGroup) {
@@ -462,27 +715,46 @@ export class DockviewComponent extends BaseGrid {
462
715
  }
463
716
  }
464
717
  if (popoutContainer === null) {
465
- console.error('dockview: failed to create popout. perhaps you need to allow pop-ups for this website');
466
- popoutWindowDisposable.dispose();
467
- this._onDidOpenPopoutWindowFail.fire();
468
- // if the popout window was blocked, we need to move the group back to the reference group
469
- // and set it to visible
470
- this.movingLock(() => moveGroupWithoutDestroying({
471
- from: group,
472
- to: referenceGroup,
473
- }));
474
- if (!referenceGroup.api.isVisible) {
475
- referenceGroup.api.setVisible(true);
476
- }
718
+ this.handleBlockedPopout({
719
+ group,
720
+ referenceGroup,
721
+ options,
722
+ popoutWindowDisposable,
723
+ });
477
724
  return false;
478
725
  }
479
726
  const gready = document.createElement('div');
480
727
  gready.className = 'dv-overlay-render-container';
481
728
  const overlayRenderContainer = new OverlayRenderContainer(gready, this);
482
729
  group.model.renderContainer = overlayRenderContainer;
483
- group.layout(_window.window.innerWidth, _window.window.innerHeight);
730
+ // The popout window hosts its own gridview so it can grow into
731
+ // a nested splitview layout. The window starts with the single
732
+ // anchor group; further groups arrive via drag-and-drop. On
733
+ // restore a pre-populated gridview is supplied instead.
734
+ const popoutGridview = (_b = options === null || options === void 0 ? void 0 : options.overridePopoutGridview) !== null && _b !== void 0 ? _b : this.createNestedGridview();
735
+ if (!(options === null || options === void 0 ? void 0 : options.overridePopoutGridview)) {
736
+ popoutGridview.addView(group, Sizing.Distribute, [0]);
737
+ }
738
+ // Fill the popout window. Unlike the main grid (explicit px) and
739
+ // floating windows (CSS inside .dv-resize-container), the popout
740
+ // gridview has no sizing context, so without this it collapses
741
+ // to 0 height and nothing renders.
742
+ popoutGridview.element.style.width = '100%';
743
+ popoutGridview.element.style.height = '100%';
744
+ popoutGridview.layout(_window.window.innerWidth, _window.window.innerHeight);
745
+ // Guarded so the teardown's re-entrant paths (window close
746
+ // re-enters via the anchor's doRemoveGroup) never double-dispose.
747
+ let popoutGridviewDisposed = false;
748
+ const disposePopoutGridview = () => {
749
+ if (!popoutGridviewDisposed) {
750
+ popoutGridviewDisposed = true;
751
+ popoutGridview.dispose();
752
+ }
753
+ };
484
754
  let floatingBox;
485
- if (!(options === null || options === void 0 ? void 0 : options.overridePopoutGroup) && isGroupAddedToDom) {
755
+ if (!(options === null || options === void 0 ? void 0 : options.overridePopoutGroup) &&
756
+ !(options === null || options === void 0 ? void 0 : options.overridePopoutGridview) &&
757
+ isGroupAddedToDom) {
486
758
  if (itemToPopout instanceof DockviewPanel) {
487
759
  this.movingLock(() => {
488
760
  const panel = referenceGroup.model.removePanel(itemToPopout);
@@ -500,9 +772,9 @@ export class DockviewComponent extends BaseGrid {
500
772
  break;
501
773
  case 'floating':
502
774
  case 'popout':
503
- floatingBox = (_a = this._floatingGroups
775
+ floatingBox = (_c = this.floatingGroups
504
776
  .find((value) => value.group.api.id ===
505
- itemToPopout.api.id)) === null || _a === void 0 ? void 0 : _a.overlay.toJSON();
777
+ itemToPopout.api.id)) === null || _c === void 0 ? void 0 : _c.overlay.toJSON();
506
778
  this.removeGroup(referenceGroup);
507
779
  break;
508
780
  }
@@ -511,7 +783,7 @@ export class DockviewComponent extends BaseGrid {
511
783
  popoutContainer.classList.add('dv-dockview');
512
784
  popoutContainer.style.overflow = 'hidden';
513
785
  popoutContainer.appendChild(gready);
514
- popoutContainer.appendChild(group.element);
786
+ popoutContainer.appendChild(popoutGridview.element);
515
787
  const anchor = document.createElement('div');
516
788
  const dropTargetContainer = new DropTargetAnchorContainer(anchor, { disabled: this.rootDropTargetContainer.disabled });
517
789
  popoutContainer.appendChild(anchor);
@@ -522,20 +794,42 @@ export class DockviewComponent extends BaseGrid {
522
794
  // their pointerdown/keydown listeners fire on the right
523
795
  // window for outside-click and Escape dismissal.
524
796
  const popoutPopupService = new PopupService(popoutContainer, _window.window);
525
- this._popoutPopupServices.set(group.id, popoutPopupService);
797
+ service.setPopupService(group.id, popoutPopupService);
526
798
  popoutWindowDisposable.addDisposables(popoutPopupService, Disposable.from(() => {
527
- this._popoutPopupServices.delete(group.id);
799
+ service.deletePopupService(group.id);
528
800
  }));
529
801
  group.model.location = {
530
802
  type: 'popout',
531
803
  getWindow: () => _window.window,
532
- popoutUrl: options === null || options === void 0 ? void 0 : options.popoutUrl,
804
+ popoutUrl: resolvedPopoutUrl,
533
805
  };
806
+ if (options === null || options === void 0 ? void 0 : options.overridePopoutGridview) {
807
+ // Restored multi-group window. Wire every member (including
808
+ // the anchor) to this window's containers and popout
809
+ // location now that the gridview is attached and laid out —
810
+ // re-setting renderContainer forces a re-render at the right
811
+ // time so 'always'-rendered content positions in this
812
+ // window rather than where it was first created.
813
+ const members = this.groups.filter((candidate) => popoutGridview.element.contains(candidate.element));
814
+ for (const member of members) {
815
+ member.model.renderContainer = overlayRenderContainer;
816
+ member.model.dropTargetContainer = dropTargetContainer;
817
+ member.model.location = {
818
+ type: 'popout',
819
+ getWindow: () => _window.window,
820
+ popoutUrl: resolvedPopoutUrl,
821
+ };
822
+ }
823
+ }
534
824
  if (isGroupAddedToDom &&
535
825
  itemToPopout.api.location.type === 'grid') {
536
826
  itemToPopout.api.setVisible(false);
537
827
  }
538
828
  this.doSetGroupAndPanelActive(group);
829
+ const resizeObserverDisposable = service.observeGridviewSize(_window, popoutGridview, overlayRenderContainer);
830
+ if (resizeObserverDisposable) {
831
+ popoutWindowDisposable.addDisposables(resizeObserverDisposable);
832
+ }
539
833
  popoutWindowDisposable.addDisposables(group.api.onDidActiveChange((event) => {
540
834
  var _a;
541
835
  if (event.isActive) {
@@ -545,20 +839,28 @@ export class DockviewComponent extends BaseGrid {
545
839
  var _a;
546
840
  (_a = _window.window) === null || _a === void 0 ? void 0 : _a.focus();
547
841
  }));
548
- let returnedGroup;
842
+ // Holder so the close teardown (extracted below) can publish
843
+ // the group that was returned to the main grid back to the
844
+ // entry's `dispose()` contract.
845
+ const closeResult = {};
549
846
  const isValidReferenceGroup = isGroupAddedToDom &&
550
847
  referenceGroup &&
551
848
  this.getPanel(referenceGroup.id);
552
849
  const value = {
553
850
  window: _window,
554
851
  popoutGroup: group,
852
+ gridview: popoutGridview,
853
+ overlayRenderContainer,
854
+ dropTargetContainer,
855
+ getWindow: () => _window.window,
856
+ popoutUrl: resolvedPopoutUrl,
555
857
  referenceGroup: isValidReferenceGroup
556
858
  ? referenceGroup.id
557
859
  : undefined,
558
860
  disposable: {
559
861
  dispose: () => {
560
862
  popoutWindowDisposable.dispose();
561
- return returnedGroup;
863
+ return closeResult.returnedGroup;
562
864
  },
563
865
  },
564
866
  };
@@ -575,73 +877,23 @@ export class DockviewComponent extends BaseGrid {
575
877
  screenY: _window.window.screenX,
576
878
  group,
577
879
  });
578
- }),
579
- /**
580
- * ResizeObserver seems slow here, I do not know why but we don't need it
581
- * since we can reply on the window resize event as we will occupy the full
582
- * window dimensions
583
- */
584
- addDisposableListener(_window.window, 'resize', () => {
585
- group.layout(_window.window.innerWidth, _window.window.innerHeight);
586
- }), overlayRenderContainer, Disposable.from(() => {
587
- if (this.isDisposed) {
588
- return; // cleanup may run after instance is disposed
589
- }
590
- if (isGroupAddedToDom &&
591
- this.getPanel(referenceGroup.id)) {
592
- this.movingLock(() => moveGroupWithoutDestroying({
593
- from: group,
594
- to: referenceGroup,
595
- }));
596
- if (!referenceGroup.api.isVisible) {
597
- referenceGroup.api.setVisible(true);
598
- }
599
- if (this.getPanel(group.id)) {
600
- this.doRemoveGroup(group, {
601
- skipPopoutAssociated: true,
602
- });
603
- }
604
- }
605
- else if (this.getPanel(group.id)) {
606
- group.model.renderContainer =
607
- this.overlayRenderContainer;
608
- group.model.dropTargetContainer =
609
- this.rootDropTargetContainer;
610
- returnedGroup = group;
611
- const alreadyRemoved = !this._popoutGroups.find((p) => p.popoutGroup === group);
612
- if (alreadyRemoved) {
613
- /**
614
- * If this popout group was explicitly removed then we shouldn't run the additional
615
- * steps. To tell if the running of this disposable is the result of this popout group
616
- * being explicitly removed we can check if this popout group is still referenced in
617
- * the `this._popoutGroups` list.
618
- */
619
- return;
620
- }
621
- if (floatingBox) {
622
- this.addFloatingGroup(group, {
623
- height: floatingBox.height,
624
- width: floatingBox.width,
625
- position: floatingBox,
626
- });
627
- }
628
- else {
629
- this.doRemoveGroup(group, {
630
- skipDispose: true,
631
- skipActive: true,
632
- skipPopoutReturn: true,
633
- });
634
- group.model.location = { type: 'grid' };
635
- this.movingLock(() => {
636
- // suppress group add events since the group already exists
637
- this.doAddGroup(group, [0]);
638
- });
639
- }
640
- this.doSetGroupAndPanelActive(group);
641
- }
642
- }));
643
- this._popoutGroups.push(value);
644
- this.updateWatermark();
880
+ }), addDisposableListener(_window.window, 'resize', () => {
881
+ popoutGridview.layout(_window.window.innerWidth, _window.window.innerHeight);
882
+ }), overlayRenderContainer, Disposable.from(() => this.disposePopoutWindow({
883
+ group,
884
+ referenceGroup,
885
+ popoutGridview,
886
+ isGroupAddedToDom,
887
+ floatingBox,
888
+ disposePopoutGridview,
889
+ closeResult,
890
+ })));
891
+ service.add(value);
892
+ this._onDidAddPopoutGroup.fire({
893
+ id: value.popoutGroup.id,
894
+ group: value.popoutGroup,
895
+ window: value.getWindow(),
896
+ });
645
897
  return true;
646
898
  })
647
899
  .catch((err) => {
@@ -649,8 +901,189 @@ export class DockviewComponent extends BaseGrid {
649
901
  return false;
650
902
  });
651
903
  }
904
+ /**
905
+ * The popout window was blocked (e.g. by the browser's popup blocker —
906
+ * common when restoring popouts on load). Fall back gracefully so the
907
+ * group(s) end up valid and visible in the main grid rather than as
908
+ * orphans that later crash clear()/remove().
909
+ */
910
+ handleBlockedPopout(params) {
911
+ const { group, referenceGroup, options, popoutWindowDisposable } = params;
912
+ console.error('dockview: failed to create popout. perhaps you need to allow pop-ups for this website');
913
+ popoutWindowDisposable.dispose();
914
+ this._onDidOpenPopoutWindowFail.fire();
915
+ if (options === null || options === void 0 ? void 0 : options.overridePopoutGridview) {
916
+ // Restoring a multi-group popout window: its nested gridview was
917
+ // built up-front but never attached to a window. Dock every member
918
+ // into the main grid so no group is lost, then discard the
919
+ // detached gridview.
920
+ const blockedGridview = options.overridePopoutGridview;
921
+ const members = this.groups.filter((candidate) => blockedGridview.element.contains(candidate.element));
922
+ for (const member of members) {
923
+ this.movingLock(() => {
924
+ blockedGridview.remove(member);
925
+ this.redockGroupToMainGrid(member);
926
+ });
927
+ }
928
+ blockedGridview.dispose();
929
+ if (referenceGroup && !referenceGroup.api.isVisible) {
930
+ referenceGroup.api.setVisible(true);
931
+ }
932
+ return;
933
+ }
934
+ if (group === referenceGroup) {
935
+ // No separate grid group to return to (e.g. restoring a popout
936
+ // straight from JSON) — dock this group into the main grid.
937
+ if (!this.gridview.element.contains(group.element)) {
938
+ this.movingLock(() => this.doAddGroup(group, [0]));
939
+ group.model.location = { type: 'grid' };
940
+ }
941
+ }
942
+ else {
943
+ // A fresh group was created for the popout — return its panels to
944
+ // the reference group and discard the now-empty popout group so it
945
+ // doesn't linger as an orphan.
946
+ this.movingLock(() => moveGroupWithoutDestroying({
947
+ from: group,
948
+ to: referenceGroup,
949
+ }));
950
+ if (group.model.size === 0 && this._groups.has(group.id)) {
951
+ group.dispose();
952
+ this._groups.delete(group.id);
953
+ this._onDidRemoveGroup.fire(group);
954
+ }
955
+ }
956
+ if (!referenceGroup.api.isVisible) {
957
+ referenceGroup.api.setVisible(true);
958
+ }
959
+ }
960
+ /**
961
+ * Wire a group that has been displaced from a floating / popout window back
962
+ * to the main grid's render & drop-target containers and dock it at the
963
+ * root. The caller is responsible for first detaching it from its old
964
+ * gridview — the detach strategy differs between the window-teardown path
965
+ * (`doRemoveGroup`) and the blocked-window path (`gridview.remove`).
966
+ */
967
+ redockGroupToMainGrid(group) {
968
+ group.model.renderContainer = this.overlayRenderContainer;
969
+ group.model.dropTargetContainer = this.rootDropTargetContainer;
970
+ group.model.location = { type: 'grid' };
971
+ this.doAddGroup(group, [0]);
972
+ }
973
+ /**
974
+ * Teardown for a popout window's `popoutWindowDisposable`. Runs when the
975
+ * window closes (by user, by `removeGroup`, or by component disposal):
976
+ * relocates every member group back to the main grid (or to a floating
977
+ * window when the anchor came from one), then disposes the nested gridview.
978
+ * `closeResult.returnedGroup` is read by the entry's `dispose()` contract.
979
+ */
980
+ disposePopoutWindow(params) {
981
+ var _a;
982
+ const { group, referenceGroup, popoutGridview, isGroupAddedToDom, floatingBox, disposePopoutGridview, closeResult, } = params;
983
+ if (this.isDisposed) {
984
+ // cleanup may run after instance is disposed; just tear down the
985
+ // nested gridview.
986
+ disposePopoutGridview();
987
+ return;
988
+ }
989
+ // Distinguish a genuine window close from an explicit `removeGroup`:
990
+ // the explicit-removal paths remove the service entry *before* this
991
+ // disposable runs. Key off the stable `popoutGridview` rather than the
992
+ // captured `group`, which may no longer be the window's anchor (it can
993
+ // have been dragged out, promoting another member to anchor).
994
+ const genuineClose = !!((_a = this._popoutWindowService) === null || _a === void 0 ? void 0 : _a.entries.find((entry) => entry.gridview === popoutGridview));
995
+ // The groups still living in this window, resolved from the gridview so
996
+ // a reassigned anchor doesn't hide surviving members.
997
+ const members = this.groups.filter((candidate) => popoutGridview.element.contains(candidate.element));
998
+ const anchorPresent = members.includes(group);
999
+ const anchorIsSoleMember = anchorPresent && members.length === 1;
1000
+ // On a genuine close, relocate every member that ISN'T the captured
1001
+ // anchor back to the main grid. The captured anchor (if still here) gets
1002
+ // the reference-return / re-float treatment below. Explicit removal
1003
+ // relocates via its own path, so the loop is skipped for it.
1004
+ if (genuineClose) {
1005
+ for (const member of members) {
1006
+ if (member === group) {
1007
+ continue;
1008
+ }
1009
+ this.movingLock(() => {
1010
+ this.doRemoveGroup(member, {
1011
+ skipDispose: true,
1012
+ skipActive: true,
1013
+ skipPopoutReturn: true,
1014
+ });
1015
+ this.redockGroupToMainGrid(member);
1016
+ });
1017
+ }
1018
+ }
1019
+ if (anchorPresent &&
1020
+ isGroupAddedToDom &&
1021
+ this.getPanel(referenceGroup.id)) {
1022
+ this.movingLock(() => moveGroupWithoutDestroying({
1023
+ from: group,
1024
+ to: referenceGroup,
1025
+ }));
1026
+ if (!referenceGroup.api.isVisible) {
1027
+ referenceGroup.api.setVisible(true);
1028
+ }
1029
+ if (this.getPanel(group.id)) {
1030
+ this.doRemoveGroup(group, {
1031
+ skipPopoutAssociated: true,
1032
+ });
1033
+ }
1034
+ }
1035
+ else if (anchorPresent && this.getPanel(group.id)) {
1036
+ group.model.renderContainer = this.overlayRenderContainer;
1037
+ group.model.dropTargetContainer = this.rootDropTargetContainer;
1038
+ closeResult.returnedGroup = group;
1039
+ if (!genuineClose) {
1040
+ /**
1041
+ * If this popout group was explicitly removed then we shouldn't run the additional
1042
+ * steps. The explicit remover re-docks the returned group itself; here we only hand
1043
+ * it back (above) and tear down the nested gridview.
1044
+ */
1045
+ disposePopoutGridview();
1046
+ return;
1047
+ }
1048
+ // Re-float only restores the pre-popout state of a SINGLE popped-out
1049
+ // group. A multi-group window must not be split (anchor re-floats
1050
+ // while the rest dock to the grid), so dock the anchor to the grid
1051
+ // alongside the other members once they're no longer alone.
1052
+ if (floatingBox && anchorIsSoleMember) {
1053
+ this.addFloatingGroup(group, {
1054
+ height: floatingBox.height,
1055
+ width: floatingBox.width,
1056
+ position: floatingBox,
1057
+ });
1058
+ }
1059
+ else {
1060
+ this.doRemoveGroup(group, {
1061
+ skipDispose: true,
1062
+ skipActive: true,
1063
+ skipPopoutReturn: true,
1064
+ });
1065
+ group.model.location = { type: 'grid' };
1066
+ this.movingLock(() => {
1067
+ // suppress group add events since the group already exists
1068
+ this.doAddGroup(group, [0]);
1069
+ });
1070
+ }
1071
+ this.doSetGroupAndPanelActive(group);
1072
+ }
1073
+ // All members have been relocated out; tear down the window's nested
1074
+ // gridview (does not dispose the leaf views — their lifecycle stays
1075
+ // with `_groups`).
1076
+ disposePopoutGridview();
1077
+ }
652
1078
  addFloatingGroup(item, options) {
653
- var _a, _b, _c, _d, _e, _f;
1079
+ this.mutation('float', () => this._doAddFloatingGroup(item, options));
1080
+ }
1081
+ _doAddFloatingGroup(item, options) {
1082
+ var _a;
1083
+ const service = assertModule(this._floatingGroupService, 'FloatingGroup', 'api.addFloatingGroup');
1084
+ if (!service) {
1085
+ return;
1086
+ }
654
1087
  if (item instanceof DockviewGroupPanel &&
655
1088
  item.model.location.type === 'edge') {
656
1089
  // edge groups are permanent structural elements and cannot be floated
@@ -669,7 +1102,7 @@ export class DockviewComponent extends BaseGrid {
669
1102
  }
670
1103
  else {
671
1104
  group = item;
672
- const popoutReferenceGroupId = (_a = this._popoutGroups.find((_) => _.popoutGroup === group)) === null || _a === void 0 ? void 0 : _a.referenceGroup;
1105
+ const popoutReferenceGroupId = (_a = this._popoutWindowService) === null || _a === void 0 ? void 0 : _a.findReferenceGroupId(group);
673
1106
  const popoutReferenceGroup = popoutReferenceGroupId
674
1107
  ? this.getPanel(popoutReferenceGroupId)
675
1108
  : undefined;
@@ -750,65 +1183,87 @@ export class DockviewComponent extends BaseGrid {
750
1183
  };
751
1184
  }
752
1185
  const anchoredBox = getAnchoredBox();
753
- const overlay = new Overlay(Object.assign(Object.assign({ container: (_b = this._floatingOverlayHost) !== null && _b !== void 0 ? _b : this.gridview.element, content: group.element }, anchoredBox), { minimumInViewportWidth: this.options.floatingGroupBounds === 'boundedWithinViewport'
1186
+ // The floating window hosts its own gridview so it can grow into a
1187
+ // nested splitview layout. The window starts with the single anchor
1188
+ // group; further groups are added via drag-and-drop.
1189
+ const floatingGridview = this.createNestedGridview();
1190
+ floatingGridview.addView(group, Sizing.Distribute, [0]);
1191
+ this.mountFloatingWindow(floatingGridview, group, [group], anchoredBox, {
1192
+ dragHandle: options === null || options === void 0 ? void 0 : options.dragHandle,
1193
+ inDragMode: options === null || options === void 0 ? void 0 : options.inDragMode,
1194
+ skipActiveGroup: options === null || options === void 0 ? void 0 : options.skipActiveGroup,
1195
+ });
1196
+ }
1197
+ /**
1198
+ * Build an empty gridview configured to match the main grid's styling, for
1199
+ * hosting a nested layout inside a floating or popout window.
1200
+ */
1201
+ createNestedGridview(orientation = Orientation.HORIZONTAL) {
1202
+ var _a, _b;
1203
+ return new Gridview(true, this.options.hideBorders
1204
+ ? { separatorBorder: 'transparent' }
1205
+ : undefined, orientation, false, (_b = (_a = this.options.theme) === null || _a === void 0 ? void 0 : _a.gap) !== null && _b !== void 0 ? _b : 0);
1206
+ }
1207
+ /**
1208
+ * Wrap a (populated) floating gridview in an overlay window: title bar /
1209
+ * move handle, drag wiring, the floating-group service entry and the
1210
+ * `floating` location tag for every member group.
1211
+ */
1212
+ mountFloatingWindow(floatingGridview, anchorGroup, members, anchoredBox, options) {
1213
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1214
+ const service = assertModule(this._floatingGroupService, 'FloatingGroup', 'api.addFloatingGroup');
1215
+ if (!service) {
1216
+ return;
1217
+ }
1218
+ const dragHandleMode = (_b = (_a = options === null || options === void 0 ? void 0 : options.dragHandle) !== null && _a !== void 0 ? _a : this.options.floatingGroupDragHandle) !== null && _b !== void 0 ? _b : 'titlebar';
1219
+ // `'titlebar'` renders a dedicated grab bar above the tab bar and uses
1220
+ // it as the move handle; `'tabbar'` falls back to moving via the
1221
+ // tab-bar void container.
1222
+ const titleBar = dragHandleMode === 'titlebar'
1223
+ ? new FloatingTitleBar(this, anchorGroup)
1224
+ : undefined;
1225
+ const overlay = new Overlay(Object.assign(Object.assign({ container: (_c = this._floatingOverlayHost) !== null && _c !== void 0 ? _c : this.gridview.element, content: floatingGridview.element, header: titleBar === null || titleBar === void 0 ? void 0 : titleBar.element }, anchoredBox), { minimumInViewportWidth: this.options.floatingGroupBounds === 'boundedWithinViewport'
754
1226
  ? undefined
755
- : ((_d = (_c = this.options.floatingGroupBounds) === null || _c === void 0 ? void 0 : _c.minimumWidthWithinViewport) !== null && _d !== void 0 ? _d : DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE), minimumInViewportHeight: this.options.floatingGroupBounds === 'boundedWithinViewport'
1227
+ : ((_e = (_d = this.options.floatingGroupBounds) === null || _d === void 0 ? void 0 : _d.minimumWidthWithinViewport) !== null && _e !== void 0 ? _e : DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE), minimumInViewportHeight: this.options.floatingGroupBounds === 'boundedWithinViewport'
756
1228
  ? undefined
757
- : ((_f = (_e = this.options.floatingGroupBounds) === null || _e === void 0 ? void 0 : _e.minimumHeightWithinViewport) !== null && _f !== void 0 ? _f : DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE) }));
758
- const el = group.element.querySelector('.dv-void-container');
759
- if (!el) {
1229
+ : ((_g = (_f = this.options.floatingGroupBounds) === null || _f === void 0 ? void 0 : _f.minimumHeightWithinViewport) !== null && _g !== void 0 ? _g : DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE), transformDragPosition: this.options.transformFloatingGroupDrag
1230
+ ? (context) => this.options.transformFloatingGroupDrag({
1231
+ group: anchorGroup,
1232
+ proposed: context.proposed,
1233
+ container: context.container,
1234
+ others: context.others,
1235
+ })
1236
+ : undefined, getSiblingBoxes: () => this._gatherFloatingGroupBoxes(anchorGroup) }));
1237
+ const dragHandle = (_h = titleBar === null || titleBar === void 0 ? void 0 : titleBar.element) !== null && _h !== void 0 ? _h : anchorGroup.element.querySelector('.dv-void-container');
1238
+ if (!dragHandle) {
760
1239
  throw new Error('dockview: failed to find drag handle');
761
1240
  }
762
- overlay.setupDrag(el, {
1241
+ overlay.setupDrag(dragHandle, {
763
1242
  inDragMode: typeof (options === null || options === void 0 ? void 0 : options.inDragMode) === 'boolean'
764
1243
  ? options.inDragMode
765
1244
  : false,
766
1245
  });
767
- const floatingGroupPanel = new DockviewFloatingGroupPanel(group, overlay);
768
- const disposable = new CompositeDisposable(group.api.onDidActiveChange((event) => {
769
- if (event.isActive) {
770
- overlay.bringToFront();
771
- }
772
- }), (() => {
773
- let lastWidth = -1;
774
- let lastHeight = -1;
775
- return watchElementResize(group.element, (entry) => {
776
- const width = Math.round(entry.contentRect.width);
777
- const height = Math.round(entry.contentRect.height);
778
- if (width === lastWidth && height === lastHeight) {
779
- return;
780
- }
781
- lastWidth = width;
782
- lastHeight = height;
783
- group.layout(width, height); // let the group know it's size is changing so it can fire events to the panel
784
- });
785
- })());
786
- floatingGroupPanel.addDisposables(overlay.onDidChange(() => {
787
- // this is either a resize or a move
788
- // to inform the panels .layout(...) the group with it's current size
789
- // don't care about resize since the above watcher handles that
790
- group.layout(group.width, group.height);
791
- }), overlay.onDidChangeEnd(() => {
792
- this._bufferOnDidLayoutChange.fire();
793
- }), group.onDidChange((event) => {
794
- overlay.setBounds({
795
- height: event === null || event === void 0 ? void 0 : event.height,
796
- width: event === null || event === void 0 ? void 0 : event.width,
797
- });
798
- }), {
799
- dispose: () => {
800
- disposable.dispose();
801
- remove(this._floatingGroups, floatingGroupPanel);
802
- group.model.location = { type: 'grid' };
803
- this.updateWatermark();
804
- },
805
- });
806
- this._floatingGroups.push(floatingGroupPanel);
807
- group.model.location = { type: 'floating' };
1246
+ const floatingGroupPanel = service.add(anchorGroup, overlay, floatingGridview);
1247
+ if (titleBar) {
1248
+ // Tie the title bar's lifetime to the floating window and surface
1249
+ // its redock drag through the same public `onWillDragGroup` event
1250
+ // the tab-bar handle uses. Register it so anchor reassignment (when
1251
+ // the original anchor leaves a multi-group window) retargets the
1252
+ // bar at a group that still lives here.
1253
+ floatingGroupPanel.setTitleBar(titleBar);
1254
+ floatingGroupPanel.addDisposables(titleBar, Disposable.from(() => floatingGroupPanel.setTitleBar(undefined)), titleBar.onDragStart((event) => {
1255
+ this._onWillDragGroup.fire({
1256
+ nativeEvent: event,
1257
+ group: floatingGroupPanel.group,
1258
+ });
1259
+ }));
1260
+ }
1261
+ for (const member of members) {
1262
+ member.model.location = { type: 'floating' };
1263
+ }
808
1264
  if (!(options === null || options === void 0 ? void 0 : options.skipActiveGroup)) {
809
- this.doSetGroupAndPanelActive(group);
1265
+ this.doSetGroupAndPanelActive(anchorGroup);
810
1266
  }
811
- this.updateWatermark();
812
1267
  }
813
1268
  orthogonalize(position, options) {
814
1269
  this.gridview.normalize();
@@ -847,29 +1302,8 @@ export class DockviewComponent extends BaseGrid {
847
1302
  updateOptions(options) {
848
1303
  var _a, _b, _c, _d, _e;
849
1304
  super.updateOptions(options);
850
- if ('floatingGroupBounds' in options) {
851
- for (const group of this._floatingGroups) {
852
- switch (options.floatingGroupBounds) {
853
- case 'boundedWithinViewport':
854
- group.overlay.minimumInViewportHeight = undefined;
855
- group.overlay.minimumInViewportWidth = undefined;
856
- break;
857
- case undefined:
858
- group.overlay.minimumInViewportHeight =
859
- DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE;
860
- group.overlay.minimumInViewportWidth =
861
- DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE;
862
- break;
863
- default:
864
- group.overlay.minimumInViewportHeight =
865
- (_a = options.floatingGroupBounds) === null || _a === void 0 ? void 0 : _a.minimumHeightWithinViewport;
866
- group.overlay.minimumInViewportWidth =
867
- (_b = options.floatingGroupBounds) === null || _b === void 0 ? void 0 : _b.minimumWidthWithinViewport;
868
- }
869
- group.overlay.setBounds();
870
- }
871
- }
872
- this.updateDropTargetModel(options);
1305
+ (_a = this._floatingGroupService) === null || _a === void 0 ? void 0 : _a.updateBounds(options);
1306
+ (_b = this._rootDropTargetService) === null || _b === void 0 ? void 0 : _b.setOptions(options);
873
1307
  const oldDisableDnd = this.options.disableDnd;
874
1308
  const oldDndStrategy = this.options.dndStrategy;
875
1309
  this._options = Object.assign(Object.assign({}, this.options), options);
@@ -885,17 +1319,10 @@ export class DockviewComponent extends BaseGrid {
885
1319
  if ('createRightHeaderActionComponent' in options ||
886
1320
  'createLeftHeaderActionComponent' in options ||
887
1321
  'createPrefixHeaderActionComponent' in options) {
888
- for (const group of this.groups) {
889
- group.model.updateHeaderActions();
890
- }
1322
+ (_c = this.headerActionsService) === null || _c === void 0 ? void 0 : _c.refreshAll();
891
1323
  }
892
1324
  if ('createWatermarkComponent' in options) {
893
- if (this._watermark) {
894
- this._watermark.element.parentElement.remove();
895
- (_d = (_c = this._watermark).dispose) === null || _d === void 0 ? void 0 : _d.call(_c);
896
- this._watermark = null;
897
- }
898
- this.updateWatermark();
1325
+ (_d = this._watermarkService) === null || _d === void 0 ? void 0 : _d.refresh();
899
1326
  for (const group of this.groups) {
900
1327
  group.model.refreshWatermark();
901
1328
  }
@@ -912,6 +1339,7 @@ export class DockviewComponent extends BaseGrid {
912
1339
  this._layoutFromShell(this.gridview.width, this.gridview.height);
913
1340
  }
914
1341
  layout(width, height, forceResize) {
1342
+ var _a, _b;
915
1343
  if (this._shellManager && !this._inShellLayout) {
916
1344
  this._shellManager.layout(width, height);
917
1345
  }
@@ -919,12 +1347,9 @@ export class DockviewComponent extends BaseGrid {
919
1347
  super.layout(width, height, forceResize);
920
1348
  }
921
1349
  this._syncFloatingOverlayHost();
922
- if (this._floatingGroups) {
923
- for (const floating of this._floatingGroups) {
924
- // ensure floting groups stay within visible boundaries
925
- floating.overlay.setBounds();
926
- }
927
- }
1350
+ // floatingGroupService may be undefined during super() (BaseGrid calls
1351
+ // layout(0, 0) before subclass field initialisers run).
1352
+ (_b = (_a = this._moduleRegistry) === null || _a === void 0 ? void 0 : _a.services.floatingGroupService) === null || _b === void 0 ? void 0 : _b.constrainBounds();
928
1353
  }
929
1354
  _syncFloatingOverlayHost() {
930
1355
  if (!this._floatingOverlayHost || !this._shellManager) {
@@ -952,27 +1377,32 @@ export class DockviewComponent extends BaseGrid {
952
1377
  }
953
1378
  }
954
1379
  addEdgeGroup(position, options) {
955
- if (this._edgeGroups.has(position)) {
1380
+ const service = assertModule(this._edgeGroupService, 'EdgeGroup', 'api.addEdgeGroup');
1381
+ if (!service) {
1382
+ throw new Error(`dockview: EdgeGroup module is not registered`);
1383
+ }
1384
+ if (service.has(position)) {
956
1385
  throw new Error(`dockview: edge group already exists at position '${position}'`);
957
1386
  }
958
- const group = this.createGroup({ id: options.id });
959
- group.model.location = { type: 'edge', position };
960
- group.model.headerPosition = position;
961
- this._edgeGroups.set(position, group);
962
- this._onDidAddGroup.fire(group);
963
- // Collapse when the group becomes empty
964
- const autoCollapseDisposable = group.model.onDidRemovePanel(() => {
965
- if (group.model.isEmpty) {
966
- this.setEdgeGroupCollapsed(group, true);
967
- }
1387
+ return this.mutation('add', () => {
1388
+ const group = this.createGroup({ id: options.id });
1389
+ group.model.location = { type: 'edge', position };
1390
+ group.model.headerPosition = position;
1391
+ // Collapse when the group becomes empty
1392
+ const autoCollapseDisposable = group.model.onDidRemovePanel(() => {
1393
+ if (group.model.isEmpty) {
1394
+ this.setEdgeGroupCollapsed(group, true);
1395
+ }
1396
+ });
1397
+ service.add(position, group, autoCollapseDisposable);
1398
+ this._onDidAddGroup.fire(group);
1399
+ this._shellManager.addEdgeView(position, options, group);
1400
+ return group.api;
968
1401
  });
969
- this._edgeGroupDisposables.set(position, autoCollapseDisposable);
970
- this._shellManager.addEdgeView(position, options, group);
971
- return group.api;
972
1402
  }
973
1403
  getEdgeGroup(position) {
974
- var _a;
975
- return (_a = this._edgeGroups.get(position)) === null || _a === void 0 ? void 0 : _a.api;
1404
+ var _a, _b;
1405
+ return (_b = (_a = this._edgeGroupService) === null || _a === void 0 ? void 0 : _a.get(position)) === null || _b === void 0 ? void 0 : _b.api;
976
1406
  }
977
1407
  setEdgeGroupVisible(position, visible) {
978
1408
  this._shellManager.setEdgeGroupVisible(position, visible);
@@ -981,54 +1411,54 @@ export class DockviewComponent extends BaseGrid {
981
1411
  return this._shellManager.isEdgeGroupVisible(position);
982
1412
  }
983
1413
  removeEdgeGroup(position) {
984
- var _a;
985
- const group = this._edgeGroups.get(position);
1414
+ const service = assertModule(this._edgeGroupService, 'EdgeGroup', 'api.removeEdgeGroup');
1415
+ if (!service) {
1416
+ return;
1417
+ }
1418
+ const group = service.get(position);
986
1419
  if (!group) {
987
1420
  throw new Error(`dockview: no edge group exists at position '${position}'`);
988
1421
  }
989
- // Remove panels inside the group first
990
- for (const panel of [...group.panels]) {
991
- this.removePanel(panel, {
992
- removeEmptyGroup: false,
993
- skipDispose: false,
994
- });
995
- }
996
- // Dispose the auto-collapse listener
997
- (_a = this._edgeGroupDisposables.get(position)) === null || _a === void 0 ? void 0 : _a.dispose();
998
- this._edgeGroupDisposables.delete(position);
999
- // Remove from the shell splitview
1000
- this._shellManager.removeEdgeView(position);
1001
- // Clean up group state
1002
- this._edgeGroups.delete(position);
1003
- group.dispose();
1004
- this._groups.delete(group.id);
1005
- this._onDidRemoveGroup.fire(group);
1006
- }
1007
- setEdgeGroupCollapsed(group, collapsed) {
1008
- for (const [position, edgeGroup] of this._edgeGroups) {
1009
- if (edgeGroup === group) {
1010
- if (this._shellManager.isEdgeGroupCollapsed(position) ===
1011
- collapsed) {
1012
- // Skip the splitview resize on a no-op: with non-zero
1013
- // theme gap, redundant resizeView calls accumulate
1014
- // rounding drift that gradually shrinks the group.
1015
- return;
1016
- }
1017
- this._shellManager.setEdgeGroupCollapsed(position, collapsed);
1018
- edgeGroup.api._onDidCollapsedChange.fire({
1019
- isCollapsed: collapsed,
1422
+ // One transaction the per-panel removals below nest via the depth
1423
+ // counter, so consumers see a single edge-group removal.
1424
+ this.mutation('remove', () => {
1425
+ // Remove panels inside the group first
1426
+ for (const panel of [...group.panels]) {
1427
+ this.removePanel(panel, {
1428
+ removeEmptyGroup: false,
1429
+ skipDispose: false,
1020
1430
  });
1021
- return;
1022
1431
  }
1432
+ // Remove from the shell splitview
1433
+ this._shellManager.removeEdgeView(position);
1434
+ // Clean up service-tracked state + group itself
1435
+ service.remove(position);
1436
+ group.dispose();
1437
+ this._groups.delete(group.id);
1438
+ this._onDidRemoveGroup.fire(group);
1439
+ });
1440
+ }
1441
+ setEdgeGroupCollapsed(group, collapsed) {
1442
+ var _a;
1443
+ const position = (_a = this._edgeGroupService) === null || _a === void 0 ? void 0 : _a.findPositionOf(group);
1444
+ if (!position) {
1445
+ return;
1446
+ }
1447
+ if (this._shellManager.isEdgeGroupCollapsed(position) === collapsed) {
1448
+ // Skip the splitview resize on a no-op: with non-zero theme gap,
1449
+ // redundant resizeView calls accumulate rounding drift that
1450
+ // gradually shrinks the group.
1451
+ return;
1023
1452
  }
1453
+ this._shellManager.setEdgeGroupCollapsed(position, collapsed);
1454
+ group.api._onDidCollapsedChange.fire({ isCollapsed: collapsed });
1024
1455
  }
1025
1456
  isEdgeGroupCollapsed(group) {
1026
- for (const [position, edgeGroup] of this._edgeGroups) {
1027
- if (edgeGroup === group) {
1028
- return this._shellManager.isEdgeGroupCollapsed(position);
1029
- }
1030
- }
1031
- return false;
1457
+ var _a;
1458
+ const position = (_a = this._edgeGroupService) === null || _a === void 0 ? void 0 : _a.findPositionOf(group);
1459
+ return position
1460
+ ? this._shellManager.isEdgeGroupCollapsed(position)
1461
+ : false;
1032
1462
  }
1033
1463
  updateDragAndDropState() {
1034
1464
  // Update draggable state for all tabs and void containers
@@ -1092,32 +1522,18 @@ export class DockviewComponent extends BaseGrid {
1092
1522
  * @returns A JSON respresentation of the layout
1093
1523
  */
1094
1524
  toJSON() {
1095
- var _a;
1525
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1096
1526
  const data = this.gridview.serialize();
1097
1527
  const panels = this.panels.reduce((collection, panel) => {
1098
1528
  collection[panel.id] = panel.toJSON();
1099
1529
  return collection;
1100
1530
  }, {});
1101
- const floats = this._floatingGroups.map((group) => {
1102
- return {
1103
- data: group.group.toJSON(),
1104
- position: group.overlay.toJSON(),
1105
- };
1106
- });
1107
- const popoutGroups = this._popoutGroups.map((group) => {
1108
- return {
1109
- data: group.popoutGroup.toJSON(),
1110
- gridReferenceGroup: group.referenceGroup,
1111
- position: group.window.dimensions(),
1112
- url: group.popoutGroup.api.location.type === 'popout'
1113
- ? group.popoutGroup.api.location.popoutUrl
1114
- : undefined,
1115
- };
1116
- });
1531
+ const floats = (_b = (_a = this._floatingGroupService) === null || _a === void 0 ? void 0 : _a.serialize()) !== null && _b !== void 0 ? _b : [];
1532
+ const popoutGroups = (_d = (_c = this._popoutWindowService) === null || _c === void 0 ? void 0 : _c.serialize()) !== null && _d !== void 0 ? _d : [];
1117
1533
  const result = {
1118
1534
  grid: data,
1119
1535
  panels,
1120
- activeGroup: (_a = this.activeGroup) === null || _a === void 0 ? void 0 : _a.id,
1536
+ activeGroup: (_e = this.activeGroup) === null || _e === void 0 ? void 0 : _e.id,
1121
1537
  };
1122
1538
  if (floats.length > 0) {
1123
1539
  result.floatingGroups = floats;
@@ -1125,10 +1541,11 @@ export class DockviewComponent extends BaseGrid {
1125
1541
  if (popoutGroups.length > 0) {
1126
1542
  result.popoutGroups = popoutGroups;
1127
1543
  }
1128
- if (this._edgeGroups.size > 0) {
1544
+ const edgeEntries = (_g = (_f = this._edgeGroupService) === null || _f === void 0 ? void 0 : _f.entries()) !== null && _g !== void 0 ? _g : [];
1545
+ if ((_h = this._edgeGroupService) === null || _h === void 0 ? void 0 : _h.hasAny()) {
1129
1546
  const shellSerialized = this._shellManager.toJSON();
1130
1547
  // Augment each entry with the serialized group state
1131
- for (const [position, group] of this._edgeGroups) {
1548
+ for (const [position, group] of edgeEntries) {
1132
1549
  const entry = shellSerialized[position];
1133
1550
  if (entry) {
1134
1551
  entry.group = group.toJSON();
@@ -1139,7 +1556,19 @@ export class DockviewComponent extends BaseGrid {
1139
1556
  return result;
1140
1557
  }
1141
1558
  fromJSON(data, options) {
1142
- var _a, _b, _c;
1559
+ // One 'load' transaction for the whole deserialization — the many
1560
+ // nested add/remove mutations join it via the depth counter.
1561
+ this.mutation('load', () => this._doFromJSON(data, options));
1562
+ }
1563
+ _doFromJSON(data, options) {
1564
+ var _a, _b, _c, _d, _e, _f, _g;
1565
+ // Cancel any popout-restoration timers queued by a previous fromJSON
1566
+ // that haven't fired yet. The cancel path also disposes orphan groups
1567
+ // registered in _groups synchronously but never parented into a popout
1568
+ // window — otherwise the upcoming clear() would call gridview.remove()
1569
+ // on an unparented element and throw "Invalid grid element". See
1570
+ // issue #1304.
1571
+ (_a = this._popoutWindowService) === null || _a === void 0 ? void 0 : _a.cancelPendingRestorations();
1143
1572
  const existingPanels = new Map();
1144
1573
  let tempGroup;
1145
1574
  if (options === null || options === void 0 ? void 0 : options.reuseExistingPanels) {
@@ -1256,121 +1685,23 @@ export class DockviewComponent extends BaseGrid {
1256
1685
  });
1257
1686
  this._layoutFromShell(width, height);
1258
1687
  if (data.edgeGroups) {
1259
- // Auto-create edge groups for positions in the serialized state
1260
- // that don't already have a group registered (e.g. when fromJSON
1261
- // is called before the user has called addEdgeGroup).
1262
- for (const _position of [
1263
- 'top',
1264
- 'bottom',
1265
- 'left',
1266
- 'right',
1267
- ]) {
1268
- const fixedData = data.edgeGroups[_position];
1269
- if (fixedData && !this._edgeGroups.has(_position)) {
1270
- const groupState = fixedData.group;
1271
- const id = (_a = groupState === null || groupState === void 0 ? void 0 : groupState.id) !== null && _a !== void 0 ? _a : `${_position}-group`;
1272
- this.addEdgeGroup(_position, { id });
1273
- }
1274
- }
1275
- // Restore panel contents of edge groups
1276
- for (const [position, edgeGroup] of this._edgeGroups) {
1277
- const edgeData = data.edgeGroups[position];
1278
- const groupState = edgeData === null || edgeData === void 0 ? void 0 : edgeData.group;
1279
- if (groupState) {
1280
- const { views, activeView } = groupState;
1281
- const createdPanels = [];
1282
- for (const panelId of views) {
1283
- if (panels[panelId]) {
1284
- const panel = this._deserializer.fromJSON(panels[panelId], edgeGroup);
1285
- createdPanels.push(panel);
1286
- }
1287
- }
1288
- for (let i = 0; i < createdPanels.length; i++) {
1289
- const panel = createdPanels[i];
1290
- const isActive = activeView === panel.id;
1291
- edgeGroup.model.openPanel(panel, {
1292
- skipSetActive: !isActive,
1293
- skipSetGroupActive: true,
1294
- });
1295
- }
1296
- // Restore tab groups before activating a fallback panel
1297
- if (groupState.tabGroups &&
1298
- groupState.tabGroups.length > 0) {
1299
- edgeGroup.model.restoreTabGroups(groupState.tabGroups);
1300
- }
1301
- if (!edgeGroup.activePanel &&
1302
- edgeGroup.panels.length > 0) {
1303
- edgeGroup.model.openPanel(edgeGroup.panels[edgeGroup.panels.length - 1], { skipSetGroupActive: true });
1304
- }
1305
- }
1306
- }
1307
- this._shellManager.fromJSON(data.edgeGroups);
1308
- }
1309
- const serializedFloatingGroups = (_b = data.floatingGroups) !== null && _b !== void 0 ? _b : [];
1310
- for (const serializedFloatingGroup of serializedFloatingGroups) {
1311
- const { data, position } = serializedFloatingGroup;
1312
- const group = createGroupFromSerializedState(data);
1313
- this.addFloatingGroup(group, {
1314
- position: position,
1315
- width: position.width,
1316
- height: position.height,
1317
- skipRemoveGroup: true,
1318
- inDragMode: false,
1319
- });
1320
- }
1321
- const serializedPopoutGroups = (_c = data.popoutGroups) !== null && _c !== void 0 ? _c : [];
1322
- // Create a promise that resolves when all popout groups are created
1323
- const popoutPromises = [];
1324
- // Queue popup group creation with delays to avoid browser blocking
1325
- serializedPopoutGroups.forEach((serializedPopoutGroup, index) => {
1326
- const { data, position, gridReferenceGroup, url } = serializedPopoutGroup;
1327
- const group = createGroupFromSerializedState(data);
1328
- // Add a small delay for each popup after the first to avoid browser popup blocking
1329
- const popoutPromise = new Promise((resolve) => {
1330
- const cleanup = () => {
1331
- this._popoutRestorationCleanups.delete(cleanup);
1332
- clearTimeout(handle);
1333
- resolve();
1334
- };
1335
- const handle = setTimeout(() => {
1336
- this._popoutRestorationCleanups.delete(cleanup);
1337
- // Guard against the component being disposed before
1338
- // this timer fires. Under React StrictMode the
1339
- // component is mounted -> disposed -> remounted, and
1340
- // without this guard the first instance's queued
1341
- // restoration would open a second popout window.
1342
- // See issue #851.
1343
- if (this.isDisposed) {
1344
- resolve();
1345
- return;
1346
- }
1347
- this.addPopoutGroup(group, {
1348
- position: position !== null && position !== void 0 ? position : undefined,
1349
- overridePopoutGroup: gridReferenceGroup
1350
- ? group
1351
- : undefined,
1352
- referenceGroup: gridReferenceGroup
1353
- ? this.getPanel(gridReferenceGroup)
1354
- : undefined,
1355
- popoutUrl: url,
1356
- });
1357
- resolve();
1358
- }, index * DESERIALIZATION_POPOUT_DELAY_MS); // 100ms delay between each popup
1359
- this._popoutRestorationCleanups.add(cleanup);
1360
- });
1361
- popoutPromises.push(popoutPromise);
1362
- });
1363
- // Store the promise for tests to wait on
1364
- this._popoutRestorationPromise = Promise.all(popoutPromises).then(() => void 0);
1365
- for (const floatingGroup of this._floatingGroups) {
1366
- floatingGroup.overlay.setBounds();
1688
+ this.deserializeEdgeGroups(data.edgeGroups, panels);
1367
1689
  }
1690
+ this.deserializeFloatingWindows((_b = data.floatingGroups) !== null && _b !== void 0 ? _b : [], createGroupFromSerializedState);
1691
+ const popoutPromises = this.deserializePopoutWindows((_c = data.popoutGroups) !== null && _c !== void 0 ? _c : [], createGroupFromSerializedState);
1692
+ (_d = this._popoutWindowService) === null || _d === void 0 ? void 0 : _d.finishRestoration(popoutPromises);
1693
+ (_e = this._floatingGroupService) === null || _e === void 0 ? void 0 : _e.constrainBounds();
1368
1694
  if (typeof activeGroup === 'string') {
1369
1695
  const panel = this.getPanel(activeGroup);
1370
1696
  if (panel) {
1371
1697
  this.doSetGroupAndPanelActive(panel);
1372
1698
  }
1373
1699
  }
1700
+ // `gridview.deserialize()` rebuilds the grid without firing the
1701
+ // BaseGrid add events the watermark module reacts to, so the
1702
+ // watermark mounted during `clear()` would otherwise persist over
1703
+ // the restored layout. Re-evaluate now the layout is in place.
1704
+ (_f = this._watermarkService) === null || _f === void 0 ? void 0 : _f.update();
1374
1705
  }
1375
1706
  catch (err) {
1376
1707
  console.error('dockview: failed to deserialize layout. Reverting changes', err);
@@ -1394,10 +1725,7 @@ export class DockviewComponent extends BaseGrid {
1394
1725
  this._groups.delete(group.id);
1395
1726
  this._onDidRemoveGroup.fire(group);
1396
1727
  }
1397
- // iterate over a reassigned array since original array will be modified
1398
- for (const floatingGroup of [...this._floatingGroups]) {
1399
- floatingGroup.dispose();
1400
- }
1728
+ (_g = this._floatingGroupService) === null || _g === void 0 ? void 0 : _g.disposeAll();
1401
1729
  // fires clean-up events and clears the underlying HTML gridview.
1402
1730
  this.clear();
1403
1731
  /**
@@ -1407,16 +1735,179 @@ export class DockviewComponent extends BaseGrid {
1407
1735
  */
1408
1736
  throw err;
1409
1737
  }
1410
- this.updateWatermark();
1411
1738
  // Force position updates for always visible panels after DOM layout is complete
1412
1739
  this.debouncedUpdateAllPositions();
1413
1740
  this._onDidLayoutFromJSON.fire();
1414
1741
  }
1742
+ /**
1743
+ * Rebuild a floating / popout window's nested gridview from its serialized
1744
+ * tree, collecting the member groups (in deserialization order) so the
1745
+ * caller can mount or restore the window.
1746
+ */
1747
+ deserializeNestedGridview(grid, createGroup) {
1748
+ const gridview = this.createNestedGridview(grid.orientation);
1749
+ const members = [];
1750
+ gridview.deserialize(grid, {
1751
+ fromJSON: (node) => {
1752
+ const group = createGroup(node.data);
1753
+ members.push(group);
1754
+ return group;
1755
+ },
1756
+ });
1757
+ return { gridview, members };
1758
+ }
1759
+ deserializeEdgeGroups(edgeGroups, panels) {
1760
+ var _a;
1761
+ const edgeService = assertModule(this._edgeGroupService, 'EdgeGroup', 'fromJSON edge restoration');
1762
+ if (!edgeService) {
1763
+ return;
1764
+ }
1765
+ // Auto-create edge groups for positions in the serialized state that
1766
+ // don't already have a group registered (e.g. when fromJSON is called
1767
+ // before the user has called addEdgeGroup).
1768
+ for (const _position of [
1769
+ 'top',
1770
+ 'bottom',
1771
+ 'left',
1772
+ 'right',
1773
+ ]) {
1774
+ const fixedData = edgeGroups[_position];
1775
+ if (fixedData && !edgeService.has(_position)) {
1776
+ const groupState = fixedData.group;
1777
+ const id = (_a = groupState === null || groupState === void 0 ? void 0 : groupState.id) !== null && _a !== void 0 ? _a : `${_position}-group`;
1778
+ this.addEdgeGroup(_position, { id });
1779
+ }
1780
+ }
1781
+ // Restore panel contents of edge groups
1782
+ for (const [position, edgeGroup] of edgeService.entries()) {
1783
+ const edgeData = edgeGroups[position];
1784
+ const groupState = edgeData === null || edgeData === void 0 ? void 0 : edgeData.group;
1785
+ if (groupState) {
1786
+ const { views, activeView } = groupState;
1787
+ const createdPanels = [];
1788
+ for (const panelId of views) {
1789
+ if (panels[panelId]) {
1790
+ const panel = this._deserializer.fromJSON(panels[panelId], edgeGroup);
1791
+ createdPanels.push(panel);
1792
+ }
1793
+ }
1794
+ for (let i = 0; i < createdPanels.length; i++) {
1795
+ const panel = createdPanels[i];
1796
+ const isActive = activeView === panel.id;
1797
+ edgeGroup.model.openPanel(panel, {
1798
+ skipSetActive: !isActive,
1799
+ skipSetGroupActive: true,
1800
+ });
1801
+ }
1802
+ // Restore tab groups before activating a fallback panel
1803
+ if (groupState.tabGroups && groupState.tabGroups.length > 0) {
1804
+ edgeGroup.model.restoreTabGroups(groupState.tabGroups);
1805
+ }
1806
+ if (!edgeGroup.activePanel && edgeGroup.panels.length > 0) {
1807
+ edgeGroup.model.openPanel(edgeGroup.panels[edgeGroup.panels.length - 1], { skipSetGroupActive: true });
1808
+ }
1809
+ }
1810
+ }
1811
+ this._shellManager.fromJSON(edgeGroups);
1812
+ }
1813
+ deserializeFloatingWindows(serialized, createGroup) {
1814
+ for (const serializedFloatingGroup of serialized) {
1815
+ const { data, grid, position } = serializedFloatingGroup;
1816
+ if (grid) {
1817
+ // Multi-group window: rebuild the window's nested gridview from
1818
+ // its serialized tree.
1819
+ const { gridview: floatingGridview, members } = this.deserializeNestedGridview(grid, createGroup);
1820
+ if (members.length === 0) {
1821
+ continue;
1822
+ }
1823
+ this.mountFloatingWindow(floatingGridview, members[0], members, position, { inDragMode: false });
1824
+ }
1825
+ else if (data) {
1826
+ const group = createGroup(data);
1827
+ this.addFloatingGroup(group, {
1828
+ position: position,
1829
+ width: position.width,
1830
+ height: position.height,
1831
+ skipRemoveGroup: true,
1832
+ inDragMode: false,
1833
+ });
1834
+ }
1835
+ }
1836
+ }
1837
+ deserializePopoutWindows(serialized, createGroup) {
1838
+ const popoutService = serialized.length > 0
1839
+ ? assertModule(this._popoutWindowService, 'PopoutWindow', 'fromJSON popout restoration')
1840
+ : this._popoutWindowService;
1841
+ if (!popoutService) {
1842
+ return [];
1843
+ }
1844
+ // Queue popup group creation with delays to avoid browser blocking
1845
+ return serialized.flatMap((serializedPopoutGroup, index) => {
1846
+ const { data, grid, position, gridReferenceGroup, url } = serializedPopoutGroup;
1847
+ // Multi-group popout windows rebuild their nested gridview from the
1848
+ // serialized tree; single-group windows use the legacy single-group
1849
+ // path.
1850
+ let overridePopoutGridview;
1851
+ let members = [];
1852
+ if (grid) {
1853
+ const built = this.deserializeNestedGridview(grid, createGroup);
1854
+ overridePopoutGridview = built.gridview;
1855
+ members = built.members;
1856
+ if (members.length === 0) {
1857
+ // A serialized window with no groups: nothing to restore.
1858
+ // Mirror the floating path's guard and discard the empty
1859
+ // gridview rather than passing an undefined anchor to
1860
+ // addPopoutGroup.
1861
+ overridePopoutGridview.dispose();
1862
+ return [];
1863
+ }
1864
+ }
1865
+ const group = grid ? members[0] : createGroup(data);
1866
+ return popoutService.scheduleRestoration(index * DESERIALIZATION_POPOUT_DELAY_MS, () => {
1867
+ this.addPopoutGroup(group, {
1868
+ position: position !== null && position !== void 0 ? position : undefined,
1869
+ overridePopoutGroup: gridReferenceGroup
1870
+ ? group
1871
+ : undefined,
1872
+ overridePopoutGridview,
1873
+ referenceGroup: gridReferenceGroup
1874
+ ? this.getPanel(gridReferenceGroup)
1875
+ : undefined,
1876
+ popoutUrl: url,
1877
+ });
1878
+ }, () => {
1879
+ // The group was registered in _groups synchronously but the
1880
+ // timer that would parent it into the popout window never
1881
+ // ran. Dispose the orphan here so the next clear() doesn't
1882
+ // trip over an unparented element. See issue #1304.
1883
+ for (const orphan of members.length > 0
1884
+ ? members
1885
+ : [group]) {
1886
+ if (!this.isDisposed &&
1887
+ this._groups.has(orphan.id) &&
1888
+ orphan.element.parentElement === null) {
1889
+ for (const panel of [...orphan.panels]) {
1890
+ this.removePanel(panel, {
1891
+ removeEmptyGroup: false,
1892
+ });
1893
+ }
1894
+ orphan.dispose();
1895
+ this._groups.delete(orphan.id);
1896
+ this._onDidRemoveGroup.fire(orphan);
1897
+ }
1898
+ }
1899
+ });
1900
+ });
1901
+ }
1415
1902
  clear() {
1903
+ this.mutation('clear', () => this._doClear());
1904
+ }
1905
+ _doClear() {
1906
+ var _a;
1416
1907
  const groups = Array.from(this._groups.values()).map((_) => _.value);
1417
1908
  const hasActiveGroup = !!this.activeGroup;
1418
1909
  for (const group of groups) {
1419
- if ([...this._edgeGroups.values()].includes(group)) {
1910
+ if ((_a = this._edgeGroupService) === null || _a === void 0 ? void 0 : _a.includes(group)) {
1420
1911
  // Edge groups are structural - only clear their panels, not the group itself
1421
1912
  const panels = [...group.panels];
1422
1913
  for (const panel of panels) {
@@ -1433,12 +1924,19 @@ export class DockviewComponent extends BaseGrid {
1433
1924
  this.gridview.clear();
1434
1925
  }
1435
1926
  closeAllGroups() {
1436
- for (const entry of this._groups.entries()) {
1437
- const [_, group] = entry;
1438
- group.value.model.closeAllPanels();
1439
- }
1927
+ // One transaction the per-panel removals inside nest via the depth
1928
+ // counter, so consumers (undo, announcements) see a single mutation.
1929
+ this.mutation('remove', () => {
1930
+ for (const entry of this._groups.entries()) {
1931
+ const [_, group] = entry;
1932
+ group.value.model.closeAllPanels();
1933
+ }
1934
+ });
1440
1935
  }
1441
1936
  addPanel(options) {
1937
+ return this.mutation('add', () => this._doAddPanel(options));
1938
+ }
1939
+ _doAddPanel(options) {
1442
1940
  var _a, _b;
1443
1941
  if (this.panels.find((_) => _.id === options.id)) {
1444
1942
  throw new Error(`dockview: panel with id ${options.id} already exists`);
@@ -1580,6 +2078,11 @@ export class DockviewComponent extends BaseGrid {
1580
2078
  }
1581
2079
  removePanel(panel, options = {
1582
2080
  removeEmptyGroup: true,
2081
+ }) {
2082
+ this.mutation('remove', () => this._doRemovePanel(panel, options));
2083
+ }
2084
+ _doRemovePanel(panel, options = {
2085
+ removeEmptyGroup: true,
1583
2086
  }) {
1584
2087
  const group = panel.group;
1585
2088
  if (!group) {
@@ -1602,28 +2105,10 @@ export class DockviewComponent extends BaseGrid {
1602
2105
  }
1603
2106
  return new Watermark();
1604
2107
  }
1605
- updateWatermark() {
1606
- var _a, _b;
1607
- if (this.groups.filter((x) => x.api.location.type === 'grid' && x.api.isVisible).length === 0) {
1608
- if (!this._watermark) {
1609
- this._watermark = this.createWatermarkComponent();
1610
- this._watermark.init({
1611
- containerApi: new DockviewApi(this),
1612
- });
1613
- const watermarkContainer = document.createElement('div');
1614
- watermarkContainer.className = 'dv-watermark-container';
1615
- addTestId(watermarkContainer, 'watermark-component');
1616
- watermarkContainer.appendChild(this._watermark.element);
1617
- this.gridview.element.appendChild(watermarkContainer);
1618
- }
1619
- }
1620
- else if (this._watermark) {
1621
- this._watermark.element.parentElement.remove();
1622
- (_b = (_a = this._watermark).dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
1623
- this._watermark = null;
1624
- }
1625
- }
1626
2108
  addGroup(options) {
2109
+ return this.mutation('add', () => this._doAddGroup(options));
2110
+ }
2111
+ _doAddGroup(options) {
1627
2112
  var _a;
1628
2113
  if (options) {
1629
2114
  let referenceGroup;
@@ -1683,12 +2168,71 @@ export class DockviewComponent extends BaseGrid {
1683
2168
  : Orientation.VERTICAL;
1684
2169
  }
1685
2170
  removeGroup(group, options) {
1686
- this.doRemoveGroup(group, options);
2171
+ this.mutation('remove', () => this.doRemoveGroup(group, options));
2172
+ }
2173
+ /**
2174
+ * Detach a single group from the nested gridview of its floating / popout
2175
+ * window, keeping the window and its remaining members alive, and reassign
2176
+ * the window's anchor if the detached group was it.
2177
+ *
2178
+ * @returns `true` if the group was detached from a multi-member window;
2179
+ * `false` if `group` is not in a nested window, or is the window's only
2180
+ * member — in which case the caller is responsible for disposing the whole
2181
+ * window.
2182
+ */
2183
+ detachFromNestedWindow(group) {
2184
+ var _a, _b;
2185
+ const floating = (_a = this._floatingGroupService) === null || _a === void 0 ? void 0 : _a.findByGroup(group);
2186
+ if (floating) {
2187
+ const members = this.nestedWindowMembers(group);
2188
+ if (members.length <= 1) {
2189
+ return false;
2190
+ }
2191
+ floating.gridview.remove(group);
2192
+ if (floating.group === group) {
2193
+ // The anchor left; promote a remaining member.
2194
+ floating.setAnchorGroup(members.find((m) => m !== group));
2195
+ }
2196
+ return true;
2197
+ }
2198
+ const popout = (_b = this._popoutWindowService) === null || _b === void 0 ? void 0 : _b.findByGroup(group);
2199
+ if (popout) {
2200
+ const members = this.nestedWindowMembers(group);
2201
+ if (members.length <= 1) {
2202
+ return false;
2203
+ }
2204
+ popout.gridview.remove(group);
2205
+ if (popout.popoutGroup === group) {
2206
+ // The anchor left; promote a remaining member.
2207
+ popout.popoutGroup = members.find((m) => m !== group);
2208
+ }
2209
+ return true;
2210
+ }
2211
+ return false;
2212
+ }
2213
+ /**
2214
+ * Dispose a group and forget it: remove it from `_groups` and fire the
2215
+ * removed event.
2216
+ */
2217
+ disposeGroupRecord(group) {
2218
+ group.dispose();
2219
+ this._groups.delete(group.id);
2220
+ this._onDidRemoveGroup.fire(group);
2221
+ }
2222
+ /**
2223
+ * When `removed` was the active group, fall the active selection back to
2224
+ * the first remaining group (or clear it when none remain).
2225
+ */
2226
+ activateFallbackGroupIfRemoved(removed, skipActive) {
2227
+ if (!skipActive && this._activeGroup === removed) {
2228
+ const groups = Array.from(this._groups.values());
2229
+ this.doSetGroupAndPanelActive(groups.length > 0 ? groups[0].value : undefined);
2230
+ }
1687
2231
  }
1688
2232
  doRemoveGroup(group, options) {
1689
- var _a;
2233
+ var _a, _b, _c, _d, _e;
1690
2234
  // Edge groups are permanent structural elements - never remove them from the layout
1691
- if ([...this._edgeGroups.values()].includes(group)) {
2235
+ if ((_a = this._edgeGroupService) === null || _a === void 0 ? void 0 : _a.includes(group)) {
1692
2236
  return group;
1693
2237
  }
1694
2238
  const panels = [...group.panels]; // reassign since group panels will mutate
@@ -1696,64 +2240,99 @@ export class DockviewComponent extends BaseGrid {
1696
2240
  for (const panel of panels) {
1697
2241
  this.removePanel(panel, {
1698
2242
  removeEmptyGroup: false,
1699
- skipDispose: (_a = options === null || options === void 0 ? void 0 : options.skipDispose) !== null && _a !== void 0 ? _a : false,
2243
+ skipDispose: (_b = options === null || options === void 0 ? void 0 : options.skipDispose) !== null && _b !== void 0 ? _b : false,
1700
2244
  });
1701
2245
  }
1702
2246
  }
1703
2247
  const activePanel = this.activePanel;
1704
2248
  if (group.api.location.type === 'floating') {
1705
- const floatingGroup = this._floatingGroups.find((_) => _.group === group);
1706
- if (floatingGroup) {
2249
+ const floatingGroup = (_c = this._floatingGroupService) === null || _c === void 0 ? void 0 : _c.findByGroup(group);
2250
+ if (!floatingGroup) {
2251
+ throw new Error('dockview: failed to find floating group');
2252
+ }
2253
+ if (this.detachFromNestedWindow(group)) {
2254
+ // The floating window hosts other groups and stays alive —
2255
+ // finalize just this group.
1707
2256
  if (!(options === null || options === void 0 ? void 0 : options.skipDispose)) {
1708
- floatingGroup.group.dispose();
1709
- this._groups.delete(group.id);
1710
- this._onDidRemoveGroup.fire(group);
2257
+ this.disposeGroupRecord(group);
1711
2258
  }
1712
- remove(this._floatingGroups, floatingGroup);
1713
- floatingGroup.dispose();
1714
- if (!(options === null || options === void 0 ? void 0 : options.skipActive) && this._activeGroup === group) {
1715
- const groups = Array.from(this._groups.values());
1716
- this.doSetGroupAndPanelActive(groups.length > 0 ? groups[0].value : undefined);
2259
+ else {
2260
+ // Relocation: reset location so the destination root can
2261
+ // re-tag it.
2262
+ group.model.location = { type: 'grid' };
1717
2263
  }
1718
- return floatingGroup.group;
2264
+ this.activateFallbackGroupIfRemoved(group, options === null || options === void 0 ? void 0 : options.skipActive);
2265
+ return group;
2266
+ }
2267
+ // Last group leaving — dispose the whole floating window.
2268
+ if (!(options === null || options === void 0 ? void 0 : options.skipDispose)) {
2269
+ this.disposeGroupRecord(group);
1719
2270
  }
1720
- throw new Error('dockview: failed to find floating group');
2271
+ // floatingGroup.dispose() removes itself from the service array
2272
+ floatingGroup.dispose();
2273
+ this.activateFallbackGroupIfRemoved(group, options === null || options === void 0 ? void 0 : options.skipActive);
2274
+ return group;
1721
2275
  }
1722
2276
  if (group.api.location.type === 'popout') {
1723
- const selectedGroup = this._popoutGroups.find((_) => _.popoutGroup === group);
1724
- if (selectedGroup) {
2277
+ const selectedGroup = (_d = this._popoutWindowService) === null || _d === void 0 ? void 0 : _d.findByGroup(group);
2278
+ if (!selectedGroup) {
2279
+ throw new Error('dockview: failed to find popout group');
2280
+ }
2281
+ if (this.detachFromNestedWindow(group)) {
2282
+ // The popout window hosts other groups and stays alive —
2283
+ // finalize just this group.
1725
2284
  if (!(options === null || options === void 0 ? void 0 : options.skipDispose)) {
1726
- if (!(options === null || options === void 0 ? void 0 : options.skipPopoutAssociated)) {
1727
- const refGroup = selectedGroup.referenceGroup
1728
- ? this.getPanel(selectedGroup.referenceGroup)
1729
- : undefined;
1730
- if (refGroup && refGroup.panels.length === 0) {
1731
- this.removeGroup(refGroup);
1732
- }
1733
- }
1734
- selectedGroup.popoutGroup.dispose();
1735
- this._groups.delete(group.id);
1736
- this._onDidRemoveGroup.fire(group);
2285
+ this.disposeGroupRecord(group);
1737
2286
  }
1738
- remove(this._popoutGroups, selectedGroup);
1739
- const removedGroup = selectedGroup.disposable.dispose();
1740
- if (!(options === null || options === void 0 ? void 0 : options.skipPopoutReturn) && removedGroup) {
1741
- this.doAddGroup(removedGroup, [0]);
1742
- this.doSetGroupAndPanelActive(removedGroup);
2287
+ else {
2288
+ // Relocation: reset location so the destination root can
2289
+ // re-tag it.
2290
+ group.model.location = { type: 'grid' };
1743
2291
  }
1744
- if (!(options === null || options === void 0 ? void 0 : options.skipActive) && this._activeGroup === group) {
1745
- const groups = Array.from(this._groups.values());
1746
- this.doSetGroupAndPanelActive(groups.length > 0 ? groups[0].value : undefined);
2292
+ this.activateFallbackGroupIfRemoved(group, options === null || options === void 0 ? void 0 : options.skipActive);
2293
+ return group;
2294
+ }
2295
+ // Last group leaving — tear the whole popout window down.
2296
+ if (!(options === null || options === void 0 ? void 0 : options.skipDispose)) {
2297
+ if (!(options === null || options === void 0 ? void 0 : options.skipPopoutAssociated)) {
2298
+ const refGroup = selectedGroup.referenceGroup
2299
+ ? this.getPanel(selectedGroup.referenceGroup)
2300
+ : undefined;
2301
+ if (refGroup && refGroup.panels.length === 0) {
2302
+ this.removeGroup(refGroup);
2303
+ }
1747
2304
  }
1748
- this.updateWatermark();
1749
- return selectedGroup.popoutGroup;
2305
+ selectedGroup.popoutGroup.dispose();
2306
+ this._groups.delete(group.id);
2307
+ this._onDidRemoveGroup.fire(group);
1750
2308
  }
1751
- throw new Error('dockview: failed to find popout group');
2309
+ (_e = this._popoutWindowService) === null || _e === void 0 ? void 0 : _e.remove(selectedGroup);
2310
+ const removedGroup = selectedGroup.disposable.dispose();
2311
+ if (!(options === null || options === void 0 ? void 0 : options.skipPopoutReturn) && removedGroup) {
2312
+ this.doAddGroup(removedGroup, [0]);
2313
+ this.doSetGroupAndPanelActive(removedGroup);
2314
+ }
2315
+ this.activateFallbackGroupIfRemoved(group, options === null || options === void 0 ? void 0 : options.skipActive);
2316
+ return selectedGroup.popoutGroup;
2317
+ }
2318
+ // A `grid`-location group whose element isn't actually in the gridview
2319
+ // is an orphan — e.g. a popout-destined group created during fromJSON
2320
+ // whose window hasn't opened yet, swept up by clear()/a re-entrant
2321
+ // fromJSON. `gridview.remove()` would throw "Invalid grid element", so
2322
+ // dispose it directly.
2323
+ if (!this.gridview.element.contains(group.element)) {
2324
+ if (!(options === null || options === void 0 ? void 0 : options.skipDispose)) {
2325
+ const item = this._groups.get(group.id);
2326
+ item === null || item === void 0 ? void 0 : item.disposable.dispose();
2327
+ this.disposeGroupRecord(group);
2328
+ }
2329
+ this.activateFallbackGroupIfRemoved(group, options === null || options === void 0 ? void 0 : options.skipActive);
2330
+ return group;
1752
2331
  }
1753
2332
  const re = super.doRemoveGroup(group, options);
1754
2333
  if (!(options === null || options === void 0 ? void 0 : options.skipActive)) {
1755
2334
  if (this.activePanel !== activePanel) {
1756
- this._onDidActivePanelChange.fire(this.activePanel);
2335
+ this.fireActivePanelChange(this.activePanel);
1757
2336
  }
1758
2337
  }
1759
2338
  return re;
@@ -1763,8 +2342,15 @@ export class DockviewComponent extends BaseGrid {
1763
2342
  cancelAnimationFrame(this._updatePositionsFrameId);
1764
2343
  }
1765
2344
  this._updatePositionsFrameId = requestAnimationFrame(() => {
2345
+ var _a, _b;
1766
2346
  this._updatePositionsFrameId = undefined;
1767
2347
  this.overlayRenderContainer.updateAllPositions();
2348
+ // Popout windows have their own render containers; reposition those
2349
+ // too so panels moved/split within a popout are laid out (the main
2350
+ // container only covers grid + floating, which share it).
2351
+ for (const entry of (_b = (_a = this._popoutWindowService) === null || _a === void 0 ? void 0 : _a.entries) !== null && _b !== void 0 ? _b : []) {
2352
+ entry.overlayRenderContainer.updateAllPositions();
2353
+ }
1768
2354
  });
1769
2355
  }
1770
2356
  movingLock(func) {
@@ -1777,8 +2363,74 @@ export class DockviewComponent extends BaseGrid {
1777
2363
  this._moving = isMoving;
1778
2364
  }
1779
2365
  }
2366
+ /**
2367
+ * Bracket a structural mutation with `onWillMutateLayout` /
2368
+ * `onDidMutateLayout`. Re-entrant: nested calls (a compound operation such
2369
+ * as a drag that relocates a panel) join the outermost transaction, so the
2370
+ * events fire exactly once around the whole operation. `kind` reflects the
2371
+ * outermost mutation.
2372
+ */
2373
+ mutation(kind, func) {
2374
+ const outer = this._mutationDepth === 0;
2375
+ const origin = this._origin;
2376
+ if (outer) {
2377
+ this._onWillMutateLayout.fire({ kind, origin });
2378
+ }
2379
+ this._mutationDepth++;
2380
+ try {
2381
+ return func();
2382
+ }
2383
+ finally {
2384
+ this._mutationDepth--;
2385
+ if (outer) {
2386
+ this._onDidMutateLayout.fire({ kind, origin });
2387
+ }
2388
+ }
2389
+ }
2390
+ /**
2391
+ * The origin of the operation currently in progress (`'user'` by default).
2392
+ * Read inside a `mutation()` or active-panel change to learn whether the
2393
+ * change was driven by application code (via the {@link DockviewApi}) or a
2394
+ * user gesture.
2395
+ */
2396
+ currentOrigin() {
2397
+ return this._origin;
2398
+ }
2399
+ /**
2400
+ * Run `func` with the operation origin set to `origin`, restoring the
2401
+ * previous value afterwards. Used by the DockviewApi boundary to tag
2402
+ * programmatic operations as `'api'`, and by user-gesture handlers to tag
2403
+ * `'user'`. Only the outermost caller sets the origin — a nested call (or a
2404
+ * call made while a mutation is already in flight) keeps whatever the
2405
+ * enclosing operation established, so the trigger always wins.
2406
+ */
2407
+ withOrigin(origin, func) {
2408
+ if (this._originDepth > 0 || this._mutationDepth > 0) {
2409
+ return func();
2410
+ }
2411
+ const previous = this._origin;
2412
+ this._origin = origin;
2413
+ this._originDepth++;
2414
+ try {
2415
+ return func();
2416
+ }
2417
+ finally {
2418
+ this._originDepth--;
2419
+ this._origin = previous;
2420
+ }
2421
+ }
2422
+ /**
2423
+ * Fire `onDidActivePanelChange` with the panel and the current operation
2424
+ * {@link DockviewOrigin}. Callers keep their own dedupe guards.
2425
+ */
2426
+ fireActivePanelChange(panel) {
2427
+ this._onDidActivePanelChange.fire({ panel, origin: this._origin });
2428
+ }
1780
2429
  moveGroupOrPanel(options) {
1781
- var _a;
2430
+ this.mutation('move', () => this._doMoveGroupOrPanel(options));
2431
+ }
2432
+ _doMoveGroupOrPanel(options) {
2433
+ var _a, _b;
1782
2434
  const destinationGroup = options.to.group;
1783
2435
  const sourceGroupId = options.from.groupId;
1784
2436
  const sourceItemId = options.from.panelId;
@@ -1859,15 +2511,20 @@ export class DockviewComponent extends BaseGrid {
1859
2511
  * Dropping a panel to the extremities of a group which will place that panel
1860
2512
  * into an adjacent group
1861
2513
  */
2514
+ // The destination group may live in the main grid or in a floating
2515
+ // window's nested gridview — resolve which root we are dropping
2516
+ // into so locations/orientation are computed against it.
2517
+ const destinationGridview = this.getGridviewForGroup(destinationGroup);
1862
2518
  const referenceLocation = getGridLocation(destinationGroup.element);
1863
- const targetLocation = getRelativeLocation(this.gridview.orientation, referenceLocation, destinationTarget);
2519
+ const targetLocation = getRelativeLocation(destinationGridview.orientation, referenceLocation, destinationTarget);
1864
2520
  if (sourceGroup.size < 2) {
1865
2521
  /**
1866
2522
  * If we are moving from a group which only has one panel left we will consider
1867
2523
  * moving the group itself rather than moving the panel into a newly created group
1868
2524
  */
1869
2525
  const [targetParentLocation, to] = tail(targetLocation);
1870
- if (sourceGroup.api.location.type === 'grid') {
2526
+ if (sourceGroup.api.location.type === 'grid' &&
2527
+ destinationGridview === this.gridview) {
1871
2528
  const sourceLocation = getGridLocation(sourceGroup.element);
1872
2529
  const [sourceParentLocation, from] = tail(sourceLocation);
1873
2530
  if (sequenceEquals(sourceParentLocation, targetParentLocation)) {
@@ -1882,9 +2539,11 @@ export class DockviewComponent extends BaseGrid {
1882
2539
  return;
1883
2540
  }
1884
2541
  }
1885
- if (sourceGroup.api.location.type === 'popout') {
2542
+ if (sourceGroup.api.location.type === 'popout' &&
2543
+ this.nestedWindowMembers(sourceGroup).length <= 1) {
1886
2544
  /**
1887
- * the source group is a popout group with a single panel
2545
+ * the source group is the only group in a popout window and
2546
+ * has a single panel
1888
2547
  *
1889
2548
  * 1. remove the panel from the group without triggering any events
1890
2549
  * 2. remove the popout group — this may cascade-remove the empty
@@ -1892,15 +2551,21 @@ export class DockviewComponent extends BaseGrid {
1892
2551
  * doRemoveGroup for popout groups), which can shift grid indices
1893
2552
  * 3. recompute the target location now that the grid is stable
1894
2553
  * 4. create a new group at the recomputed location and add that panel
2554
+ *
2555
+ * Multi-group popout windows fall through to the generic
2556
+ * detach-and-re-add path so the window stays alive.
1895
2557
  */
1896
- const popoutGroup = this._popoutGroups.find((group) => group.popoutGroup === sourceGroup);
2558
+ const popoutGroup = (_b = this._popoutWindowService) === null || _b === void 0 ? void 0 : _b.findByGroup(sourceGroup);
2559
+ if (!popoutGroup) {
2560
+ return;
2561
+ }
1897
2562
  const removedPanel = this.movingLock(() => popoutGroup.popoutGroup.model.removePanel(popoutGroup.popoutGroup.panels[0], {
1898
2563
  skipSetActive: true,
1899
2564
  skipSetActiveGroup: true,
1900
2565
  }));
1901
2566
  this.doRemoveGroup(sourceGroup, { skipActive: true });
1902
- const updatedTargetLocation = getRelativeLocation(this.gridview.orientation, getGridLocation(destinationGroup.element), destinationTarget);
1903
- const newGroup = this.createGroupAtLocation(updatedTargetLocation);
2567
+ const updatedTargetLocation = getRelativeLocation(destinationGridview.orientation, getGridLocation(destinationGroup.element), destinationTarget);
2568
+ const newGroup = this.createGroupAtLocation(updatedTargetLocation, undefined, undefined, destinationGridview);
1904
2569
  this.movingLock(() => newGroup.model.openPanel(removedPanel, {
1905
2570
  skipSetActive: true,
1906
2571
  }));
@@ -1924,7 +2589,7 @@ export class DockviewComponent extends BaseGrid {
1924
2589
  if (!removedPanel) {
1925
2590
  throw new Error(`dockview: No panel with id ${sourceItemId}`);
1926
2591
  }
1927
- const newGroup = this.createGroupAtLocation(targetLocation);
2592
+ const newGroup = this.createGroupAtLocation(targetLocation, undefined, undefined, destinationGridview);
1928
2593
  this.movingLock(() => newGroup.model.openPanel(removedPanel, {
1929
2594
  skipSetGroupActive: true,
1930
2595
  }));
@@ -1942,8 +2607,9 @@ export class DockviewComponent extends BaseGrid {
1942
2607
  }));
1943
2608
  // after deleting the group we need to re-evaulate the ref location
1944
2609
  const updatedReferenceLocation = getGridLocation(destinationGroup.element);
1945
- const location = getRelativeLocation(this.gridview.orientation, updatedReferenceLocation, destinationTarget);
1946
- this.movingLock(() => this.doAddGroup(targetGroup, location));
2610
+ const location = getRelativeLocation(destinationGridview.orientation, updatedReferenceLocation, destinationTarget);
2611
+ this.movingLock(() => this.doAddGroup(targetGroup, location, undefined, destinationGridview));
2612
+ this.setGroupLocationForRoot(targetGroup, destinationGridview);
1947
2613
  this.doSetGroupAndPanelActive(targetGroup);
1948
2614
  this._onDidMovePanel.fire({
1949
2615
  panel: this.getGroupPanel(sourceItemId),
@@ -1962,8 +2628,8 @@ export class DockviewComponent extends BaseGrid {
1962
2628
  if (!removedPanel) {
1963
2629
  throw new Error(`dockview: No panel with id ${sourceItemId}`);
1964
2630
  }
1965
- const dropLocation = getRelativeLocation(this.gridview.orientation, referenceLocation, destinationTarget);
1966
- const group = this.createGroupAtLocation(dropLocation);
2631
+ const dropLocation = getRelativeLocation(destinationGridview.orientation, referenceLocation, destinationTarget);
2632
+ const group = this.createGroupAtLocation(dropLocation, undefined, undefined, destinationGridview);
1967
2633
  this.movingLock(() => group.model.openPanel(removedPanel, {
1968
2634
  skipSetGroupActive: true,
1969
2635
  }));
@@ -2058,6 +2724,21 @@ export class DockviewComponent extends BaseGrid {
2058
2724
  addPanelsToGroup(targetGroup);
2059
2725
  }
2060
2726
  moveGroup(options) {
2727
+ this.mutation('move', () => this._doMoveGroup(options));
2728
+ }
2729
+ // Bracket maximize/restore as a 'maximize' transaction. The maximized node
2730
+ // is serialized by the gridview (`SerializedGridview.maximizedNode`), so
2731
+ // the state round-trips through toJSON/fromJSON and is restorable on undo.
2732
+ // When the exit is a side-effect of another bracketed operation (e.g. a
2733
+ // move that activates a different group) the depth counter folds it in.
2734
+ maximizeGroup(panel) {
2735
+ this.mutation('maximize', () => super.maximizeGroup(panel));
2736
+ }
2737
+ exitMaximizedGroup() {
2738
+ this.mutation('maximize', () => super.exitMaximizedGroup());
2739
+ }
2740
+ _doMoveGroup(options) {
2741
+ var _a, _b, _c;
2061
2742
  const from = options.from.group;
2062
2743
  const to = options.to.group;
2063
2744
  const target = options.to.position;
@@ -2138,6 +2819,13 @@ export class DockviewComponent extends BaseGrid {
2138
2819
  }));
2139
2820
  const movedPanels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, { skipSetActive: true })));
2140
2821
  source = this.createGroup();
2822
+ // The new source group enters the layout via gridview.addView
2823
+ // below, which bypasses doAddGroup and so doesn't fire
2824
+ // BaseGrid._onDidAdd. Modules (TabGroupChips, etc.) drive
2825
+ // per-group attachment off _onDidAddGroup, so we fire it
2826
+ // explicitly here — matches the pattern in addFloatingGroup
2827
+ // and addEdgeGroup.
2828
+ this._onDidAddGroup.fire(source);
2141
2829
  this.movingLock(() => {
2142
2830
  for (const panel of movedPanels) {
2143
2831
  source.model.openPanel(panel, {
@@ -2164,23 +2852,34 @@ export class DockviewComponent extends BaseGrid {
2164
2852
  this.gridview.removeView(getGridLocation(from.element));
2165
2853
  break;
2166
2854
  case 'floating': {
2167
- const selectedFloatingGroup = this._floatingGroups.find((x) => x.group === from);
2855
+ const selectedFloatingGroup = (_a = this._floatingGroupService) === null || _a === void 0 ? void 0 : _a.findByGroup(from);
2168
2856
  if (!selectedFloatingGroup) {
2169
2857
  throw new Error('dockview: failed to find floating group');
2170
2858
  }
2171
- selectedFloatingGroup.dispose();
2859
+ // Detach just this group from the floating window's
2860
+ // nested gridview, keeping the window (and its other
2861
+ // groups) alive. If it was the only member, dispose the
2862
+ // whole window.
2863
+ if (!this.detachFromNestedWindow(from)) {
2864
+ selectedFloatingGroup.dispose();
2865
+ }
2172
2866
  break;
2173
2867
  }
2174
2868
  case 'popout': {
2175
- const selectedPopoutGroup = this._popoutGroups.find((x) => x.popoutGroup === from);
2869
+ const selectedPopoutGroup = (_b = this._popoutWindowService) === null || _b === void 0 ? void 0 : _b.findByGroup(from);
2176
2870
  if (!selectedPopoutGroup) {
2177
2871
  throw new Error('dockview: failed to find popout group');
2178
2872
  }
2179
- // Remove from popout groups list to prevent automatic restoration
2180
- const index = this._popoutGroups.indexOf(selectedPopoutGroup);
2181
- if (index >= 0) {
2182
- this._popoutGroups.splice(index, 1);
2873
+ // Detach just this group from the popout window's
2874
+ // nested gridview, keeping the window + its other groups
2875
+ // alive. Destination containers/location are applied by
2876
+ // the placement block below.
2877
+ if (this.detachFromNestedWindow(from)) {
2878
+ break;
2183
2879
  }
2880
+ // Last group leaving — tear the window down. Remove from
2881
+ // the service first to prevent automatic restoration.
2882
+ (_c = this._popoutWindowService) === null || _c === void 0 ? void 0 : _c.remove(selectedPopoutGroup);
2184
2883
  // Clean up the reference group (ghost) if it exists and is hidden
2185
2884
  if (selectedPopoutGroup.referenceGroup) {
2186
2885
  const referenceGroup = this.getPanel(selectedPopoutGroup.referenceGroup);
@@ -2191,34 +2890,25 @@ export class DockviewComponent extends BaseGrid {
2191
2890
  });
2192
2891
  }
2193
2892
  }
2194
- // Manually dispose the window without triggering restoration
2893
+ // Dispose the window without triggering restoration. The
2894
+ // placement block below applies the destination
2895
+ // location and containers to `from`.
2195
2896
  selectedPopoutGroup.window.dispose();
2196
- // Update group's location and containers for target
2197
- if (to.api.location.type === 'grid') {
2198
- from.model.renderContainer =
2199
- this.overlayRenderContainer;
2200
- from.model.dropTargetContainer =
2201
- this.rootDropTargetContainer;
2202
- from.model.location = { type: 'grid' };
2203
- }
2204
- else if (to.api.location.type === 'floating') {
2205
- from.model.renderContainer =
2206
- this.overlayRenderContainer;
2207
- from.model.dropTargetContainer =
2208
- this.rootDropTargetContainer;
2209
- from.model.location = { type: 'floating' };
2210
- }
2211
2897
  break;
2212
2898
  }
2213
2899
  }
2214
2900
  }
2215
- // For moves to grid locations
2216
- if (to.api.location.type === 'grid') {
2901
+ // Place `source` next to `to`, in whichever gridview root `to`
2902
+ // lives in. When `to` is inside a floating / popout window this
2903
+ // splits that window's nested layout rather than spawning a new one.
2904
+ if (to.api.location.type === 'grid' ||
2905
+ to.api.location.type === 'floating' ||
2906
+ to.api.location.type === 'popout') {
2907
+ const destGridview = this.getGridviewForGroup(to);
2217
2908
  const referenceLocation = getGridLocation(to.element);
2218
- const dropLocation = getRelativeLocation(this.gridview.orientation, referenceLocation, target);
2219
- // Add to grid for all moves targeting grid location
2909
+ const dropLocation = getRelativeLocation(destGridview.orientation, referenceLocation, target);
2220
2910
  let size;
2221
- switch (this.gridview.orientation) {
2911
+ switch (destGridview.orientation) {
2222
2912
  case Orientation.VERTICAL:
2223
2913
  size =
2224
2914
  referenceLocation.length % 2 == 0
@@ -2232,43 +2922,8 @@ export class DockviewComponent extends BaseGrid {
2232
2922
  : from.api.width;
2233
2923
  break;
2234
2924
  }
2235
- this.gridview.addView(source, size, dropLocation);
2236
- }
2237
- else if (to.api.location.type === 'floating') {
2238
- // For moves to floating locations, add as floating group
2239
- // Get the position/size from the target floating group
2240
- const targetFloatingGroup = this._floatingGroups.find((x) => x.group === to);
2241
- if (targetFloatingGroup) {
2242
- const box = targetFloatingGroup.overlay.toJSON();
2243
- // Calculate position based on available properties
2244
- let left, top;
2245
- if ('left' in box) {
2246
- left = box.left + 50;
2247
- }
2248
- else if ('right' in box) {
2249
- left = Math.max(0, box.right - box.width - 50);
2250
- }
2251
- else {
2252
- left = 50; // Default fallback
2253
- }
2254
- if ('top' in box) {
2255
- top = box.top + 50;
2256
- }
2257
- else if ('bottom' in box) {
2258
- top = Math.max(0, box.bottom - box.height - 50);
2259
- }
2260
- else {
2261
- top = 50; // Default fallback
2262
- }
2263
- this.addFloatingGroup(source, {
2264
- height: box.height,
2265
- width: box.width,
2266
- position: {
2267
- left,
2268
- top,
2269
- },
2270
- });
2271
- }
2925
+ destGridview.addView(source, size, dropLocation);
2926
+ this.setGroupLocationForRoot(source, destGridview);
2272
2927
  }
2273
2928
  }
2274
2929
  source.panels.forEach((panel) => {
@@ -2289,14 +2944,16 @@ export class DockviewComponent extends BaseGrid {
2289
2944
  }
2290
2945
  }
2291
2946
  doSetGroupActive(group) {
2947
+ var _a;
2292
2948
  super.doSetGroupActive(group);
2293
2949
  const activePanel = this.activePanel;
2294
2950
  if (!this._moving &&
2295
- activePanel !== this._onDidActivePanelChange.value) {
2296
- this._onDidActivePanelChange.fire(activePanel);
2951
+ activePanel !== ((_a = this._onDidActivePanelChange.value) === null || _a === void 0 ? void 0 : _a.panel)) {
2952
+ this.fireActivePanelChange(activePanel);
2297
2953
  }
2298
2954
  }
2299
2955
  doSetGroupAndPanelActive(group) {
2956
+ var _a;
2300
2957
  super.doSetGroupActive(group);
2301
2958
  const activePanel = this.activePanel;
2302
2959
  if (group &&
@@ -2305,8 +2962,8 @@ export class DockviewComponent extends BaseGrid {
2305
2962
  this.exitMaximizedGroup();
2306
2963
  }
2307
2964
  if (!this._moving &&
2308
- activePanel !== this._onDidActivePanelChange.value) {
2309
- this._onDidActivePanelChange.fire(activePanel);
2965
+ activePanel !== ((_a = this._onDidActivePanelChange.value) === null || _a === void 0 ? void 0 : _a.panel)) {
2966
+ this.fireActivePanelChange(activePanel);
2310
2967
  }
2311
2968
  }
2312
2969
  getNextGroupId() {
@@ -2335,9 +2992,11 @@ export class DockviewComponent extends BaseGrid {
2335
2992
  view.init({ params: {}, accessor: this });
2336
2993
  if (!this._groups.has(view.id)) {
2337
2994
  const disposable = new CompositeDisposable(view.model.onTabDragStart((event) => {
2338
- this._onWillDragPanel.fire(event);
2995
+ var _a;
2996
+ (_a = this._advancedDnDService) === null || _a === void 0 ? void 0 : _a.dispatchWillDragPanel(event);
2339
2997
  }), view.model.onGroupDragStart((event) => {
2340
- this._onWillDragGroup.fire(event);
2998
+ var _a;
2999
+ (_a = this._advancedDnDService) === null || _a === void 0 ? void 0 : _a.dispatchWillDragGroup(event);
2341
3000
  }), view.model.onMove((event) => {
2342
3001
  const { groupId, itemId, target, index, tabGroupId } = event;
2343
3002
  this.moveGroupOrPanel({
@@ -2355,15 +3014,19 @@ export class DockviewComponent extends BaseGrid {
2355
3014
  }), view.model.onDidDrop((event) => {
2356
3015
  this._onDidDrop.fire(event);
2357
3016
  }), view.model.onWillDrop((event) => {
2358
- this._onWillDrop.fire(event);
3017
+ var _a;
3018
+ (_a = this._advancedDnDService) === null || _a === void 0 ? void 0 : _a.dispatchWillDrop(event);
2359
3019
  }), view.model.onWillShowOverlay((event) => {
3020
+ var _a;
2360
3021
  if (this.options.disableDnd) {
3022
+ // Engine policy — stays in core, ahead of any
3023
+ // customisation dispatch.
2361
3024
  event.preventDefault();
2362
3025
  return;
2363
3026
  }
2364
- this._onWillShowOverlay.fire(event);
2365
- }), view.model.onUnhandledDragOverEvent((event) => {
2366
- this._onUnhandledDragOverEvent.fire(event);
3027
+ (_a = this._advancedDnDService) === null || _a === void 0 ? void 0 : _a.dispatchWillShowOverlay(event);
3028
+ }), view.model.onUnhandledDragOver((event) => {
3029
+ this._onUnhandledDragOver.fire(event);
2367
3030
  }), view.model.onDidAddPanel((event) => {
2368
3031
  if (this._moving) {
2369
3032
  return;
@@ -2375,27 +3038,17 @@ export class DockviewComponent extends BaseGrid {
2375
3038
  }
2376
3039
  this._onDidRemovePanel.fire(event.panel);
2377
3040
  }), view.model.onDidActivePanelChange((event) => {
3041
+ var _a;
2378
3042
  if (this._moving) {
2379
3043
  return;
2380
3044
  }
2381
3045
  if (event.panel !== this.activePanel) {
2382
3046
  return;
2383
3047
  }
2384
- if (this._onDidActivePanelChange.value !== event.panel) {
2385
- this._onDidActivePanelChange.fire(event.panel);
2386
- }
2387
- }), view.model.onDidCreateTabGroup((e) => {
2388
- this._onDidCreateTabGroup.fire(e);
2389
- }), view.model.onDidDestroyTabGroup((e) => {
2390
- this._onDidDestroyTabGroup.fire(e);
2391
- }), view.model.onDidAddPanelToTabGroup((e) => {
2392
- this._onDidAddPanelToTabGroup.fire(e);
2393
- }), view.model.onDidRemovePanelFromTabGroup((e) => {
2394
- this._onDidRemovePanelFromTabGroup.fire(e);
2395
- }), view.model.onDidTabGroupChange((e) => {
2396
- this._onDidTabGroupChange.fire(e);
2397
- }), view.model.onDidTabGroupCollapsedChange((e) => {
2398
- this._onDidTabGroupCollapsedChange.fire(e);
3048
+ if (((_a = this._onDidActivePanelChange.value) === null || _a === void 0 ? void 0 : _a.panel) !==
3049
+ event.panel) {
3050
+ this.fireActivePanelChange(event.panel);
3051
+ }
2399
3052
  }), Event.any(view.model.onDidPanelTitleChange, view.model.onDidPanelParametersChange)(() => {
2400
3053
  this._bufferOnDidLayoutChange.fire();
2401
3054
  }));
@@ -2423,11 +3076,73 @@ export class DockviewComponent extends BaseGrid {
2423
3076
  });
2424
3077
  return panel;
2425
3078
  }
2426
- createGroupAtLocation(location, size, options) {
3079
+ createGroupAtLocation(location, size, options, gridview = this.gridview) {
2427
3080
  const group = this.createGroup(options);
2428
- this.doAddGroup(group, location, size);
3081
+ this.doAddGroup(group, location, size, gridview);
3082
+ this.setGroupLocationForRoot(group, gridview);
2429
3083
  return group;
2430
3084
  }
3085
+ /**
3086
+ * Tag a group with the location and render / drop-target containers
3087
+ * matching the gridview root it now lives in: the main grid, a floating
3088
+ * window (shares the main containers), or a popout window (uses its own
3089
+ * window-local containers).
3090
+ */
3091
+ setGroupLocationForRoot(group, gridview) {
3092
+ var _a;
3093
+ const popout = (_a = this._popoutWindowService) === null || _a === void 0 ? void 0 : _a.entries.find((entry) => entry.gridview === gridview);
3094
+ if (popout) {
3095
+ if (group.model.renderContainer !== popout.overlayRenderContainer) {
3096
+ group.model.renderContainer = popout.overlayRenderContainer;
3097
+ }
3098
+ group.model.dropTargetContainer = popout.dropTargetContainer;
3099
+ group.model.location = {
3100
+ type: 'popout',
3101
+ getWindow: popout.getWindow,
3102
+ popoutUrl: popout.popoutUrl,
3103
+ };
3104
+ return;
3105
+ }
3106
+ // grid / floating both render through the main containers
3107
+ if (group.model.renderContainer !== this.overlayRenderContainer) {
3108
+ group.model.renderContainer = this.overlayRenderContainer;
3109
+ }
3110
+ group.model.dropTargetContainer = this.rootDropTargetContainer;
3111
+ group.model.location =
3112
+ gridview === this.gridview
3113
+ ? { type: 'grid' }
3114
+ : { type: 'floating' };
3115
+ }
3116
+ /**
3117
+ * Resolve which gridview root currently owns a group: the main grid, or
3118
+ * the nested gridview of the floating / popout window it lives in.
3119
+ */
3120
+ getGridviewForGroup(group) {
3121
+ var _a, _b;
3122
+ const floating = (_a = this._floatingGroupService) === null || _a === void 0 ? void 0 : _a.findByGroup(group);
3123
+ if (floating) {
3124
+ return floating.gridview;
3125
+ }
3126
+ // Use findByGroup (anchor-identity OR containment) for symmetry with
3127
+ // the floating branch — it also resolves an anchor whose element is
3128
+ // briefly detached from the gridview during a move/restore.
3129
+ const popout = (_b = this._popoutWindowService) === null || _b === void 0 ? void 0 : _b.findByGroup(group);
3130
+ if (popout) {
3131
+ return popout.gridview;
3132
+ }
3133
+ return this.gridview;
3134
+ }
3135
+ /**
3136
+ * The groups that live within the same floating / popout window as `group`
3137
+ * (including `group` itself). Empty when `group` is in the main grid.
3138
+ */
3139
+ nestedWindowMembers(group) {
3140
+ const gridview = this.getGridviewForGroup(group);
3141
+ if (gridview === this.gridview) {
3142
+ return [];
3143
+ }
3144
+ return this.groups.filter((candidate) => gridview.element.contains(candidate.element));
3145
+ }
2431
3146
  findGroup(panel) {
2432
3147
  var _a;
2433
3148
  return (_a = Array.from(this._groups.values()).find((group) => group.value.model.containsPanel(panel))) === null || _a === void 0 ? void 0 : _a.value;
@@ -2438,43 +3153,32 @@ export class DockviewComponent extends BaseGrid {
2438
3153
  ? rootOrientation
2439
3154
  : orthogonal(rootOrientation);
2440
3155
  }
2441
- updateDropTargetModel(options) {
2442
- if ('dndEdges' in options) {
2443
- const disabled = typeof options.dndEdges === 'boolean' &&
2444
- options.dndEdges === false;
2445
- this._rootDropTarget.disabled = disabled;
2446
- this._rootPointerDropTarget.disabled = disabled;
2447
- if (typeof options.dndEdges === 'object' &&
2448
- options.dndEdges !== null) {
2449
- this._rootDropTarget.setOverlayModel(options.dndEdges);
2450
- this._rootPointerDropTarget.setOverlayModel(options.dndEdges);
2451
- }
2452
- else {
2453
- this._rootDropTarget.setOverlayModel(DEFAULT_ROOT_OVERLAY_MODEL);
2454
- this._rootPointerDropTarget.setOverlayModel(DEFAULT_ROOT_OVERLAY_MODEL);
2455
- }
2456
- }
2457
- if ('rootOverlayModel' in options) {
2458
- this.updateDropTargetModel({ dndEdges: options.dndEdges });
2459
- }
2460
- }
2461
3156
  updateTheme() {
2462
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
3157
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
2463
3158
  const theme = (_a = this._options.theme) !== null && _a !== void 0 ? _a : themeAbyss;
2464
3159
  // Apply the theme class only to the shell so edge groups and the
2465
3160
  // main grid both inherit its CSS custom properties via the cascade.
2466
3161
  // Re-declaring it on `.dv-dockview` would block consumer overrides
2467
3162
  // set on the shell from reaching the dockview subtree.
2468
3163
  (_b = this._shellThemeClassnames) === null || _b === void 0 ? void 0 : _b.setClassNames(theme.className);
2469
- this.gridview.margin = (_c = theme.gap) !== null && _c !== void 0 ? _c : 0;
2470
- (_d = this._shellManager) === null || _d === void 0 ? void 0 : _d.updateTheme((_e = theme.gap) !== null && _e !== void 0 ? _e : 0, (_f = theme.edgeGroupCollapsedSize) !== null && _f !== void 0 ? _f : 35);
3164
+ const gap = (_c = theme.gap) !== null && _c !== void 0 ? _c : 0;
3165
+ this.gridview.margin = gap;
3166
+ // Floating / popout windows host their own nested gridviews; keep their
3167
+ // gap in sync with the main grid when the theme changes at runtime.
3168
+ for (const floating of this.floatingGroups) {
3169
+ floating.gridview.margin = gap;
3170
+ }
3171
+ for (const entry of (_e = (_d = this._popoutWindowService) === null || _d === void 0 ? void 0 : _d.entries) !== null && _e !== void 0 ? _e : []) {
3172
+ entry.gridview.margin = gap;
3173
+ }
3174
+ (_f = this._shellManager) === null || _f === void 0 ? void 0 : _f.updateTheme(gap, (_g = theme.edgeGroupCollapsedSize) !== null && _g !== void 0 ? _g : 35);
2471
3175
  if (theme.dndOverlayBorder !== undefined) {
2472
3176
  this.element.style.setProperty('--dv-drag-over-border', theme.dndOverlayBorder);
2473
- (_g = this._shellManager) === null || _g === void 0 ? void 0 : _g.element.style.setProperty('--dv-drag-over-border', theme.dndOverlayBorder);
3177
+ (_h = this._shellManager) === null || _h === void 0 ? void 0 : _h.element.style.setProperty('--dv-drag-over-border', theme.dndOverlayBorder);
2474
3178
  }
2475
3179
  else {
2476
3180
  this.element.style.removeProperty('--dv-drag-over-border');
2477
- (_h = this._shellManager) === null || _h === void 0 ? void 0 : _h.element.style.removeProperty('--dv-drag-over-border');
3181
+ (_j = this._shellManager) === null || _j === void 0 ? void 0 : _j.element.style.removeProperty('--dv-drag-over-border');
2478
3182
  }
2479
3183
  switch (theme.dndOverlayMounting) {
2480
3184
  case 'absolute':
@@ -2487,7 +3191,7 @@ export class DockviewComponent extends BaseGrid {
2487
3191
  }
2488
3192
  // Toggle a CSS class so theme stylesheets can scope pure-CSS
2489
3193
  // tab group indicator rules to the 'none' mode only.
2490
- const indicatorNone = ((_j = theme.tabGroupIndicator) !== null && _j !== void 0 ? _j : 'wrap') === 'none';
3194
+ const indicatorNone = ((_k = theme.tabGroupIndicator) !== null && _k !== void 0 ? _k : 'wrap') === 'none';
2491
3195
  toggleClass(this.element, 'dv-tab-group-indicator-none', indicatorNone);
2492
3196
  if (this._shellManager) {
2493
3197
  toggleClass(this._shellManager.element, 'dv-tab-group-indicator-none', indicatorNone);