dockview-core 6.2.2 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/cjs/api/dockviewGroupPanelApi.d.ts +10 -1
  2. package/dist/cjs/api/dockviewGroupPanelApi.js +16 -0
  3. package/dist/cjs/dnd/groupDragHandler.js +34 -12
  4. package/dist/cjs/dockview/components/titlebar/tabGroupIndicator.js +2 -2
  5. package/dist/cjs/dockview/components/titlebar/tabs.js +9 -2
  6. package/dist/cjs/dockview/components/titlebar/voidContainer.js +6 -0
  7. package/dist/cjs/dockview/dockviewComponent.d.ts +1 -0
  8. package/dist/cjs/dockview/dockviewComponent.js +187 -125
  9. package/dist/cjs/dockview/dockviewGroupPanelModel.d.ts +1 -0
  10. package/dist/cjs/dockview/dockviewGroupPanelModel.js +9 -0
  11. package/dist/cjs/dockview/dockviewPanel.js +5 -0
  12. package/dist/cjs/dockview/dockviewPanelModel.d.ts +2 -0
  13. package/dist/cjs/dockview/dockviewPanelModel.js +8 -0
  14. package/dist/cjs/dockview/framework.d.ts +8 -0
  15. package/dist/cjs/dockview/options.d.ts +13 -2
  16. package/dist/cjs/dockview/options.js +2 -0
  17. package/dist/cjs/dom.d.ts +5 -1
  18. package/dist/cjs/dom.js +14 -2
  19. package/dist/cjs/index.d.ts +1 -1
  20. package/dist/cjs/popoutWindow.d.ts +2 -0
  21. package/dist/cjs/popoutWindow.js +3 -1
  22. package/dist/dockview-core.js +156 -27
  23. package/dist/dockview-core.min.js +2 -2
  24. package/dist/dockview-core.min.js.map +1 -1
  25. package/dist/dockview-core.min.noStyle.js +2 -2
  26. package/dist/dockview-core.min.noStyle.js.map +1 -1
  27. package/dist/dockview-core.noStyle.js +156 -27
  28. package/dist/esm/api/dockviewGroupPanelApi.d.ts +10 -1
  29. package/dist/esm/api/dockviewGroupPanelApi.js +12 -0
  30. package/dist/esm/dnd/groupDragHandler.js +34 -12
  31. package/dist/esm/dockview/components/titlebar/tabGroupIndicator.js +2 -2
  32. package/dist/esm/dockview/components/titlebar/tabs.js +12 -2
  33. package/dist/esm/dockview/components/titlebar/voidContainer.js +6 -0
  34. package/dist/esm/dockview/dockviewComponent.d.ts +1 -0
  35. package/dist/esm/dockview/dockviewComponent.js +49 -7
  36. package/dist/esm/dockview/dockviewGroupPanelModel.d.ts +1 -0
  37. package/dist/esm/dockview/dockviewGroupPanelModel.js +9 -0
  38. package/dist/esm/dockview/dockviewPanel.js +5 -0
  39. package/dist/esm/dockview/dockviewPanelModel.d.ts +2 -0
  40. package/dist/esm/dockview/dockviewPanelModel.js +8 -0
  41. package/dist/esm/dockview/framework.d.ts +8 -0
  42. package/dist/esm/dockview/options.d.ts +13 -2
  43. package/dist/esm/dockview/options.js +2 -0
  44. package/dist/esm/dom.d.ts +5 -1
  45. package/dist/esm/dom.js +13 -2
  46. package/dist/esm/index.d.ts +1 -1
  47. package/dist/esm/popoutWindow.d.ts +2 -0
  48. package/dist/esm/popoutWindow.js +3 -1
  49. package/dist/package/main.cjs.js +156 -27
  50. package/dist/package/main.cjs.min.js +2 -2
  51. package/dist/package/main.esm.min.mjs +2 -2
  52. package/dist/package/main.esm.mjs +156 -27
  53. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * dockview-core
3
- * @version 6.2.2
3
+ * @version 6.3.0
4
4
  * @link https://github.com/mathuo/dockview
5
5
  * @license MIT
6
6
  */
@@ -478,8 +478,10 @@
478
478
  function quasiDefaultPrevented(event) {
479
479
  return event[QUASI_PREVENT_DEFAULT_KEY];
480
480
  }
481
- function addStyles(document, styleSheetList) {
481
+ function addStyles(document, styleSheetList, options = {}) {
482
482
  const styleSheets = Array.from(styleSheetList);
483
+ const { nonce } = options;
484
+ const resolvedNonce = typeof nonce === 'function' ? nonce(document) : nonce;
483
485
  for (const styleSheet of styleSheets) {
484
486
  if (styleSheet.href) {
485
487
  const link = document.createElement('link');
@@ -487,6 +489,10 @@
487
489
  link.type = styleSheet.type;
488
490
  link.rel = 'stylesheet';
489
491
  document.head.appendChild(link);
492
+ // The <link> will load and apply its rules in the target
493
+ // document. Reading cssRules here would duplicate them
494
+ // (and throws for cross-origin sheets).
495
+ continue;
490
496
  }
491
497
  let cssTexts = [];
492
498
  try {
@@ -497,11 +503,16 @@
497
503
  catch (err) {
498
504
  console.warn('dockview: failed to access stylesheet rules due to security restrictions', err);
499
505
  }
506
+ const fragment = document.createDocumentFragment();
500
507
  for (const rule of cssTexts) {
501
508
  const style = document.createElement('style');
509
+ if (resolvedNonce) {
510
+ style.setAttribute('nonce', resolvedNonce);
511
+ }
502
512
  style.appendChild(document.createTextNode(rule));
503
- document.head.appendChild(style);
513
+ fragment.appendChild(style);
504
514
  }
515
+ document.head.appendChild(fragment);
505
516
  }
506
517
  }
507
518
  function getDomNodePagePosition(domNode) {
@@ -5493,19 +5504,41 @@
5493
5504
  const bgColor = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-background-color');
5494
5505
  const color = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-color');
5495
5506
  if (dataTransfer) {
5496
- const ghostElement = document.createElement('div');
5497
- ghostElement.style.backgroundColor = bgColor;
5498
- ghostElement.style.color = color;
5499
- ghostElement.style.padding = '2px 8px';
5500
- ghostElement.style.height = '24px';
5501
- ghostElement.style.fontSize = '11px';
5502
- ghostElement.style.lineHeight = '20px';
5503
- ghostElement.style.borderRadius = '12px';
5504
- ghostElement.style.position = 'absolute';
5505
- ghostElement.style.pointerEvents = 'none';
5506
- ghostElement.style.top = '-9999px';
5507
- ghostElement.textContent = `Multiple Panels (${this.group.size})`;
5507
+ const createGhost = this.accessor.options.createGroupDragGhostComponent;
5508
+ let ghostElement;
5509
+ let customRenderer;
5510
+ if (createGhost) {
5511
+ customRenderer = createGhost(this.group);
5512
+ customRenderer.init({
5513
+ group: this.group,
5514
+ api: this.accessor.api,
5515
+ });
5516
+ ghostElement = customRenderer.element;
5517
+ ghostElement.style.position = 'absolute';
5518
+ ghostElement.style.pointerEvents = 'none';
5519
+ ghostElement.style.top = '-9999px';
5520
+ }
5521
+ else {
5522
+ ghostElement = document.createElement('div');
5523
+ ghostElement.style.backgroundColor = bgColor;
5524
+ ghostElement.style.color = color;
5525
+ ghostElement.style.padding = '2px 8px';
5526
+ ghostElement.style.height = '24px';
5527
+ ghostElement.style.fontSize = '11px';
5528
+ ghostElement.style.lineHeight = '20px';
5529
+ ghostElement.style.borderRadius = '12px';
5530
+ ghostElement.style.position = 'absolute';
5531
+ ghostElement.style.pointerEvents = 'none';
5532
+ ghostElement.style.top = '-9999px';
5533
+ ghostElement.textContent = `Multiple Panels (${this.group.size})`;
5534
+ }
5508
5535
  addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 });
5536
+ if (customRenderer === null || customRenderer === void 0 ? void 0 : customRenderer.dispose) {
5537
+ // addGhostImage removes the element from the DOM on the next
5538
+ // tick; dispose the framework renderer on the same schedule.
5539
+ const renderer = customRenderer;
5540
+ setTimeout(() => { var _a; return (_a = renderer.dispose) === null || _a === void 0 ? void 0 : _a.call(renderer); }, 0);
5541
+ }
5509
5542
  }
5510
5543
  return {
5511
5544
  dispose: () => {
@@ -5538,6 +5571,12 @@
5538
5571
  this.dropTarget = new Droptarget(this._element, {
5539
5572
  acceptedTargetZones: ['center'],
5540
5573
  canDisplayOverlay: (event, position) => {
5574
+ if (this.group.api.locked) {
5575
+ // Dropping on the void/header space adds the panel
5576
+ // to this group, which `locked` is meant to prevent
5577
+ // (both `true` and `'no-drop-target'`).
5578
+ return false;
5579
+ }
5541
5580
  const data = getPanelData();
5542
5581
  if (data && this.accessor.id === data.viewId) {
5543
5582
  return true;
@@ -6119,7 +6158,7 @@
6119
6158
  let svg = underline.firstElementChild;
6120
6159
  let path;
6121
6160
  if (!svg || svg.tagName !== 'svg') {
6122
- underline.innerHTML = '';
6161
+ underline.replaceChildren();
6123
6162
  svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
6124
6163
  svg.style.display = 'block';
6125
6164
  path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
@@ -6217,7 +6256,7 @@
6217
6256
  underline.style.display = '';
6218
6257
  // Clear any SVG content left over from a mode switch
6219
6258
  if (underline.firstElementChild) {
6220
- underline.innerHTML = '';
6259
+ underline.replaceChildren();
6221
6260
  }
6222
6261
  underline.style.backgroundColor = color;
6223
6262
  if (isVertical) {
@@ -7610,11 +7649,15 @@
7610
7649
  if (!isInsideRange && !isJustBeforeGroup) {
7611
7650
  continue;
7612
7651
  }
7613
- if (isGroupDrag) {
7652
+ if (isGroupDrag && isInsideRange) {
7614
7653
  // A group cannot be dropped inside another group.
7615
7654
  // Snap the insertion index to just before or just
7616
7655
  // after this group based on cursor position relative
7617
- // to the group's midpoint.
7656
+ // to the group's midpoint. Only applies when the
7657
+ // insertion would land *inside* the group — for
7658
+ // `isJustBeforeGroup`, the index is already outside
7659
+ // (immediately left of the group) and is a valid
7660
+ // drop position, so leave it untouched (issue #1264).
7618
7661
  const groupMid = (firstIdx + lastIdx + 1) / 2;
7619
7662
  if (insertionIndex < groupMid) {
7620
7663
  insertionIndex = firstIdx;
@@ -7625,6 +7668,12 @@
7625
7668
  // targetTabGroupId stays null
7626
7669
  break;
7627
7670
  }
7671
+ if (isGroupDrag && isJustBeforeGroup) {
7672
+ // Cursor is just before the group — accept this
7673
+ // index as-is. Groups can be dropped at the slot
7674
+ // immediately left of another group's first tab.
7675
+ break;
7676
+ }
7628
7677
  if (isJustBeforeGroup) {
7629
7678
  // Check whether only the source tab (or source group
7630
7679
  // tabs) sits between insertionIndex and firstIdx.
@@ -8466,6 +8515,7 @@
8466
8515
  disableFloatingGroups: undefined,
8467
8516
  floatingGroupBounds: undefined,
8468
8517
  popoutUrl: undefined,
8518
+ nonce: undefined,
8469
8519
  defaultRenderer: undefined,
8470
8520
  defaultHeaderPosition: undefined,
8471
8521
  debug: undefined,
@@ -8481,6 +8531,7 @@
8481
8531
  getTabContextMenuItems: undefined,
8482
8532
  getTabGroupChipContextMenuItems: undefined,
8483
8533
  createTabGroupChipComponent: undefined,
8534
+ createGroupDragGhostComponent: undefined,
8484
8535
  tabGroupColors: undefined,
8485
8536
  tabGroupAccent: undefined,
8486
8537
  };
@@ -9173,6 +9224,15 @@
9173
9224
  refreshTabGroupAccent() {
9174
9225
  this.tabsContainer.refreshTabGroupAccent();
9175
9226
  }
9227
+ refreshWatermark() {
9228
+ var _a, _b;
9229
+ if (this.watermark) {
9230
+ this.watermark.element.remove();
9231
+ (_b = (_a = this.watermark).dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
9232
+ this.watermark = undefined;
9233
+ }
9234
+ this.updateContainer();
9235
+ }
9176
9236
  getTabGroupForPanel(panelId) {
9177
9237
  return this._findTabGroupForPanel(panelId);
9178
9238
  }
@@ -10003,6 +10063,18 @@
10003
10063
  }
10004
10064
  return this._group.model.location;
10005
10065
  }
10066
+ get locked() {
10067
+ if (!this._group) {
10068
+ throw new Error(NOT_INITIALIZED_MESSAGE);
10069
+ }
10070
+ return this._group.locked;
10071
+ }
10072
+ set locked(value) {
10073
+ if (!this._group) {
10074
+ throw new Error(NOT_INITIALIZED_MESSAGE);
10075
+ }
10076
+ this._group.locked = value;
10077
+ }
10006
10078
  constructor(id, accessor) {
10007
10079
  super(id, '__dockviewgroup__');
10008
10080
  this.accessor = accessor;
@@ -10593,6 +10665,11 @@
10593
10665
  const didTitleChange = title !== this.title;
10594
10666
  if (didTitleChange) {
10595
10667
  this._title = title;
10668
+ // keep the view-model's cached init params in sync so that tab
10669
+ // renderers constructed lazily (e.g. the header overflow
10670
+ // dropdown via createTabRenderer) see the updated title
10671
+ // (#914).
10672
+ this.view.setTitle(title);
10596
10673
  this.api._onDidTitleChange.fire({ title });
10597
10674
  }
10598
10675
  }
@@ -10753,6 +10830,14 @@
10753
10830
  this.content.init(params);
10754
10831
  this.tab.init(Object.assign(Object.assign({}, params), { tabLocation: 'header' }));
10755
10832
  }
10833
+ setTitle(title) {
10834
+ // keep the cached init params in sync so that tab renderers created
10835
+ // lazily after the title changes (e.g. for the header overflow
10836
+ // dropdown) see the current title rather than the stale original.
10837
+ if (this._params) {
10838
+ this._params.title = title;
10839
+ }
10840
+ }
10756
10841
  layout(width, height) {
10757
10842
  var _a, _b;
10758
10843
  (_b = (_a = this.content).layout) === null || _b === void 0 ? void 0 : _b.call(_a, width, height);
@@ -11672,7 +11757,9 @@
11672
11757
  const externalDocument = externalWindow.document;
11673
11758
  externalDocument.title = document.title;
11674
11759
  externalDocument.body.appendChild(container);
11675
- addStyles(externalDocument, window.document.styleSheets);
11760
+ addStyles(externalDocument, window.document.styleSheets, {
11761
+ nonce: this.options.nonce,
11762
+ });
11676
11763
  /**
11677
11764
  * beforeunload must be registered after load for reasons I could not determine
11678
11765
  * otherwise the beforeunload event will not fire when the window is closed
@@ -12862,6 +12949,7 @@
12862
12949
  this._floatingGroups = [];
12863
12950
  this._popoutGroups = [];
12864
12951
  this._popoutRestorationPromise = Promise.resolve();
12952
+ this._popoutRestorationCleanups = new Set();
12865
12953
  this._onDidRemoveGroup = new Emitter();
12866
12954
  this.onDidRemoveGroup = this._onDidRemoveGroup.event;
12867
12955
  this._onDidAddGroup = new Emitter();
@@ -12967,6 +13055,14 @@
12967
13055
  this._bufferOnDidLayoutChange.fire();
12968
13056
  }), exports.DockviewDisposable.from(() => {
12969
13057
  var _a;
13058
+ // Cancel any pending popout-restoration timers scheduled by
13059
+ // fromJSON so they don't open new browser windows after
13060
+ // dispose, and resolve their promises so callers awaiting
13061
+ // popoutRestorationPromise don't hang. See issue #851.
13062
+ for (const cleanup of [...this._popoutRestorationCleanups]) {
13063
+ cleanup();
13064
+ }
13065
+ this._popoutRestorationCleanups.clear();
12970
13066
  // iterate over a copy of the array since .dispose() mutates the original array
12971
13067
  for (const group of [...this._floatingGroups]) {
12972
13068
  group.dispose();
@@ -13063,7 +13159,7 @@
13063
13159
  return (_a = this._popoutPopupServices.get(group.id)) !== null && _a !== void 0 ? _a : this.popupService;
13064
13160
  }
13065
13161
  addPopoutGroup(itemToPopout, options) {
13066
- var _a, _b, _c, _d, _e;
13162
+ var _a, _b, _c, _d, _e, _f;
13067
13163
  if (itemToPopout instanceof DockviewGroupPanel &&
13068
13164
  itemToPopout.model.location.type === 'edge') {
13069
13165
  // edge groups are permanent structural elements and cannot be popped out
@@ -13098,6 +13194,7 @@
13098
13194
  height: box.height,
13099
13195
  onDidOpen: options === null || options === void 0 ? void 0 : options.onDidOpen,
13100
13196
  onWillClose: options === null || options === void 0 ? void 0 : options.onWillClose,
13197
+ nonce: (_f = this.options) === null || _f === void 0 ? void 0 : _f.nonce,
13101
13198
  });
13102
13199
  const popoutWindowDisposable = new CompositeDisposable(_window, _window.onDidClose(() => {
13103
13200
  popoutWindowDisposable.dispose();
@@ -13515,7 +13612,7 @@
13515
13612
  }
13516
13613
  }
13517
13614
  updateOptions(options) {
13518
- var _a, _b, _c;
13615
+ var _a, _b, _c, _d, _e;
13519
13616
  super.updateOptions(options);
13520
13617
  if ('floatingGroupBounds' in options) {
13521
13618
  for (const group of this._floatingGroups) {
@@ -13556,8 +13653,19 @@
13556
13653
  group.model.updateHeaderActions();
13557
13654
  }
13558
13655
  }
13656
+ if ('createWatermarkComponent' in options) {
13657
+ if (this._watermark) {
13658
+ this._watermark.element.parentElement.remove();
13659
+ (_d = (_c = this._watermark).dispose) === null || _d === void 0 ? void 0 : _d.call(_c);
13660
+ this._watermark = null;
13661
+ }
13662
+ this.updateWatermark();
13663
+ for (const group of this.groups) {
13664
+ group.model.refreshWatermark();
13665
+ }
13666
+ }
13559
13667
  if ('tabGroupColors' in options || 'tabGroupAccent' in options) {
13560
- this._tabGroupColorPalette.setEntries((_c = this._options.tabGroupColors) !== null && _c !== void 0 ? _c : DEFAULT_TAB_GROUP_COLORS);
13668
+ this._tabGroupColorPalette.setEntries((_e = this._options.tabGroupColors) !== null && _e !== void 0 ? _e : DEFAULT_TAB_GROUP_COLORS);
13561
13669
  this._tabGroupColorPalette.enabled =
13562
13670
  this._options.tabGroupAccent !== 'off';
13563
13671
  for (const group of this.groups) {
@@ -13983,7 +14091,23 @@
13983
14091
  const group = createGroupFromSerializedState(data);
13984
14092
  // Add a small delay for each popup after the first to avoid browser popup blocking
13985
14093
  const popoutPromise = new Promise((resolve) => {
13986
- setTimeout(() => {
14094
+ const cleanup = () => {
14095
+ this._popoutRestorationCleanups.delete(cleanup);
14096
+ clearTimeout(handle);
14097
+ resolve();
14098
+ };
14099
+ const handle = setTimeout(() => {
14100
+ this._popoutRestorationCleanups.delete(cleanup);
14101
+ // Guard against the component being disposed before
14102
+ // this timer fires. Under React StrictMode the
14103
+ // component is mounted -> disposed -> remounted, and
14104
+ // without this guard the first instance's queued
14105
+ // restoration would open a second popout window.
14106
+ // See issue #851.
14107
+ if (this.isDisposed) {
14108
+ resolve();
14109
+ return;
14110
+ }
13987
14111
  this.addPopoutGroup(group, {
13988
14112
  position: position !== null && position !== void 0 ? position : undefined,
13989
14113
  overridePopoutGroup: gridReferenceGroup
@@ -13996,6 +14120,7 @@
13996
14120
  });
13997
14121
  resolve();
13998
14122
  }, index * DESERIALIZATION_POPOUT_DELAY_MS); // 100ms delay between each popup
14123
+ this._popoutRestorationCleanups.add(cleanup);
13999
14124
  });
14000
14125
  popoutPromises.push(popoutPromise);
14001
14126
  });
@@ -14526,8 +14651,11 @@
14526
14651
  * the source group is a popout group with a single panel
14527
14652
  *
14528
14653
  * 1. remove the panel from the group without triggering any events
14529
- * 2. remove the popout group
14530
- * 3. create a new group at the requested location and add that panel
14654
+ * 2. remove the popout group — this may cascade-remove the empty
14655
+ * reference group it left behind in the main grid (see
14656
+ * doRemoveGroup for popout groups), which can shift grid indices
14657
+ * 3. recompute the target location now that the grid is stable
14658
+ * 4. create a new group at the recomputed location and add that panel
14531
14659
  */
14532
14660
  const popoutGroup = this._popoutGroups.find((group) => group.popoutGroup === sourceGroup);
14533
14661
  const removedPanel = this.movingLock(() => popoutGroup.popoutGroup.model.removePanel(popoutGroup.popoutGroup.panels[0], {
@@ -14535,7 +14663,8 @@
14535
14663
  skipSetActiveGroup: true,
14536
14664
  }));
14537
14665
  this.doRemoveGroup(sourceGroup, { skipActive: true });
14538
- const newGroup = this.createGroupAtLocation(targetLocation);
14666
+ const updatedTargetLocation = getRelativeLocation(this.gridview.orientation, getGridLocation(destinationGroup.element), destinationTarget);
14667
+ const newGroup = this.createGroupAtLocation(updatedTargetLocation);
14539
14668
  this.movingLock(() => newGroup.model.openPanel(removedPanel, {
14540
14669
  skipSetActive: true,
14541
14670
  }));
@@ -1,7 +1,7 @@
1
1
  import { Position } from '../dnd/droptarget';
2
2
  import { DockviewComponent } from '../dockview/dockviewComponent';
3
3
  import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
4
- import { DockviewGroupChangeEvent, DockviewGroupLocation } from '../dockview/dockviewGroupPanelModel';
4
+ import { DockviewGroupChangeEvent, DockviewGroupLocation, DockviewGroupPanelLocked } from '../dockview/dockviewGroupPanelModel';
5
5
  import { DockviewHeaderPosition } from '../dockview/options';
6
6
  import { Emitter, Event } from '../events';
7
7
  import { GridviewPanelApi, GridviewPanelApiImpl, SizeEvent } from './gridviewPanelApi';
@@ -29,6 +29,13 @@ export interface DockviewGroupPanelApi extends GridviewPanelApi {
29
29
  */
30
30
  readonly onDidCollapsedChange: Event<DockviewGroupPanelCollapsedChangeEvent>;
31
31
  readonly location: DockviewGroupLocation;
32
+ /**
33
+ * Whether this group is locked against drop interactions.
34
+ * - `true`: panels cannot be dropped into the group (center / tabs),
35
+ * but the group can still be split from its edges.
36
+ * - `'no-drop-target'`: all drop zones are disabled for this group.
37
+ */
38
+ locked: DockviewGroupPanelLocked;
32
39
  /**
33
40
  * If you require the Window object
34
41
  */
@@ -68,6 +75,8 @@ export declare class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
68
75
  readonly _onDidCollapsedChange: Emitter<DockviewGroupPanelCollapsedChangeEvent>;
69
76
  readonly onDidCollapsedChange: Event<DockviewGroupPanelCollapsedChangeEvent>;
70
77
  get location(): DockviewGroupLocation;
78
+ get locked(): DockviewGroupPanelLocked;
79
+ set locked(value: DockviewGroupPanelLocked);
71
80
  constructor(id: string, accessor: DockviewComponent);
72
81
  setSize(event: SizeEvent): void;
73
82
  close(): void;
@@ -9,6 +9,18 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
9
9
  }
10
10
  return this._group.model.location;
11
11
  }
12
+ get locked() {
13
+ if (!this._group) {
14
+ throw new Error(NOT_INITIALIZED_MESSAGE);
15
+ }
16
+ return this._group.locked;
17
+ }
18
+ set locked(value) {
19
+ if (!this._group) {
20
+ throw new Error(NOT_INITIALIZED_MESSAGE);
21
+ }
22
+ this._group.locked = value;
23
+ }
12
24
  constructor(id, accessor) {
13
25
  super(id, '__dockviewgroup__');
14
26
  this.accessor = accessor;
@@ -36,19 +36,41 @@ export class GroupDragHandler extends DragHandler {
36
36
  const bgColor = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-background-color');
37
37
  const color = style.getPropertyValue('--dv-activegroup-visiblepanel-tab-color');
38
38
  if (dataTransfer) {
39
- const ghostElement = document.createElement('div');
40
- ghostElement.style.backgroundColor = bgColor;
41
- ghostElement.style.color = color;
42
- ghostElement.style.padding = '2px 8px';
43
- ghostElement.style.height = '24px';
44
- ghostElement.style.fontSize = '11px';
45
- ghostElement.style.lineHeight = '20px';
46
- ghostElement.style.borderRadius = '12px';
47
- ghostElement.style.position = 'absolute';
48
- ghostElement.style.pointerEvents = 'none';
49
- ghostElement.style.top = '-9999px';
50
- ghostElement.textContent = `Multiple Panels (${this.group.size})`;
39
+ const createGhost = this.accessor.options.createGroupDragGhostComponent;
40
+ let ghostElement;
41
+ let customRenderer;
42
+ if (createGhost) {
43
+ customRenderer = createGhost(this.group);
44
+ customRenderer.init({
45
+ group: this.group,
46
+ api: this.accessor.api,
47
+ });
48
+ ghostElement = customRenderer.element;
49
+ ghostElement.style.position = 'absolute';
50
+ ghostElement.style.pointerEvents = 'none';
51
+ ghostElement.style.top = '-9999px';
52
+ }
53
+ else {
54
+ ghostElement = document.createElement('div');
55
+ ghostElement.style.backgroundColor = bgColor;
56
+ ghostElement.style.color = color;
57
+ ghostElement.style.padding = '2px 8px';
58
+ ghostElement.style.height = '24px';
59
+ ghostElement.style.fontSize = '11px';
60
+ ghostElement.style.lineHeight = '20px';
61
+ ghostElement.style.borderRadius = '12px';
62
+ ghostElement.style.position = 'absolute';
63
+ ghostElement.style.pointerEvents = 'none';
64
+ ghostElement.style.top = '-9999px';
65
+ ghostElement.textContent = `Multiple Panels (${this.group.size})`;
66
+ }
51
67
  addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 });
68
+ if (customRenderer === null || customRenderer === void 0 ? void 0 : customRenderer.dispose) {
69
+ // addGhostImage removes the element from the DOM on the next
70
+ // tick; dispose the framework renderer on the same schedule.
71
+ const renderer = customRenderer;
72
+ setTimeout(() => { var _a; return (_a = renderer.dispose) === null || _a === void 0 ? void 0 : _a.call(renderer); }, 0);
73
+ }
52
74
  }
53
75
  return {
54
76
  dispose: () => {
@@ -241,7 +241,7 @@ export class WrapTabGroupIndicator extends BaseTabGroupIndicator {
241
241
  let svg = underline.firstElementChild;
242
242
  let path;
243
243
  if (!svg || svg.tagName !== 'svg') {
244
- underline.innerHTML = '';
244
+ underline.replaceChildren();
245
245
  svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
246
246
  svg.style.display = 'block';
247
247
  path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
@@ -339,7 +339,7 @@ export class NoneTabGroupIndicator extends BaseTabGroupIndicator {
339
339
  underline.style.display = '';
340
340
  // Clear any SVG content left over from a mode switch
341
341
  if (underline.firstElementChild) {
342
- underline.innerHTML = '';
342
+ underline.replaceChildren();
343
343
  }
344
344
  underline.style.backgroundColor = color;
345
345
  if (isVertical) {
@@ -987,11 +987,15 @@ export class Tabs extends CompositeDisposable {
987
987
  if (!isInsideRange && !isJustBeforeGroup) {
988
988
  continue;
989
989
  }
990
- if (isGroupDrag) {
990
+ if (isGroupDrag && isInsideRange) {
991
991
  // A group cannot be dropped inside another group.
992
992
  // Snap the insertion index to just before or just
993
993
  // after this group based on cursor position relative
994
- // to the group's midpoint.
994
+ // to the group's midpoint. Only applies when the
995
+ // insertion would land *inside* the group — for
996
+ // `isJustBeforeGroup`, the index is already outside
997
+ // (immediately left of the group) and is a valid
998
+ // drop position, so leave it untouched (issue #1264).
995
999
  const groupMid = (firstIdx + lastIdx + 1) / 2;
996
1000
  if (insertionIndex < groupMid) {
997
1001
  insertionIndex = firstIdx;
@@ -1002,6 +1006,12 @@ export class Tabs extends CompositeDisposable {
1002
1006
  // targetTabGroupId stays null
1003
1007
  break;
1004
1008
  }
1009
+ if (isGroupDrag && isJustBeforeGroup) {
1010
+ // Cursor is just before the group — accept this
1011
+ // index as-is. Groups can be dropped at the slot
1012
+ // immediately left of another group's first tab.
1013
+ break;
1014
+ }
1005
1015
  if (isJustBeforeGroup) {
1006
1016
  // Check whether only the source tab (or source group
1007
1017
  // tabs) sits between insertionIndex and firstIdx.
@@ -27,6 +27,12 @@ export class VoidContainer extends CompositeDisposable {
27
27
  this.dropTarget = new Droptarget(this._element, {
28
28
  acceptedTargetZones: ['center'],
29
29
  canDisplayOverlay: (event, position) => {
30
+ if (this.group.api.locked) {
31
+ // Dropping on the void/header space adds the panel
32
+ // to this group, which `locked` is meant to prevent
33
+ // (both `true` and `'no-drop-target'`).
34
+ return false;
35
+ }
30
36
  const data = getPanelData();
31
37
  if (data && this.accessor.id === data.viewId) {
32
38
  return true;
@@ -262,6 +262,7 @@ export declare class DockviewComponent extends BaseGrid<DockviewGroupPanel> impl
262
262
  private readonly _popoutGroups;
263
263
  private readonly _rootDropTarget;
264
264
  private _popoutRestorationPromise;
265
+ private readonly _popoutRestorationCleanups;
265
266
  private readonly _onDidRemoveGroup;
266
267
  readonly onDidRemoveGroup: Event<DockviewGroupPanel>;
267
268
  protected readonly _onDidAddGroup: Emitter<DockviewGroupPanel>;