mirror of
https://github.com/mathuo/dockview
synced 2025-05-01 17:18:27 +00:00
Merge branch 'master' of https://github.com/mathuo/dockview into master
This commit is contained in:
commit
cfb1b5c38b
@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "0.0.25",
|
||||
"version": "0.0.26",
|
||||
"command": {
|
||||
"publish": {
|
||||
"message": "chore(release): publish %s"
|
||||
|
2
packages/dockview-demo/package-lock.json
generated
2
packages/dockview-demo/package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-demo",
|
||||
"version": "0.0.25",
|
||||
"version": "0.0.26",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "dockview-demo",
|
||||
"private": true,
|
||||
"version": "0.0.25",
|
||||
"version": "0.0.26",
|
||||
"description": "Demo project for https://github.com/mathuo/dockview",
|
||||
"scripts": {
|
||||
"build": "npm run build-webpack && npm run build-storybook",
|
||||
@ -14,7 +14,7 @@
|
||||
"author": "https://github.com/mathuo",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dockview": "^0.0.25",
|
||||
"dockview": "^0.0.26",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"recoil": "^0.4.1"
|
||||
|
135
packages/dockview-demo/src/services/sidebarItem.tsx
Normal file
135
packages/dockview-demo/src/services/sidebarItem.tsx
Normal file
@ -0,0 +1,135 @@
|
||||
import { ViewContainer } from './viewContainer';
|
||||
import * as React from 'react';
|
||||
import { toggleClass } from '../dom';
|
||||
|
||||
export const Container = (props: {
|
||||
container: ViewContainer;
|
||||
isActive: boolean;
|
||||
onDragOver: (e: React.DragEvent) => void;
|
||||
onDrop: (e: React.DragEvent, direction: 'top' | 'bottom') => void;
|
||||
onClick: (e: React.MouseEvent) => void;
|
||||
}) => {
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const [selection, setSelection] = React.useState<
|
||||
'top' | 'bottom' | undefined
|
||||
>(undefined);
|
||||
const isDragging = React.useRef<boolean>(false);
|
||||
|
||||
const [dragEntered, setDragEntered] = React.useState<boolean>(false);
|
||||
|
||||
const onDragOver = (e: React.DragEvent) => {
|
||||
if (isDragging.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDragEntered(true);
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const target = e.target as HTMLDivElement;
|
||||
|
||||
const width = target.clientWidth;
|
||||
const height = target.clientHeight;
|
||||
|
||||
if (width === 0 || height === 0) {
|
||||
return; // avoid div!0
|
||||
}
|
||||
|
||||
const x = e.nativeEvent.offsetX;
|
||||
const y = e.nativeEvent.offsetY;
|
||||
const xp = (100 * x) / width;
|
||||
const yp = (100 * y) / height;
|
||||
|
||||
const isTop = yp < 50;
|
||||
const isBottom = yp >= 50;
|
||||
|
||||
setSelection(isTop ? 'top' : 'bottom');
|
||||
|
||||
props.onDragOver(e);
|
||||
};
|
||||
|
||||
const onDragLeave = (e: React.DragEvent) => {
|
||||
if (isDragging.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDragEntered(false);
|
||||
|
||||
setSelection(undefined);
|
||||
};
|
||||
|
||||
const onDrop = (e: React.DragEvent) => {
|
||||
if (isDragging.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDragEntered(false);
|
||||
|
||||
props.onDrop(e, selection);
|
||||
|
||||
setSelection(undefined);
|
||||
};
|
||||
|
||||
const onDragEnter = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const onDragStart = (e: React.DragEvent) => {
|
||||
isDragging.current = true;
|
||||
|
||||
e.dataTransfer.setData(
|
||||
'application/json',
|
||||
JSON.stringify({ container: props.container.id })
|
||||
);
|
||||
};
|
||||
|
||||
const onDragEnd = (e: React.DragEvent) => {
|
||||
isDragging.current = false;
|
||||
|
||||
setDragEntered(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
draggable={true}
|
||||
onClick={props.onClick}
|
||||
onDragOver={onDragOver}
|
||||
onDragEnter={onDragEnter}
|
||||
onDragStart={onDragStart}
|
||||
onDragLeave={onDragLeave}
|
||||
onDragEnd={onDragEnd}
|
||||
onDrop={onDrop}
|
||||
style={{
|
||||
borderLeft: props.isActive
|
||||
? '1px solid white'
|
||||
: '1px solid transparent',
|
||||
}}
|
||||
className="container-item"
|
||||
>
|
||||
{dragEntered && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
backgroundColor: 'transparent',
|
||||
boxSizing: 'border-box',
|
||||
borderTop: selection === 'top' ? '2px solid white' : '',
|
||||
borderBottom:
|
||||
selection === 'bottom' ? '2px solid white' : '',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
style={{ fontSize: '30px' }}
|
||||
className="material-icons-outlined"
|
||||
>
|
||||
{props.container.icon}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -45,7 +45,7 @@ export class PaneviewContainer implements ViewContainer<SerializedPaneview> {
|
||||
}
|
||||
|
||||
get views() {
|
||||
return this._views;
|
||||
return [...this._views];
|
||||
}
|
||||
|
||||
get schema(): SerializedPaneview | undefined {
|
||||
|
@ -34,6 +34,7 @@ export interface IViewService extends IDisposable {
|
||||
targetLocation: number
|
||||
): void;
|
||||
insertContainerAfter(source: ViewContainer, target: ViewContainer): void;
|
||||
insertContainerBefore(source: ViewContainer, target: ViewContainer): void;
|
||||
addViews(view: View, viewContainer: ViewContainer, location?: number): void;
|
||||
removeViews(removeViews: View[], viewContainer: ViewContainer): void;
|
||||
getViewContainer(id: string): ViewContainer | undefined;
|
||||
@ -103,6 +104,23 @@ export class ViewService implements IViewService {
|
||||
this._onDidContainersChange.fire();
|
||||
}
|
||||
|
||||
insertContainerBefore(source: ViewContainer, target: ViewContainer): void {
|
||||
const sourceIndex = this._viewContainers.findIndex(
|
||||
(c) => c.id === source.id
|
||||
);
|
||||
|
||||
const view = this._viewContainers.splice(sourceIndex, 1)[0];
|
||||
|
||||
const targetIndex = this._viewContainers.findIndex(
|
||||
(c) => c.id === target.id
|
||||
);
|
||||
|
||||
this._viewContainers.splice(Math.max(targetIndex, 0), 0, view);
|
||||
this._viewContainers = [...this._viewContainers];
|
||||
|
||||
this._onDidContainersChange.fire();
|
||||
}
|
||||
|
||||
addContainer(container: ViewContainer): void {
|
||||
this._viewContainers = [...this._viewContainers, container];
|
||||
this._activeViewContainerId = container.id;
|
||||
|
@ -3,3 +3,12 @@
|
||||
background-color: green !important;
|
||||
}
|
||||
}
|
||||
|
||||
.container-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import { IViewService, ViewService } from './viewService';
|
||||
import { DefaultView } from './view';
|
||||
import { RegisteredView, VIEW_REGISTRY } from './viewRegistry';
|
||||
import { toggleClass } from '../dom';
|
||||
import { Container } from './sidebarItem';
|
||||
import './widgets.scss';
|
||||
|
||||
class ViewServiceModel {
|
||||
@ -172,7 +173,8 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
};
|
||||
|
||||
const onContainerDrop = (targetContainer: ViewContainer) => (
|
||||
event: React.DragEvent
|
||||
event: React.DragEvent,
|
||||
direction: 'top' | 'bottom'
|
||||
) => {
|
||||
const data = event.dataTransfer.getData('application/json');
|
||||
if (data) {
|
||||
@ -180,10 +182,21 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
const sourceContainer = viewService.model.getViewContainer(
|
||||
container
|
||||
);
|
||||
viewService.model.insertContainerAfter(
|
||||
sourceContainer,
|
||||
targetContainer
|
||||
);
|
||||
|
||||
switch (direction) {
|
||||
case 'bottom':
|
||||
viewService.model.insertContainerAfter(
|
||||
sourceContainer,
|
||||
targetContainer
|
||||
);
|
||||
break;
|
||||
case 'top':
|
||||
viewService.model.insertContainerBefore(
|
||||
sourceContainer,
|
||||
targetContainer
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -204,48 +217,31 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onDragOver = (container: ViewContainer) => (e: React.DragEvent) => {
|
||||
const api = registry.get<GridviewApi>('gridview');
|
||||
|
||||
const sidebarPanel = api.getPanel('sidebar');
|
||||
if (!sidebarPanel.api.isVisible) {
|
||||
api.setVisible(sidebarPanel, true);
|
||||
sidebarPanel.focus();
|
||||
}
|
||||
|
||||
viewService.model.setActiveViewContainer(container.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ background: 'rgb(51,51,51)', cursor: 'pointer' }}>
|
||||
{containers.map((container, i) => {
|
||||
const isActive = activeContainerid === container.id;
|
||||
return (
|
||||
<div
|
||||
onClick={onClick(container)}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
onClick(container, true)(e);
|
||||
}}
|
||||
onDragEnter={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
draggable={true}
|
||||
onDragStart={(e) => {
|
||||
e.dataTransfer.setData(
|
||||
'application/json',
|
||||
JSON.stringify({ container: container.id })
|
||||
);
|
||||
}}
|
||||
onDrop={onContainerDrop(container)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '48px',
|
||||
boxSizing: 'border-box',
|
||||
borderLeft: isActive
|
||||
? '1px solid white'
|
||||
: '1px solid transparent',
|
||||
}}
|
||||
<Container
|
||||
key={i}
|
||||
>
|
||||
{/* {container.id} */}
|
||||
<span
|
||||
style={{ fontSize: '30px' }}
|
||||
className="material-icons-outlined"
|
||||
>
|
||||
{container.icon}
|
||||
</span>
|
||||
</div>
|
||||
container={container}
|
||||
isActive={isActive}
|
||||
onDragOver={onDragOver(container)}
|
||||
onClick={onClick(container)}
|
||||
onDrop={onContainerDrop(container)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<ExtraSpace onNewContainer={onNewContainer} />
|
||||
@ -360,25 +356,40 @@ export const SidebarPart = (props: { id: string }) => {
|
||||
};
|
||||
|
||||
const onDidDrop = (event: PaneviewDropEvent) => {
|
||||
const data = event.event.getData();
|
||||
const data = event.getData();
|
||||
|
||||
const containerData = event.event.dataTransfer.getData(
|
||||
'application/json'
|
||||
);
|
||||
|
||||
if (containerData) {
|
||||
const { container } = JSON.parse(containerData);
|
||||
|
||||
const sourceContainer = viewService.model.getViewContainer(
|
||||
container
|
||||
);
|
||||
const targetContainer = viewService.model.getViewContainer(
|
||||
props.id
|
||||
);
|
||||
|
||||
sourceContainer.views.forEach((v) => {
|
||||
viewService.model.moveViewToLocation(v, targetContainer, 0);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetPanel = event.event.panel;
|
||||
const targetPanel = event.panel;
|
||||
const allPanels = event.api.getPanels();
|
||||
let toIndex = allPanels.indexOf(targetPanel);
|
||||
|
||||
// if (
|
||||
// event.event.position === Position.Left ||
|
||||
// event.event.position === Position.Top
|
||||
// ) {
|
||||
// toIndex = Math.max(0, toIndex - 1);
|
||||
// }
|
||||
if (
|
||||
event.event.position === Position.Right ||
|
||||
event.event.position === Position.Bottom
|
||||
event.position === Position.Right ||
|
||||
event.position === Position.Bottom
|
||||
) {
|
||||
toIndex = Math.min(allPanels.length, toIndex + 1);
|
||||
}
|
||||
|
2
packages/dockview/package-lock.json
generated
2
packages/dockview/package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview",
|
||||
"version": "0.0.25",
|
||||
"version": "0.0.26",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview",
|
||||
"version": "0.0.25",
|
||||
"version": "0.0.26",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { getElementsByTagName } from '../dom';
|
||||
import { addDisposableListener, Emitter } from '../events';
|
||||
import { focusedElement } from '../focusedElement';
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
|
||||
export abstract class DragHandler extends CompositeDisposable {
|
||||
|
@ -1,11 +0,0 @@
|
||||
export const focusedElement: { element: Element | null } = { element: null };
|
||||
|
||||
window.addEventListener(
|
||||
'focusin',
|
||||
() => {
|
||||
focusedElement.element = document.activeElement;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
focusedElement.element = document.activeElement;
|
@ -17,7 +17,6 @@ import { ContentContainer, IContentContainer } from './panel/content';
|
||||
import { ITabsContainer, TabsContainer } from './titlebar/tabsContainer';
|
||||
import { IWatermarkRenderer } from './types';
|
||||
import { GroupviewPanel } from './groupviewPanel';
|
||||
import { focusedElement } from '../focusedElement';
|
||||
import { DockviewDropTargets } from './dnd';
|
||||
|
||||
export enum GroupChangeKind2 {
|
||||
@ -252,11 +251,11 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
|
||||
isContentFocused() {
|
||||
if (!focusedElement.element) {
|
||||
if (!document.activeElement) {
|
||||
return false;
|
||||
}
|
||||
return isAncestor(
|
||||
focusedElement.element,
|
||||
document.activeElement,
|
||||
this.contentContainer.element
|
||||
);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import { getPanelData, LocalSelectionTransfer } from '../dnd/dataTransfer';
|
||||
import { getElementsByTagName, toggleClass } from '../dom';
|
||||
import { IDockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { ITabRenderer } from './types';
|
||||
import { focusedElement } from '../focusedElement';
|
||||
import { IGroupPanel } from './groupPanel';
|
||||
import { GroupviewPanel } from './groupviewPanel';
|
||||
import { DroptargetEvent, Droptarget } from '../dnd/droptarget';
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
PanePanelInitParameter,
|
||||
PaneviewPanel,
|
||||
} from './paneviewPanel';
|
||||
import { addClasses } from '../dom';
|
||||
|
||||
interface ViewContainer {
|
||||
readonly title: string;
|
||||
@ -56,8 +57,12 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
}
|
||||
|
||||
private initDragFeatures() {
|
||||
if (!this.header) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = this.id;
|
||||
this.header!.draggable = true;
|
||||
this.header.draggable = true;
|
||||
|
||||
this.handler = new (class PaneDragHandler extends DragHandler {
|
||||
getData(): IDisposable {
|
||||
@ -74,7 +79,7 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
},
|
||||
};
|
||||
}
|
||||
})(this.header!);
|
||||
})(this.header);
|
||||
|
||||
this.target = new Droptarget(this.element, {
|
||||
validOverlays: 'vertical',
|
||||
|
@ -52,6 +52,10 @@
|
||||
position: relative;
|
||||
outline: none;
|
||||
|
||||
&.pane-draggable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
&:before {
|
||||
|
@ -22,9 +22,8 @@ export interface IPaneviewPanelProps<T extends {} = Record<string, any>>
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface PaneviewDropEvent {
|
||||
export interface PaneviewDropEvent extends PaneviewDropEvent2 {
|
||||
api: PaneviewApi;
|
||||
event: PaneviewDropEvent2;
|
||||
}
|
||||
|
||||
export interface IPaneviewReactProps {
|
||||
@ -128,7 +127,7 @@ export const PaneviewReact = React.forwardRef(
|
||||
const disposable = paneview.onDidDrop((event) => {
|
||||
if (props.onDidDrop) {
|
||||
props.onDidDrop({
|
||||
event,
|
||||
...event,
|
||||
api: new PaneviewApi(paneview),
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user