mirror of
https://github.com/mathuo/dockview
synced 2025-05-04 10:38:24 +00:00
feat: improved dnd model
This commit is contained in:
parent
bb93c9e4c8
commit
d811ca6554
@ -16,10 +16,10 @@ describe('droptarget', () => {
|
||||
beforeEach(() => {
|
||||
element = document.createElement('div');
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 200);
|
||||
});
|
||||
|
||||
test('that dragover events are marked', () => {
|
||||
|
@ -8,6 +8,7 @@ import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel';
|
||||
import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel';
|
||||
import { Tab } from '../../../dockview/components/tab/tab';
|
||||
import { IDockviewPanel } from '../../../dockview/dockviewPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
|
||||
describe('tab', () => {
|
||||
test('that empty tab has inactive-tab class', () => {
|
||||
@ -46,15 +47,10 @@ describe('tab', () => {
|
||||
id: 'testcomponentid',
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -72,38 +68,33 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(cut.element);
|
||||
fireEvent.dragOver(cut.element);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toBeCalled();
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that if you drag over yourself no drop target is shown', () => {
|
||||
test('that if you drag over yourself a drop target is shown', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -121,10 +112,10 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -136,11 +127,11 @@ describe('tab', () => {
|
||||
fireEvent.dragEnter(cut.element);
|
||||
fireEvent.dragOver(cut.element);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that if you drag over another tab a drop target is shown', () => {
|
||||
@ -175,10 +166,10 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -229,10 +220,10 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -289,10 +280,10 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
|
@ -42,16 +42,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -73,15 +73,14 @@ describe('tabsContainer', () => {
|
||||
options: {},
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
const dropTargetContainer = document.createElement('div');
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
// dropTargetContainer: new DropTargetAnchorContainer(
|
||||
// dropTargetContainer
|
||||
// ),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -97,16 +96,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -129,6 +128,10 @@ describe('tabsContainer', () => {
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
// expect(
|
||||
// dropTargetContainer.getElementsByClassName('dv-drop-target-anchor')
|
||||
// .length
|
||||
// ).toBe(1);
|
||||
});
|
||||
|
||||
test('that dropping over the empty space should render a drop target', () => {
|
||||
@ -166,16 +169,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -229,16 +232,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -291,16 +294,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
|
@ -133,11 +133,11 @@ describe('dockviewComponent', () => {
|
||||
},
|
||||
className: 'test-a test-b',
|
||||
});
|
||||
expect(dockview.element.className).toBe('test-a test-b');
|
||||
expect(dockview.element.className).toBe('test-a test-b dockview-theme-abyss');
|
||||
|
||||
dockview.updateOptions({ className: 'test-b test-c' });
|
||||
|
||||
expect(dockview.element.className).toBe('test-b test-c');
|
||||
expect(dockview.element.className).toBe('dockview-theme-abyss test-b test-c');
|
||||
});
|
||||
|
||||
describe('memory leakage', () => {
|
||||
@ -3339,10 +3339,10 @@ describe('dockviewComponent', () => {
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
Object.defineProperty(dockview.element, 'clientWidth', {
|
||||
Object.defineProperty(dockview.element, 'offsetWidth', {
|
||||
get: () => 100,
|
||||
});
|
||||
Object.defineProperty(dockview.element, 'clientHeight', {
|
||||
Object.defineProperty(dockview.element, 'offsetHeight', {
|
||||
get: () => 100,
|
||||
});
|
||||
|
||||
@ -6652,36 +6652,4 @@ describe('dockviewComponent', () => {
|
||||
expect(api.panels.length).toBe(3);
|
||||
expect(api.groups.length).toBe(3);
|
||||
});
|
||||
|
||||
describe('updateOptions', () => {
|
||||
test('gap', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
gap: 6,
|
||||
});
|
||||
|
||||
expect(dockview.gap).toBe(6);
|
||||
|
||||
dockview.updateOptions({ gap: 10 });
|
||||
expect(dockview.gap).toBe(10);
|
||||
|
||||
dockview.updateOptions({});
|
||||
expect(dockview.gap).toBe(10);
|
||||
|
||||
dockview.updateOptions({ gap: 15 });
|
||||
expect(dockview.gap).toBe(15);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -684,12 +684,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0)! as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
@ -744,12 +744,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0)! as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
function run(value: number) {
|
||||
fireEvent.dragEnter(element);
|
||||
@ -792,7 +792,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
fireEvent.dragEnd(element);
|
||||
});
|
||||
|
||||
test('that should not show drop target if dropping on self', () => {
|
||||
test('that should show drop target if dropping on self', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
options: {},
|
||||
@ -806,15 +806,9 @@ describe('dockviewGroupPanelModel', () => {
|
||||
),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -842,12 +836,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0)! as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
|
||||
@ -861,10 +855,10 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that should not allow drop when dropping on self for same component id', () => {
|
||||
test('that should allow drop when dropping on self for same component id', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
options: {},
|
||||
@ -915,12 +909,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
|
||||
@ -934,7 +928,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that should not allow drop when not dropping for different component id', () => {
|
||||
@ -988,12 +982,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('anothercomponentid', 'groupviewid', 'panel1')],
|
||||
|
@ -629,10 +629,20 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
return this.component.totalPanels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated dockview: dockviewComponent.gap has been deprecated. Use `theme` instead. This will be removed in a future version.
|
||||
*/
|
||||
get gap(): number {
|
||||
return this.component.gap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated dockview: dockviewComponent.setGap has been deprecated. Use `theme` instead. This will be removed in a future version.
|
||||
*/
|
||||
setGap(gap: number | undefined): void {
|
||||
this.component.updateOptions({ gap: gap });
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the active group changes. May be undefined if no group is active.
|
||||
*/
|
||||
@ -914,10 +924,6 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
return this.component.addPopoutGroup(item, options);
|
||||
}
|
||||
|
||||
setGap(gap: number | undefined): void {
|
||||
this.component.updateOptions({ gap });
|
||||
}
|
||||
|
||||
updateOptions(options: Partial<DockviewComponentOptions>) {
|
||||
this.component.updateOptions(options);
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
* For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled
|
||||
* through .preventDefault(). Since this is applied globally to all drag events this would break dockviews
|
||||
* dnd logic. You can see the code at
|
||||
* https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
|
||||
P * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
|
||||
*/
|
||||
event.dataTransfer.setData('text/plain', '');
|
||||
}
|
||||
@ -75,7 +75,9 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
}),
|
||||
addDisposableListener(this.el, 'dragend', () => {
|
||||
this.pointerEventsDisposable.dispose();
|
||||
this.dataDisposable.dispose();
|
||||
setTimeout(() => {
|
||||
this.dataDisposable.dispose(); // allow the data to be read by other handlers before disposing
|
||||
}, 0);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
.dv-drop-target-container {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
--dv-transition-duration: 300ms;
|
||||
|
||||
.dv-drop-target-anchor {
|
||||
position: relative;
|
||||
border: var(--dv-drag-over-border);
|
||||
transition: opacity var(--dv-transition-duration) ease-in,
|
||||
top var(--dv-transition-duration) ease-out,
|
||||
left var(--dv-transition-duration) ease-out,
|
||||
width var(--dv-transition-duration) ease-out,
|
||||
height var(--dv-transition-duration) ease-out;
|
||||
background-color: var(--dv-drag-over-background-color);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
102
packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts
Normal file
102
packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { CompositeDisposable, Disposable } from '../lifecycle';
|
||||
import { DropTargetTargetModel } from './droptarget';
|
||||
|
||||
export class DropTargetAnchorContainer extends CompositeDisposable {
|
||||
private _model:
|
||||
| { root: HTMLElement; overlay: HTMLElement; changed: boolean }
|
||||
| undefined;
|
||||
|
||||
private _outline: HTMLElement | undefined;
|
||||
|
||||
private _disabled = false;
|
||||
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
if (this.disabled === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._disabled = value;
|
||||
|
||||
if (value) {
|
||||
this.model?.clear();
|
||||
}
|
||||
}
|
||||
|
||||
get model(): DropTargetTargetModel | undefined {
|
||||
if (this.disabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
clear: () => {
|
||||
if (this._model) {
|
||||
this._model.root.parentElement?.removeChild(
|
||||
this._model.root
|
||||
);
|
||||
}
|
||||
this._model = undefined;
|
||||
},
|
||||
exists: () => {
|
||||
return !!this._model;
|
||||
},
|
||||
getElements: (event?: DragEvent, outline?: HTMLElement) => {
|
||||
const changed = this._outline !== outline;
|
||||
this._outline = outline;
|
||||
|
||||
if (this._model) {
|
||||
this._model.changed = changed;
|
||||
return this._model;
|
||||
}
|
||||
|
||||
const container = this.createContainer();
|
||||
const anchor = this.createAnchor();
|
||||
|
||||
this._model = { root: container, overlay: anchor, changed };
|
||||
|
||||
container.appendChild(anchor);
|
||||
this.element.appendChild(container);
|
||||
|
||||
if (event?.target instanceof HTMLElement) {
|
||||
const targetBox = event.target.getBoundingClientRect();
|
||||
const box = this.element.getBoundingClientRect();
|
||||
|
||||
anchor.style.left = `${targetBox.left - box.left}px`;
|
||||
anchor.style.top = `${targetBox.top - box.top}px`;
|
||||
}
|
||||
|
||||
return this._model;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor(readonly element: HTMLElement, options: { disabled: boolean }) {
|
||||
super();
|
||||
|
||||
this._disabled = options.disabled;
|
||||
|
||||
this.addDisposables(
|
||||
Disposable.from(() => {
|
||||
this.model?.clear();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private createContainer(): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'dv-drop-target-container';
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
private createAnchor(): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'dv-drop-target-anchor';
|
||||
el.style.visibility = 'hidden';
|
||||
|
||||
return el;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
.dv-drop-target {
|
||||
position: relative;
|
||||
--dv-transition-duration: 70ms;
|
||||
|
||||
> .dv-drop-target-dropzone {
|
||||
position: absolute;
|
||||
@ -15,10 +16,13 @@
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: var(--dv-drag-over-border);
|
||||
background-color: var(--dv-drag-over-background-color);
|
||||
transition: top 70ms ease-out, left 70ms ease-out,
|
||||
width 70ms ease-out, height 70ms ease-out,
|
||||
opacity 0.15s ease-out;
|
||||
transition: top var(--dv-transition-duration) ease-out,
|
||||
left var(--dv-transition-duration) ease-out,
|
||||
width var(--dv-transition-duration) ease-out,
|
||||
height var(--dv-transition-duration) ease-out,
|
||||
opacity var(--dv-transition-duration) ease-out;
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
|
||||
|
@ -93,10 +93,26 @@ const DEFAULT_SIZE: MeasuredValue = {
|
||||
const SMALL_WIDTH_BOUNDARY = 100;
|
||||
const SMALL_HEIGHT_BOUNDARY = 100;
|
||||
|
||||
export interface DropTargetTargetModel {
|
||||
getElements(
|
||||
event?: DragEvent,
|
||||
outline?: HTMLElement
|
||||
): {
|
||||
root: HTMLElement;
|
||||
overlay: HTMLElement;
|
||||
changed: boolean;
|
||||
};
|
||||
exists(): boolean;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
export interface DroptargetOptions {
|
||||
canDisplayOverlay: CanDisplayOverlay;
|
||||
acceptedTargetZones: Position[];
|
||||
overlayModel?: DroptargetOverlayModel;
|
||||
getOverrideTarget?: () => DropTargetTargetModel | undefined;
|
||||
className?: string;
|
||||
getOverlayOutline?: () => HTMLElement | null;
|
||||
}
|
||||
|
||||
export class Droptarget extends CompositeDisposable {
|
||||
@ -116,6 +132,18 @@ export class Droptarget extends CompositeDisposable {
|
||||
|
||||
private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__';
|
||||
|
||||
private static ACTUAL_TARGET: Droptarget | undefined;
|
||||
|
||||
private _disabled: boolean;
|
||||
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
this._disabled = value;
|
||||
}
|
||||
|
||||
get state(): Position | undefined {
|
||||
return this._state;
|
||||
}
|
||||
@ -126,21 +154,35 @@ export class Droptarget extends CompositeDisposable {
|
||||
) {
|
||||
super();
|
||||
|
||||
this._disabled = false;
|
||||
|
||||
// use a set to take advantage of #<set>.has
|
||||
this._acceptedTargetZonesSet = new Set(
|
||||
this.options.acceptedTargetZones
|
||||
);
|
||||
|
||||
this.dnd = new DragAndDropObserver(this.element, {
|
||||
onDragEnter: () => undefined,
|
||||
onDragEnter: () => {
|
||||
this.options.getOverrideTarget?.()?.getElements();
|
||||
},
|
||||
onDragOver: (e) => {
|
||||
Droptarget.ACTUAL_TARGET = this;
|
||||
|
||||
const overrideTraget = this.options.getOverrideTarget?.();
|
||||
|
||||
if (this._acceptedTargetZonesSet.size === 0) {
|
||||
if (overrideTraget) {
|
||||
return;
|
||||
}
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
const width = this.element.clientWidth;
|
||||
const height = this.element.clientHeight;
|
||||
const target =
|
||||
this.options.getOverlayOutline?.() ?? this.element;
|
||||
|
||||
const width = target.offsetWidth;
|
||||
const height = target.offsetHeight;
|
||||
|
||||
if (width === 0 || height === 0) {
|
||||
return; // avoid div!0
|
||||
@ -149,8 +191,8 @@ export class Droptarget extends CompositeDisposable {
|
||||
const rect = (
|
||||
e.currentTarget as HTMLElement
|
||||
).getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
const x = (e.clientX ?? 0) - rect.left;
|
||||
const y = (e.clientY ?? 0) - rect.top;
|
||||
|
||||
const quadrant = this.calculateQuadrant(
|
||||
this._acceptedTargetZonesSet,
|
||||
@ -172,6 +214,9 @@ export class Droptarget extends CompositeDisposable {
|
||||
}
|
||||
|
||||
if (!this.options.canDisplayOverlay(e, quadrant)) {
|
||||
if (overrideTraget) {
|
||||
return;
|
||||
}
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
@ -194,7 +239,9 @@ export class Droptarget extends CompositeDisposable {
|
||||
|
||||
this.markAsUsed(e);
|
||||
|
||||
if (!this.targetElement) {
|
||||
if (overrideTraget) {
|
||||
//
|
||||
} else if (!this.targetElement) {
|
||||
this.targetElement = document.createElement('div');
|
||||
this.targetElement.className = 'dv-drop-target-dropzone';
|
||||
this.overlayElement = document.createElement('div');
|
||||
@ -202,8 +249,16 @@ export class Droptarget extends CompositeDisposable {
|
||||
this._state = 'center';
|
||||
this.targetElement.appendChild(this.overlayElement);
|
||||
|
||||
this.element.classList.add('dv-drop-target');
|
||||
this.element.append(this.targetElement);
|
||||
target.classList.add('dv-drop-target');
|
||||
target.append(this.targetElement);
|
||||
|
||||
// this.overlayElement.style.opacity = '0';
|
||||
|
||||
// requestAnimationFrame(() => {
|
||||
// if (this.overlayElement) {
|
||||
// this.overlayElement.style.opacity = '';
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
this.toggleClasses(quadrant, width, height);
|
||||
@ -211,10 +266,32 @@ export class Droptarget extends CompositeDisposable {
|
||||
this._state = quadrant;
|
||||
},
|
||||
onDragLeave: () => {
|
||||
const target = this.options.getOverrideTarget?.();
|
||||
|
||||
if (target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeDropTarget();
|
||||
},
|
||||
onDragEnd: () => {
|
||||
onDragEnd: (e) => {
|
||||
const target = this.options.getOverrideTarget?.();
|
||||
|
||||
if (target && Droptarget.ACTUAL_TARGET === this) {
|
||||
if (this._state) {
|
||||
// only stop the propagation of the event if we are dealing with it
|
||||
// which is only when the target has state
|
||||
e.stopPropagation();
|
||||
this._onDrop.fire({
|
||||
position: this._state,
|
||||
nativeEvent: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.removeDropTarget();
|
||||
|
||||
target?.clear();
|
||||
},
|
||||
onDrop: (e) => {
|
||||
e.preventDefault();
|
||||
@ -223,6 +300,8 @@ export class Droptarget extends CompositeDisposable {
|
||||
|
||||
this.removeDropTarget();
|
||||
|
||||
this.options.getOverrideTarget?.()?.clear();
|
||||
|
||||
if (state) {
|
||||
// only stop the propagation of the event if we are dealing with it
|
||||
// which is only when the target has state
|
||||
@ -268,7 +347,9 @@ export class Droptarget extends CompositeDisposable {
|
||||
width: number,
|
||||
height: number
|
||||
): void {
|
||||
if (!this.overlayElement) {
|
||||
const target = this.options.getOverrideTarget?.();
|
||||
|
||||
if (!target && !this.overlayElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -300,6 +381,103 @@ export class Droptarget extends CompositeDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
if (target) {
|
||||
const outlineEl =
|
||||
this.options.getOverlayOutline?.() ?? this.element;
|
||||
const elBox = outlineEl.getBoundingClientRect();
|
||||
|
||||
const ta = target.getElements(undefined, outlineEl);
|
||||
const el = ta.root;
|
||||
const overlay = ta.overlay;
|
||||
|
||||
const bigbox = el.getBoundingClientRect();
|
||||
|
||||
const rootTop = elBox.top - bigbox.top;
|
||||
const rootLeft = elBox.left - bigbox.left;
|
||||
|
||||
const box = {
|
||||
top: rootTop,
|
||||
left: rootLeft,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
|
||||
if (rightClass) {
|
||||
box.left = rootLeft + width * (1 - size);
|
||||
box.width = width * size;
|
||||
} else if (leftClass) {
|
||||
box.width = width * size;
|
||||
} else if (topClass) {
|
||||
box.height = height * size;
|
||||
} else if (bottomClass) {
|
||||
box.top = rootTop + height * (1 - size);
|
||||
box.height = height * size;
|
||||
}
|
||||
|
||||
if (isSmallX && isLeft) {
|
||||
box.width = 4;
|
||||
}
|
||||
if (isSmallX && isRight) {
|
||||
box.left = rootLeft + width - 4;
|
||||
box.width = 4;
|
||||
}
|
||||
|
||||
const topPx = `${Math.round(box.top)}px`;
|
||||
const leftPx = `${Math.round(box.left)}px`;
|
||||
const widthPx = `${Math.round(box.width)}px`;
|
||||
const heightPx = `${Math.round(box.height)}px`;
|
||||
|
||||
if (
|
||||
overlay.style.top === topPx &&
|
||||
overlay.style.left === leftPx &&
|
||||
overlay.style.width === widthPx &&
|
||||
overlay.style.height === heightPx
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
overlay.style.top = topPx;
|
||||
overlay.style.left = leftPx;
|
||||
overlay.style.width = widthPx;
|
||||
overlay.style.height = heightPx;
|
||||
overlay.style.visibility = 'visible';
|
||||
|
||||
overlay.className = `dv-drop-target-anchor${
|
||||
this.options.className ? ` ${this.options.className}` : ''
|
||||
}`;
|
||||
|
||||
toggleClass(overlay, 'dv-drop-target-left', isLeft);
|
||||
toggleClass(overlay, 'dv-drop-target-right', isRight);
|
||||
toggleClass(overlay, 'dv-drop-target-top', isTop);
|
||||
toggleClass(overlay, 'dv-drop-target-bottom', isBottom);
|
||||
toggleClass(
|
||||
overlay,
|
||||
'dv-drop-target-center',
|
||||
quadrant === 'center'
|
||||
);
|
||||
|
||||
if (ta.changed) {
|
||||
toggleClass(
|
||||
overlay,
|
||||
'dv-drop-target-anchor-container-changed',
|
||||
true
|
||||
);
|
||||
setTimeout(() => {
|
||||
toggleClass(
|
||||
overlay,
|
||||
'dv-drop-target-anchor-container-changed',
|
||||
false
|
||||
);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.overlayElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const box = { top: '0px', left: '0px', width: '100%', height: '100%' };
|
||||
|
||||
/**
|
||||
@ -396,10 +574,12 @@ export class Droptarget extends CompositeDisposable {
|
||||
private removeDropTarget(): void {
|
||||
if (this.targetElement) {
|
||||
this._state = undefined;
|
||||
this.element.removeChild(this.targetElement);
|
||||
this.targetElement.parentElement?.classList.remove(
|
||||
'dv-drop-target'
|
||||
);
|
||||
this.targetElement.remove();
|
||||
this.targetElement = undefined;
|
||||
this.overlayElement = undefined;
|
||||
this.element.classList.remove('dv-drop-target');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ import { addClasses, removeClasses } from '../dom';
|
||||
|
||||
export function addGhostImage(
|
||||
dataTransfer: DataTransfer,
|
||||
ghostElement: HTMLElement
|
||||
ghostElement: HTMLElement,
|
||||
options?: { x?: number; y?: number }
|
||||
): void {
|
||||
// class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
|
||||
addClasses(ghostElement, 'dv-dragged');
|
||||
|
||||
document.body.appendChild(ghostElement);
|
||||
dataTransfer.setDragImage(ghostElement, 0, 0);
|
||||
dataTransfer.setDragImage(ghostElement, options?.x ?? 0, options?.y ?? 0);
|
||||
|
||||
setTimeout(() => {
|
||||
removeClasses(ghostElement, 'dv-dragged');
|
||||
|
@ -72,9 +72,11 @@ export class GroupDragHandler extends DragHandler {
|
||||
ghostElement.style.lineHeight = '20px';
|
||||
ghostElement.style.borderRadius = '12px';
|
||||
ghostElement.style.position = 'absolute';
|
||||
ghostElement.style.pointerEvents = 'none';
|
||||
ghostElement.style.top = '-9999px';
|
||||
ghostElement.textContent = `Multiple Panels (${this.group.size})`;
|
||||
|
||||
addGhostImage(dataTransfer, ghostElement);
|
||||
addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 });
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -55,7 +55,15 @@ export class ContentContainer
|
||||
|
||||
this.addDisposables(this._onDidFocus, this._onDidBlur);
|
||||
|
||||
const target = group.dropTargetContainer;
|
||||
|
||||
this.dropTarget = new Droptarget(this.element, {
|
||||
getOverlayOutline: () => {
|
||||
return accessor.options.theme?.includeHeaderWhenHoverOverContent
|
||||
? this.element.parentElement
|
||||
: null;
|
||||
},
|
||||
className: 'dv-drop-target-content',
|
||||
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (
|
||||
@ -76,26 +84,12 @@ export class ContentContainer
|
||||
}
|
||||
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
if (data.groupId === this.group.id) {
|
||||
if (position === 'center') {
|
||||
// don't allow to drop on self for center position
|
||||
return false;
|
||||
}
|
||||
if (data.panelId === null) {
|
||||
// don't allow group move to drop anywhere on self
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const groupHasOnePanelAndIsActiveDragElement =
|
||||
this.group.panels.length === 1 &&
|
||||
data.groupId === this.group.id;
|
||||
|
||||
return !groupHasOnePanelAndIsActiveDragElement;
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.group.canDisplayOverlay(event, position, 'content');
|
||||
},
|
||||
getOverrideTarget: target ? () => target.model : undefined,
|
||||
});
|
||||
|
||||
this.addDisposables(this.dropTarget);
|
||||
|
@ -58,15 +58,13 @@
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
min-width: 80px;
|
||||
align-items: center;
|
||||
padding: 0px 8px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.dv-default-tab-content {
|
||||
padding: 0px 8px;
|
||||
flex-grow: 1;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.dv-default-tab-action {
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
} from '../../../dnd/droptarget';
|
||||
import { DragHandler } from '../../../dnd/abstractDragHandler';
|
||||
import { IDockviewPanel } from '../../dockviewPanel';
|
||||
import { addGhostImage } from '../../../dnd/ghost';
|
||||
|
||||
class TabDragHandler extends DragHandler {
|
||||
private readonly panelTransfer =
|
||||
@ -86,7 +87,8 @@ export class Tab extends CompositeDisposable {
|
||||
);
|
||||
|
||||
this.dropTarget = new Droptarget(this._element, {
|
||||
acceptedTargetZones: ['center'],
|
||||
acceptedTargetZones: ['left', 'right'],
|
||||
overlayModel: { activationSize: { value: 50, type: 'percentage' } },
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (this.group.locked) {
|
||||
return false;
|
||||
@ -95,15 +97,7 @@ export class Tab extends CompositeDisposable {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.panel.id !== data.panelId;
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.group.model.canDisplayOverlay(
|
||||
@ -112,6 +106,7 @@ export class Tab extends CompositeDisposable {
|
||||
'tab'
|
||||
);
|
||||
},
|
||||
getOverrideTarget: () => group.model.dropTargetContainer?.model,
|
||||
});
|
||||
|
||||
this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
|
||||
@ -121,6 +116,23 @@ export class Tab extends CompositeDisposable {
|
||||
this._onDropped,
|
||||
this._onDragStart,
|
||||
dragHandler.onDragStart((event) => {
|
||||
if (event.dataTransfer) {
|
||||
const style = getComputedStyle(this.element);
|
||||
const newNode = this.element.cloneNode(true) as HTMLElement;
|
||||
Array.from(style).forEach((key) =>
|
||||
newNode.style.setProperty(
|
||||
key,
|
||||
style.getPropertyValue(key),
|
||||
style.getPropertyPriority(key)
|
||||
)
|
||||
);
|
||||
newNode.style.position = 'absolute';
|
||||
|
||||
addGhostImage(event.dataTransfer, newNode, {
|
||||
y: -10,
|
||||
x: 30,
|
||||
});
|
||||
}
|
||||
this._onDragStart.fire(event);
|
||||
}),
|
||||
dragHandler,
|
||||
|
@ -7,17 +7,17 @@
|
||||
font-size: var(--dv-tabs-and-actions-container-font-size);
|
||||
|
||||
&.dv-single-tab.dv-full-width-single-tab {
|
||||
.dv-tabs-container {
|
||||
flex-grow: 1;
|
||||
|
||||
.dv-tab {
|
||||
.dv-tabs-container {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-void-container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
.dv-tab {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-void-container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-void-container {
|
||||
@ -50,10 +50,20 @@
|
||||
.dv-tab {
|
||||
-webkit-user-drag: element;
|
||||
outline: none;
|
||||
min-width: 75px;
|
||||
padding: 0.25rem 0.5rem;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
font-size: var(-dv-tab-font-size);
|
||||
margin: var(--dv-tab-margin);
|
||||
|
||||
&:first-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:not(:nth-last-child(1)) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:not(:first-child)::before {
|
||||
content: ' ';
|
||||
|
@ -10,7 +10,10 @@ import { VoidContainer } from './voidContainer';
|
||||
import { toggleClass } from '../../../dom';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
||||
import {
|
||||
DockviewGroupPanelModel,
|
||||
WillShowOverlayLocationEvent,
|
||||
} from '../../dockviewGroupPanelModel';
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
|
||||
export interface TabDropIndexEvent {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { last } from '../../../array';
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
import {
|
||||
Droptarget,
|
||||
@ -10,6 +9,7 @@ import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel';
|
||||
|
||||
export class VoidContainer extends CompositeDisposable {
|
||||
private readonly _element: HTMLElement;
|
||||
@ -54,16 +54,7 @@ export class VoidContainer extends CompositeDisposable {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't show the overlay if the tab being dragged is the last panel of this group
|
||||
return last(this.group.panels)?.id !== data.panelId;
|
||||
return true;
|
||||
}
|
||||
|
||||
return group.model.canDisplayOverlay(
|
||||
@ -72,6 +63,7 @@ export class VoidContainer extends CompositeDisposable {
|
||||
'header_space'
|
||||
);
|
||||
},
|
||||
getOverrideTarget: () => group.model.dropTargetContainer?.model,
|
||||
});
|
||||
|
||||
this.onWillShowOverlay = this.dropTraget.onWillShowOverlay;
|
||||
|
@ -54,6 +54,7 @@ import { Parameters } from '../panel/types';
|
||||
import { Overlay } from '../overlay/overlay';
|
||||
import {
|
||||
addTestId,
|
||||
Classnames,
|
||||
getDockviewTheme,
|
||||
toggleClass,
|
||||
watchElementResize,
|
||||
@ -74,6 +75,8 @@ import {
|
||||
} from '../overlay/overlayRenderContainer';
|
||||
import { PopoutWindow } from '../popoutWindow';
|
||||
import { StrictEventsSequencing } from './strictEventsSequencing';
|
||||
import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer';
|
||||
import { DockviewTheme, themeAbyss } from './theme';
|
||||
|
||||
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
|
||||
activationSize: { type: 'pixels', value: 10 },
|
||||
@ -191,6 +194,9 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
readonly totalPanels: number;
|
||||
readonly panels: IDockviewPanel[];
|
||||
readonly orientation: Orientation;
|
||||
/**
|
||||
* @deprecated use `theme` instead. This will be removed in a future version
|
||||
*/
|
||||
readonly gap: number;
|
||||
readonly onDidDrop: Event<DockviewDidDropEvent>;
|
||||
readonly onWillDrop: Event<DockviewWillDropEvent>;
|
||||
@ -253,9 +259,11 @@ export class DockviewComponent
|
||||
private readonly _deserializer = new DefaultDockviewDeserialzier(this);
|
||||
private readonly _api: DockviewApi;
|
||||
private _options: Exclude<DockviewComponentOptions, 'orientation'>;
|
||||
private watermark: IWatermarkRenderer | null = null;
|
||||
private _watermark: IWatermarkRenderer | null = null;
|
||||
private readonly _themeClassnames: Classnames;
|
||||
|
||||
readonly overlayRenderContainer: OverlayRenderContainer;
|
||||
readonly rootDropTargetContainer: DropTargetAnchorContainer;
|
||||
|
||||
private readonly _onWillDragPanel = new Emitter<TabDragEvent>();
|
||||
readonly onWillDragPanel: Event<TabDragEvent> = this._onWillDragPanel.event;
|
||||
@ -361,6 +369,9 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
get gap(): number {
|
||||
console.warn(
|
||||
'dockview: dockviewComponent.gap has been deprecated. Use `theme` instead. This will be removed in a future version.'
|
||||
);
|
||||
return this.gridview.margin;
|
||||
}
|
||||
|
||||
@ -377,10 +388,18 @@ export class DockviewComponent
|
||||
: undefined,
|
||||
disableAutoResizing: options.disableAutoResizing,
|
||||
locked: options.locked,
|
||||
margin: options.gap,
|
||||
margin: options.theme?.gap ?? 0,
|
||||
className: options.className,
|
||||
});
|
||||
|
||||
this.updateDropTargetModel(options);
|
||||
|
||||
this._themeClassnames = new Classnames(this.element);
|
||||
|
||||
this.rootDropTargetContainer = new DropTargetAnchorContainer(
|
||||
this.element,
|
||||
{ disabled: true }
|
||||
);
|
||||
this.overlayRenderContainer = new OverlayRenderContainer(
|
||||
this.gridview.element,
|
||||
this
|
||||
@ -394,6 +413,7 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
this.addDisposables(
|
||||
this.rootDropTargetContainer,
|
||||
this.overlayRenderContainer,
|
||||
this._onWillDragPanel,
|
||||
this._onWillDragGroup,
|
||||
@ -464,8 +484,10 @@ export class DockviewComponent
|
||||
);
|
||||
|
||||
this._options = options;
|
||||
this.updateTheme();
|
||||
|
||||
this._rootDropTarget = new Droptarget(this.element, {
|
||||
className: 'dv-drop-target-edge',
|
||||
canDisplayOverlay: (event, position) => {
|
||||
const data = getPanelData();
|
||||
|
||||
@ -506,6 +528,7 @@ export class DockviewComponent
|
||||
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
|
||||
overlayModel:
|
||||
this.options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL,
|
||||
getOverrideTarget: () => this.rootDropTargetContainer?.model,
|
||||
});
|
||||
|
||||
this.addDisposables(
|
||||
@ -756,6 +779,15 @@ export class DockviewComponent
|
||||
|
||||
popoutContainer.appendChild(group.element);
|
||||
|
||||
const anchor = document.createElement('div');
|
||||
const dropTargetContainer = new DropTargetAnchorContainer(
|
||||
anchor,
|
||||
{ disabled: this.rootDropTargetContainer.disabled }
|
||||
);
|
||||
popoutContainer.appendChild(anchor);
|
||||
|
||||
group.model.dropTargetContainer = dropTargetContainer;
|
||||
|
||||
group.model.location = {
|
||||
type: 'popout',
|
||||
getWindow: () => _window.window!,
|
||||
@ -844,6 +876,8 @@ export class DockviewComponent
|
||||
} else if (this.getPanel(group.id)) {
|
||||
group.model.renderContainer =
|
||||
this.overlayRenderContainer;
|
||||
group.model.dropTargetContainer =
|
||||
this.rootDropTargetContainer;
|
||||
returnedGroup = group;
|
||||
|
||||
const alreadyRemoved = !this._popoutGroups.find(
|
||||
@ -1134,6 +1168,13 @@ export class DockviewComponent
|
||||
override updateOptions(options: Partial<DockviewComponentOptions>): void {
|
||||
super.updateOptions(options);
|
||||
|
||||
if ('gap' in options) {
|
||||
console.warn(
|
||||
'dockview: dockviewComponent.setGap has been deprecated. Use `theme` instead. This will be removed in a future version.'
|
||||
);
|
||||
this.gridview.margin = options.gap ?? 0;
|
||||
}
|
||||
|
||||
if ('floatingGroupBounds' in options) {
|
||||
for (const group of this._floatingGroups) {
|
||||
switch (options.floatingGroupBounds) {
|
||||
@ -1158,18 +1199,14 @@ export class DockviewComponent
|
||||
}
|
||||
}
|
||||
|
||||
if ('rootOverlayModel' in options) {
|
||||
this._rootDropTarget.setOverlayModel(
|
||||
options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL
|
||||
);
|
||||
}
|
||||
|
||||
if ('gap' in options) {
|
||||
this.gridview.margin = options.gap ?? 0;
|
||||
}
|
||||
this.updateDropTargetModel(options);
|
||||
|
||||
this._options = { ...this.options, ...options };
|
||||
|
||||
if ('theme' in options) {
|
||||
this.updateTheme();
|
||||
}
|
||||
|
||||
this.layout(this.gridview.width, this.gridview.height, true);
|
||||
}
|
||||
|
||||
@ -1745,24 +1782,24 @@ export class DockviewComponent
|
||||
(x) => x.api.location.type === 'grid' && x.api.isVisible
|
||||
).length === 0
|
||||
) {
|
||||
if (!this.watermark) {
|
||||
this.watermark = this.createWatermarkComponent();
|
||||
if (!this._watermark) {
|
||||
this._watermark = this.createWatermarkComponent();
|
||||
|
||||
this.watermark.init({
|
||||
this._watermark.init({
|
||||
containerApi: new DockviewApi(this),
|
||||
});
|
||||
|
||||
const watermarkContainer = document.createElement('div');
|
||||
watermarkContainer.className = 'dv-watermark-container';
|
||||
addTestId(watermarkContainer, 'watermark-component');
|
||||
watermarkContainer.appendChild(this.watermark.element);
|
||||
watermarkContainer.appendChild(this._watermark.element);
|
||||
|
||||
this.gridview.element.appendChild(watermarkContainer);
|
||||
}
|
||||
} else if (this.watermark) {
|
||||
this.watermark.element.parentElement!.remove();
|
||||
this.watermark.dispose?.();
|
||||
this.watermark = null;
|
||||
} else if (this._watermark) {
|
||||
this._watermark.element.parentElement!.remove();
|
||||
this._watermark.dispose?.();
|
||||
this._watermark = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2404,9 +2441,11 @@ export class DockviewComponent
|
||||
if (this._moving) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.panel !== this.activePanel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._onDidActivePanelChange.value !== event.panel) {
|
||||
this._onDidActivePanelChange.fire(event.panel);
|
||||
}
|
||||
@ -2489,4 +2528,44 @@ export class DockviewComponent
|
||||
? rootOrientation
|
||||
: orthogonal(rootOrientation);
|
||||
}
|
||||
|
||||
private updateDropTargetModel(options: Partial<DockviewComponentOptions>) {
|
||||
if ('dndEdges' in options) {
|
||||
this._rootDropTarget.disabled =
|
||||
typeof options.dndEdges === 'boolean' &&
|
||||
options.dndEdges === false;
|
||||
|
||||
if (
|
||||
typeof options.dndEdges === 'object' &&
|
||||
options.dndEdges !== null
|
||||
) {
|
||||
this._rootDropTarget.setOverlayModel(options.dndEdges);
|
||||
} else {
|
||||
this._rootDropTarget.setOverlayModel(
|
||||
DEFAULT_ROOT_OVERLAY_MODEL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ('rootOverlayModel' in options) {
|
||||
this.updateDropTargetModel({ dndEdges: options.dndEdges });
|
||||
}
|
||||
}
|
||||
|
||||
private updateTheme(): void {
|
||||
const theme = this._options.theme ?? themeAbyss;
|
||||
this._themeClassnames.setClassNames(theme.className);
|
||||
|
||||
this.gridview.margin = theme.gap ?? 0;
|
||||
|
||||
switch (theme.dndOverlayMounting) {
|
||||
case 'absolute':
|
||||
this.rootDropTargetContainer.disabled = false;
|
||||
break;
|
||||
case 'relative':
|
||||
default:
|
||||
this.rootDropTargetContainer.disabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import {
|
||||
import { OverlayRenderContainer } from '../overlay/overlayRenderContainer';
|
||||
import { TitleEvent } from '../api/dockviewPanelApi';
|
||||
import { Contraints } from '../gridview/gridviewPanel';
|
||||
import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer';
|
||||
|
||||
interface GroupMoveEvent {
|
||||
groupId: string;
|
||||
@ -265,6 +266,8 @@ export class DockviewGroupPanelModel
|
||||
|
||||
private mostRecentlyUsed: IDockviewPanel[] = [];
|
||||
private _overwriteRenderContainer: OverlayRenderContainer | null = null;
|
||||
private _overwriteDropTargetContainer: DropTargetAnchorContainer | null =
|
||||
null;
|
||||
|
||||
private readonly _onDidChange = new Emitter<IViewSize | undefined>();
|
||||
readonly onDidChange: Event<IViewSize | undefined> =
|
||||
@ -535,6 +538,17 @@ export class DockviewGroupPanelModel
|
||||
);
|
||||
}
|
||||
|
||||
set dropTargetContainer(value: DropTargetAnchorContainer | null) {
|
||||
this._overwriteDropTargetContainer = value;
|
||||
}
|
||||
|
||||
get dropTargetContainer(): DropTargetAnchorContainer | null {
|
||||
return (
|
||||
this._overwriteDropTargetContainer ??
|
||||
this.accessor.rootDropTargetContainer
|
||||
);
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
if (this.options.panels) {
|
||||
this.options.panels.forEach((panel) => {
|
||||
@ -1049,6 +1063,29 @@ export class DockviewGroupPanelModel
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
if (type === 'content') {
|
||||
if (data.groupId === this.id) {
|
||||
// don't allow to drop on self for center position
|
||||
|
||||
if (position === 'center') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.panelId === null) {
|
||||
// don't allow group move to drop anywhere on self
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'header') {
|
||||
if (data.groupId === this.id) {
|
||||
if (data.panelId === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.panelId === null) {
|
||||
// this is a group move dnd event
|
||||
const { groupId } = data;
|
||||
|
@ -17,6 +17,7 @@ import { IGroupHeaderProps } from './framework';
|
||||
import { FloatingGroupOptions } from './dockviewComponent';
|
||||
import { Contraints } from '../gridview/gridviewPanel';
|
||||
import { AcceptableEvent, IAcceptableEvent } from '../events';
|
||||
import { DockviewTheme } from './theme';
|
||||
|
||||
export interface IHeaderActionsRenderer extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
@ -51,19 +52,26 @@ export interface DockviewOptions {
|
||||
};
|
||||
popoutUrl?: string;
|
||||
defaultRenderer?: DockviewPanelRenderer;
|
||||
debug?: boolean;
|
||||
rootOverlayModel?: DroptargetOverlayModel;
|
||||
locked?: boolean;
|
||||
disableDnd?: boolean;
|
||||
className?: string;
|
||||
/**
|
||||
* Pixel gap between groups
|
||||
* @deprecated dockview: dockviewComponent.gap has been deprecated. Use `theme` instead. This will be removed in a future version.
|
||||
*/
|
||||
gap?: number;
|
||||
debug?: boolean;
|
||||
// #start dnd
|
||||
dndEdges?: false | DroptargetOverlayModel;
|
||||
/**
|
||||
* @deprecated use `dndEdges` instead. To be removed in a future version.
|
||||
* */
|
||||
rootOverlayModel?: DroptargetOverlayModel;
|
||||
disableDnd?: boolean;
|
||||
// #end dnd
|
||||
locked?: boolean;
|
||||
className?: string;
|
||||
/**
|
||||
* Define the behaviour of the dock when there are no panels to display. Defaults to `watermark`.
|
||||
*/
|
||||
noPanelsOverlay?: 'emptyGroup' | 'watermark';
|
||||
theme?: DockviewTheme;
|
||||
}
|
||||
|
||||
export interface DockviewDndOverlayEvent extends IAcceptableEvent {
|
||||
@ -106,9 +114,11 @@ export const PROPERTY_KEYS_DOCKVIEW: (keyof DockviewOptions)[] = (() => {
|
||||
rootOverlayModel: undefined,
|
||||
locked: undefined,
|
||||
disableDnd: undefined,
|
||||
gap: undefined,
|
||||
className: undefined,
|
||||
noPanelsOverlay: undefined,
|
||||
dndEdges: undefined,
|
||||
theme: undefined,
|
||||
gap: undefined,
|
||||
};
|
||||
|
||||
return Object.keys(properties) as (keyof DockviewOptions)[];
|
||||
|
54
packages/dockview-core/src/dockview/theme.ts
Normal file
54
packages/dockview-core/src/dockview/theme.ts
Normal file
@ -0,0 +1,54 @@
|
||||
export interface DockviewTheme {
|
||||
name: string;
|
||||
className: string;
|
||||
gap?: number;
|
||||
dndOverlayMounting?: 'absolute' | 'relative';
|
||||
includeHeaderWhenHoverOverContent?: boolean;
|
||||
}
|
||||
|
||||
export const themeDark: DockviewTheme = {
|
||||
name: 'dark',
|
||||
className: 'dockview-theme-dark',
|
||||
};
|
||||
|
||||
export const themeLight: DockviewTheme = {
|
||||
name: 'light',
|
||||
className: 'dockview-theme-light',
|
||||
};
|
||||
|
||||
export const themeVisualStudio: DockviewTheme = {
|
||||
name: 'visualStudio',
|
||||
className: 'dockview-theme-vs',
|
||||
};
|
||||
|
||||
export const themeAbyss: DockviewTheme = {
|
||||
name: 'abyss',
|
||||
className: 'dockview-theme-abyss',
|
||||
};
|
||||
|
||||
export const themeDracula: DockviewTheme = {
|
||||
name: 'dracula',
|
||||
className: 'dockview-theme-dracula',
|
||||
};
|
||||
|
||||
export const themeReplit: DockviewTheme = {
|
||||
name: 'replit',
|
||||
className: 'dockview-theme-replit',
|
||||
gap: 10,
|
||||
};
|
||||
|
||||
export const themeAbyssSpaced: DockviewTheme = {
|
||||
name: 'abyssSpaced',
|
||||
className: 'dockview-theme-abyss-spaced',
|
||||
gap: 10,
|
||||
dndOverlayMounting: 'absolute',
|
||||
includeHeaderWhenHoverOverContent: true,
|
||||
};
|
||||
|
||||
export const themeLightSpaced: DockviewTheme = {
|
||||
name: 'lightSpaced',
|
||||
className: 'dockview-theme-light-spaced',
|
||||
gap: 10,
|
||||
dndOverlayMounting: 'absolute',
|
||||
includeHeaderWhenHoverOverContent: true,
|
||||
};
|
@ -64,6 +64,7 @@ export {
|
||||
} from './dockview/framework';
|
||||
|
||||
export * from './dockview/options';
|
||||
export * from './dockview/theme';
|
||||
export * from './dockview/dockviewPanel';
|
||||
export { DefaultTab } from './dockview/components/tab/defaultTab';
|
||||
export {
|
||||
|
@ -116,6 +116,7 @@
|
||||
-moz-user-select: none; // Firefox
|
||||
-ms-user-select: none; // IE 10 and IE 11
|
||||
touch-action: none;
|
||||
background-color: var(--dv-sash-color, transparent);
|
||||
|
||||
&:not(.disabled):active {
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
|
@ -219,6 +219,8 @@ export class Splitview {
|
||||
|
||||
set margin(value: number) {
|
||||
this._margin = value;
|
||||
|
||||
toggleClass(this.element, 'dv-splitview-has-margin', value !== 0);
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
@ -1,17 +1,29 @@
|
||||
@import 'theme/_sash-handle-mixin';
|
||||
@import 'theme/_drop-target-static-mixin';
|
||||
@import 'theme/_space-mixin';
|
||||
|
||||
@mixin dockview-theme-core-mixin {
|
||||
--dv-paneview-active-outline-color: dodgerblue;
|
||||
--dv-tabs-and-actions-container-font-size: 13px;
|
||||
--dv-tabs-and-actions-container-height: 35px;
|
||||
--dv-drag-over-background-color: rgba(83, 89, 93, 0.5);
|
||||
--dv-drag-over-border-color: white;
|
||||
--dv-drag-over-border-color: transparent;
|
||||
--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);
|
||||
--dv-overlay-z-index: 999;
|
||||
//
|
||||
|
||||
--dv-tab-font-size: inherit;
|
||||
--dv-border-radius: 0px;
|
||||
--dv-tab-margin: 0;
|
||||
--dv-sash-color: transparent;
|
||||
--dv-active-sash-color: transparent;
|
||||
}
|
||||
|
||||
@mixin dockview-theme-dark-mixin {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #1e1e1e;
|
||||
@ -35,6 +47,8 @@
|
||||
|
||||
@mixin dockview-theme-light-mixin {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: white;
|
||||
//
|
||||
@ -131,30 +145,49 @@
|
||||
|
||||
@mixin dockview-theme-abyss-mixin {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
--dv-color-abyss-dark: #000c18;
|
||||
--dv-color-abyss: #10192c;
|
||||
--dv-color-abyss-light: #1c1c2a;
|
||||
--dv-color-abyss-lighter: #2b2b4a;
|
||||
--dv-color-abyss-accent: rgb(91, 30, 207);
|
||||
|
||||
--dv-color-abyss-primary-text: white;
|
||||
--dv-color-abyss-secondary-text: rgb(148, 151, 169);
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #000c18;
|
||||
--dv-group-view-background-color: var(--dv-color-abyss-dark);
|
||||
//
|
||||
--dv-tabs-and-actions-container-background-color: #1c1c2a;
|
||||
--dv-tabs-and-actions-container-background-color: var(
|
||||
--dv-color-abyss-light
|
||||
);
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-background-color: #000c18;
|
||||
--dv-activegroup-hiddenpanel-tab-background-color: #10192c;
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color: #000c18;
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color: #10192c;
|
||||
--dv-tab-divider-color: #2b2b4a;
|
||||
--dv-activegroup-visiblepanel-tab-background-color: var(
|
||||
--dv-color-abyss-dark
|
||||
);
|
||||
--dv-activegroup-hiddenpanel-tab-background-color: var(--dv-color-abyss);
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color: var(
|
||||
--dv-color-abyss-dark
|
||||
);
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color: var(--dv-color-abyss);
|
||||
--dv-tab-divider-color: var(--dv-color-abyss-lighter);
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-color: white;
|
||||
--dv-activegroup-hiddenpanel-tab-color: rgba(255, 255, 255, 0.5);
|
||||
--dv-inactivegroup-visiblepanel-tab-color: rgba(255, 255, 255, 0.5);
|
||||
--dv-inactivegroup-hiddenpanel-tab-color: rgba(255, 255, 255, 0.25);
|
||||
//
|
||||
--dv-separator-border: #2b2b4a;
|
||||
--dv-paneview-header-border-color: #2b2b4a;
|
||||
--dv-separator-border: var(--dv-color-abyss-lighter);
|
||||
--dv-paneview-header-border-color: var(--dv-color-abyss-lighter);
|
||||
|
||||
--dv-paneview-active-outline-color: #596f99;
|
||||
}
|
||||
|
||||
@mixin dockview-theme-dracula-mixin {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #282a36;
|
||||
//
|
||||
@ -229,10 +262,17 @@
|
||||
}
|
||||
|
||||
@mixin dockview-design-replit-mixin {
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
.dv-resize-container:has(> .dv-groupview) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dv-resize-container {
|
||||
border-radius: 10px !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dv-groupview {
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
@ -266,59 +306,16 @@
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-vertical > .dv-sash-container > .dv-sash {
|
||||
&:not(.disabled) {
|
||||
&::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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dv-horizontal > .dv-sash-container > .dv-sash {
|
||||
&:not(.disabled) {
|
||||
&::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();
|
||||
@include dockview-design-handle-mixin();
|
||||
|
||||
padding: 10px;
|
||||
background-color: #ebeced;
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #ebeced;
|
||||
//
|
||||
@ -339,6 +336,115 @@
|
||||
--dv-paneview-header-border-color: rgb(51, 51, 51);
|
||||
|
||||
/////
|
||||
--dv-separator-handle-background-color: #cfd1d3;
|
||||
--dv-separator-handle-hover-background-color: #babbbb;
|
||||
--dv-sash-color: #cfd1d3;
|
||||
--dv-active-sash-color: #babbbb;
|
||||
}
|
||||
|
||||
.dockview-theme-abyss-spaced {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-design-space-mixin();
|
||||
|
||||
// stylesheet
|
||||
--dv-color-abyss-dark: rgb(11, 6, 17);
|
||||
--dv-color-abyss: #16121f;
|
||||
--dv-color-abyss-light: #201d2b;
|
||||
--dv-color-abyss-lighter: #2a2837;
|
||||
--dv-color-abyss-accent: rgb(91, 30, 207);
|
||||
--dv-color-abyss-primary-text: white;
|
||||
--dv-color-abyss-secondary-text: rgb(148, 151, 169);
|
||||
|
||||
//
|
||||
--dv-drag-over-border: 2px solid var(--dv-color-abyss-accent);
|
||||
--dv-drag-over-background-color: '';
|
||||
//
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: var(--dv-color-abyss-dark);
|
||||
//
|
||||
--dv-tabs-and-actions-container-background-color: var(--dv-color-abyss);
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-background-color: var(
|
||||
--dv-color-abyss-lighter
|
||||
);
|
||||
--dv-activegroup-hiddenpanel-tab-background-color: var(
|
||||
--dv-color-abyss-light
|
||||
);
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color: var(
|
||||
--dv-color-abyss-lighter
|
||||
);
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color: var(
|
||||
--dv-color-abyss-light
|
||||
);
|
||||
--dv-tab-divider-color: transparent;
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-color: var(--dv-color-abyss-primary-text);
|
||||
--dv-activegroup-hiddenpanel-tab-color: var(
|
||||
--dv-color-abyss-secondary-text
|
||||
);
|
||||
--dv-inactivegroup-visiblepanel-tab-color: var(
|
||||
--dv-color-abyss-primary-text
|
||||
);
|
||||
--dv-inactivegroup-hiddenpanel-tab-color: var(
|
||||
--dv-color-abyss-secondary-text
|
||||
);
|
||||
//
|
||||
--dv-separator-border: transparent;
|
||||
--dv-paneview-header-border-color: rgb(51, 51, 51);
|
||||
|
||||
/////
|
||||
--dv-active-sash-color: var(--dv-color-abyss-accent);
|
||||
//
|
||||
--dv-floating-box-shadow: 8px 8px 8px 0px rgba(0, 0, 0, 0.5);
|
||||
|
||||
padding: 10px;
|
||||
background-color: var(--dv-color-abyss-dark);
|
||||
|
||||
.dv-resize-container {
|
||||
.dv-groupview {
|
||||
border: 2px solid var(--dv-color-abyss-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dockview-theme-light-spaced {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-design-space-mixin();
|
||||
|
||||
//
|
||||
--dv-drag-over-border: 2px solid rgb(91, 30, 207);
|
||||
--dv-drag-over-background-color: '';
|
||||
//
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #f6f5f9;
|
||||
//
|
||||
--dv-tabs-and-actions-container-background-color: white;
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-background-color: #ededf0;
|
||||
--dv-activegroup-hiddenpanel-tab-background-color: #f9f9fa;
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color: #ededf0;
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color: #f9f9fa;
|
||||
--dv-tab-divider-color: transparent;
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-color: rgb(104, 107, 130);
|
||||
--dv-activegroup-hiddenpanel-tab-color: rgb(148, 151, 169);
|
||||
--dv-inactivegroup-visiblepanel-tab-color: rgb(104, 107, 130);
|
||||
--dv-inactivegroup-hiddenpanel-tab-color: rgb(148, 151, 169);
|
||||
//
|
||||
--dv-separator-border: transparent;
|
||||
--dv-paneview-header-border-color: rgb(51, 51, 51);
|
||||
|
||||
/////
|
||||
--dv-active-sash-color: rgb(91, 30, 207);
|
||||
//
|
||||
--dv-floating-box-shadow: 8px 8px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 10px;
|
||||
background-color: #f6f5f9;
|
||||
|
||||
.dv-resize-container {
|
||||
.dv-groupview {
|
||||
border: 2px solid rgb(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
@mixin dockview-drop-target-no-travel {
|
||||
.dv-drop-target-container {
|
||||
.dv-drop-target-anchor {
|
||||
&.dv-drop-target-anchor-container-changed {
|
||||
opacity: 0;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
packages/dockview-core/src/theme/_sash-handle-mixin.scss
Normal file
53
packages/dockview-core/src/theme/_sash-handle-mixin.scss
Normal file
@ -0,0 +1,53 @@
|
||||
@mixin dockview-design-handle-mixin {
|
||||
.dv-vertical > .dv-sash-container > .dv-sash {
|
||||
background-color: transparent;
|
||||
|
||||
&:not(.disabled) {
|
||||
&::after {
|
||||
content: '';
|
||||
height: 4px;
|
||||
width: 40px;
|
||||
border-radius: 2px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--dv-sash-color);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: transparent;
|
||||
&::after {
|
||||
background-color: var(--dv-active-sash-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dv-horizontal > .dv-sash-container > .dv-sash {
|
||||
background-color: transparent;
|
||||
|
||||
&:not(.disabled) {
|
||||
&::after {
|
||||
content: '';
|
||||
height: 40px;
|
||||
width: 4px;
|
||||
border-radius: 2px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--dv-sash-color);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: transparent;
|
||||
&::after {
|
||||
background-color: var(--dv-active-sash-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
packages/dockview-core/src/theme/_space-mixin.scss
Normal file
52
packages/dockview-core/src/theme/_space-mixin.scss
Normal file
@ -0,0 +1,52 @@
|
||||
@mixin dockview-design-space-mixin {
|
||||
--dv-tab-font-size: 12px;
|
||||
--dv-border-radius: 20px;
|
||||
--dv-tab-margin: 0.5rem 0.25rem;
|
||||
--dv-tabs-and-actions-container-height: 44px;
|
||||
|
||||
|
||||
--dv-border-radius
|
||||
|
||||
.dv-resize-container:has(> .dv-groupview) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dv-sash {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dv-drop-target-anchor {
|
||||
border-radius: calc(var(--dv-border-radius) / 4);
|
||||
&.dv-drop-target-content {
|
||||
border-radius: var(--dv-border-radius);
|
||||
}
|
||||
}
|
||||
|
||||
.dv-resize-container {
|
||||
border-radius: var(--dv-border-radius) !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dv-groupview {
|
||||
border-radius: var(--dv-border-radius);
|
||||
|
||||
.dv-tabs-and-actions-container {
|
||||
padding: 0px calc(var(--dv-border-radius) / 2);
|
||||
|
||||
.dv-tab {
|
||||
border-radius: 8px;
|
||||
|
||||
.dv-svg {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dv-content-container {
|
||||
background-color: var(
|
||||
--dv-tabs-and-actions-container-background-color
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ export const CloseButton = () => (
|
||||
viewBox="0 0 28 28"
|
||||
aria-hidden={'false'}
|
||||
focusable={false}
|
||||
className="dockview-svg"
|
||||
className="dv-svg"
|
||||
>
|
||||
<path d="M2.1 27.3L0 25.2L11.55 13.65L0 2.1L2.1 0L13.65 11.55L25.2 0L27.3 2.1L15.75 13.65L27.3 25.2L25.2 27.3L13.65 15.75L2.1 27.3Z"></path>
|
||||
</svg>
|
||||
@ -21,7 +21,7 @@ export const ExpandMore = () => {
|
||||
viewBox="0 0 24 15"
|
||||
aria-hidden={'false'}
|
||||
focusable={false}
|
||||
className="dockview-svg"
|
||||
className="dv-svg"
|
||||
>
|
||||
<path d="M12 14.15L0 2.15L2.15 0L12 9.9L21.85 0.0499992L24 2.2L12 14.15Z" />
|
||||
</svg>
|
||||
|
@ -7,7 +7,9 @@ title: Theme
|
||||
|
||||
import { CSSVariablesTable, ThemeTable } from '@site/src/components/cssVariables';
|
||||
|
||||
Theming is controlled through CSS and is highly customizable.
|
||||
|
||||
Dockview components accept a `theme` property which is highly customizable, the theme is largly controlled through CSS however some properties can only be adjusted
|
||||
by direct editing variables of the `theme` object.
|
||||
|
||||
Firstly, you should import `dockview.css`:
|
||||
|
||||
@ -38,7 +40,7 @@ To use a `dockview` theme the CSS must encapsulate the component. The current li
|
||||
<ThemeTable/>
|
||||
|
||||
:::info
|
||||
The source code for all themes can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss).
|
||||
The source code for all themes can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss) and the associated CSS [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss).
|
||||
:::
|
||||
|
||||
## Customizing Theme
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
&:hover {
|
||||
border-radius: 2px;
|
||||
color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||
background-color: var(--dv-icon-hover-background-color);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
IDockviewPanelHeaderProps,
|
||||
IDockviewPanelProps,
|
||||
DockviewApi,
|
||||
DockviewTheme,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import './app.scss';
|
||||
@ -80,6 +81,7 @@ const components = {
|
||||
);
|
||||
},
|
||||
nested: (props: IDockviewPanelProps) => {
|
||||
const theme = React.useContext(ThemeContext);
|
||||
return (
|
||||
<DockviewReact
|
||||
components={components}
|
||||
@ -95,7 +97,7 @@ const components = {
|
||||
console.log('remove', e);
|
||||
});
|
||||
}}
|
||||
className={'dockview-theme-abyss'}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@ -141,7 +143,9 @@ const WatermarkComponent = () => {
|
||||
return <div>custom watermark</div>;
|
||||
};
|
||||
|
||||
const DockviewDemo = (props: { theme?: string }) => {
|
||||
const ThemeContext = React.createContext<DockviewTheme | undefined>(undefined);
|
||||
|
||||
const DockviewDemo = (props: { theme?: DockviewTheme }) => {
|
||||
const [logLines, setLogLines] = React.useState<
|
||||
{ text: string; timestamp?: Date; backgroundColor?: string }[]
|
||||
>([]);
|
||||
@ -380,18 +384,22 @@ const DockviewDemo = (props: { theme?: string }) => {
|
||||
}}
|
||||
>
|
||||
<DebugContext.Provider value={debug}>
|
||||
<DockviewReact
|
||||
components={components}
|
||||
defaultTabComponent={headerComponents.default}
|
||||
rightHeaderActionsComponent={RightControls}
|
||||
leftHeaderActionsComponent={LeftControls}
|
||||
prefixHeaderActionsComponent={PrefixHeaderControls}
|
||||
watermarkComponent={
|
||||
watermark ? WatermarkComponent : undefined
|
||||
}
|
||||
onReady={onReady}
|
||||
className={props.theme || 'dockview-theme-abyss'}
|
||||
/>
|
||||
<ThemeContext.Provider value={props.theme}>
|
||||
<DockviewReact
|
||||
components={components}
|
||||
defaultTabComponent={headerComponents.default}
|
||||
rightHeaderActionsComponent={RightControls}
|
||||
leftHeaderActionsComponent={LeftControls}
|
||||
prefixHeaderActionsComponent={
|
||||
PrefixHeaderControls
|
||||
}
|
||||
watermarkComponent={
|
||||
watermark ? WatermarkComponent : undefined
|
||||
}
|
||||
onReady={onReady}
|
||||
theme={props.theme}
|
||||
/>
|
||||
</ThemeContext.Provider>
|
||||
</DebugContext.Provider>
|
||||
</div>
|
||||
|
||||
|
@ -81,7 +81,7 @@ export const RightControls = (props: IDockviewHeaderActionsProps) => {
|
||||
alignItems: 'center',
|
||||
padding: '0px 8px',
|
||||
height: '100%',
|
||||
color: 'var(--dv-activegroup-visiblepanel-tab-color)',
|
||||
color: 'var(--dv-activegroup-hiddenpanel-tab-color)',
|
||||
}}
|
||||
>
|
||||
{props.isGroupActive && <Icon icon="star" />}
|
||||
|
@ -151,11 +151,20 @@ export const GridActions = (props: {
|
||||
props.api?.addGroup();
|
||||
};
|
||||
|
||||
const [gap, setGap] = React.useState(0);
|
||||
// const [gap, setGap] = React.useState<number | undefined>(undefined);
|
||||
|
||||
React.useEffect(() => {
|
||||
props.api?.setGap(gap);
|
||||
}, [gap, props.api]);
|
||||
const [overlayMode, setOverlayMode] = React.useState<boolean>(false);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (!props.api) {
|
||||
// return;
|
||||
// }
|
||||
// if (typeof gap === 'number') {
|
||||
// props.api.setGap(gap);
|
||||
// } else {
|
||||
// setGap(props.api.gap);
|
||||
// }
|
||||
// }, [gap, props.api]);
|
||||
|
||||
return (
|
||||
<div className="action-container">
|
||||
@ -191,6 +200,23 @@ export const GridActions = (props: {
|
||||
Use Custom Watermark
|
||||
</button>
|
||||
</span>
|
||||
{/* <span className="button-action">
|
||||
<button
|
||||
className={
|
||||
overlayMode ? 'demo-button selected' : 'demo-button'
|
||||
}
|
||||
onClick={() => {
|
||||
props.api?.updateOptions({
|
||||
dndOverlayMode: !overlayMode
|
||||
? 'static'
|
||||
: 'transitional',
|
||||
});
|
||||
setOverlayMode(!overlayMode);
|
||||
}}
|
||||
>
|
||||
Use static overlay
|
||||
</button>
|
||||
</span> */}
|
||||
<button className="text-button" onClick={onClear}>
|
||||
Clear
|
||||
</button>
|
||||
@ -204,7 +230,7 @@ export const GridActions = (props: {
|
||||
Reset
|
||||
</button>
|
||||
<span style={{ flexGrow: 1 }} />
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{/* <div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span style={{ paddingRight: '4px' }}>Grid Gap</span>
|
||||
<input
|
||||
style={{ width: 40 }}
|
||||
@ -212,11 +238,11 @@ export const GridActions = (props: {
|
||||
min={0}
|
||||
max={99}
|
||||
step={1}
|
||||
value={gap}
|
||||
value={gap ?? 0}
|
||||
onChange={(event) => setGap(Number(event.target.value))}
|
||||
/>
|
||||
<button onClick={() => setGap(0)}>Reset</button>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,13 +1,16 @@
|
||||
|
||||
.DropdownMenuContent {
|
||||
/* min-width: 220px; */
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
background-color: var(--ifm-dropdown-background-color);
|
||||
color: var(--ifm-color-primary);
|
||||
border: var(--ifm-dropdown-border);
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2);
|
||||
animation-duration: 400ms;
|
||||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
will-change: transform, opacity;
|
||||
z-index: 99;
|
||||
}
|
||||
.DropdownMenuContent[data-side='top'],
|
||||
.DropdownMenuSubContent[data-side='top'] {
|
||||
@ -39,25 +42,33 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100px;
|
||||
height: 25px;
|
||||
width: 120px;
|
||||
/* height: 25px; */
|
||||
padding: 4px 8px;
|
||||
font-size: 13px;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
|
||||
color: var(--ifm-menu-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ifm-hover-overlay);
|
||||
}
|
||||
}
|
||||
|
||||
.framework-menu-item-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 120px;
|
||||
width: 130px;
|
||||
height: 35px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgba(0,0,0, 0.1);
|
||||
|
||||
border: 1px solid rgba(60, 60, 66,0.5);
|
||||
}
|
||||
|
||||
@keyframes slideUpAndFade {
|
||||
|
@ -1,6 +1,8 @@
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
import { DockviewEmitter } from 'dockview';
|
||||
import * as React from 'react';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import './frameworkSpecific.css';
|
||||
|
||||
export interface FrameworkDescriptor {
|
||||
@ -51,8 +53,7 @@ export function useActiveFramework(): [
|
||||
return [option, setter];
|
||||
}
|
||||
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
|
||||
const FrameworkSelector1 = () => {
|
||||
const [activeFramework, setActiveFramework] = useActiveFramework();
|
||||
|
@ -28,7 +28,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dockview-svg {
|
||||
.dv-svg {
|
||||
display: inline-block;
|
||||
fill: currentcolor;
|
||||
line-height: 1;
|
||||
|
@ -17,7 +17,7 @@ const createSvgElementFromPath = (params: {
|
||||
width={params.width}
|
||||
viewBox={params.viewbox}
|
||||
focusable={false}
|
||||
className={'dockview-svg'}
|
||||
className={'dv-svg'}
|
||||
>
|
||||
<path d={params.path} />
|
||||
</svg>
|
||||
@ -54,7 +54,7 @@ export const CodeSandboxButton = (props: {
|
||||
<a
|
||||
href={url}
|
||||
target={'_blank'}
|
||||
rel='noopener'
|
||||
rel="noopener"
|
||||
className="codesandbox-button-content"
|
||||
>
|
||||
<span
|
||||
|
@ -57,7 +57,7 @@ export const Container = (props: {
|
||||
const ReactIcon = (props: { height: number; width: number }) => {
|
||||
return (
|
||||
<img
|
||||
// className="dockview-svg"
|
||||
// className="dv-svg"
|
||||
style={{ marginRight: '0px 4px' }}
|
||||
height={props.height}
|
||||
width={props.width}
|
||||
@ -69,7 +69,7 @@ const ReactIcon = (props: { height: number; width: number }) => {
|
||||
const JavascriptIcon = (props: { height: number; width: number }) => {
|
||||
return (
|
||||
<img
|
||||
// className="dockview-svg "
|
||||
// className="dv-svg "
|
||||
style={{ marginRight: '0px 4px' }}
|
||||
height={props.height}
|
||||
width={props.width}
|
||||
@ -85,6 +85,7 @@ const themes = [
|
||||
'dockview-theme-vs',
|
||||
'dockview-theme-dracula',
|
||||
'dockview-theme-replit',
|
||||
'dockview-theme-kraken',
|
||||
];
|
||||
|
||||
function useLocalStorageItem(key: string, defaultValue: string): string {
|
||||
@ -122,7 +123,6 @@ export const ThemePicker = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
|
@ -1,10 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { CodeSandboxButton } from './codeSandboxButton';
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
import { DockviewTheme } from 'dockview';
|
||||
|
||||
const ExampleFrame = (props: {
|
||||
framework: string;
|
||||
theme?: string;
|
||||
theme?: DockviewTheme;
|
||||
id: string;
|
||||
height?: string;
|
||||
}) => {
|
||||
|
@ -1,33 +1,54 @@
|
||||
import {
|
||||
themeAbyss,
|
||||
themeDark,
|
||||
themeDracula,
|
||||
themeAbyssSpaced,
|
||||
themeLightSpaced,
|
||||
themeLight,
|
||||
themeReplit,
|
||||
themeVisualStudio,
|
||||
} from 'dockview';
|
||||
|
||||
export const themeConfig = [
|
||||
{
|
||||
id: 'dockview-theme-dark',
|
||||
id: themeDark,
|
||||
key: '**[dockview-theme-dark](/demo?theme=dockview-theme-dark)**',
|
||||
text: '',
|
||||
},
|
||||
{
|
||||
id: 'dockview-theme-light',
|
||||
id: themeLight,
|
||||
key: '**[dockview-theme-light](/demo?theme=dockview-theme-light)**',
|
||||
text: '',
|
||||
},
|
||||
{
|
||||
id: 'dockview-theme-vs',
|
||||
id: themeVisualStudio,
|
||||
key: '**[dockview-theme-vs](/demo?theme=dockview-theme-vs)**',
|
||||
|
||||
text: 'Based on [Visual Studio](https://visualstudio.microsoft.com)',
|
||||
},
|
||||
{
|
||||
id: 'dockview-theme-abyss',
|
||||
id: themeAbyss,
|
||||
key: '**[dockview-theme-abyss](/demo?theme=dockview-theme-abyss)**',
|
||||
text: 'Based on [Visual Studio Code](https://code.visualstudio.com/docs/getstarted/themes) abyss theme',
|
||||
},
|
||||
{
|
||||
id: 'dockview-theme-dracula',
|
||||
id: themeDracula,
|
||||
key: '**[dockview-theme-dracula](/demo?theme=dockview-theme-dracula)**',
|
||||
text: 'Based on [Visual Studio Code](https://code.visualstudio.com/docs/getstarted/themes) dracula theme',
|
||||
},
|
||||
{
|
||||
id: 'dockview-theme-replit',
|
||||
id: themeReplit,
|
||||
key: '**[dockview-theme-replit](/demo?theme=dockview-theme-replit)**',
|
||||
text: 'Based on [Replit](https://replit.com)',
|
||||
},
|
||||
{
|
||||
id: themeLightSpaced,
|
||||
key: '**[dockview-theme-replit](/demo?theme=dockview-theme-kraken)**',
|
||||
text: '',
|
||||
},
|
||||
{
|
||||
id: themeAbyssSpaced,
|
||||
key: '**[dockview-theme-replit](/demo?theme=dockview-theme-kraken)**',
|
||||
text: '',
|
||||
},
|
||||
];
|
||||
|
@ -11,10 +11,10 @@
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-font-family-base: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system,
|
||||
BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans,
|
||||
sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
|
||||
Noto Color Emoji;
|
||||
--ifm-font-family-base: 'IBM Plex Sans', ui-sans-serif, system-ui,
|
||||
-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue,
|
||||
Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji,
|
||||
Segoe UI Symbol, Noto Color Emoji;
|
||||
|
||||
--ifm-font-weight-bold: 600;
|
||||
|
||||
@ -36,6 +36,9 @@
|
||||
|
||||
--ifm-color-primary: black;
|
||||
|
||||
--ifm-dropdown-background-color: white;
|
||||
--ifm-dropdown-border: 1px solid var(--ifm-color-primary-darkest);
|
||||
|
||||
--ifm-navbar-link-color: white;
|
||||
--ifm-navbar-link-hover-color: white;
|
||||
|
||||
@ -54,15 +57,18 @@
|
||||
}
|
||||
|
||||
/* --ifm-color-primary: #0c111d; */
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-color-primary-dark: #21af90;
|
||||
--ifm-color-primary-darker: #1fa588;
|
||||
--ifm-color-primary-darkest: #1a8870;
|
||||
--ifm-color-primary-light: #29d5b0;
|
||||
--ifm-color-primary-lighter: #32d8b4;
|
||||
--ifm-color-primary-lightest: #4fddbf;
|
||||
--ifm-color-primary: #98a2b3;
|
||||
--ifm-color-primary-dark: #828a99;
|
||||
--ifm-color-primary-darker: #6a707c;
|
||||
--ifm-color-primary-darkest: #474b53;
|
||||
--ifm-color-primary-light: #acb7ca;
|
||||
--ifm-color-primary-lighter: #bcc9df;
|
||||
--ifm-color-primary-lightest: #d2e1fa;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
|
||||
--ifm-dropdown-background-color: #373d4b;
|
||||
--ifm-dropdown-border: 1px solid var(--ifm-color-primary-darkest);
|
||||
|
||||
--dv-docs-markdown-text-color: #cdced8;
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,29 @@ import Layout from '@theme/Layout';
|
||||
import { themeConfig } from '../config/theme.config';
|
||||
import ExampleFrame from '../components/ui/exampleFrame';
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
import { DockviewTheme, themeAbyss } from 'dockview';
|
||||
|
||||
const updateTheme = (theme: DockviewTheme) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
urlParams.set('theme', theme.name);
|
||||
|
||||
const newUrl = window.location.pathname + '?' + urlParams.toString();
|
||||
|
||||
window.history.pushState({ path: newUrl }, '', newUrl);
|
||||
};
|
||||
|
||||
const ThemeToggle: React.FC = () => {
|
||||
const [theme, setTheme] = React.useState<string>(
|
||||
new URLSearchParams(location.search).get('theme') ?? themeConfig[3].id
|
||||
);
|
||||
const [theme, setTheme] = React.useState<DockviewTheme>(themeAbyss);
|
||||
|
||||
React.useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const themeName = urlParams.get('theme');
|
||||
const newTheme =
|
||||
themeConfig.find((c) => c.id.name === themeName)?.id ?? themeAbyss;
|
||||
setTheme(newTheme);
|
||||
updateTheme(newTheme);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -16,20 +34,48 @@ const ThemeToggle: React.FC = () => {
|
||||
height: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0px 15px',
|
||||
}}
|
||||
>
|
||||
<select
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
paddingRight: 8,
|
||||
color: 'var(--ifm-color-primary)',
|
||||
fontSize: '0.9em',
|
||||
}}
|
||||
>
|
||||
{'Theme: '}
|
||||
</div>
|
||||
<ThemeSelector
|
||||
value={theme.name}
|
||||
options={themeConfig.map((theme) => theme.id.name)}
|
||||
onChanged={(value) => {
|
||||
const theme =
|
||||
themeConfig.find(
|
||||
(theme) => theme.id.name === value
|
||||
)?.id ?? themeAbyss;
|
||||
setTheme(theme);
|
||||
updateTheme(theme);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* <select
|
||||
onChange={(event) => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('theme', event.target.value);
|
||||
window.location.href = url.toString();
|
||||
const theme = themeConfig.find(
|
||||
(theme) => theme.id.name === event.target.value
|
||||
).id;
|
||||
setTheme(theme);
|
||||
updateTheme(theme);
|
||||
}}
|
||||
value={theme}
|
||||
value={theme.name}
|
||||
>
|
||||
{themeConfig.map((theme) => {
|
||||
return <option key={theme.id}>{theme.id}</option>;
|
||||
return (
|
||||
<option key={theme.id.name}>{theme.id.name}</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</select> */}
|
||||
</div>
|
||||
<ExampleFrame
|
||||
theme={theme}
|
||||
@ -58,3 +104,72 @@ export default function Popout() {
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from '@radix-ui/react-dropdown-menu';
|
||||
|
||||
const ThemeSelector = (props: {
|
||||
options: string[];
|
||||
value: string;
|
||||
onChanged: (value: string) => void;
|
||||
}) => {
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<DropdownMenu
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const el = ref.current!.querySelector(
|
||||
`[data-dropdown-menu-value="${props.value}"]`
|
||||
);
|
||||
if (el) {
|
||||
(el as HTMLElement).focus();
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DropdownMenuTrigger asChild={true}>
|
||||
<div className="framework-menu-item-select">
|
||||
{props.value}
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="bottom"
|
||||
align="end"
|
||||
sideOffset={10}
|
||||
className="DropdownMenuContent"
|
||||
>
|
||||
{props.options.map((option) => {
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
data-dropdown-menu-value={option}
|
||||
onClick={() => props.onChanged(option)}
|
||||
className="DropdownMenuItem"
|
||||
>
|
||||
<div className="framework-menu-item">
|
||||
<span>{option}</span>
|
||||
<span>
|
||||
{option === props.value ? '✓' : ''}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user