Merge pull request #431 from mathuo/430-support-droptarget-size-options

feat: expose dockview root droptarget overlay options (work-in-progress)
This commit is contained in:
mathuo 2024-01-18 19:32:27 +00:00 committed by GitHub
commit 6c670c1fbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 88 additions and 68 deletions

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;
}
}
@ -281,26 +291,6 @@ export class Droptarget extends CompositeDisposable {
toggleClass(this.overlayElement, 'dv-overlay-bottom', 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;
}
}
private calculateQuadrant(
overlayType: Set<Position>,
x: number,
@ -308,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(
@ -324,7 +311,7 @@ export class Droptarget extends CompositeDisposable {
y,
width,
height,
value
activationSizeOptions.value
);
}
@ -334,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';
@ -63,6 +68,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[] = [];
@ -219,6 +229,7 @@ export type DockviewComponentUpdateOptions = Pick<
| 'createPrefixHeaderActionsElement'
| 'disableFloatingGroups'
| 'floatingGroupBounds'
| 'rootOverlayModel'
>;
export interface DockviewDropEvent extends GroupviewDropEvent {
@ -319,6 +330,7 @@ export class DockviewComponent
private readonly _floatingGroups: DockviewFloatingGroupPanel[] = [];
private readonly _popoutGroups: DockviewPopoutGroupPanel[] = [];
private readonly _rootDropTarget: Droptarget;
get orientation(): Orientation {
return this.gridview.orientation;
@ -424,7 +436,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();
@ -463,14 +475,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) {
@ -489,7 +499,7 @@ export class DockviewComponent
});
}
}),
dropTarget
this._rootDropTarget
);
this._api = new DockviewApi(this);
@ -720,20 +730,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':
@ -757,6 +771,10 @@ export class DockviewComponent
}
}
if (changed_rootOverlayOptions) {
this._rootDropTarget.setOverlayModel(options.rootOverlayModel!);
}
this.layout(this.gridview.width, this.gridview.height, true);
}

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

@ -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;