feat: floating group viewport rules

This commit is contained in:
mathuo 2023-08-26 23:25:40 +01:00
parent a1b2b9b1da
commit 715281b804
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
5 changed files with 76 additions and 37 deletions

View File

@ -11,6 +11,7 @@
"/packages/docs/sandboxes/dockview-app", "/packages/docs/sandboxes/dockview-app",
"/packages/docs/sandboxes/events-dockview", "/packages/docs/sandboxes/events-dockview",
"/packages/docs/sandboxes/externaldnd-dockview", "/packages/docs/sandboxes/externaldnd-dockview",
"/packages/docs/sandboxes/floatinggroup-dockview",
"/packages/docs/sandboxes/fullwidthtab-dockview", "/packages/docs/sandboxes/fullwidthtab-dockview",
"/packages/docs/sandboxes/groupcontol-dockview", "/packages/docs/sandboxes/groupcontol-dockview",
"/packages/docs/sandboxes/iframe-dockview", "/packages/docs/sandboxes/iframe-dockview",

View File

@ -1,3 +1,4 @@
import { toHaveDescription } from '@testing-library/jest-dom/matchers';
import { import {
getElementsByTagName, getElementsByTagName,
quasiDefaultPrevented, quasiDefaultPrevented,
@ -47,8 +48,8 @@ export class Overlay extends CompositeDisposable {
top: number; top: number;
container: HTMLElement; container: HTMLElement;
content: HTMLElement; content: HTMLElement;
minimumInViewportWidth: number; minimumInViewportWidth?: number;
minimumInViewportHeight: number; minimumInViewportHeight?: number;
} }
) { ) {
super(); super();
@ -105,16 +106,10 @@ export class Overlay extends CompositeDisposable {
// region: ensure bounds within allowable limits // region: ensure bounds within allowable limits
// a minimum width of minimumViewportWidth must be inside the viewport // a minimum width of minimumViewportWidth must be inside the viewport
const xOffset = Math.max( const xOffset = Math.max(0, this.getMinimumWidth(overlayRect.width));
0,
overlayRect.width - this.options.minimumInViewportWidth
);
// a minimum height of minimumViewportHeight must be inside the viewport // a minimum height of minimumViewportHeight must be inside the viewport
const yOffset = Math.max( const yOffset = Math.max(0, this.getMinimumHeight(overlayRect.height));
0,
overlayRect.height - this.options.minimumInViewportHeight
);
const left = clamp( const left = clamp(
overlayRect.left - containerRect.left, overlayRect.left - containerRect.left,
@ -194,12 +189,13 @@ export class Overlay extends CompositeDisposable {
const xOffset = Math.max( const xOffset = Math.max(
0, 0,
overlayRect.width - this.options.minimumInViewportWidth this.getMinimumWidth(overlayRect.width)
); );
const yOffset = Math.max( const yOffset = Math.max(
0, 0,
overlayRect.height -
this.options.minimumInViewportHeight this.options.minimumInViewportHeight
? this.getMinimumHeight(overlayRect.height)
: 0
); );
const left = clamp( const left = clamp(
@ -350,20 +346,16 @@ export class Overlay extends CompositeDisposable {
let left: number | undefined = undefined; let left: number | undefined = undefined;
let width: number | undefined = undefined; let width: number | undefined = undefined;
const minimumInViewportHeight = const moveTop = () => {
this.options.minimumInViewportHeight;
const minimumInViewportWidth =
this.options.minimumInViewportWidth;
function moveTop(): void {
top = clamp( top = clamp(
y, y,
-Number.MAX_VALUE, -Number.MAX_VALUE,
startPosition!.originalY + startPosition!.originalY +
startPosition!.originalHeight > startPosition!.originalHeight >
containerRect.height containerRect.height
? containerRect.height - ? this.getMinimumHeight(
minimumInViewportHeight containerRect.height
)
: Math.max( : Math.max(
0, 0,
startPosition!.originalY + startPosition!.originalY +
@ -375,31 +367,31 @@ export class Overlay extends CompositeDisposable {
startPosition!.originalY + startPosition!.originalY +
startPosition!.originalHeight - startPosition!.originalHeight -
top; top;
} };
function moveBottom(): void { const moveBottom = () => {
top = top =
startPosition!.originalY - startPosition!.originalY -
startPosition!.originalHeight; startPosition!.originalHeight;
height = clamp( height = clamp(
y - top, y - top,
top < 0 top < 0 && this.options.minimumInViewportHeight
? -top + minimumInViewportHeight ? -top +
this.options.minimumInViewportHeight
: Overlay.MINIMUM_HEIGHT, : Overlay.MINIMUM_HEIGHT,
Number.MAX_VALUE Number.MAX_VALUE
); );
} };
function moveLeft(): void { const moveLeft = () => {
left = clamp( left = clamp(
x, x,
-Number.MAX_VALUE, -Number.MAX_VALUE,
startPosition!.originalX + startPosition!.originalX +
startPosition!.originalWidth > startPosition!.originalWidth >
containerRect.width containerRect.width
? containerRect.width - ? this.getMinimumWidth(containerRect.width)
minimumInViewportWidth
: Math.max( : Math.max(
0, 0,
startPosition!.originalX + startPosition!.originalX +
@ -412,21 +404,22 @@ export class Overlay extends CompositeDisposable {
startPosition!.originalX + startPosition!.originalX +
startPosition!.originalWidth - startPosition!.originalWidth -
left; left;
} };
function moveRight(): void { const moveRight = () => {
left = left =
startPosition!.originalX - startPosition!.originalX -
startPosition!.originalWidth; startPosition!.originalWidth;
width = clamp( width = clamp(
x - left, x - left,
left < 0 left < 0 && this.options.minimumInViewportWidth
? -left + minimumInViewportWidth ? -left +
this.options.minimumInViewportWidth
: Overlay.MINIMUM_WIDTH, : Overlay.MINIMUM_WIDTH,
Number.MAX_VALUE Number.MAX_VALUE
); );
} };
switch (direction) { switch (direction) {
case 'top': case 'top':
@ -477,6 +470,20 @@ export class Overlay extends CompositeDisposable {
); );
} }
private getMinimumWidth(width: number) {
if (typeof this.options.minimumInViewportWidth === 'number') {
return width - this.options.minimumInViewportWidth;
}
return 0;
}
private getMinimumHeight(height: number) {
if (typeof this.options.minimumInViewportHeight === 'number') {
return height - this.options.minimumInViewportHeight;
}
return height;
}
override dispose(): void { override dispose(): void {
this._element.remove(); this._element.remove();
super.dispose(); super.dispose();

View File

@ -87,6 +87,7 @@ export type DockviewComponentUpdateOptions = Pick<
| 'createLeftHeaderActionsElement' | 'createLeftHeaderActionsElement'
| 'createRightHeaderActionsElement' | 'createRightHeaderActionsElement'
| 'disableFloatingGroups' | 'disableFloatingGroups'
| 'floatingGroupsPosition'
>; >;
export interface DockviewDropEvent extends GroupviewDropEvent { export interface DockviewDropEvent extends GroupviewDropEvent {
@ -359,8 +360,16 @@ export class DockviewComponent
width: coord?.width ?? 300, width: coord?.width ?? 300,
left: overlayLeft, left: overlayLeft,
top: overlayTop, top: overlayTop,
minimumInViewportWidth: 100, minimumInViewportWidth:
minimumInViewportHeight: 100, this.options.floatingGroupsPosition === 'boundedWithinViewport'
? undefined
: this.options.floatingGroupsPosition
?.minimumWidthWithinViewport ?? 100,
minimumInViewportHeight:
this.options.floatingGroupsPosition === 'boundedWithinViewport'
? undefined
: this.options.floatingGroupsPosition
?.minimumHeightWithinViewport ?? 100,
}); });
const el = group.element.querySelector('.void-container'); const el = group.element.querySelector('.void-container');

View File

@ -87,6 +87,12 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
singleTabMode?: 'fullwidth' | 'default'; singleTabMode?: 'fullwidth' | 'default';
parentElement?: HTMLElement; parentElement?: HTMLElement;
disableFloatingGroups?: boolean; disableFloatingGroups?: boolean;
floatingGroupsPosition?:
| 'boundedWithinViewport'
| {
minimumHeightWithinViewport?: number;
minimumWidthWithinViewport?: number;
};
} }
export interface PanelOptions<P extends object = Parameters> { export interface PanelOptions<P extends object = Parameters> {

View File

@ -69,6 +69,12 @@ export interface IDockviewReactProps {
leftHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>; leftHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
singleTabMode?: 'fullwidth' | 'default'; singleTabMode?: 'fullwidth' | 'default';
disableFloatingGroups?: boolean; disableFloatingGroups?: boolean;
floatingGroupsPosition?:
| 'boundedWithinViewport'
| {
minimumHeightWithinViewport?: number;
minimumWidthWithinViewport?: number;
};
} }
const DEFAULT_REACT_TAB = 'props.defaultTabComponent'; const DEFAULT_REACT_TAB = 'props.defaultTabComponent';
@ -162,6 +168,7 @@ export const DockviewReact = React.forwardRef(
), ),
singleTabMode: props.singleTabMode, singleTabMode: props.singleTabMode,
disableFloatingGroups: props.disableFloatingGroups, disableFloatingGroups: props.disableFloatingGroups,
floatingGroupsPosition: props.floatingGroupsPosition,
}); });
const { clientWidth, clientHeight } = domRef.current; const { clientWidth, clientHeight } = domRef.current;
@ -205,6 +212,15 @@ export const DockviewReact = React.forwardRef(
}); });
}, [props.components]); }, [props.components]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
floatingGroupsPosition: props.floatingGroupsPosition,
});
}, [props.floatingGroupsPosition]);
React.useEffect(() => { React.useEffect(() => {
if (!dockviewRef.current) { if (!dockviewRef.current) {
return; return;