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 { clamp } from '../math';
import { Direction } from '../gridview/baseComponentGridview'; import { Direction } from '../gridview/baseComponentGridview';
function numberOrFallback(maybeNumber: any, fallback: number): number {
return typeof maybeNumber === 'number' ? maybeNumber : fallback;
}
export function directionToPosition(direction: Direction): Position { export function directionToPosition(direction: Direction): Position {
switch (direction) { switch (direction) {
case 'above': case 'above':
@ -54,6 +50,26 @@ export type CanDisplayOverlay =
| boolean | boolean
| ((dragEvent: DragEvent, state: Position) => 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 { export class Droptarget extends CompositeDisposable {
private targetElement: HTMLElement | undefined; private targetElement: HTMLElement | undefined;
private overlayElement: HTMLElement | undefined; private overlayElement: HTMLElement | undefined;
@ -76,13 +92,7 @@ export class Droptarget extends CompositeDisposable {
private readonly options: { private readonly options: {
canDisplayOverlay: CanDisplayOverlay; canDisplayOverlay: CanDisplayOverlay;
acceptedTargetZones: Position[]; acceptedTargetZones: Position[];
overlayModel?: { overlayModel?: DroptargetOverlayModel;
size?: { value: number; type: 'pixels' | 'percentage' };
activationSize?: {
value: number;
type: 'pixels' | 'percentage';
};
};
} }
) { ) {
super(); super();
@ -158,7 +168,7 @@ export class Droptarget extends CompositeDisposable {
this.toggleClasses(quadrant, width, height); this.toggleClasses(quadrant, width, height);
this.setState(quadrant); this._state = quadrant;
}, },
onDragLeave: () => { onDragLeave: () => {
this.removeDropTarget(); this.removeDropTarget();
@ -189,6 +199,10 @@ export class Droptarget extends CompositeDisposable {
this._acceptedTargetZonesSet = new Set(acceptedTargetZones); this._acceptedTargetZonesSet = new Set(acceptedTargetZones);
} }
setOverlayModel(model: DroptargetOverlayModel): void {
this.options.overlayModel = model;
}
dispose(): void { dispose(): void {
this.removeDropTarget(); this.removeDropTarget();
super.dispose(); 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 { private isAlreadyUsed(event: DragEvent): boolean {
const value = (event as any)[Droptarget.USED_EVENT_ID]; const value = (event as any)[Droptarget.USED_EVENT_ID];
@ -218,8 +232,8 @@ export class Droptarget extends CompositeDisposable {
return; return;
} }
const isSmallX = width < 100; const isSmallX = width < SMALL_WIDTH_BOUNDARY;
const isSmallY = height < 100; const isSmallY = height < SMALL_HEIGHT_BOUNDARY;
const isLeft = quadrant === 'left'; const isLeft = quadrant === 'left';
const isRight = quadrant === 'right'; const isRight = quadrant === 'right';
@ -231,22 +245,18 @@ export class Droptarget extends CompositeDisposable {
const topClass = !isSmallY && isTop; const topClass = !isSmallY && isTop;
const bottomClass = !isSmallY && isBottom; const bottomClass = !isSmallY && isBottom;
let size = 0.5; let size = 1;
if (this.options.overlayModel?.size?.type === 'percentage') { const sizeOptions = this.options.overlayModel?.size ?? DEFAULT_SIZE;
size = clamp(this.options.overlayModel.size.value, 0, 100) / 100;
}
if (this.options.overlayModel?.size?.type === 'pixels') { if (sizeOptions.type === 'percentage') {
size = clamp(sizeOptions.value, 0, 100) / 100;
} else {
if (rightClass || leftClass) { if (rightClass || leftClass) {
size = size = clamp(0, sizeOptions.value, width) / width;
clamp(0, this.options.overlayModel.size.value, width) /
width;
} }
if (topClass || bottomClass) { if (topClass || bottomClass) {
size = size = clamp(0, sizeOptions.value, height) / height;
clamp(0, this.options.overlayModel.size.value, height) /
height;
} }
} }
@ -281,26 +291,6 @@ export class Droptarget extends CompositeDisposable {
toggleClass(this.overlayElement, 'dv-overlay-bottom', isBottom); 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( private calculateQuadrant(
overlayType: Set<Position>, overlayType: Set<Position>,
x: number, x: number,
@ -308,14 +298,11 @@ export class Droptarget extends CompositeDisposable {
width: number, width: number,
height: number height: number
): Position | null { ): Position | null {
const isPercentage = const activationSizeOptions =
this.options.overlayModel?.activationSize === undefined || this.options.overlayModel?.activationSize ??
this.options.overlayModel?.activationSize?.type === 'percentage'; DEFAULT_ACTIVATION_SIZE;
const value = numberOrFallback( const isPercentage = activationSizeOptions.type === 'percentage';
this.options?.overlayModel?.activationSize?.value,
20
);
if (isPercentage) { if (isPercentage) {
return calculateQuadrantAsPercentage( return calculateQuadrantAsPercentage(
@ -324,7 +311,7 @@ export class Droptarget extends CompositeDisposable {
y, y,
width, width,
height, height,
value activationSizeOptions.value
); );
} }
@ -334,7 +321,7 @@ export class Droptarget extends CompositeDisposable {
y, y,
width, width,
height, height,
value activationSizeOptions.value
); );
} }

View File

@ -4,7 +4,12 @@ import {
getGridLocation, getGridLocation,
ISerializedLeafNode, ISerializedLeafNode,
} from '../gridview/gridview'; } 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 { tail, sequenceEquals, remove } from '../array';
import { DockviewPanel, IDockviewPanel } from './dockviewPanel'; import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
import { CompositeDisposable, Disposable } from '../lifecycle'; import { CompositeDisposable, Disposable } from '../lifecycle';
@ -63,6 +68,11 @@ import {
OverlayRenderContainer, OverlayRenderContainer,
} from '../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 getTheme(element: HTMLElement): string | undefined {
function toClassList(element: HTMLElement) { function toClassList(element: HTMLElement) {
const list: string[] = []; const list: string[] = [];
@ -219,6 +229,7 @@ export type DockviewComponentUpdateOptions = Pick<
| 'createPrefixHeaderActionsElement' | 'createPrefixHeaderActionsElement'
| 'disableFloatingGroups' | 'disableFloatingGroups'
| 'floatingGroupBounds' | 'floatingGroupBounds'
| 'rootOverlayModel'
>; >;
export interface DockviewDropEvent extends GroupviewDropEvent { export interface DockviewDropEvent extends GroupviewDropEvent {
@ -319,6 +330,7 @@ export class DockviewComponent
private readonly _floatingGroups: DockviewFloatingGroupPanel[] = []; private readonly _floatingGroups: DockviewFloatingGroupPanel[] = [];
private readonly _popoutGroups: DockviewPopoutGroupPanel[] = []; private readonly _popoutGroups: DockviewPopoutGroupPanel[] = [];
private readonly _rootDropTarget: Droptarget;
get orientation(): Orientation { get orientation(): Orientation {
return this.gridview.orientation; return this.gridview.orientation;
@ -424,7 +436,7 @@ export class DockviewComponent
this.options.watermarkComponent = Watermark; this.options.watermarkComponent = Watermark;
} }
const dropTarget = new Droptarget(this.element, { this._rootDropTarget = new Droptarget(this.element, {
canDisplayOverlay: (event, position) => { canDisplayOverlay: (event, position) => {
const data = getPanelData(); const data = getPanelData();
@ -463,14 +475,12 @@ export class DockviewComponent
return false; return false;
}, },
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
overlayModel: { overlayModel:
activationSize: { type: 'pixels', value: 10 }, this.options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL,
size: { type: 'pixels', value: 20 },
},
}); });
this.addDisposables( this.addDisposables(
dropTarget.onDrop((event) => { this._rootDropTarget.onDrop((event) => {
const data = getPanelData(); const data = getPanelData();
if (data) { if (data) {
@ -489,7 +499,7 @@ export class DockviewComponent
}); });
} }
}), }),
dropTarget this._rootDropTarget
); );
this._api = new DockviewApi(this); this._api = new DockviewApi(this);
@ -720,20 +730,24 @@ export class DockviewComponent
} }
updateOptions(options: DockviewComponentUpdateOptions): void { updateOptions(options: DockviewComponentUpdateOptions): void {
const hasOrientationChanged = const changed_orientation =
typeof options.orientation === 'string' && typeof options.orientation === 'string' &&
this.gridview.orientation !== options.orientation; this.gridview.orientation !== options.orientation;
const hasFloatingGroupOptionsChanged = const changed_floatingGroupBounds =
options.floatingGroupBounds !== undefined && options.floatingGroupBounds !== undefined &&
options.floatingGroupBounds !== this.options.floatingGroupBounds; options.floatingGroupBounds !== this.options.floatingGroupBounds;
const changed_rootOverlayOptions =
options.rootOverlayModel !== undefined &&
options.rootOverlayModel !== this.options.rootOverlayModel;
this._options = { ...this.options, ...options }; this._options = { ...this.options, ...options };
if (hasOrientationChanged) { if (changed_orientation) {
this.gridview.orientation = options.orientation!; this.gridview.orientation = options.orientation!;
} }
if (hasFloatingGroupOptionsChanged) { if (changed_floatingGroupBounds) {
for (const group of this._floatingGroups) { for (const group of this._floatingGroups) {
switch (this.options.floatingGroupBounds) { switch (this.options.floatingGroupBounds) {
case 'boundedWithinViewport': 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); 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 { ISplitviewStyles, Orientation } from '../splitview/splitview';
import { PanelTransfer } from '../dnd/dataTransfer'; import { PanelTransfer } from '../dnd/dataTransfer';
import { IDisposable } from '../lifecycle'; import { IDisposable } from '../lifecycle';
import { Position } from '../dnd/droptarget'; import { DroptargetOverlayModel, Position } from '../dnd/droptarget';
import { IDockviewPanel } from './dockviewPanel'; import { IDockviewPanel } from './dockviewPanel';
import { import {
ComponentConstructor, ComponentConstructor,
@ -100,6 +100,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
popoutUrl?: string; popoutUrl?: string;
defaultRenderer?: DockviewPanelRenderer; defaultRenderer?: DockviewPanelRenderer;
debug?: boolean; debug?: boolean;
rootOverlayModel?: DroptargetOverlayModel;
} }
export interface PanelOptions<P extends object = Parameters> { export interface PanelOptions<P extends object = Parameters> {

View File

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

View File

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