Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
--dv-tabs-and-actions-container-font-size: 12px;
}

.bb-dockview .dv-resize-container {
border: 1px solid var(--bs-border-color);
}

.bb-dockview .bb-dockview-panel {
height: 100%;
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,10 @@
.dv-tabs-and-actions-container .dv-right-actions-container {
display: flex;
}
.dv-watermark {
display: flex;
height: 100%;
}
.dv-dragged {
transform: translate3d(0px, 0px, 0px); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
}
Expand Down Expand Up @@ -1185,8 +1189,4 @@
.dv-tab .dv-default-tab .dv-default-tab-action:hover {
border-radius: 2px;
background-color: var(--dv-icon-hover-background-color);
}
.dv-watermark {
display: flex;
height: 100%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,25 @@ const saveConfig = dockview => {
const json = dockview.toJSON();
if (dockview.floatingGroups && dockview.floatingGroups.length > 0) {
json.floatingGroups.forEach((fg, index) => {
const width = dockview.floatingGroups[index].group.width
fg.position.width = fg.position.width || (width ? width + 2 : 300)
fg.position.height = fg.position.height || dockview.floatingGroups[index].group.height
const group = dockview.floatingGroups[index].group
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const group = dockview.floatingGroups[index].group
const {group} = dockview.floatingGroups[index]


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

if (fg.position.width > 0) {
group.panels.forEach(panel => {
!panel.params.currentPosition && (panel.params.currentPosition = {})
panel.params.currentPosition.width = fg.position.width
})
}
else {
fg.position.width = group.params.currentPosition.width || 500
Comment on lines +321 to +328
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Possible undefined 'group.params.currentPosition' access.

Accessing 'width' or 'height' on an undefined 'currentPosition' will cause a runtime error. Please add a check to ensure 'currentPosition' is defined before accessing its properties.

}
if (fg.position.height > 0) {
group.panels.forEach(panel => {
!panel.params.currentPosition && (panel.params.currentPosition = {})
panel.params.currentPosition.height = fg.position.height
})
}
else {
fg.position.height = group.params.currentPosition.height || 350
}
})
}
localStorage.setItem(dockview.params.options.localStorageKey, JSON.stringify(json));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* dockview-core
* @version 4.3.1
* @version 4.4.0
* @link https://github.com/mathuo/dockview
* @license MIT
*/
Expand Down Expand Up @@ -3619,6 +3619,9 @@ class DockviewApi {
get onDidPopoutGroupPositionChange() {
return this.component.onDidPopoutGroupPositionChange;
}
get onDidOpenPopoutWindowFail() {
return this.component.onDidOpenPopoutWindowFail;
}
/**
* All panel objects.
*/
Expand Down Expand Up @@ -8351,6 +8354,8 @@ class DockviewComponent extends BaseGrid {
this.onDidPopoutGroupSizeChange = this._onDidPopoutGroupSizeChange.event;
this._onDidPopoutGroupPositionChange = new Emitter();
this.onDidPopoutGroupPositionChange = this._onDidPopoutGroupPositionChange.event;
this._onDidOpenPopoutWindowFail = new Emitter();
this.onDidOpenPopoutWindowFail = this._onDidOpenPopoutWindowFail.event;
this._onDidLayoutFromJSON = new Emitter();
this.onDidLayoutFromJSON = this._onDidLayoutFromJSON.event;
this._onDidActivePanelChange = new Emitter({ replay: true });
Expand Down Expand Up @@ -8415,7 +8420,7 @@ class DockviewComponent extends BaseGrid {
if (options.debug) {
this.addDisposables(new StrictEventsSequencing(this));
}
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._onDidAddGroup, this._onDidRemoveGroup, this._onDidActiveGroupChange, this._onUnhandledDragOverEvent, this._onDidMaximizedGroupChange, this._onDidOptionsChange, this._onDidPopoutGroupSizeChange, this._onDidPopoutGroupPositionChange, this.onDidViewVisibilityChangeMicroTaskQueue(() => {
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._onDidAddGroup, this._onDidRemoveGroup, this._onDidActiveGroupChange, this._onUnhandledDragOverEvent, this._onDidMaximizedGroupChange, this._onDidOptionsChange, this._onDidPopoutGroupSizeChange, this._onDidPopoutGroupPositionChange, this._onDidOpenPopoutWindowFail, this.onDidViewVisibilityChangeMicroTaskQueue(() => {
this.updateWatermark();
}), this.onDidAdd((event) => {
if (!this._moving) {
Expand Down Expand Up @@ -8565,21 +8570,14 @@ class DockviewComponent extends BaseGrid {
if (_window.isDisposed) {
return false;
}
if (popoutContainer === null) {
popoutWindowDisposable.dispose();
return false;
}
const gready = document.createElement('div');
gready.className = 'dv-overlay-render-container';
const overlayRenderContainer = new OverlayRenderContainer(gready, this);
const referenceGroup = (options === null || options === void 0 ? void 0 : options.referenceGroup)
? options.referenceGroup
: itemToPopout instanceof DockviewPanel
? itemToPopout.group
: itemToPopout;
const referenceLocation = itemToPopout.api.location.type;
/**
* The group that is being added doesn't already exist within the DOM, the most likely occurance
* The group that is being added doesn't already exist within the DOM, the most likely occurrence
* of this case is when being called from the `fromJSON(...)` method
*/
const isGroupAddedToDom = referenceGroup.element.parentElement !== null;
Expand All @@ -8592,8 +8590,28 @@ class DockviewComponent extends BaseGrid {
}
else {
group = this.createGroup({ id: groupId });
this._onDidAddGroup.fire(group);
if (popoutContainer) {
this._onDidAddGroup.fire(group);
}
}
if (popoutContainer === null) {
console.error('dockview: failed to create popout. perhaps you need to allow pop-ups for this website');
popoutWindowDisposable.dispose();
this._onDidOpenPopoutWindowFail.fire();
// if the popout window was blocked, we need to move the group back to the reference group
// and set it to visible
this.movingLock(() => moveGroupWithoutDestroying({
from: group,
to: referenceGroup,
}));
if (!referenceGroup.api.isVisible) {
referenceGroup.api.setVisible(true);
}
return false;
}
const gready = document.createElement('div');
gready.className = 'dv-overlay-render-container';
const overlayRenderContainer = new OverlayRenderContainer(gready, this);
group.model.renderContainer = overlayRenderContainer;
group.layout(_window.window.innerWidth, _window.window.innerHeight);
let floatingBox;
Expand Down Expand Up @@ -8750,7 +8768,7 @@ class DockviewComponent extends BaseGrid {
return true;
})
.catch((err) => {
console.error('dockview: failed to create popout window', err);
console.error('dockview: failed to create popout.', err);
return false;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ DockviewComponent.prototype.on = function (eventType, callback) {
const dispose = DockviewComponent.prototype.dispose;
DockviewComponent.prototype.dispose = function () {
this.params.observer?.disconnect();
this.groups.forEach(group => {
if (group.mutationObserver) {
group.mutationObserver.disconnect();
}
})
saveConfig(this);
dispose.call(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ const createGroupActions = (group, groupType) => {
if (item.name !== 'bar') {
const icon = getIcon(item.name);
actionContainer.append(icon);
if(icon.classList.contains('bb-dockview-control-icon-dropdown')){
setTimeout(() => {
if (group.model.location.type == 'floating' && group.panels.some(panel => panel.renderer == 'always')) {
observeDisplayChange(icon, group)
}
}, 0)
}
}
});
setTimeout(() => {
Expand All @@ -178,13 +185,47 @@ const createGroupActions = (group, groupType) => {
}, 0)
addActionEvent(group, actionContainer);
}
const observeDisplayChange = (icon, group) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider replacing the MutationObserver logic with Bootstrap dropdown event listeners and extracting the panel move logic into two helper functions.

You can simplify/remove the MutationObserver boilerplate by listening to the dropdown’s built-in events (e.g. Bootstrap’s `show.bs.dropdown`/`hide.bs.dropdown`) and extracting the “portal” logic into two small helpers. This keeps the same functionality but is far more declarative and easier to follow.

```js
// 1) In createGroupActions, replace observeDisplayChange(…) with two event listeners:
if (icon.classList.contains('bb-dockview-control-icon-dropdown')) {
  // bootstrap dropdown events
  icon.addEventListener('show.bs.dropdown',  () => portalPanelIntoDropdown(group));
  icon.addEventListener('hide.bs.dropdown',  () => restorePanelFromDropdown(group));
}
// 2) Move all the move/unwrap logic into two focused functions:

function portalPanelIntoDropdown(group) {
  const panelEle       = group.activePanel.view.content.element;
  const wrapper        = panelEle.parentElement;
  const container      = group.element.querySelector(':scope > .dv-content-container');

  // “portal” the panel into dropdown
  wrapper.style.zIndex = '-1';
  panelEle.__wrapper   = wrapper;            // temp link back to wrapper
  container.appendChild(panelEle);
}

function restorePanelFromDropdown(group) {
  const container = group.element.querySelector(':scope > .dv-content-container');
  Array.from(container.children).forEach(child => {
    const wrapper = child.__wrapper;
    delete child.__wrapper;

    // move child back into its original wrapper, reset z-index
    wrapper.style.zIndex = '';
    wrapper.appendChild(child);
  });

  // re-attach wrappers back into the dockview DOM
  const panels = Array.from(group.element.querySelector(':scope > .dv-content-container').children)
    .map(c => c.__wrapper);
  group.element.parentElement.parentElement.append(...panels);
}

Benefits:

  • No manual MutationObserver or setTimeout
  • Clear semantic hooks (show.bs.dropdown / hide.bs.dropdown)
  • Portal logic lives in two tiny, testable functions

const dockview = group.api.accessor
const element = icon.querySelector('.dropdown-menu')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Potential null reference if '.dropdown-menu' is not found.

Add a null check for 'element' before creating the MutationObserver to prevent runtime errors.

const mutationObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.attributeName == 'class') {
if(mutation.target.classList.contains('show')) {
const currentPanelEle = group.activePanel.view.content.element.parentElement
const childEle = currentPanelEle.children[0]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): No check for existence of 'currentPanelEle' or its children.

Accessing properties of a null 'currentPanelEle' or missing children will cause a runtime error. Please add null and existence checks before using these elements.

group.element.querySelector('&>.dv-content-container').append(childEle)
currentPanelEle.style.zIndex = -1
childEle.wrapperEle = currentPanelEle
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Attaching custom properties to DOM elements can cause memory leaks.

Instead of assigning 'wrapperEle' directly to the DOM node, use a WeakMap or similar structure to store metadata and avoid potential memory leaks.

}
else {
const panelEleList = [...group.element.querySelector('&>.dv-content-container').children].map(item => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Repeated use of non-standard selector '&>.dv-content-container'.

This selector may not function as expected, which could cause 'panelEleList' to be empty or undefined and lead to errors.

const wrapperEle = item.wrapperEle
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const wrapperEle = item.wrapperEle
const {wrapperEle} = item


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

delete item.wrapperEle
wrapperEle.append(item)
return wrapperEle
})
group.element.parentElement.parentElement.append(...panelEleList)
}
}
})
});
group.mutationObserver = mutationObserver
mutationObserver.observe(element, {
attributes: true,
attributeFilter: ["class"],
});
}
Comment on lines +213 to +218
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Potential for multiple observers if 'observeDisplayChange' is called multiple times.

Disconnect any existing observer before assigning a new one to prevent resource leaks.

Suggested change
group.mutationObserver = mutationObserver
mutationObserver.observe(element, {
attributes: true,
attributeFilter: ["class"],
});
}
if (group.mutationObserver) {
group.mutationObserver.disconnect();
}
group.mutationObserver = mutationObserver
mutationObserver.observe(element, {
attributes: true,
attributeFilter: ["class"],
});
}


const disposeGroup = group => {
const { observer } = group.api.accessor.params;
if (observer) {
observer.unobserve(group.header.element);
observer.unobserve(group.header.tabs._tabsList);
}
if (group.mutationObserver) {
group.mutationObserver.disconnect();
}
removeActionEvent(group);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ const initDockview = (dockview, options, template) => {
}

dockview.update = options => {
if (options.layoutConfig) {
reloadFromConfig(dockview, options);
}
if (dockview.params.options.lock !== options.lock) {
dockview.params.options.lock = options.lock;
toggleGroupLock(dockview, options);
Expand All @@ -56,6 +53,9 @@ const initDockview = (dockview, options, template) => {
dockview.options.theme.className = options.theme;
dockview.updateTheme();
}
if (options.layoutConfig) {
reloadFromConfig(dockview, options);
}
else {
toggleComponent(dockview, options);
}
Expand Down