Merge branch 'master' of https://github.com/mathuo/dockview into 395-showdndoverlay-is-not-called-always

This commit is contained in:
mathuo 2024-01-18 19:33:07 +00:00
commit 8da26f1484
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
17 changed files with 376 additions and 82 deletions

View File

@ -28,6 +28,7 @@
"/packages/docs/sandboxes/rendermode-dockview",
"/packages/docs/sandboxes/resize-dockview",
"/packages/docs/sandboxes/resizecontainer-dockview",
"/packages/docs/sandboxes/scrollbars-dockview",
"/packages/docs/sandboxes/simple-dockview",
"/packages/docs/sandboxes/simple-gridview",
"/packages/docs/sandboxes/simple-paneview",
@ -40,4 +41,4 @@
"/packages/docs/sandboxes/javascript/vanilla-dockview"
],
"node": "18"
}
}

View File

@ -22,20 +22,28 @@
will-change: transform;
pointer-events: none;
&.small-top {
border-top: 1px solid var(--dv-drag-over-border-color);
&.dv-overlay-top {
&.dv-overlay-small-vertical {
border-top: 1px solid var(--dv-drag-over-border-color);
}
}
&.small-bottom {
border-bottom: 1px solid var(--dv-drag-over-border-color);
&.dv-overlay-bottom {
&.dv-overlay-small-vertical {
border-bottom: 1px solid var(--dv-drag-over-border-color);
}
}
&.small-left {
border-left: 1px solid var(--dv-drag-over-border-color);
&.dv-overlay-left {
&.dv-overlay-small-horizontal {
border-left: 1px solid var(--dv-drag-over-border-color);
}
}
&.small-right {
border-right: 1px solid var(--dv-drag-over-border-color);
&.dv-overlay-right {
&.dv-overlay-small-horizontal {
border-right: 1px solid var(--dv-drag-over-border-color);
}
}
}
}

View File

@ -5,10 +5,6 @@ import { DragAndDropObserver } from './dnd';
import { clamp } from '../math';
import { Direction } from '../gridview/baseComponentGridview';
function numberOrFallback(maybeNumber: any, fallback: number): number {
return typeof maybeNumber === 'number' ? maybeNumber : fallback;
}
export function directionToPosition(direction: Direction): Position {
switch (direction) {
case 'above':
@ -54,6 +50,26 @@ export type CanDisplayOverlay =
| boolean
| ((dragEvent: DragEvent, state: Position) => boolean);
export type MeasuredValue = { value: number; type: 'pixels' | 'percentage' };
export type DroptargetOverlayModel = {
size?: MeasuredValue;
activationSize?: MeasuredValue;
};
const DEFAULT_ACTIVATION_SIZE: MeasuredValue = {
value: 20,
type: 'percentage',
};
const DEFAULT_SIZE: MeasuredValue = {
value: 50,
type: 'percentage',
};
const SMALL_WIDTH_BOUNDARY = 100;
const SMALL_HEIGHT_BOUNDARY = 100;
export class Droptarget extends CompositeDisposable {
private targetElement: HTMLElement | undefined;
private overlayElement: HTMLElement | undefined;
@ -76,13 +92,7 @@ export class Droptarget extends CompositeDisposable {
private readonly options: {
canDisplayOverlay: CanDisplayOverlay;
acceptedTargetZones: Position[];
overlayModel?: {
size?: { value: number; type: 'pixels' | 'percentage' };
activationSize?: {
value: number;
type: 'pixels' | 'percentage';
};
};
overlayModel?: DroptargetOverlayModel;
}
) {
super();
@ -158,7 +168,7 @@ export class Droptarget extends CompositeDisposable {
this.toggleClasses(quadrant, width, height);
this.setState(quadrant);
this._state = quadrant;
},
onDragLeave: () => {
this.removeDropTarget();
@ -189,6 +199,10 @@ export class Droptarget extends CompositeDisposable {
this._acceptedTargetZonesSet = new Set(acceptedTargetZones);
}
setOverlayModel(model: DroptargetOverlayModel): void {
this.options.overlayModel = model;
}
dispose(): void {
this.removeDropTarget();
super.dispose();
@ -202,7 +216,7 @@ export class Droptarget extends CompositeDisposable {
}
/**
* Check is the event has already been used by another instance od DropTarget
* Check is the event has already been used by another instance of DropTarget
*/
private isAlreadyUsed(event: DragEvent): boolean {
const value = (event as any)[Droptarget.USED_EVENT_ID];
@ -218,8 +232,8 @@ export class Droptarget extends CompositeDisposable {
return;
}
const isSmallX = width < 100;
const isSmallY = height < 100;
const isSmallX = width < SMALL_WIDTH_BOUNDARY;
const isSmallY = height < SMALL_HEIGHT_BOUNDARY;
const isLeft = quadrant === 'left';
const isRight = quadrant === 'right';
@ -231,22 +245,18 @@ export class Droptarget extends CompositeDisposable {
const topClass = !isSmallY && isTop;
const bottomClass = !isSmallY && isBottom;
let size = 0.5;
let size = 1;
if (this.options.overlayModel?.size?.type === 'percentage') {
size = clamp(this.options.overlayModel.size.value, 0, 100) / 100;
}
const sizeOptions = this.options.overlayModel?.size ?? DEFAULT_SIZE;
if (this.options.overlayModel?.size?.type === 'pixels') {
if (sizeOptions.type === 'percentage') {
size = clamp(sizeOptions.value, 0, 100) / 100;
} else {
if (rightClass || leftClass) {
size =
clamp(0, this.options.overlayModel.size.value, width) /
width;
size = clamp(0, sizeOptions.value, width) / width;
}
if (topClass || bottomClass) {
size =
clamp(0, this.options.overlayModel.size.value, height) /
height;
size = clamp(0, sizeOptions.value, height) / height;
}
}
@ -269,30 +279,16 @@ export class Droptarget extends CompositeDisposable {
this.overlayElement.style.transform = transform;
toggleClass(this.overlayElement, 'small-right', isSmallX && isRight);
toggleClass(this.overlayElement, 'small-left', isSmallX && isLeft);
toggleClass(this.overlayElement, 'small-top', isSmallY && isTop);
toggleClass(this.overlayElement, 'small-bottom', isSmallY && isBottom);
}
private setState(quadrant: Position): void {
switch (quadrant) {
case 'top':
this._state = 'top';
break;
case 'left':
this._state = 'left';
break;
case 'bottom':
this._state = 'bottom';
break;
case 'right':
this._state = 'right';
break;
case 'center':
this._state = 'center';
break;
}
toggleClass(this.overlayElement, 'dv-overlay-small-vertical', isSmallY);
toggleClass(
this.overlayElement,
'dv-overlay-small-horizontal',
isSmallX
);
toggleClass(this.overlayElement, 'dv-overlay-left', isLeft);
toggleClass(this.overlayElement, 'dv-overlay-right', isRight);
toggleClass(this.overlayElement, 'dv-overlay-top', isTop);
toggleClass(this.overlayElement, 'dv-overlay-bottom', isBottom);
}
private calculateQuadrant(
@ -302,14 +298,11 @@ export class Droptarget extends CompositeDisposable {
width: number,
height: number
): Position | null {
const isPercentage =
this.options.overlayModel?.activationSize === undefined ||
this.options.overlayModel?.activationSize?.type === 'percentage';
const activationSizeOptions =
this.options.overlayModel?.activationSize ??
DEFAULT_ACTIVATION_SIZE;
const value = numberOrFallback(
this.options?.overlayModel?.activationSize?.value,
20
);
const isPercentage = activationSizeOptions.type === 'percentage';
if (isPercentage) {
return calculateQuadrantAsPercentage(
@ -318,7 +311,7 @@ export class Droptarget extends CompositeDisposable {
y,
width,
height,
value
activationSizeOptions.value
);
}
@ -328,7 +321,7 @@ export class Droptarget extends CompositeDisposable {
y,
width,
height,
value
activationSizeOptions.value
);
}

View File

@ -4,7 +4,12 @@ import {
getGridLocation,
ISerializedLeafNode,
} from '../gridview/gridview';
import { directionToPosition, Droptarget, Position } from '../dnd/droptarget';
import {
directionToPosition,
Droptarget,
DroptargetOverlayModel,
Position,
} from '../dnd/droptarget';
import { tail, sequenceEquals, remove } from '../array';
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
import { CompositeDisposable, Disposable } from '../lifecycle';
@ -59,6 +64,11 @@ import {
OverlayRenderContainer,
} from '../overlayRenderContainer';
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
activationSize: { type: 'pixels', value: 10 },
size: { type: 'pixels', value: 20 },
};
function getTheme(element: HTMLElement): string | undefined {
function toClassList(element: HTMLElement) {
const list: string[] = [];
@ -215,6 +225,7 @@ export type DockviewComponentUpdateOptions = Pick<
| 'createPrefixHeaderActionsElement'
| 'disableFloatingGroups'
| 'floatingGroupBounds'
| 'rootOverlayModel'
>;
export interface DockviewDropEvent extends GroupviewDropEvent {
@ -315,6 +326,7 @@ export class DockviewComponent
private readonly _floatingGroups: DockviewFloatingGroupPanel[] = [];
private readonly _popoutGroups: DockviewPopoutGroupPanel[] = [];
private readonly _rootDropTarget: Droptarget;
get orientation(): Orientation {
return this.gridview.orientation;
@ -420,7 +432,7 @@ export class DockviewComponent
this.options.watermarkComponent = Watermark;
}
const dropTarget = new Droptarget(this.element, {
this._rootDropTarget = new Droptarget(this.element, {
canDisplayOverlay: (event, position) => {
const data = getPanelData();
@ -459,14 +471,12 @@ export class DockviewComponent
return false;
},
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
overlayModel: {
activationSize: { type: 'pixels', value: 10 },
size: { type: 'pixels', value: 20 },
},
overlayModel:
this.options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL,
});
this.addDisposables(
dropTarget.onDrop((event) => {
this._rootDropTarget.onDrop((event) => {
const data = getPanelData();
if (data) {
@ -485,7 +495,7 @@ export class DockviewComponent
});
}
}),
dropTarget
this._rootDropTarget
);
this._api = new DockviewApi(this);
@ -716,20 +726,24 @@ export class DockviewComponent
}
updateOptions(options: DockviewComponentUpdateOptions): void {
const hasOrientationChanged =
const changed_orientation =
typeof options.orientation === 'string' &&
this.gridview.orientation !== options.orientation;
const hasFloatingGroupOptionsChanged =
const changed_floatingGroupBounds =
options.floatingGroupBounds !== undefined &&
options.floatingGroupBounds !== this.options.floatingGroupBounds;
const changed_rootOverlayOptions =
options.rootOverlayModel !== undefined &&
options.rootOverlayModel !== this.options.rootOverlayModel;
this._options = { ...this.options, ...options };
if (hasOrientationChanged) {
if (changed_orientation) {
this.gridview.orientation = options.orientation!;
}
if (hasFloatingGroupOptionsChanged) {
if (changed_floatingGroupBounds) {
for (const group of this._floatingGroups) {
switch (this.options.floatingGroupBounds) {
case 'boundedWithinViewport':
@ -753,6 +767,10 @@ export class DockviewComponent
}
}
if (changed_rootOverlayOptions) {
this._rootDropTarget.setOverlayModel(options.rootOverlayModel!);
}
this.layout(this.gridview.width, this.gridview.height, true);
}

View File

@ -17,7 +17,7 @@
> .content-container {
flex-grow: 1;
overflow: hidden;
min-height: 0;
outline: none;
}
}

View File

@ -13,7 +13,7 @@ import { DockviewGroupPanel } from './dockviewGroupPanel';
import { ISplitviewStyles, Orientation } from '../splitview/splitview';
import { PanelTransfer } from '../dnd/dataTransfer';
import { IDisposable } from '../lifecycle';
import { Position } from '../dnd/droptarget';
import { DroptargetOverlayModel, Position } from '../dnd/droptarget';
import { IDockviewPanel } from './dockviewPanel';
import {
ComponentConstructor,
@ -100,6 +100,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
popoutUrl?: string;
defaultRenderer?: DockviewPanelRenderer;
debug?: boolean;
rootOverlayModel?: DroptargetOverlayModel;
}
export interface PanelOptions<P extends object = Parameters> {

View File

@ -55,6 +55,8 @@ export {
Position,
positionToDirection,
directionToPosition,
MeasuredValue,
DroptargetOverlayModel,
} from './dnd/droptarget';
export {
FocusEvent,

View File

@ -45,6 +45,22 @@ export abstract class Resizable extends CompositeDisposable {
return;
}
if (!this._element.offsetParent) {
/**
* offsetParent === null is equivalent to display: none being set on the element or one
* of it's parents. In the display: none case the size will become (0, 0) which we do
* not want to propagate.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
*
* You could use checkVisibility() but at the time of writing it's not supported across
* all Browsers
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility
*/
return;
}
if (!isInDocument(this._element)) {
/**
* since the event is dispatched through requestAnimationFrame there is a small chance

View File

@ -11,6 +11,7 @@ import {
DockviewGroupPanel,
IHeaderActionsRenderer,
DockviewPanelRenderer,
DroptargetOverlayModel,
} from 'dockview-core';
import { ReactPanelContentPart } from './reactContentPart';
import { ReactPanelHeaderPart } from './reactHeaderPart';
@ -79,6 +80,7 @@ export interface IDockviewReactProps {
};
debug?: boolean;
defaultRenderer?: DockviewPanelRenderer;
rootOverlayModel?: DroptargetOverlayModel;
}
const DEFAULT_REACT_TAB = 'props.defaultTabComponent';
@ -180,6 +182,7 @@ export const DockviewReact = React.forwardRef(
floatingGroupBounds: props.floatingGroupBounds,
defaultRenderer: props.defaultRenderer,
debug: props.debug,
rootOverlayModel: props.rootOverlayModel,
});
const { clientWidth, clientHeight } = domRef.current;
@ -312,6 +315,15 @@ export const DockviewReact = React.forwardRef(
});
}, [props.leftHeaderActionsComponent]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
rootOverlayModel: props.rootOverlayModel,
});
}, [props.rootOverlayModel]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;

View File

@ -31,6 +31,7 @@ import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app';
import DockviewPopoutGroup from '@site/sandboxes/popoutgroup-dockview/src/app';
import DockviewMaximizeGroup from '@site/sandboxes/maximizegroup-dockview/src/app';
import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app';
import DockviewScrollbars from '@site/sandboxes/scrollbars-dockview/src/app';
import { DocRef } from '@site/src/components/ui/reference/docRef';
@ -196,6 +197,22 @@ If you refresh the page you should notice your layout is loaded as you left it.
react={DockviewPersistance}
/>
## Scrollbars
Scrollbars will appear if the contents of your view has a fixed height. If you are using a relative height such
as *100%* you will need to define an inner container with the appropiate `overflow` value to allow scrollbars to appear.
The following container three views:
- **Panel 1**: Sets `height: 100%` and no scrollbar appears even, the content is clipped.
- **Panel 2**: Sets `height: 2000px` and a scrollbar does appear since a fixed height has been used.
- **Panel 3**: Sets `height: 100%` and defines an inner component with `overflow: auto` to enable the scrollbars.
<MultiFrameworkContainer
sandboxId="scrollbars-dockview"
react={DockviewScrollbars}
/>
## Resizing
### Panel Resizing

View File

@ -0,0 +1,34 @@
{
"name": "scrollbars-dockview",
"description": "",
"keywords": [
"dockview"
],
"version": "1.0.0",
"main": "src/index.tsx",
"dependencies": {
"dockview": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/uuid": "^9.0.0",
"typescript": "^4.9.5",
"react-scripts": "*"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,16 @@
.group-control {
.action {
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
font-size: 18px;
cursor: pointer;
&:hover {
border-radius: 2px;
background-color: var(--dv-icon-hover-background-color);
}
}
}

View File

@ -0,0 +1,77 @@
import {
DockviewReact,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview';
import './app.scss';
const TEXT =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
const components = {
fixedHeightContainer: (props: IDockviewPanelProps<{ title: string }>) => {
return (
<div style={{ height: '100%', color: 'white' }}>
{[TEXT, '\n\n'].join('').repeat(20)}
</div>
);
},
overflowContainer: (props: IDockviewPanelProps<{ title: string }>) => {
return (
<div style={{ height: '2000px', overflow: 'auto', color: 'white' }}>
{[TEXT, '\n\n'].join('').repeat(20)}
</div>
);
},
userDefinedOverflowContainer: (
props: IDockviewPanelProps<{ title: string }>
) => {
return (
<div style={{ height: '100%', color: 'white' }}>
<div
style={{
height: '100%',
color: 'white',
overflow: 'auto',
}}
>
{[TEXT, '\n\n'].join('').repeat(20)}
</div>
</div>
);
},
};
const DockviewComponent = (props: { theme?: string }) => {
const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'fixedHeightContainer',
title: 'Panel 1',
});
event.api.addPanel({
id: 'panel_2',
component: 'overflowContainer',
title: 'Panel 2',
position: { direction: 'right' },
});
event.api.addPanel({
id: 'panel_3',
component: 'userDefinedOverflowContainer',
title: 'Panel 3',
position: { direction: 'right' },
});
};
return (
<DockviewReact
components={components}
onReady={onReady}
className={props.theme || 'dockview-theme-abyss'}
/>
);
};
export default DockviewComponent;

View File

@ -0,0 +1,20 @@
import { StrictMode } from 'react';
import * as ReactDOMClient from 'react-dom/client';
import './styles.css';
import 'dockview/dist/styles/dockview.css';
import App from './app';
const rootElement = document.getElementById('root');
if (rootElement) {
const root = ReactDOMClient.createRoot(rootElement);
root.render(
<StrictMode>
<div className="app">
<App />
</div>
</StrictMode>
);
}

View File

@ -0,0 +1,16 @@
body {
margin: 0px;
color: white;
font-family: sans-serif;
text-align: center;
}
#root {
height: 100vh;
width: 100vw;
}
.app {
height: 100%;
}

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}