diff --git a/lerna.json b/lerna.json index 60584cc1c..7d9e0dfe8 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "0.0.25", + "version": "0.0.26", "command": { "publish": { "message": "chore(release): publish %s" diff --git a/packages/dockview-demo/package-lock.json b/packages/dockview-demo/package-lock.json index 56bf55c0d..72b7a0a6f 100644 --- a/packages/dockview-demo/package-lock.json +++ b/packages/dockview-demo/package-lock.json @@ -1,6 +1,6 @@ { "name": "dockview-demo", - "version": "0.0.25", + "version": "0.0.26", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/dockview-demo/package.json b/packages/dockview-demo/package.json index 1b15510a7..660aa5eb8 100644 --- a/packages/dockview-demo/package.json +++ b/packages/dockview-demo/package.json @@ -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" diff --git a/packages/dockview-demo/src/services/sidebarItem.tsx b/packages/dockview-demo/src/services/sidebarItem.tsx new file mode 100644 index 000000000..892b336e4 --- /dev/null +++ b/packages/dockview-demo/src/services/sidebarItem.tsx @@ -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(null); + const [selection, setSelection] = React.useState< + 'top' | 'bottom' | undefined + >(undefined); + const isDragging = React.useRef(false); + + const [dragEntered, setDragEntered] = React.useState(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 ( +
+ {dragEntered && ( +
+ )} + + {props.container.icon} + +
+ ); +}; diff --git a/packages/dockview-demo/src/services/viewContainer.ts b/packages/dockview-demo/src/services/viewContainer.ts index 70245db75..057af131d 100644 --- a/packages/dockview-demo/src/services/viewContainer.ts +++ b/packages/dockview-demo/src/services/viewContainer.ts @@ -45,7 +45,7 @@ export class PaneviewContainer implements ViewContainer { } get views() { - return this._views; + return [...this._views]; } get schema(): SerializedPaneview | undefined { diff --git a/packages/dockview-demo/src/services/viewService.ts b/packages/dockview-demo/src/services/viewService.ts index 1c9dc4d77..95a7769ef 100644 --- a/packages/dockview-demo/src/services/viewService.ts +++ b/packages/dockview-demo/src/services/viewService.ts @@ -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; diff --git a/packages/dockview-demo/src/services/widgets.scss b/packages/dockview-demo/src/services/widgets.scss index 7859ccb12..1e8bd3aee 100644 --- a/packages/dockview-demo/src/services/widgets.scss +++ b/packages/dockview-demo/src/services/widgets.scss @@ -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; +} diff --git a/packages/dockview-demo/src/services/widgets.tsx b/packages/dockview-demo/src/services/widgets.tsx index 4408f492d..7fa75c12a 100644 --- a/packages/dockview-demo/src/services/widgets.tsx +++ b/packages/dockview-demo/src/services/widgets.tsx @@ -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('gridview'); + + const sidebarPanel = api.getPanel('sidebar'); + if (!sidebarPanel.api.isVisible) { + api.setVisible(sidebarPanel, true); + sidebarPanel.focus(); + } + + viewService.model.setActiveViewContainer(container.id); + }; + return (
{containers.map((container, i) => { const isActive = activeContainerid === container.id; return ( -
{ - 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', - }} - key={i} - > - {/* {container.id} */} - - {container.icon} - -
+ /> ); })} @@ -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); } diff --git a/packages/dockview/package-lock.json b/packages/dockview/package-lock.json index 57056f13b..879fbff18 100644 --- a/packages/dockview/package-lock.json +++ b/packages/dockview/package-lock.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "0.0.25", + "version": "0.0.26", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 1f84087e1..44eccb6ad 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -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", diff --git a/packages/dockview/src/dnd/abstractDragHandler.ts b/packages/dockview/src/dnd/abstractDragHandler.ts index 2f01a3810..1e45bc6ac 100644 --- a/packages/dockview/src/dnd/abstractDragHandler.ts +++ b/packages/dockview/src/dnd/abstractDragHandler.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 { diff --git a/packages/dockview/src/focusedElement.ts b/packages/dockview/src/focusedElement.ts deleted file mode 100644 index b694815b8..000000000 --- a/packages/dockview/src/focusedElement.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const focusedElement: { element: Element | null } = { element: null }; - -window.addEventListener( - 'focusin', - () => { - focusedElement.element = document.activeElement; - }, - true -); - -focusedElement.element = document.activeElement; diff --git a/packages/dockview/src/groupview/groupview.ts b/packages/dockview/src/groupview/groupview.ts index fb1bcfd6c..079bd305c 100644 --- a/packages/dockview/src/groupview/groupview.ts +++ b/packages/dockview/src/groupview/groupview.ts @@ -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 ); } diff --git a/packages/dockview/src/groupview/tab.ts b/packages/dockview/src/groupview/tab.ts index fe4e52399..809507612 100644 --- a/packages/dockview/src/groupview/tab.ts +++ b/packages/dockview/src/groupview/tab.ts @@ -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'; diff --git a/packages/dockview/src/paneview/draggablePaneviewPanel.ts b/packages/dockview/src/paneview/draggablePaneviewPanel.ts index cf48708b2..d9b4ac7e1 100644 --- a/packages/dockview/src/paneview/draggablePaneviewPanel.ts +++ b/packages/dockview/src/paneview/draggablePaneviewPanel.ts @@ -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', diff --git a/packages/dockview/src/paneview/paneview.scss b/packages/dockview/src/paneview/paneview.scss index 51effb4a3..2f6723c9b 100644 --- a/packages/dockview/src/paneview/paneview.scss +++ b/packages/dockview/src/paneview/paneview.scss @@ -52,6 +52,10 @@ position: relative; outline: none; + &.pane-draggable { + cursor: pointer; + } + &:focus, &:focus-within { &:before { diff --git a/packages/dockview/src/react/paneview/paneview.tsx b/packages/dockview/src/react/paneview/paneview.tsx index b7e36d153..719dc3276 100644 --- a/packages/dockview/src/react/paneview/paneview.tsx +++ b/packages/dockview/src/react/paneview/paneview.tsx @@ -22,9 +22,8 @@ export interface IPaneviewPanelProps> 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), }); }