dockview-react 8.6.0 → 8.6.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * dockview-react
3
- * @version 8.6.0
3
+ * @version 8.6.2
4
4
  * @link https://github.com/dockview/dockview
5
5
  * @license MIT
6
6
  */
@@ -51,7 +51,7 @@ react_dom = __toESM(react_dom);
51
51
  //#region ../dockview-core/dist/package/main.esm.mjs
52
52
  /**
53
53
  * dockview-core
54
- * @version 8.6.0
54
+ * @version 8.6.2
55
55
  * @link https://github.com/dockview/dockview
56
56
  * @license MIT
57
57
  */
@@ -725,7 +725,7 @@ react_dom = __toESM(react_dom);
725
725
  resizeTimeout = setTimeout(() => {
726
726
  cb();
727
727
  }, DEBOUCE_DELAY);
728
- }));
728
+ }), Disposable.from(() => clearTimeout(resizeTimeout)));
729
729
  }
730
730
  function shiftAbsoluteElementIntoView(element, root, options) {
731
731
  var _options$buffer;
@@ -2259,12 +2259,20 @@ react_dom = __toESM(react_dom);
2259
2259
  if (candiateNode instanceof BranchNode) return findLeaf(candiateNode.children[last ? candiateNode.children.length - 1 : 0], last);
2260
2260
  throw new Error("invalid node");
2261
2261
  }
2262
+ /**
2263
+ * The sizing to re-add a branch's child with, so a hidden child stays hidden
2264
+ * (with its cached size) instead of coming back visible at size 0.
2265
+ */
2266
+ function childSizing(node, index, size, scale = 1) {
2267
+ const cachedVisibleSize = node.getChildCachedVisibleSize(index);
2268
+ return cachedVisibleSize === void 0 ? size : Sizing.Invisible(Math.round(cachedVisibleSize * scale));
2269
+ }
2262
2270
  function cloneNode(node, size, orthogonalSize) {
2263
2271
  if (node instanceof BranchNode) {
2264
2272
  const result = new BranchNode(node.orientation, node.proportionalLayout, node.styles, size, orthogonalSize, node.disabled, node.margin);
2265
2273
  for (let i = node.children.length - 1; i >= 0; i--) {
2266
2274
  const child = node.children[i];
2267
- result.addChild(cloneNode(child, child.size, child.orthogonalSize), child.size, 0, true);
2275
+ result.addChild(cloneNode(child, child.size, child.orthogonalSize), childSizing(node, i, child.size), 0, true);
2268
2276
  }
2269
2277
  return result;
2270
2278
  } else return new LeafNode(node.view, node.orientation, orthogonalSize);
@@ -2273,13 +2281,14 @@ react_dom = __toESM(react_dom);
2273
2281
  if (node instanceof BranchNode) {
2274
2282
  const result = new BranchNode(orthogonal(node.orientation), node.proportionalLayout, node.styles, size, orthogonalSize, node.disabled, node.margin);
2275
2283
  let totalSize = 0;
2284
+ const cachedSizeScale = node.size === 0 ? 1 : size / node.size;
2276
2285
  for (let i = node.children.length - 1; i >= 0; i--) {
2277
2286
  const child = node.children[i];
2278
2287
  const childSize = child instanceof BranchNode ? child.orthogonalSize : child.size;
2279
2288
  let newSize = node.size === 0 ? 0 : Math.round(size * childSize / node.size);
2280
2289
  totalSize += newSize;
2281
2290
  if (i === 0) newSize += size - totalSize;
2282
- result.addChild(flipNode(child, orthogonalSize, newSize), newSize, 0, true);
2291
+ result.addChild(flipNode(child, orthogonalSize, newSize), childSizing(node, i, newSize, cachedSizeScale), 0, true);
2283
2292
  }
2284
2293
  return result;
2285
2294
  } else return new LeafNode(node.view, orthogonal(node.orientation), orthogonalSize);
@@ -2765,7 +2774,8 @@ react_dom = __toESM(react_dom);
2765
2774
  sizes.splice(parentIndex, 1, ...sibling.children.map((c) => c.size));
2766
2775
  for (let i = 0; i < sibling.children.length; i++) {
2767
2776
  const child = sibling.children[i];
2768
- grandParent.addChild(child, child.size, parentIndex + i);
2777
+ const childSize = childSizing(sibling, i, child.size);
2778
+ grandParent.addChild(child, !isSiblingVisible && typeof childSize === "number" ? Sizing.Invisible(childSize) : childSize, parentIndex + i);
2769
2779
  }
2770
2780
  /**
2771
2781
  * clean down the branch node since we need to dipose of it and
@@ -3943,6 +3953,33 @@ react_dom = __toESM(react_dom);
3943
3953
  return this.component.withOrigin("api", () => this.component.addGroup(options));
3944
3954
  }
3945
3955
  /**
3956
+ * Serialize one group together with the state of every panel it holds.
3957
+ * The result is self-contained and can be rehydrated with
3958
+ * `addGroupFromJSON`, on this instance or another one.
3959
+ */
3960
+ serializeGroup(group) {
3961
+ return this.component.serializeGroup(group);
3962
+ }
3963
+ /**
3964
+ * Rebuild a group from `serializeGroup` output and add it to the live
3965
+ * layout, positioned as for `addGroup`, leaving the rest of the layout
3966
+ * untouched. Throws before changing anything if a panel id is already in
3967
+ * use.
3968
+ */
3969
+ addGroupFromJSON(data, options) {
3970
+ return this.component.withOrigin("api", () => this.component.addGroupFromJSON(data, options));
3971
+ }
3972
+ /**
3973
+ * Add a panel from its `toJSON()` state, placed as `addPanel` places
3974
+ * one: the counterpart to `panel.toJSON()` for a single tab moving
3975
+ * between layouts or instances, as `addGroupFromJSON` is to
3976
+ * `serializeGroup`. Throws before changing anything if the panel id is
3977
+ * already in use.
3978
+ */
3979
+ addPanelFromJSON(state, options) {
3980
+ return this.component.withOrigin("api", () => this.component.addPanelFromJSON(state, options));
3981
+ }
3982
+ /**
3946
3983
  * Close all groups and panels.
3947
3984
  */
3948
3985
  closeAllGroups() {
@@ -5937,6 +5974,9 @@ react_dom = __toESM(react_dom);
5937
5974
  get element() {
5938
5975
  return this._element;
5939
5976
  }
5977
+ get dimensions() {
5978
+ return this._dimensions;
5979
+ }
5940
5980
  constructor(accessor, group) {
5941
5981
  var _this$accessor$resolv, _this$accessor, _this$accessor$resolv2, _this$accessor2, _this$accessor$onDidO, _this$accessor$onDidO2, _this$accessor3;
5942
5982
  super();
@@ -5947,12 +5987,14 @@ react_dom = __toESM(react_dom);
5947
5987
  this.onDidFocus = this._onDidFocus.event;
5948
5988
  this._onDidBlur = new Emitter();
5949
5989
  this.onDidBlur = this._onDidBlur.event;
5990
+ this._onDidDimensionsChange = new Emitter();
5991
+ this.onDidDimensionsChange = this._onDidDimensionsChange.event;
5950
5992
  this._element = document.createElement("div");
5951
5993
  this._element.className = "dv-content-container";
5952
5994
  this._element.tabIndex = -1;
5953
5995
  this._element.id = nextContentId();
5954
5996
  this._element.setAttribute("role", "tabpanel");
5955
- this.addDisposables(this._onDidFocus, this._onDidBlur);
5997
+ this.addDisposables(this._onDidFocus, this._onDidBlur, this._onDidDimensionsChange);
5956
5998
  const getOverrideTarget = () => {
5957
5999
  var _group$dropTargetCont;
5958
6000
  return (_group$dropTargetCont = group.dropTargetContainer) === null || _group$dropTargetCont === void 0 ? void 0 : _group$dropTargetCont.model;
@@ -6061,7 +6103,15 @@ react_dom = __toESM(react_dom);
6061
6103
  if (this.panel === panel) return;
6062
6104
  this.renderPanel(panel);
6063
6105
  }
6064
- layout(_width, _height) {}
6106
+ layout(width, height) {
6107
+ var _this$_dimensions;
6108
+ if (((_this$_dimensions = this._dimensions) === null || _this$_dimensions === void 0 ? void 0 : _this$_dimensions.width) === width && this._dimensions.height === height) return;
6109
+ this._dimensions = {
6110
+ width,
6111
+ height
6112
+ };
6113
+ this._onDidDimensionsChange.fire(this._dimensions);
6114
+ }
6065
6115
  closePanel() {
6066
6116
  if (this.panel) {
6067
6117
  if (this.panel.api.renderer === "onlyWhenVisible") {
@@ -6910,6 +6960,7 @@ react_dom = __toESM(react_dom);
6910
6960
  className: void 0,
6911
6961
  noPanelsOverlay: void 0,
6912
6962
  closeEmptyGroups: void 0,
6963
+ groupIdGenerator: void 0,
6913
6964
  dndEdges: void 0,
6914
6965
  dropPositionResolver: void 0,
6915
6966
  dndCompass: void 0,
@@ -10438,6 +10489,15 @@ react_dom = __toESM(react_dom);
10438
10489
  get panel() {
10439
10490
  return this.options.panel;
10440
10491
  }
10492
+ /**
10493
+ * For a drop on a group's tab bar, the tab index the drop landed at:
10494
+ * where a panel added in response belongs (`addPanel`'s `index`, or
10495
+ * `addGroupFromJSON` with `direction: 'within'`). `undefined` for a
10496
+ * drop on a group's content or a layout edge.
10497
+ */
10498
+ get index() {
10499
+ return this.options.index;
10500
+ }
10441
10501
  get group() {
10442
10502
  return this.options.group;
10443
10503
  }
@@ -10526,7 +10586,6 @@ react_dom = __toESM(react_dom);
10526
10586
  return (_this$_headerPosition = this._headerPosition) !== null && _this$_headerPosition !== void 0 ? _this$_headerPosition : "top";
10527
10587
  }
10528
10588
  set headerPosition(value) {
10529
- var _this$_activePanel;
10530
10589
  this._headerPosition = value;
10531
10590
  this.invalidateHeaderSize();
10532
10591
  removeClasses(this.container, "dv-groupview-header-top", "dv-groupview-header-bottom", "dv-groupview-header-left", "dv-groupview-header-right");
@@ -10536,10 +10595,7 @@ react_dom = __toESM(react_dom);
10536
10595
  this._headerDirection = direction;
10537
10596
  this.tabsContainer.direction = direction;
10538
10597
  this.header.direction = direction;
10539
- if ((_this$_activePanel = this._activePanel) === null || _this$_activePanel === void 0 ? void 0 : _this$_activePanel.layout) {
10540
- const { width, height } = this.contentDimensions();
10541
- this._activePanel.layout(width, height);
10542
- }
10598
+ if (this.panels.length > 0) this.layoutContent();
10543
10599
  this.updateHeaderActions();
10544
10600
  if (previousDirection !== void 0 && previousDirection !== direction) {
10545
10601
  var _this$groupPanel;
@@ -11042,10 +11098,7 @@ react_dom = __toESM(react_dom);
11042
11098
  this.panels.forEach((panel) => {
11043
11099
  this.rerender(panel);
11044
11100
  });
11045
- if (this._activePanel) {
11046
- const { width, height } = this.contentDimensions();
11047
- this._activePanel.layout(width, height);
11048
- }
11101
+ if (this.panels.length > 0) this.layoutContent();
11049
11102
  }
11050
11103
  get renderContainer() {
11051
11104
  var _this$_overwriteRende;
@@ -11092,10 +11145,10 @@ react_dom = __toESM(react_dom);
11092
11145
  return this.tabsContainer.indexOf(panel.id);
11093
11146
  }
11094
11147
  toJSON() {
11095
- var _this$_activePanel2;
11148
+ var _this$_activePanel;
11096
11149
  const result = {
11097
11150
  views: this.tabsContainer.panels,
11098
- activeView: (_this$_activePanel2 = this._activePanel) === null || _this$_activePanel2 === void 0 ? void 0 : _this$_activePanel2.id,
11151
+ activeView: (_this$_activePanel = this._activePanel) === null || _this$_activePanel === void 0 ? void 0 : _this$_activePanel.id,
11099
11152
  id: this.id
11100
11153
  };
11101
11154
  if (this.locked !== false) result.locked = this.locked;
@@ -11133,8 +11186,8 @@ react_dom = __toESM(react_dom);
11133
11186
  init(_params) {}
11134
11187
  update(_params) {}
11135
11188
  focus() {
11136
- var _this$_activePanel3;
11137
- (_this$_activePanel3 = this._activePanel) === null || _this$_activePanel3 === void 0 || _this$_activePanel3.focus();
11189
+ var _this$_activePanel2;
11190
+ (_this$_activePanel2 = this._activePanel) === null || _this$_activePanel2 === void 0 || _this$_activePanel2.focus();
11138
11191
  }
11139
11192
  openPanel(panel, options = {}) {
11140
11193
  if (typeof options.index !== "number" || options.index > this.panels.length) options.index = this.panels.length;
@@ -11192,12 +11245,24 @@ react_dom = __toESM(react_dom);
11192
11245
  this.updateContainer();
11193
11246
  }
11194
11247
  layout(width, height) {
11195
- var _this$_activePanel4;
11196
11248
  this._width = width;
11197
11249
  this._height = height;
11198
- const { width: contentWidth, height: contentHeight } = this.contentDimensions();
11199
- this.contentContainer.layout(contentWidth, contentHeight);
11200
- if ((_this$_activePanel4 = this._activePanel) === null || _this$_activePanel4 === void 0 ? void 0 : _this$_activePanel4.layout) this._activePanel.layout(contentWidth, contentHeight);
11250
+ this.layoutContent();
11251
+ }
11252
+ /**
11253
+ * Apply the current content box (group box minus header extent) to the
11254
+ * content container and the active panel. Every path that can change that
11255
+ * box goes through here so the container's recorded dimensions, which size
11256
+ * overlay-rendered panels, never lag the active panel's. Callers other
11257
+ * than `layout()` skip it for a group with no panels: there is nothing to
11258
+ * size, and measuring the header of a group that is not in the document
11259
+ * yet (the constructor sets `headerPosition`) would cache a zero extent.
11260
+ */
11261
+ layoutContent() {
11262
+ var _this$_activePanel3;
11263
+ const { width, height } = this.contentDimensions();
11264
+ this.contentContainer.layout(width, height);
11265
+ if ((_this$_activePanel3 = this._activePanel) === null || _this$_activePanel3 === void 0 ? void 0 : _this$_activePanel3.layout) this._activePanel.layout(width, height);
11201
11266
  }
11202
11267
  /**
11203
11268
  * Re-run the group's layout with its current dimensions. Used to propagate a
@@ -11290,8 +11355,7 @@ react_dom = __toESM(react_dom);
11290
11355
  this.tabsContainer.setActivePanel(panel);
11291
11356
  this.contentContainer.setLabelledBy(this.tabsContainer.getTabId(panel.id));
11292
11357
  this.contentContainer.openPanel(panel);
11293
- const { width, height } = this.contentDimensions();
11294
- panel.layout(width, height);
11358
+ this.layoutContent();
11295
11359
  this.updateMru(panel);
11296
11360
  this.contentContainer.refreshFocusState();
11297
11361
  this._onDidActivePanelChange.fire({
@@ -11303,8 +11367,8 @@ react_dom = __toESM(react_dom);
11303
11367
  }
11304
11368
  /** Label the group region with its active panel's title (the WAI-ARIA region name). */
11305
11369
  updateAccessibleLabel() {
11306
- var _this$_activePanel5;
11307
- const title = (_this$_activePanel5 = this._activePanel) === null || _this$_activePanel5 === void 0 ? void 0 : _this$_activePanel5.title;
11370
+ var _this$_activePanel4;
11371
+ const title = (_this$_activePanel4 = this._activePanel) === null || _this$_activePanel4 === void 0 ? void 0 : _this$_activePanel4.title;
11308
11372
  if (title) this.container.setAttribute("aria-label", title);
11309
11373
  else this.container.removeAttribute("aria-label");
11310
11374
  }
@@ -11369,6 +11433,7 @@ react_dom = __toESM(react_dom);
11369
11433
  nativeEvent: event,
11370
11434
  position,
11371
11435
  panel,
11436
+ index,
11372
11437
  getData: () => getPanelData(),
11373
11438
  kind: getKind(),
11374
11439
  group: this.groupPanel,
@@ -11414,6 +11479,7 @@ react_dom = __toESM(react_dom);
11414
11479
  nativeEvent: event,
11415
11480
  position,
11416
11481
  panel,
11482
+ index,
11417
11483
  getData: () => getPanelData(),
11418
11484
  group: this.groupPanel,
11419
11485
  api: this._api
@@ -14350,6 +14416,15 @@ react_dom = __toESM(react_dom);
14350
14416
  element.tabIndex = -1;
14351
14417
  return element;
14352
14418
  }
14419
+ /**
14420
+ * Write an inline pixel length only when it differs from what is already set,
14421
+ * so a pass that re-derives geometry already in place leaves the element's
14422
+ * style clean and the next measurement does not relayout the overlay subtree.
14423
+ */
14424
+ function setPixels(style, property, value) {
14425
+ const next = `${value}px`;
14426
+ if (style[property] !== next) style[property] = next;
14427
+ }
14353
14428
  var OverlayRenderContainer = class extends CompositeDisposable {
14354
14429
  constructor(element, accessor) {
14355
14430
  super();
@@ -14468,6 +14543,32 @@ react_dom = __toESM(react_dom);
14468
14543
  const generation = mapEntry.generation;
14469
14544
  const focusContainer = mapEntry.element;
14470
14545
  const contentElement = panel.view.content.element;
14546
+ /**
14547
+ * Size the overlay from the layout engine's numbers (the content box
14548
+ * the group model also hands to `panel.layout()`): a style write with
14549
+ * no forced layout, so the content has its real box before anything
14550
+ * can measure it, and `.dv-render-overlay`'s 100%/100% default is never
14551
+ * observed. Hidden panels are sized too: their content is laid out
14552
+ * (hidden by `visibility`) and must not keep the root's box.
14553
+ *
14554
+ * Only while the overlay is not on screen at a measured position,
14555
+ * i.e. hidden or not yet positioned. Once it is, the frame pass owns
14556
+ * the whole box: size and position must change together, and group
14557
+ * layouts driven from an animation frame would otherwise paint one
14558
+ * frame at the new size and the old position. Likewise not while the
14559
+ * peek force-shows the overlay, whose geometry belongs to
14560
+ * `repositionPanelOverlay`: the collapsed group's own layouts describe
14561
+ * the strip, not the peek. A zero box (a container awaiting layout) is
14562
+ * skipped too: whether 0x0 is a real collapse is the frame pass's call.
14563
+ */
14564
+ const applySize = (size) => {
14565
+ if (!size || size.width <= 0 || size.height <= 0) return;
14566
+ const entry = this.map[panel.api.id];
14567
+ if (!entry || entry.forceVisible || entry.positioned && panel.api.isVisible) return;
14568
+ setPixels(focusContainer.style, "width", size.width);
14569
+ setPixels(focusContainer.style, "height", size.height);
14570
+ };
14571
+ applySize(referenceContainer.dimensions);
14471
14572
  if (contentElement.parentElement !== focusContainer) focusContainer.appendChild(contentElement);
14472
14573
  if (focusContainer.parentElement !== this.element) this.element.appendChild(focusContainer);
14473
14574
  const resize = () => {
@@ -14507,10 +14608,10 @@ react_dom = __toESM(react_dom);
14507
14608
  }
14508
14609
  entry.retainPreviousGeometry = false;
14509
14610
  entry.positioned = true;
14510
- focusContainer.style.left = `${left}px`;
14511
- focusContainer.style.top = `${top}px`;
14512
- focusContainer.style.width = `${width}px`;
14513
- focusContainer.style.height = `${height}px`;
14611
+ setPixels(focusContainer.style, "left", left);
14612
+ setPixels(focusContainer.style, "top", top);
14613
+ setPixels(focusContainer.style, "width", width);
14614
+ setPixels(focusContainer.style, "height", height);
14514
14615
  if (panel.api.isVisible || forceVisible) {
14515
14616
  focusContainer.style.visibility = "";
14516
14617
  focusContainer.style.pointerEvents = "";
@@ -14626,6 +14727,9 @@ react_dom = __toESM(react_dom);
14626
14727
  if (!panel.api.isVisible) return;
14627
14728
  resize();
14628
14729
  }),
14730
+ referenceContainer.onDidDimensionsChange((size) => {
14731
+ applySize(size);
14732
+ }),
14629
14733
  panel.api.onDidLocationChange(() => {
14630
14734
  correctLayerPosition();
14631
14735
  })
@@ -15591,6 +15695,8 @@ react_dom = __toESM(react_dom);
15591
15695
  /** A never-firing history-change event, the fallback `onDidChangeHistory`
15592
15696
  * returns when the LayoutHistory module is absent. */
15593
15697
  const NO_LAYOUT_HISTORY_CHANGES = NO_EVENT;
15698
+ /** How many live ids a custom `groupIdGenerator` may return before giving up. */
15699
+ const MAX_GROUP_ID_ATTEMPTS = 100;
15594
15700
  var DockviewComponent = class extends BaseGrid {
15595
15701
  fireDidCreateTabGroup(event) {
15596
15702
  this._onDidCreateTabGroup.fire(event);
@@ -17440,7 +17546,7 @@ react_dom = __toESM(react_dom);
17440
17546
  if (typeof id !== "string") throw new TypeError("dockview: group id must be of type string");
17441
17547
  const group = this.createGroup({
17442
17548
  id,
17443
- locked: !!locked,
17549
+ locked: locked !== null && locked !== void 0 ? locked : false,
17444
17550
  hideHeader: !!hideHeader,
17445
17551
  headerPosition
17446
17552
  });
@@ -17713,6 +17819,32 @@ react_dom = __toESM(react_dom);
17713
17819
  addPanel(options) {
17714
17820
  return this.mutation("add", () => this._doAddPanel(options));
17715
17821
  }
17822
+ /**
17823
+ * Add a panel from its `toJSON()` state, placed as `addPanel` places
17824
+ * one: the counterpart to `panel.toJSON()` for a single tab moving
17825
+ * between layouts or instances, as `addGroupFromJSON` is to
17826
+ * `serializeGroup`. Throws before changing anything if the panel id is
17827
+ * already in use.
17828
+ */
17829
+ addPanelFromJSON(state, options) {
17830
+ if (!state.contentComponent) throw new Error(`dockview: serialized panel ${state.id} has no contentComponent`);
17831
+ return this.mutation("add", () => {
17832
+ const panel = this._doAddPanel(_objectSpread2$1(_objectSpread2$1({}, options), {}, {
17833
+ id: state.id,
17834
+ component: state.contentComponent,
17835
+ tabComponent: state.tabComponent,
17836
+ title: state.title,
17837
+ renderer: state.renderer,
17838
+ params: state.params,
17839
+ minimumWidth: state.minimumWidth,
17840
+ minimumHeight: state.minimumHeight,
17841
+ maximumWidth: state.maximumWidth,
17842
+ maximumHeight: state.maximumHeight
17843
+ }));
17844
+ if (state.pinned) this.setPanelPinned(panel, true);
17845
+ return panel;
17846
+ });
17847
+ }
17716
17848
  _doAddPanel(options) {
17717
17849
  if (this.panels.some((_) => _.id === options.id)) throw new Error(`dockview: panel with id ${options.id} already exists`);
17718
17850
  let referenceGroup;
@@ -17854,6 +17986,152 @@ react_dom = __toESM(react_dom);
17854
17986
  addGroup(options) {
17855
17987
  return this.mutation("add", () => this._doAddGroup(options));
17856
17988
  }
17989
+ /**
17990
+ * Serialize one group and the state of every panel it holds. The result
17991
+ * is self-contained: `addGroupFromJSON` on any dockview instance rebuilds
17992
+ * the group from it, with the same tab order, active tab, panel titles,
17993
+ * parameters and renderers.
17994
+ */
17995
+ serializeGroup(group) {
17996
+ const panels = {};
17997
+ for (const panel of group.panels) panels[panel.id] = panel.toJSON();
17998
+ return {
17999
+ group: group.model.toJSON(),
18000
+ panels
18001
+ };
18002
+ }
18003
+ /**
18004
+ * Rebuild a group from `serializeGroup` output and add it to the live
18005
+ * layout at `options` (as for `addGroup`), without touching the rest of
18006
+ * the layout. This is the counterpart to `fromJSON`, which replaces the
18007
+ * whole layout, for moving one group between layouts or instances.
18008
+ *
18009
+ * With `direction: 'within'` and a reference group or panel, no group is
18010
+ * created: the serialized panels are merged into that group, in their
18011
+ * order, at `index` if given, and the serialized active panel becomes
18012
+ * the group's. The serialized group's own shape (id, locked, header,
18013
+ * tab groups) does not apply then. This is what a drop on a group's
18014
+ * centre or tab bar calls for, and the only form that works against a
18015
+ * floating or edge group, which has no grid neighbours.
18016
+ *
18017
+ * A panel id that is already live throws, as `addPanel` does, before
18018
+ * anything is changed. A group id that is already live is reassigned with
18019
+ * a warning, as `addGroup` does.
18020
+ */
18021
+ addGroupFromJSON(data, options) {
18022
+ this.validateSerializedGroup(data);
18023
+ return this.mutation("add", () => this._doAddGroupFromJSON(data, options));
18024
+ }
18025
+ /**
18026
+ * Refuse an envelope that cannot be applied cleanly, before any mutation
18027
+ * is opened, so a refused call leaves no trace in the layout or its
18028
+ * events.
18029
+ */
18030
+ validateSerializedGroup(data) {
18031
+ const state = data === null || data === void 0 ? void 0 : data.group;
18032
+ if (typeof (state === null || state === void 0 ? void 0 : state.id) !== "string") throw new TypeError("dockview: group id must be of type string");
18033
+ if (!Array.isArray(state.views)) throw new TypeError(`dockview: serialized group ${state.id} has no views`);
18034
+ for (const id of state.views) {
18035
+ var _data$panels;
18036
+ if (!((_data$panels = data.panels) === null || _data$panels === void 0 ? void 0 : _data$panels[id])) throw new Error(`dockview: serialized group ${state.id} is missing panel state for ${id}`);
18037
+ if (this.panels.some((panel) => panel.id === id)) throw new Error(`dockview: panel with id ${id} already exists`);
18038
+ }
18039
+ }
18040
+ _doAddGroupFromJSON(data, options) {
18041
+ var _options$direction;
18042
+ const { id, locked, hideHeader, headerPosition, constraints, activeView, tabGroups } = data.group;
18043
+ if (options && ((_options$direction = options.direction) !== null && _options$direction !== void 0 ? _options$direction : "within") === "within") {
18044
+ const target = this.resolveReferenceGroup(options);
18045
+ if (target) return this.mergeGroupFromJSON(data, target, options);
18046
+ }
18047
+ const groupOptions = {
18048
+ id,
18049
+ locked: locked !== null && locked !== void 0 ? locked : false,
18050
+ hideHeader: !!hideHeader,
18051
+ headerPosition,
18052
+ constraints,
18053
+ initialWidth: options === null || options === void 0 ? void 0 : options.initialWidth,
18054
+ initialHeight: options === null || options === void 0 ? void 0 : options.initialHeight,
18055
+ skipSetActive: true
18056
+ };
18057
+ const group = this.addEmptyGroupAt(groupOptions, options);
18058
+ let panels;
18059
+ try {
18060
+ panels = this.deserializePanels(data, group);
18061
+ } catch (err) {
18062
+ this.doRemoveGroup(group, { skipActive: true });
18063
+ throw err;
18064
+ }
18065
+ for (const panel of panels) group.model.openPanel(panel, {
18066
+ skipSetActive: panel.id !== activeView,
18067
+ skipSetGroupActive: true
18068
+ });
18069
+ if (tabGroups && tabGroups.length > 0) group.model.restoreTabGroups(tabGroups);
18070
+ if (!group.activePanel && group.panels.length > 0) group.model.openPanel(group.panels[group.panels.length - 1], { skipSetGroupActive: true });
18071
+ if (!(options === null || options === void 0 ? void 0 : options.skipSetActive)) this.doSetGroupAndPanelActive(group);
18072
+ return group;
18073
+ }
18074
+ /**
18075
+ * Add an empty group at the placement `options` names, or append it
18076
+ * when they name none.
18077
+ */
18078
+ addEmptyGroupAt(groupOptions, options) {
18079
+ if (options !== void 0 && (isGroupOptionsWithPanel(options) || isGroupOptionsWithGroup(options) || typeof options.direction === "string")) return this._doAddGroup(_objectSpread2$1(_objectSpread2$1({}, options), groupOptions));
18080
+ const group = this.createGroup(groupOptions);
18081
+ this.doAddGroup(group);
18082
+ return group;
18083
+ }
18084
+ /**
18085
+ * Create every panel of a serialized group in `group`, in tab order,
18086
+ * before any is opened, so a corrupt panel state fails before a single
18087
+ * add event has fired (the ordering `fromJSON` uses). On failure the
18088
+ * panels made so far are disposed and the error rethrown.
18089
+ */
18090
+ deserializePanels(data, group) {
18091
+ const panels = [];
18092
+ try {
18093
+ for (const child of data.group.views) panels.push(this._deserializer.fromJSON(data.panels[child], group));
18094
+ } catch (err) {
18095
+ for (const panel of panels) panel.dispose();
18096
+ throw err;
18097
+ }
18098
+ return panels;
18099
+ }
18100
+ /**
18101
+ * The group an `AddGroupOptions` placement names, or `undefined` for an
18102
+ * absolute placement. Throws for a reference that names nothing.
18103
+ */
18104
+ resolveReferenceGroup(options) {
18105
+ if (isGroupOptionsWithPanel(options)) {
18106
+ const referencePanelId = typeof options.referencePanel === "string" ? options.referencePanel : options.referencePanel.id;
18107
+ const referencePanel = this.getGroupPanel(referencePanelId);
18108
+ if (!referencePanel) throw new Error(`dockview: reference panel ${referencePanelId} does not exist`);
18109
+ return referencePanel.group;
18110
+ }
18111
+ if (isGroupOptionsWithGroup(options)) {
18112
+ const referenceId = typeof options.referenceGroup === "string" ? options.referenceGroup : options.referenceGroup.id;
18113
+ const referenceGroup = this.getPanel(referenceId);
18114
+ if (!referenceGroup) throw new Error(`dockview: reference group ${referenceId} does not exist`);
18115
+ return referenceGroup;
18116
+ }
18117
+ }
18118
+ /**
18119
+ * The `direction: 'within'` form of `addGroupFromJSON`: the serialized
18120
+ * panels join `target` rather than a group of their own.
18121
+ */
18122
+ mergeGroupFromJSON(data, target, options) {
18123
+ const { activeView } = data.group;
18124
+ const index = options.index;
18125
+ this.deserializePanels(data, target).forEach((panel, offset) => {
18126
+ target.model.openPanel(panel, {
18127
+ index: index === void 0 ? void 0 : index + offset,
18128
+ skipSetActive: panel.id !== activeView,
18129
+ skipSetGroupActive: true
18130
+ });
18131
+ });
18132
+ if (!options.skipSetActive) this.doSetGroupAndPanelActive(target);
18133
+ return target;
18134
+ }
17857
18135
  _doAddGroup(options) {
17858
18136
  if (options) {
17859
18137
  let referenceGroup;
@@ -18558,10 +18836,26 @@ react_dom = __toESM(react_dom);
18558
18836
  if (group && this.hasMaximizedGroup() && !this.isMaximizedGroup(group)) this.exitMaximizedGroup();
18559
18837
  if (!this._moving && activePanel !== ((_this$_onDidActivePan2 = this._onDidActivePanelChange.value) === null || _this$_onDidActivePan2 === void 0 ? void 0 : _this$_onDidActivePan2.panel)) this.fireActivePanelChange(activePanel);
18560
18838
  }
18839
+ /**
18840
+ * The id for a group created without one: from `groupIdGenerator` when
18841
+ * the option is set, else the sequential counter. An id already in use is
18842
+ * skipped. The counter never repeats, so it can skip any number of live
18843
+ * ids (a restored layout may hold many); a custom generator is given a
18844
+ * bounded number of tries so a broken one cannot hang the call.
18845
+ */
18561
18846
  getNextGroupId() {
18562
- let id = this.nextGroupId.next();
18563
- while (this._groups.has(id)) id = this.nextGroupId.next();
18564
- return id;
18847
+ const generate = this.options.groupIdGenerator;
18848
+ if (!generate) {
18849
+ let id = this.nextGroupId.next();
18850
+ while (this._groups.has(id)) id = this.nextGroupId.next();
18851
+ return id;
18852
+ }
18853
+ for (let attempt = 0; attempt < MAX_GROUP_ID_ATTEMPTS; attempt++) {
18854
+ const id = generate();
18855
+ if (typeof id !== "string" || id.length === 0) throw new TypeError("dockview: groupIdGenerator must return a non-empty string");
18856
+ if (!this._groups.has(id)) return id;
18857
+ }
18858
+ throw new Error(`dockview: groupIdGenerator returned an id already in use ${MAX_GROUP_ID_ATTEMPTS} times in a row`);
18565
18859
  }
18566
18860
  createGroup(options) {
18567
18861
  var _options;
@@ -18571,10 +18865,7 @@ react_dom = __toESM(react_dom);
18571
18865
  console.warn(`dockview: Duplicate group id ${options === null || options === void 0 ? void 0 : options.id}. reassigning group id to avoid errors`);
18572
18866
  id = void 0;
18573
18867
  }
18574
- if (!id) {
18575
- id = this.nextGroupId.next();
18576
- while (this._groups.has(id)) id = this.nextGroupId.next();
18577
- }
18868
+ if (!id) id = this.getNextGroupId();
18578
18869
  const view = new DockviewGroupPanel(this, id, options);
18579
18870
  view.init({
18580
18871
  params: {},