mirror of
https://github.com/mathuo/dockview
synced 2025-02-12 19:35:45 +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…
Reference in New Issue
Block a user