mirror of
				https://github.com/mathuo/dockview
				synced 2025-11-04 14:10:32 +00:00 
			
		
		
		
	feat: experimental floating groups
This commit is contained in:
		
							parent
							
								
									7fdede6952
								
							
						
					
					
						commit
						5b493b95e0
					
				@ -329,10 +329,6 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class DockviewApi implements CommonApi<SerializedDockview> {
 | 
			
		||||
    addFloating() {
 | 
			
		||||
        return this.component.addFloating();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get id(): string {
 | 
			
		||||
        return this.component.id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -61,3 +61,13 @@ export function firstIndex<T>(
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function remove<T>(array: T[], value: T): boolean {
 | 
			
		||||
    const index = array.findIndex((t) => t === value);
 | 
			
		||||
 | 
			
		||||
    if (index > -1) {
 | 
			
		||||
        array.splice(index, 1);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,10 +21,21 @@ export abstract class DragHandler extends CompositeDisposable {
 | 
			
		||||
 | 
			
		||||
    abstract getData(dataTransfer?: DataTransfer | null): IDisposable;
 | 
			
		||||
 | 
			
		||||
    protected isCancelled(_event: DragEvent): boolean {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private configure(): void {
 | 
			
		||||
        this.addDisposables(
 | 
			
		||||
            this._onDragStart,
 | 
			
		||||
            addDisposableListener(this.el, 'dragstart', (event) => {
 | 
			
		||||
                if (this.isCancelled(event)) {
 | 
			
		||||
                    event.preventDefault();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.disposable.value = this.getData(event.dataTransfer);
 | 
			
		||||
 | 
			
		||||
                this.iframes = [
 | 
			
		||||
                    ...getElementsByTagName('iframe'),
 | 
			
		||||
                    ...getElementsByTagName('webview'),
 | 
			
		||||
@ -37,8 +48,6 @@ export abstract class DragHandler extends CompositeDisposable {
 | 
			
		||||
                this.el.classList.add('dv-dragged');
 | 
			
		||||
                setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
 | 
			
		||||
 | 
			
		||||
                this.disposable.value = this.getData(event.dataTransfer);
 | 
			
		||||
 | 
			
		||||
                if (event.dataTransfer) {
 | 
			
		||||
                    event.dataTransfer.effectAllowed = 'move';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -58,6 +58,7 @@ export class Droptarget extends CompositeDisposable {
 | 
			
		||||
    private targetElement: HTMLElement | undefined;
 | 
			
		||||
    private overlayElement: HTMLElement | undefined;
 | 
			
		||||
    private _state: Position | undefined;
 | 
			
		||||
    private _acceptedTargetZonesSet: Set<Position>;
 | 
			
		||||
 | 
			
		||||
    private readonly _onDrop = new Emitter<DroptargetEvent>();
 | 
			
		||||
    readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
 | 
			
		||||
@ -83,7 +84,7 @@ export class Droptarget extends CompositeDisposable {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        // use a set to take advantage of #<set>.has
 | 
			
		||||
        const acceptedTargetZonesSet = new Set(
 | 
			
		||||
        this._acceptedTargetZonesSet = new Set(
 | 
			
		||||
            this.options.acceptedTargetZones
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
@ -106,7 +107,7 @@ export class Droptarget extends CompositeDisposable {
 | 
			
		||||
                    const y = e.clientY - rect.top;
 | 
			
		||||
 | 
			
		||||
                    const quadrant = this.calculateQuadrant(
 | 
			
		||||
                        acceptedTargetZonesSet,
 | 
			
		||||
                        this._acceptedTargetZonesSet,
 | 
			
		||||
                        x,
 | 
			
		||||
                        y,
 | 
			
		||||
                        width,
 | 
			
		||||
@ -175,6 +176,10 @@ export class Droptarget extends CompositeDisposable {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setTargetZones(acceptedTargetZones: Position[]): void {
 | 
			
		||||
        this._acceptedTargetZonesSet = new Set(acceptedTargetZones);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public dispose(): void {
 | 
			
		||||
        this.removeDropTarget();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,13 @@ export class GroupDragHandler extends DragHandler {
 | 
			
		||||
        super(element);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override isCancelled(_event: DragEvent): boolean {
 | 
			
		||||
        if (this.group.model.isFloating) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getData(dataTransfer: DataTransfer | null): IDisposable {
 | 
			
		||||
        this.panelTransfer.setData(
 | 
			
		||||
            [new PanelTransfer(this.accessorId, this.group.id, null)],
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,43 @@
 | 
			
		||||
.dv-debug {
 | 
			
		||||
    .dv-resize-container {
 | 
			
		||||
        .dv-resize-handle-top {
 | 
			
		||||
            background-color: red;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .dv-resize-handle-bottom {
 | 
			
		||||
            background-color: green;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .dv-resize-handle-left {
 | 
			
		||||
            background-color: yellow;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .dv-resize-handle-right {
 | 
			
		||||
            background-color: blue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .dv-resize-handle-topleft,
 | 
			
		||||
        .dv-resize-handle-topright,
 | 
			
		||||
        .dv-resize-handle-bottomleft,
 | 
			
		||||
        .dv-resize-handle-bottomright {
 | 
			
		||||
            background-color: cyan;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dv-resize-container {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    z-index: 9998;
 | 
			
		||||
    z-index: 9997;
 | 
			
		||||
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    &.dv-resize-container-priority {
 | 
			
		||||
        z-index: 9998;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    border: 1px solid var(--dv-tab-divider-color);
 | 
			
		||||
    box-shadow: var(--dv-floating-box-shadow);
 | 
			
		||||
 | 
			
		||||
    &.dv-resize-container-dragging {
 | 
			
		||||
        opacity: 0.2;
 | 
			
		||||
        opacity: 0.5;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .dv-resize-handle-top {
 | 
			
		||||
@ -16,8 +48,6 @@
 | 
			
		||||
        z-index: 9999;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        cursor: ns-resize;
 | 
			
		||||
 | 
			
		||||
        background-color: red;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .dv-resize-handle-bottom {
 | 
			
		||||
@ -28,8 +58,6 @@
 | 
			
		||||
        z-index: 9999;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        cursor: ns-resize;
 | 
			
		||||
 | 
			
		||||
        background-color: green;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .dv-resize-handle-left {
 | 
			
		||||
@ -40,8 +68,6 @@
 | 
			
		||||
        z-index: 9999;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        cursor: ew-resize;
 | 
			
		||||
 | 
			
		||||
        background-color: yellow;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .dv-resize-handle-right {
 | 
			
		||||
@ -52,8 +78,6 @@
 | 
			
		||||
        z-index: 9999;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        cursor: ew-resize;
 | 
			
		||||
 | 
			
		||||
        background-color: blue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .dv-resize-handle-topleft {
 | 
			
		||||
@ -64,8 +88,6 @@
 | 
			
		||||
        z-index: 9999;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        cursor: nw-resize;
 | 
			
		||||
 | 
			
		||||
        background-color: cyan;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .dv-resize-handle-topright {
 | 
			
		||||
@ -76,8 +98,6 @@
 | 
			
		||||
        z-index: 9999;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        cursor: ne-resize;
 | 
			
		||||
 | 
			
		||||
        background-color: cyan;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .dv-resize-handle-bottomleft {
 | 
			
		||||
@ -88,8 +108,6 @@
 | 
			
		||||
        z-index: 9999;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        cursor: sw-resize;
 | 
			
		||||
 | 
			
		||||
        background-color: cyan;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .dv-resize-handle-bottomright {
 | 
			
		||||
@ -100,7 +118,5 @@
 | 
			
		||||
        z-index: 9999;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        cursor: se-resize;
 | 
			
		||||
 | 
			
		||||
        background-color: cyan;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,23 +3,40 @@ import { addDisposableListener, addDisposableWindowListener } from '../events';
 | 
			
		||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
 | 
			
		||||
import { clamp } from '../math';
 | 
			
		||||
 | 
			
		||||
const bringElementToFront = (() => {
 | 
			
		||||
    let previous: HTMLElement | null = null;
 | 
			
		||||
 | 
			
		||||
    function pushToTop(element: HTMLElement) {
 | 
			
		||||
        if (previous !== element && previous !== null) {
 | 
			
		||||
            toggleClass(previous, 'dv-resize-container-priority', false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        toggleClass(element, 'dv-resize-container-priority', true);
 | 
			
		||||
        previous = element;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return pushToTop;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
export class Overlay extends CompositeDisposable {
 | 
			
		||||
    private _element: HTMLElement = document.createElement('div');
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        private readonly container: HTMLElement,
 | 
			
		||||
        private readonly content: HTMLElement,
 | 
			
		||||
        private readonly options: {
 | 
			
		||||
            height: number;
 | 
			
		||||
            width: number;
 | 
			
		||||
            left: number;
 | 
			
		||||
            top: number;
 | 
			
		||||
            container: HTMLElement;
 | 
			
		||||
            content: HTMLElement;
 | 
			
		||||
            minX: number;
 | 
			
		||||
            minY: number;
 | 
			
		||||
        }
 | 
			
		||||
    ) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this.setupOverlay();
 | 
			
		||||
        this.setupDrag();
 | 
			
		||||
        // this.setupDrag(true,this._element);
 | 
			
		||||
        this.setupResize('top');
 | 
			
		||||
        this.setupResize('bottom');
 | 
			
		||||
        this.setupResize('left');
 | 
			
		||||
@ -29,8 +46,10 @@ export class Overlay extends CompositeDisposable {
 | 
			
		||||
        this.setupResize('bottomleft');
 | 
			
		||||
        this.setupResize('bottomright');
 | 
			
		||||
 | 
			
		||||
        this._element.appendChild(content);
 | 
			
		||||
        this.container.appendChild(this._element);
 | 
			
		||||
        this._element.appendChild(this.options.content);
 | 
			
		||||
        this.options.container.appendChild(this._element);
 | 
			
		||||
 | 
			
		||||
        // this.renderWithinBoundaryConditions();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private setupResize(
 | 
			
		||||
@ -52,8 +71,8 @@ export class Overlay extends CompositeDisposable {
 | 
			
		||||
 | 
			
		||||
        this.addDisposables(
 | 
			
		||||
            move,
 | 
			
		||||
            addDisposableListener(resizeHandleElement, 'mousedown', (_) => {
 | 
			
		||||
                _.preventDefault();
 | 
			
		||||
            addDisposableListener(resizeHandleElement, 'mousedown', (e) => {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
 | 
			
		||||
                let offset: {
 | 
			
		||||
                    originalY: number;
 | 
			
		||||
@ -64,7 +83,8 @@ export class Overlay extends CompositeDisposable {
 | 
			
		||||
 | 
			
		||||
                move.value = new CompositeDisposable(
 | 
			
		||||
                    addDisposableWindowListener(window, 'mousemove', (e) => {
 | 
			
		||||
                        const rect = this.container.getBoundingClientRect();
 | 
			
		||||
                        const rect =
 | 
			
		||||
                            this.options.container.getBoundingClientRect();
 | 
			
		||||
                        const y = e.clientY - rect.top;
 | 
			
		||||
                        const x = e.clientX - rect.left;
 | 
			
		||||
 | 
			
		||||
@ -91,9 +111,12 @@ export class Overlay extends CompositeDisposable {
 | 
			
		||||
                            top = clamp(
 | 
			
		||||
                                y,
 | 
			
		||||
                                0,
 | 
			
		||||
                                offset!.originalY +
 | 
			
		||||
                                    offset!.originalHeight -
 | 
			
		||||
                                    MIN_HEIGHT
 | 
			
		||||
                                Math.max(
 | 
			
		||||
                                    0,
 | 
			
		||||
                                    offset!.originalY +
 | 
			
		||||
                                        offset!.originalHeight -
 | 
			
		||||
                                        MIN_HEIGHT
 | 
			
		||||
                                )
 | 
			
		||||
                            );
 | 
			
		||||
                            height =
 | 
			
		||||
                                offset!.originalY +
 | 
			
		||||
@ -107,9 +130,12 @@ export class Overlay extends CompositeDisposable {
 | 
			
		||||
                            height = clamp(
 | 
			
		||||
                                y - top,
 | 
			
		||||
                                MIN_HEIGHT,
 | 
			
		||||
                                rect.height -
 | 
			
		||||
                                    offset!.originalY +
 | 
			
		||||
                                    offset!.originalHeight
 | 
			
		||||
                                Math.max(
 | 
			
		||||
                                    0,
 | 
			
		||||
                                    rect.height -
 | 
			
		||||
                                        offset!.originalY +
 | 
			
		||||
                                        offset!.originalHeight
 | 
			
		||||
                                )
 | 
			
		||||
                            );
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
@ -117,9 +143,12 @@ export class Overlay extends CompositeDisposable {
 | 
			
		||||
                            left = clamp(
 | 
			
		||||
                                x,
 | 
			
		||||
                                0,
 | 
			
		||||
                                offset!.originalX +
 | 
			
		||||
                                    offset!.originalWidth -
 | 
			
		||||
                                    MIN_WIDTH
 | 
			
		||||
                                Math.max(
 | 
			
		||||
                                    0,
 | 
			
		||||
                                    offset!.originalX +
 | 
			
		||||
                                        offset!.originalWidth -
 | 
			
		||||
                                        MIN_WIDTH
 | 
			
		||||
                                )
 | 
			
		||||
                            );
 | 
			
		||||
                            width =
 | 
			
		||||
                                offset!.originalX +
 | 
			
		||||
@ -132,9 +161,12 @@ export class Overlay extends CompositeDisposable {
 | 
			
		||||
                            width = clamp(
 | 
			
		||||
                                x - left,
 | 
			
		||||
                                MIN_WIDTH,
 | 
			
		||||
                                rect.width -
 | 
			
		||||
                                    offset!.originalX +
 | 
			
		||||
                                    offset!.originalWidth
 | 
			
		||||
                                Math.max(
 | 
			
		||||
                                    0,
 | 
			
		||||
                                    rect.width -
 | 
			
		||||
                                        offset!.originalX +
 | 
			
		||||
                                        offset!.originalWidth
 | 
			
		||||
                                )
 | 
			
		||||
                            );
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
@ -199,68 +231,128 @@ export class Overlay extends CompositeDisposable {
 | 
			
		||||
        this._element.className = 'dv-resize-container';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private setupDrag(): void {
 | 
			
		||||
    setupDrag(connect: boolean, dragTarget: HTMLElement): void {
 | 
			
		||||
        const move = new MutableDisposable();
 | 
			
		||||
 | 
			
		||||
        const track = () => {
 | 
			
		||||
            let offset: { x: number; y: number } | null = null;
 | 
			
		||||
 | 
			
		||||
            move.value = new CompositeDisposable(
 | 
			
		||||
                addDisposableWindowListener(window, 'mousemove', (e) => {
 | 
			
		||||
                    const containerRect =
 | 
			
		||||
                        this.options.container.getBoundingClientRect();
 | 
			
		||||
                    const x = e.clientX - containerRect.left;
 | 
			
		||||
                    const y = e.clientY - containerRect.top;
 | 
			
		||||
 | 
			
		||||
                    toggleClass(
 | 
			
		||||
                        this._element,
 | 
			
		||||
                        'dv-resize-container-dragging',
 | 
			
		||||
                        true
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    const overlayRect = this._element.getBoundingClientRect();
 | 
			
		||||
                    if (offset === null) {
 | 
			
		||||
                        offset = {
 | 
			
		||||
                            x: e.clientX - overlayRect.left,
 | 
			
		||||
                            y: e.clientY - overlayRect.top,
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    const xOffset = Math.max(
 | 
			
		||||
                        0,
 | 
			
		||||
                        overlayRect.width - this.options.minX
 | 
			
		||||
                    );
 | 
			
		||||
                    const yOffset = Math.max(
 | 
			
		||||
                        0,
 | 
			
		||||
                        overlayRect.height - this.options.minY
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    const left = clamp(
 | 
			
		||||
                        x - offset.x,
 | 
			
		||||
                        -xOffset,
 | 
			
		||||
                        Math.max(
 | 
			
		||||
                            0,
 | 
			
		||||
                            containerRect.width - overlayRect.width + xOffset
 | 
			
		||||
                        )
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    const top = clamp(
 | 
			
		||||
                        y - offset.y,
 | 
			
		||||
                        -yOffset,
 | 
			
		||||
                        Math.max(
 | 
			
		||||
                            0,
 | 
			
		||||
                            containerRect.height - overlayRect.height + yOffset
 | 
			
		||||
                        )
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    this._element.style.left = `${left}px`;
 | 
			
		||||
                    this._element.style.top = `${top}px`;
 | 
			
		||||
                }),
 | 
			
		||||
                addDisposableWindowListener(window, 'mouseup', () => {
 | 
			
		||||
                    toggleClass(
 | 
			
		||||
                        this._element,
 | 
			
		||||
                        'dv-resize-container-dragging',
 | 
			
		||||
                        false
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    move.dispose();
 | 
			
		||||
                })
 | 
			
		||||
            );
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.addDisposables(
 | 
			
		||||
            move,
 | 
			
		||||
            addDisposableListener(this._element, 'mousedown', (_) => {
 | 
			
		||||
            addDisposableListener(dragTarget, 'mousedown', (_) => {
 | 
			
		||||
                if (_.defaultPrevented) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let offset: { x: number; y: number } | null = null;
 | 
			
		||||
 | 
			
		||||
                move.value = new CompositeDisposable(
 | 
			
		||||
                    addDisposableWindowListener(window, 'mousemove', (e) => {
 | 
			
		||||
                        const rect = this.container.getBoundingClientRect();
 | 
			
		||||
                        const x = e.clientX - rect.left;
 | 
			
		||||
                        const y = e.clientY - rect.top;
 | 
			
		||||
 | 
			
		||||
                        toggleClass(
 | 
			
		||||
                            this._element,
 | 
			
		||||
                            'dv-resize-container-dragging',
 | 
			
		||||
                            true
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
                        const rect2 = this._element.getBoundingClientRect();
 | 
			
		||||
                        if (offset === null) {
 | 
			
		||||
                            offset = {
 | 
			
		||||
                                x: e.clientX - rect2.left,
 | 
			
		||||
                                y: e.clientY - rect2.top,
 | 
			
		||||
                            };
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        const left = clamp(
 | 
			
		||||
                            Math.max(0, x - offset.x),
 | 
			
		||||
                            0,
 | 
			
		||||
                            rect.width - rect2.width
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
                        const top = clamp(
 | 
			
		||||
                            Math.max(0, y - offset.y),
 | 
			
		||||
                            0,
 | 
			
		||||
                            rect.height - rect2.height
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
                        this._element.style.left = `${left}px`;
 | 
			
		||||
                        this._element.style.top = `${top}px`;
 | 
			
		||||
                    }),
 | 
			
		||||
                    addDisposableWindowListener(window, 'mouseup', () => {
 | 
			
		||||
                        toggleClass(
 | 
			
		||||
                            this._element,
 | 
			
		||||
                            'dv-resize-container-dragging',
 | 
			
		||||
                            false
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
                        move.dispose();
 | 
			
		||||
                    })
 | 
			
		||||
                );
 | 
			
		||||
            })
 | 
			
		||||
                track();
 | 
			
		||||
            }),
 | 
			
		||||
            addDisposableListener(this.options.content, 'mousedown', (_) => {
 | 
			
		||||
                if (_.shiftKey) {
 | 
			
		||||
                    track();
 | 
			
		||||
                }
 | 
			
		||||
            }),
 | 
			
		||||
            addDisposableListener(
 | 
			
		||||
                this.options.content,
 | 
			
		||||
                'mousedown',
 | 
			
		||||
                () => {
 | 
			
		||||
                    bringElementToFront(this._element);
 | 
			
		||||
                },
 | 
			
		||||
                true
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (connect) {
 | 
			
		||||
            track();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dispose(): void {
 | 
			
		||||
    renderWithinBoundaryConditions(): void {
 | 
			
		||||
        const rect = this.options.container.getBoundingClientRect();
 | 
			
		||||
        const rect2 = this._element.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
        const left = clamp(
 | 
			
		||||
            Math.max(this.options.left, 0),
 | 
			
		||||
            0,
 | 
			
		||||
            Math.max(0, rect.width - rect2.width)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const top = clamp(
 | 
			
		||||
            Math.max(this.options.top, 0),
 | 
			
		||||
            0,
 | 
			
		||||
            Math.max(0, rect.height - rect2.height)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        console.log(new Error().stack);
 | 
			
		||||
 | 
			
		||||
        this._element.style.left = `${left}px`;
 | 
			
		||||
        this._element.style.top = `${top}px`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override dispose(): void {
 | 
			
		||||
        this._element.remove();
 | 
			
		||||
        super.dispose();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ import { DockviewDropTargets, ITabRenderer } from '../../types';
 | 
			
		||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
 | 
			
		||||
import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget';
 | 
			
		||||
import { DragHandler } from '../../../dnd/abstractDragHandler';
 | 
			
		||||
import { DockviewPanel } from '../../dockviewPanel';
 | 
			
		||||
 | 
			
		||||
export interface ITab {
 | 
			
		||||
    readonly panelId: string;
 | 
			
		||||
@ -80,6 +81,19 @@ export class Tab extends CompositeDisposable implements ITab {
 | 
			
		||||
 | 
			
		||||
        this.addDisposables(
 | 
			
		||||
            addDisposableListener(this._element, 'mousedown', (event) => {
 | 
			
		||||
                if (event.shiftKey) {
 | 
			
		||||
                    event.preventDefault();
 | 
			
		||||
 | 
			
		||||
                    const panel = this.accessor.getGroupPanel(this.panelId);
 | 
			
		||||
 | 
			
		||||
                    const { top, left } = this.element.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
                    this.accessor.addFloating(panel as DockviewPanel, {
 | 
			
		||||
                        x: left,
 | 
			
		||||
                        y: top,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (event.defaultPrevented) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,7 @@ export class VoidContainer extends CompositeDisposable {
 | 
			
		||||
        this._element = document.createElement('div');
 | 
			
		||||
 | 
			
		||||
        this._element.className = 'void-container';
 | 
			
		||||
        this._element.id = 'dv-group-float-drag-handle';
 | 
			
		||||
        this._element.tabIndex = 0;
 | 
			
		||||
        this._element.draggable = true;
 | 
			
		||||
 | 
			
		||||
@ -68,6 +69,16 @@ export class VoidContainer extends CompositeDisposable {
 | 
			
		||||
 | 
			
		||||
        this.addDisposables(
 | 
			
		||||
            handler,
 | 
			
		||||
            addDisposableListener(this._element, 'mousedown', (event) => {
 | 
			
		||||
                if (event.shiftKey && !this.group.model.isFloating) {
 | 
			
		||||
                    event.preventDefault();
 | 
			
		||||
                    this.accessor.addFloating(this.group, {
 | 
			
		||||
                        x: event.clientX + 20,
 | 
			
		||||
                        y: event.clientY + 20,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }),
 | 
			
		||||
 | 
			
		||||
            this.voidDropTarget.onDrop((event) => {
 | 
			
		||||
                this._onDrop.fire(event);
 | 
			
		||||
            }),
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,15 @@
 | 
			
		||||
.dv-dockview {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  background-color: var(--dv-group-view-background-color);
 | 
			
		||||
    position: relative;
 | 
			
		||||
    background-color: var(--dv-group-view-background-color);
 | 
			
		||||
 | 
			
		||||
  .dv-watermark-container {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0px;
 | 
			
		||||
    left: 0px;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
    .dv-watermark-container {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 0px;
 | 
			
		||||
        left: 0px;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        z-index: 9999;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.groupview {
 | 
			
		||||
 | 
			
		||||
@ -5,9 +5,9 @@ import {
 | 
			
		||||
    ISerializedLeafNode,
 | 
			
		||||
} from '../gridview/gridview';
 | 
			
		||||
import { directionToPosition, Droptarget, Position } from '../dnd/droptarget';
 | 
			
		||||
import { tail, sequenceEquals } from '../array';
 | 
			
		||||
import { tail, sequenceEquals, remove } from '../array';
 | 
			
		||||
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
 | 
			
		||||
import { CompositeDisposable } from '../lifecycle';
 | 
			
		||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
 | 
			
		||||
import { Event, Emitter } from '../events';
 | 
			
		||||
import { Watermark } from './components/watermark/watermark';
 | 
			
		||||
import {
 | 
			
		||||
@ -45,6 +45,7 @@ import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
 | 
			
		||||
import { DockviewPanelModel } from './dockviewPanelModel';
 | 
			
		||||
import { getPanelData } from '../dnd/dataTransfer';
 | 
			
		||||
import { Overlay } from '../dnd/overlay';
 | 
			
		||||
import { toggleClass } from '../dom';
 | 
			
		||||
 | 
			
		||||
export interface PanelReference {
 | 
			
		||||
    update: (event: { params: { [key: string]: any } }) => void;
 | 
			
		||||
@ -116,7 +117,10 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
 | 
			
		||||
    readonly onDidAddPanel: Event<IDockviewPanel>;
 | 
			
		||||
    readonly onDidLayoutFromJSON: Event<void>;
 | 
			
		||||
    readonly onDidActivePanelChange: Event<IDockviewPanel | undefined>;
 | 
			
		||||
    addFloating(): void;
 | 
			
		||||
    addFloating(
 | 
			
		||||
        item: DockviewPanel | DockviewGroupPanel,
 | 
			
		||||
        coord?: { x: number; y: number }
 | 
			
		||||
    ): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class DockviewComponent
 | 
			
		||||
@ -148,6 +152,12 @@ export class DockviewComponent
 | 
			
		||||
    readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
 | 
			
		||||
        this._onDidActivePanelChange.event;
 | 
			
		||||
 | 
			
		||||
    private readonly floatingGroups: {
 | 
			
		||||
        instance: DockviewGroupPanel;
 | 
			
		||||
        disposable: IDisposable;
 | 
			
		||||
        render: () => void;
 | 
			
		||||
    }[] = [];
 | 
			
		||||
 | 
			
		||||
    get orientation(): Orientation {
 | 
			
		||||
        return this.gridview.orientation;
 | 
			
		||||
    }
 | 
			
		||||
@ -182,7 +192,7 @@ export class DockviewComponent
 | 
			
		||||
            parentElement: options.parentElement,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.element.classList.add('dv-dockview');
 | 
			
		||||
        toggleClass(this.gridview.element, 'dv-dockview', true);
 | 
			
		||||
 | 
			
		||||
        this.addDisposables(
 | 
			
		||||
            this._onDidDrop,
 | 
			
		||||
@ -277,8 +287,68 @@ export class DockviewComponent
 | 
			
		||||
        this._api = new DockviewApi(this);
 | 
			
		||||
 | 
			
		||||
        this.updateWatermark();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        this.element.style.position = 'relative';
 | 
			
		||||
    addFloating(
 | 
			
		||||
        item: DockviewPanel | DockviewGroupPanel,
 | 
			
		||||
        coord?: { x: number; y: number }
 | 
			
		||||
    ): void {
 | 
			
		||||
        let group: DockviewGroupPanel;
 | 
			
		||||
 | 
			
		||||
        if (item instanceof DockviewPanel) {
 | 
			
		||||
            group = this.createGroup();
 | 
			
		||||
 | 
			
		||||
            this.removePanel(item, {
 | 
			
		||||
                removeEmptyGroup: true,
 | 
			
		||||
                skipDispose: true,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            group.model.openPanel(item);
 | 
			
		||||
        } else {
 | 
			
		||||
            group = item;
 | 
			
		||||
            this.doRemoveGroup(item, { skipDispose: true });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        group.model.isFloating = true;
 | 
			
		||||
 | 
			
		||||
        const { left, top } = this.element.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
        const overlayLeft =
 | 
			
		||||
            typeof coord?.x === 'number' ? Math.max(coord.x - left, 0) : 100;
 | 
			
		||||
        const overlayTop =
 | 
			
		||||
            typeof coord?.y === 'number' ? Math.max(0, coord.y - top) : 100;
 | 
			
		||||
 | 
			
		||||
        const overlay = new Overlay({
 | 
			
		||||
            container: this.gridview.element,
 | 
			
		||||
            content: group.element,
 | 
			
		||||
            height: 300,
 | 
			
		||||
            width: 300,
 | 
			
		||||
            left: overlayLeft,
 | 
			
		||||
            top: overlayTop,
 | 
			
		||||
            minX: 100,
 | 
			
		||||
            minY: 100,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const el = group.element.querySelector('#dv-group-float-drag-handle');
 | 
			
		||||
 | 
			
		||||
        if (el) {
 | 
			
		||||
            overlay.setupDrag(true, el as HTMLElement);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const instance = {
 | 
			
		||||
            instance: group,
 | 
			
		||||
            render: () => {
 | 
			
		||||
                overlay.renderWithinBoundaryConditions();
 | 
			
		||||
            },
 | 
			
		||||
            disposable: new CompositeDisposable(overlay, {
 | 
			
		||||
                dispose: () => {
 | 
			
		||||
                    group.model.isFloating = false;
 | 
			
		||||
                    remove(this.floatingGroups, instance);
 | 
			
		||||
                },
 | 
			
		||||
            }),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.floatingGroups.push(instance);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private orthogonalize(position: Position): DockviewGroupPanel {
 | 
			
		||||
@ -329,6 +399,20 @@ export class DockviewComponent
 | 
			
		||||
        this.layout(this.gridview.width, this.gridview.height, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override layout(
 | 
			
		||||
        width: number,
 | 
			
		||||
        height: number,
 | 
			
		||||
        forceResize?: boolean | undefined
 | 
			
		||||
    ): void {
 | 
			
		||||
        super.layout(width, height, forceResize);
 | 
			
		||||
 | 
			
		||||
        if (this.floatingGroups) {
 | 
			
		||||
            for (const floating of this.floatingGroups) {
 | 
			
		||||
                floating.render();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    focus(): void {
 | 
			
		||||
        this.activeGroup?.focus();
 | 
			
		||||
    }
 | 
			
		||||
@ -477,7 +561,7 @@ export class DockviewComponent
 | 
			
		||||
 | 
			
		||||
        for (const group of groups) {
 | 
			
		||||
            // remove the group will automatically remove the panels
 | 
			
		||||
            this.removeGroup(group, true);
 | 
			
		||||
            this.removeGroup(group, { skipActive: true });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (hasActiveGroup) {
 | 
			
		||||
@ -591,7 +675,9 @@ export class DockviewComponent
 | 
			
		||||
 | 
			
		||||
        group.model.removePanel(panel);
 | 
			
		||||
 | 
			
		||||
        panel.dispose();
 | 
			
		||||
        if (!options.skipDispose) {
 | 
			
		||||
            panel.dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (group.size === 0 && options.removeEmptyGroup) {
 | 
			
		||||
            this.removeGroup(group);
 | 
			
		||||
@ -625,7 +711,7 @@ export class DockviewComponent
 | 
			
		||||
                watermarkContainer.className = 'dv-watermark-container';
 | 
			
		||||
                watermarkContainer.appendChild(this.watermark.element);
 | 
			
		||||
 | 
			
		||||
                this.element.appendChild(watermarkContainer);
 | 
			
		||||
                this.gridview.element.appendChild(watermarkContainer);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (this.watermark) {
 | 
			
		||||
            this.watermark.element.parentElement!.remove();
 | 
			
		||||
@ -695,17 +781,49 @@ export class DockviewComponent
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeGroup(group: DockviewGroupPanel, skipActive = false): void {
 | 
			
		||||
    removeGroup(
 | 
			
		||||
        group: DockviewGroupPanel,
 | 
			
		||||
        options?:
 | 
			
		||||
            | {
 | 
			
		||||
                  skipActive?: boolean | undefined;
 | 
			
		||||
                  skipDispose?: boolean | undefined;
 | 
			
		||||
              }
 | 
			
		||||
            | undefined
 | 
			
		||||
    ): void {
 | 
			
		||||
        const panels = [...group.panels]; // reassign since group panels will mutate
 | 
			
		||||
 | 
			
		||||
        for (const panel of panels) {
 | 
			
		||||
            this.removePanel(panel, {
 | 
			
		||||
                removeEmptyGroup: false,
 | 
			
		||||
                skipDispose: false,
 | 
			
		||||
                skipDispose: options?.skipDispose ?? false,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        super.doRemoveGroup(group, { skipActive });
 | 
			
		||||
        this.doRemoveGroup(group, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected override doRemoveGroup(
 | 
			
		||||
        group: DockviewGroupPanel,
 | 
			
		||||
        options?:
 | 
			
		||||
            | {
 | 
			
		||||
                  skipActive?: boolean | undefined;
 | 
			
		||||
                  skipDispose?: boolean | undefined;
 | 
			
		||||
              }
 | 
			
		||||
            | undefined
 | 
			
		||||
    ): DockviewGroupPanel {
 | 
			
		||||
        const floatingGroup = this.floatingGroups.find(
 | 
			
		||||
            (_) => _.instance === group
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (floatingGroup) {
 | 
			
		||||
            if (!options?.skipDispose) {
 | 
			
		||||
                floatingGroup.instance.dispose();
 | 
			
		||||
            }
 | 
			
		||||
            floatingGroup.disposable.dispose();
 | 
			
		||||
            return floatingGroup.instance;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return super.doRemoveGroup(group, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    moveGroupOrPanel(
 | 
			
		||||
@ -721,7 +839,7 @@ export class DockviewComponent
 | 
			
		||||
 | 
			
		||||
        if (itemId === undefined) {
 | 
			
		||||
            if (sourceGroup) {
 | 
			
		||||
              this.moveGroup(sourceGroup, referenceGroup, target);
 | 
			
		||||
                this.moveGroup(sourceGroup, referenceGroup, target);
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@ -750,34 +868,44 @@ export class DockviewComponent
 | 
			
		||||
 | 
			
		||||
            if (sourceGroup && sourceGroup.size < 2) {
 | 
			
		||||
                const [targetParentLocation, to] = tail(targetLocation);
 | 
			
		||||
                const sourceLocation = getGridLocation(sourceGroup.element);
 | 
			
		||||
                const [sourceParentLocation, from] = tail(sourceLocation);
 | 
			
		||||
 | 
			
		||||
                if (
 | 
			
		||||
                    sequenceEquals(sourceParentLocation, targetParentLocation)
 | 
			
		||||
                ) {
 | 
			
		||||
                    // special case when 'swapping' two views within same grid location
 | 
			
		||||
                    // if a group has one tab - we are essentially moving the 'group'
 | 
			
		||||
                    // which is equivalent to swapping two views in this case
 | 
			
		||||
                    this.gridview.moveView(sourceParentLocation, from, to);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // source group will become empty so delete the group
 | 
			
		||||
                    const targetGroup = this.doRemoveGroup(sourceGroup, {
 | 
			
		||||
                        skipActive: true,
 | 
			
		||||
                        skipDispose: true,
 | 
			
		||||
                    });
 | 
			
		||||
                const isFloating = this.floatingGroups.find(
 | 
			
		||||
                    (x) => x.instance === sourceGroup
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                    // after deleting the group we need to re-evaulate the ref location
 | 
			
		||||
                    const updatedReferenceLocation = getGridLocation(
 | 
			
		||||
                        referenceGroup.element
 | 
			
		||||
                    );
 | 
			
		||||
                    const location = getRelativeLocation(
 | 
			
		||||
                        this.gridview.orientation,
 | 
			
		||||
                        updatedReferenceLocation,
 | 
			
		||||
                        target
 | 
			
		||||
                    );
 | 
			
		||||
                    this.doAddGroup(targetGroup, location);
 | 
			
		||||
                if (!isFloating) {
 | 
			
		||||
                    const sourceLocation = getGridLocation(sourceGroup.element);
 | 
			
		||||
                    const [sourceParentLocation, from] = tail(sourceLocation);
 | 
			
		||||
 | 
			
		||||
                    if (
 | 
			
		||||
                        sequenceEquals(
 | 
			
		||||
                            sourceParentLocation,
 | 
			
		||||
                            targetParentLocation
 | 
			
		||||
                        )
 | 
			
		||||
                    ) {
 | 
			
		||||
                        // special case when 'swapping' two views within same grid location
 | 
			
		||||
                        // if a group has one tab - we are essentially moving the 'group'
 | 
			
		||||
                        // which is equivalent to swapping two views in this case
 | 
			
		||||
                        this.gridview.moveView(sourceParentLocation, from, to);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // source group will become empty so delete the group
 | 
			
		||||
                const targetGroup = this.doRemoveGroup(sourceGroup, {
 | 
			
		||||
                    skipActive: true,
 | 
			
		||||
                    skipDispose: true,
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // after deleting the group we need to re-evaulate the ref location
 | 
			
		||||
                const updatedReferenceLocation = getGridLocation(
 | 
			
		||||
                    referenceGroup.element
 | 
			
		||||
                );
 | 
			
		||||
                const location = getRelativeLocation(
 | 
			
		||||
                    this.gridview.orientation,
 | 
			
		||||
                    updatedReferenceLocation,
 | 
			
		||||
                    target
 | 
			
		||||
                );
 | 
			
		||||
                this.doAddGroup(targetGroup, location);
 | 
			
		||||
            } else {
 | 
			
		||||
                const groupItem: IDockviewPanel | undefined =
 | 
			
		||||
                    sourceGroup?.model.removePanel(itemId) ||
 | 
			
		||||
@ -821,7 +949,17 @@ export class DockviewComponent
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                this.gridview.removeView(getGridLocation(sourceGroup.element));
 | 
			
		||||
                const floatingGroup = this.floatingGroups.find(
 | 
			
		||||
                    (x) => x.instance === sourceGroup
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                if (floatingGroup) {
 | 
			
		||||
                    floatingGroup.disposable.dispose();
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.gridview.removeView(
 | 
			
		||||
                        getGridLocation(sourceGroup.element)
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const referenceLocation = getGridLocation(
 | 
			
		||||
                    referenceGroup.element
 | 
			
		||||
@ -963,60 +1101,4 @@ export class DockviewComponent
 | 
			
		||||
        this._onDidRemovePanel.dispose();
 | 
			
		||||
        this._onDidLayoutFromJSON.dispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
 | 
			
		||||
    addFloating() {
 | 
			
		||||
        const parentDockview = this;
 | 
			
		||||
 | 
			
		||||
        const floatingDockview = new DockviewComponent({
 | 
			
		||||
            ...this.options,
 | 
			
		||||
            parentElement: undefined,
 | 
			
		||||
            showDndOverlay: (event) => {
 | 
			
		||||
                const data = event.getData();
 | 
			
		||||
 | 
			
		||||
                if (data && data.viewId === parentDockview.id) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return false;
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        floatingDockview.onDidDrop((event) => {
 | 
			
		||||
            const data = event.getData();
 | 
			
		||||
 | 
			
		||||
            if (!data || data.viewId !== parentDockview.id) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (data.panelId === null) {
 | 
			
		||||
                const group = parentDockview.removeGroup(
 | 
			
		||||
                    parentDockview.getPanel(data.groupId)!
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                const panel = parentDockview.removePanel(
 | 
			
		||||
                    parentDockview.getGroupPanel(data.panelId)!
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                parentDockview.moveGroupOrPanel()
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        floatingDockview.addPanel({
 | 
			
		||||
            id: '__test__',
 | 
			
		||||
            component: 'default',
 | 
			
		||||
        });
 | 
			
		||||
        floatingDockview.addPanel({
 | 
			
		||||
            id: '__test__2__',
 | 
			
		||||
            component: 'default',
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const overlay = new Overlay(this.element, floatingDockview.element, {
 | 
			
		||||
            height: 300,
 | 
			
		||||
            width: 300,
 | 
			
		||||
            left: 100,
 | 
			
		||||
            top: 100,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ export class DockviewGroupPanel
 | 
			
		||||
    extends GridviewPanel
 | 
			
		||||
    implements IDockviewGroupPanel
 | 
			
		||||
{
 | 
			
		||||
    private readonly _model: IDockviewGroupPanelModel;
 | 
			
		||||
    private readonly _model: DockviewGroupPanelModel;
 | 
			
		||||
 | 
			
		||||
    get panels(): IDockviewPanel[] {
 | 
			
		||||
        return this._model.panels;
 | 
			
		||||
@ -40,7 +40,7 @@ export class DockviewGroupPanel
 | 
			
		||||
        return this._model.size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get model(): IDockviewGroupPanelModel {
 | 
			
		||||
    get model(): DockviewGroupPanelModel {
 | 
			
		||||
        return this._model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -138,6 +138,7 @@ export class DockviewGroupPanelModel
 | 
			
		||||
    private _isGroupActive = false;
 | 
			
		||||
    private _locked = false;
 | 
			
		||||
    private _control: IGroupControlRenderer | undefined;
 | 
			
		||||
    private _isFloating = false;
 | 
			
		||||
 | 
			
		||||
    private mostRecentlyUsed: IDockviewPanel[] = [];
 | 
			
		||||
 | 
			
		||||
@ -223,6 +224,20 @@ export class DockviewGroupPanelModel
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get isFloating(): boolean {
 | 
			
		||||
        return this._isFloating;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set isFloating(value: boolean) {
 | 
			
		||||
        this._isFloating = value;
 | 
			
		||||
 | 
			
		||||
        this.dropTarget.setTargetZones(
 | 
			
		||||
            value ? ['center'] : ['top', 'bottom', 'left', 'right', 'center']
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        toggleClass(this.container, 'dv-groupview-floating', value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        private readonly container: HTMLElement,
 | 
			
		||||
        private accessor: DockviewComponent,
 | 
			
		||||
@ -232,7 +247,7 @@ export class DockviewGroupPanelModel
 | 
			
		||||
    ) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this.container.classList.add('groupview');
 | 
			
		||||
        toggleClass(this.container, 'groupview', true);
 | 
			
		||||
 | 
			
		||||
        this.tabsContainer = new TabsContainer(this.accessor, this.groupPanel);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@
 | 
			
		||||
    --dv-drag-over-border-color: white;
 | 
			
		||||
    --dv-tabs-container-scrollbar-color: #888;
 | 
			
		||||
    --dv-icon-hover-background-color: rgba(90, 93, 94, 0.31);
 | 
			
		||||
    --dv-floating-box-shadow: 8px 8px 8px 0px rgba(83, 89, 93, 0.5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin dockview-theme-dark-mixin {
 | 
			
		||||
@ -225,3 +226,124 @@
 | 
			
		||||
.dockview-theme-dracula {
 | 
			
		||||
    @include dockview-theme-dracula-mixin();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin dockview-design-replit-mixin {
 | 
			
		||||
    &.dv-dockview {
 | 
			
		||||
        padding: 3px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .view:has(> .groupview) {
 | 
			
		||||
        padding: 3px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .dv-resize-container:has(> .groupview) {
 | 
			
		||||
        border-radius: 8px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .groupview {
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        border-radius: 10px;
 | 
			
		||||
 | 
			
		||||
        .tabs-and-actions-container {
 | 
			
		||||
            .tab {
 | 
			
		||||
                margin: 4px;
 | 
			
		||||
                border-radius: 8px;
 | 
			
		||||
 | 
			
		||||
                .dockview-svg {
 | 
			
		||||
                    height: 8px;
 | 
			
		||||
                    width: 8px;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                &:hover {
 | 
			
		||||
                    background-color: #e4e5e6 !important;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            border-bottom: 1px solid rgba(128, 128, 128, 0.35);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .content-container {
 | 
			
		||||
            background-color: #fcfcfc;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.active-group {
 | 
			
		||||
            border: 1px solid rgba(128, 128, 128, 0.35);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.inactive-group {
 | 
			
		||||
            border: 1px solid transparent;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .vertical > .sash-container > .sash {
 | 
			
		||||
        &::after {
 | 
			
		||||
            content: '';
 | 
			
		||||
            height: 4px;
 | 
			
		||||
            width: 40px;
 | 
			
		||||
            border-radius: 2px;
 | 
			
		||||
            top: 50%;
 | 
			
		||||
            left: 50%;
 | 
			
		||||
            transform: translate(-50%, -50%);
 | 
			
		||||
            background-color: var(--dv-separator-handle-background-color);
 | 
			
		||||
            position: absolute;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
            &::after {
 | 
			
		||||
                background-color: var(
 | 
			
		||||
                    --dv-separator-handle-hover-background-color
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .horizontal > .sash-container > .sash {
 | 
			
		||||
        &::after {
 | 
			
		||||
            content: '';
 | 
			
		||||
            height: 40px;
 | 
			
		||||
            width: 4px;
 | 
			
		||||
            border-radius: 2px;
 | 
			
		||||
            top: 50%;
 | 
			
		||||
            left: 50%;
 | 
			
		||||
            transform: translate(-50%, -50%);
 | 
			
		||||
            background-color: var(--dv-separator-handle-background-color);
 | 
			
		||||
            position: absolute;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
            &::after {
 | 
			
		||||
                background-color: var(
 | 
			
		||||
                    --dv-separator-handle-hover-background-color
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dockview-theme-replit {
 | 
			
		||||
    @include dockview-theme-core-mixin();
 | 
			
		||||
    @include dockview-design-replit-mixin();
 | 
			
		||||
    //
 | 
			
		||||
    --dv-group-view-background-color: #ebeced;
 | 
			
		||||
    //
 | 
			
		||||
    --dv-tabs-and-actions-container-background-color: #fcfcfc;
 | 
			
		||||
    //
 | 
			
		||||
    --dv-activegroup-visiblepanel-tab-background-color: #f0f1f2;
 | 
			
		||||
    --dv-activegroup-hiddenpanel-tab-background-color: ##fcfcfc;
 | 
			
		||||
    --dv-inactivegroup-visiblepanel-tab-background-color: #f0f1f2;
 | 
			
		||||
    --dv-inactivegroup-hiddenpanel-tab-background-color: #fcfcfc;
 | 
			
		||||
    --dv-tab-divider-color: transparent;
 | 
			
		||||
    //
 | 
			
		||||
    --dv-activegroup-visiblepanel-tab-color: rgb(51, 51, 51);
 | 
			
		||||
    --dv-activegroup-hiddenpanel-tab-color: rgb(51, 51, 51);
 | 
			
		||||
    --dv-inactivegroup-visiblepanel-tab-color: rgb(51, 51, 51);
 | 
			
		||||
    --dv-inactivegroup-hiddenpanel-tab-color: rgb(51, 51, 51);
 | 
			
		||||
    //
 | 
			
		||||
    --dv-separator-border: transparent;
 | 
			
		||||
    --dv-paneview-header-border-color: rgb(51, 51, 51);
 | 
			
		||||
 | 
			
		||||
    --dv-background-color: #ebeced;
 | 
			
		||||
 | 
			
		||||
    /////
 | 
			
		||||
    --dv-separator-handle-background-color: #cfd1d3;
 | 
			
		||||
    --dv-separator-handle-hover-background-color: #babbbb;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,6 @@
 | 
			
		||||
export * from 'dockview-core';
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
    IDockviewPanelHeaderProps,
 | 
			
		||||
    IDockviewPanelProps,
 | 
			
		||||
    DockviewReadyEvent,
 | 
			
		||||
    IDockviewReactProps,
 | 
			
		||||
    DockviewReact,
 | 
			
		||||
} from './dockview/dockview';
 | 
			
		||||
export * from './dockview/dockview';
 | 
			
		||||
export * from './dockview/defaultTab';
 | 
			
		||||
export * from './splitview/splitview';
 | 
			
		||||
export * from './gridview/gridview';
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,28 @@ import * as ReactDOM from 'react-dom';
 | 
			
		||||
import { v4 } from 'uuid';
 | 
			
		||||
import './app.scss';
 | 
			
		||||
 | 
			
		||||
function useLocalStorageItem(key: string, defaultValue: string): string {
 | 
			
		||||
    const [item, setItem] = React.useState<string | null>(
 | 
			
		||||
        localStorage.getItem(key)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
        const listener = (event: StorageEvent) => {
 | 
			
		||||
            setItem(localStorage.getItem(key));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        window.addEventListener('storage', listener);
 | 
			
		||||
 | 
			
		||||
        setItem(localStorage.getItem(key));
 | 
			
		||||
 | 
			
		||||
        return () => {
 | 
			
		||||
            window.removeEventListener('storage', listener);
 | 
			
		||||
        };
 | 
			
		||||
    }, [key]);
 | 
			
		||||
 | 
			
		||||
    return item === null ? defaultValue : item;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const components = {
 | 
			
		||||
    default: (props: IDockviewPanelProps<{ title: string }>) => {
 | 
			
		||||
        return <div style={{ padding: '20px' }}>{props.params.title}</div>;
 | 
			
		||||
@ -196,8 +218,8 @@ const DockviewDemo = () => {
 | 
			
		||||
            title: 'Panel 6',
 | 
			
		||||
            position: { referencePanel: 'panel_4', direction: 'below' },
 | 
			
		||||
        });
 | 
			
		||||
        panel6.group.locked = true;
 | 
			
		||||
        panel6.group.header.hidden = true;
 | 
			
		||||
        // panel6.group.locked = true;
 | 
			
		||||
        // panel6.group.header.hidden = true;
 | 
			
		||||
        event.api.addPanel({
 | 
			
		||||
            id: 'panel_7',
 | 
			
		||||
            component: 'default',
 | 
			
		||||
@ -211,18 +233,23 @@ const DockviewDemo = () => {
 | 
			
		||||
            position: { referencePanel: 'panel_7', direction: 'within' },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        event.api.addGroup();
 | 
			
		||||
        // event.api.addGroup();
 | 
			
		||||
 | 
			
		||||
        event.api.getPanel('panel_1')!.api.setActive();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const theme = useLocalStorageItem(
 | 
			
		||||
        'dv-theme-class-name',
 | 
			
		||||
        'dockview-theme-abyss'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <DockviewReact
 | 
			
		||||
            components={components}
 | 
			
		||||
            defaultTabComponent={headerComponents.default}
 | 
			
		||||
            groupControlComponent={GroupControls}
 | 
			
		||||
            onReady={onReady}
 | 
			
		||||
            className="dockview-theme-abyss"
 | 
			
		||||
            className={theme}
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import './codeSandboxButton.scss';
 | 
			
		||||
import { ThemePicker } from './container';
 | 
			
		||||
 | 
			
		||||
const BASE_SANDBOX_URL =
 | 
			
		||||
    'https://codesandbox.io/s/github/mathuo/dockview/tree/master/packages/docs/sandboxes';
 | 
			
		||||
@ -40,26 +41,29 @@ export const CodeSandboxButton = (props: { id: string }) => {
 | 
			
		||||
    }, [props.id]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <span
 | 
			
		||||
            className="codesandbox-button"
 | 
			
		||||
            style={{ display: 'flex', alignItems: 'center' }}
 | 
			
		||||
        >
 | 
			
		||||
            <span className="codesandbox-button-pretext">{`Open in `}</span>
 | 
			
		||||
            <a
 | 
			
		||||
                href={url}
 | 
			
		||||
                target={'_blank'}
 | 
			
		||||
                className="codesandbox-button-content"
 | 
			
		||||
        <>
 | 
			
		||||
            <ThemePicker />
 | 
			
		||||
            <span
 | 
			
		||||
                className="codesandbox-button"
 | 
			
		||||
                style={{ display: 'flex', alignItems: 'center' }}
 | 
			
		||||
            >
 | 
			
		||||
                <span
 | 
			
		||||
                    style={{
 | 
			
		||||
                        fontWeight: 'bold',
 | 
			
		||||
                        paddingRight: '4px',
 | 
			
		||||
                    }}
 | 
			
		||||
                <span className="codesandbox-button-pretext">{`Open in `}</span>
 | 
			
		||||
                <a
 | 
			
		||||
                    href={url}
 | 
			
		||||
                    target={'_blank'}
 | 
			
		||||
                    className="codesandbox-button-content"
 | 
			
		||||
                >
 | 
			
		||||
                    CodeSandbox
 | 
			
		||||
                </span>
 | 
			
		||||
                <CloseButton />
 | 
			
		||||
            </a>
 | 
			
		||||
        </span>
 | 
			
		||||
                    <span
 | 
			
		||||
                        style={{
 | 
			
		||||
                            fontWeight: 'bold',
 | 
			
		||||
                            paddingRight: '4px',
 | 
			
		||||
                        }}
 | 
			
		||||
                    >
 | 
			
		||||
                        CodeSandbox
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <CloseButton />
 | 
			
		||||
                </a>
 | 
			
		||||
            </span>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -69,6 +69,49 @@ const JavascriptIcon = (props: { height: number; width: number }) => {
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const themes = [
 | 
			
		||||
    'dockview-theme-dark',
 | 
			
		||||
    'dockview-theme-light',
 | 
			
		||||
    'dockview-theme-vs',
 | 
			
		||||
    'dockview-theme-dracula',
 | 
			
		||||
    'dockview-theme-replit',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const ThemePicker = () => {
 | 
			
		||||
    const [theme, setTheme] = React.useState<string>(
 | 
			
		||||
        localStorage.getItem('dv-theme-class-name') || themes[0]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
        localStorage.setItem('dv-theme-class-name', theme);
 | 
			
		||||
        window.dispatchEvent(new StorageEvent('storage'));
 | 
			
		||||
    }, [theme]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div
 | 
			
		||||
            style={{
 | 
			
		||||
                height: '20px',
 | 
			
		||||
                display: 'flex',
 | 
			
		||||
                alignItems: 'center',
 | 
			
		||||
                padding: '0px 0px 0px 4px',
 | 
			
		||||
            }}
 | 
			
		||||
        >
 | 
			
		||||
            <span style={{ paddingRight: '4px' }}>{'Theme: '}</span>
 | 
			
		||||
            <select
 | 
			
		||||
                style={{ backgroundColor: 'inherit', color: 'inherit' }}
 | 
			
		||||
                onChange={(e) => setTheme(e.target.value)}
 | 
			
		||||
                value={theme}
 | 
			
		||||
            >
 | 
			
		||||
                {themes.map((theme) => (
 | 
			
		||||
                    <option key={theme} value={theme}>
 | 
			
		||||
                        {theme}
 | 
			
		||||
                    </option>
 | 
			
		||||
                ))}
 | 
			
		||||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const MultiFrameworkContainer = (props: {
 | 
			
		||||
    react: React.FC;
 | 
			
		||||
    typescript: (parent: HTMLElement) => { dispose: () => void };
 | 
			
		||||
@ -183,6 +226,7 @@ export const MultiFrameworkContainer = (props: {
 | 
			
		||||
                    </select>
 | 
			
		||||
                </div>
 | 
			
		||||
                <span style={{ flexGrow: 1 }} />
 | 
			
		||||
                <ThemePicker />
 | 
			
		||||
                <CodeSandboxButton id={sandboxId} />
 | 
			
		||||
            </div>
 | 
			
		||||
        </>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user