dockview-core
Version:
Zero dependency layout manager supporting tabs, groups, grids and splitviews for vanilla TypeScript
289 lines (288 loc) • 11.3 kB
JavaScript
import { DockviewEvent, Emitter } from '../events';
import { CompositeDisposable } from '../lifecycle';
import { DragAndDropObserver } from './dnd';
import { createOverlayElements, renderAnchoredOverlay, renderInPlaceOverlay, } from './dropOverlay';
export class WillShowOverlayEvent extends DockviewEvent {
get nativeEvent() {
return this.options.nativeEvent;
}
get position() {
return this.options.position;
}
constructor(options) {
super();
this.options = options;
}
}
export function directionToPosition(direction) {
switch (direction) {
case 'above':
return 'top';
case 'below':
return 'bottom';
case 'left':
return 'left';
case 'right':
return 'right';
case 'within':
return 'center';
default:
throw new Error(`invalid direction '${direction}'`);
}
}
export function positionToDirection(position) {
switch (position) {
case 'top':
return 'above';
case 'bottom':
return 'below';
case 'left':
return 'left';
case 'right':
return 'right';
case 'center':
return 'within';
default:
throw new Error(`invalid position '${position}'`);
}
}
const DEFAULT_ACTIVATION_SIZE = {
value: 20,
type: 'percentage',
};
export class Droptarget extends CompositeDisposable {
get disabled() {
return this._disabled;
}
set disabled(value) {
this._disabled = value;
}
get state() {
return this._state;
}
constructor(element, options) {
super();
this.element = element;
this.options = options;
this._onDrop = new Emitter();
this.onDrop = this._onDrop.event;
this._onWillShowOverlay = new Emitter();
this.onWillShowOverlay = this._onWillShowOverlay.event;
this._disabled = false;
// use a set to take advantage of #<set>.has
this._acceptedTargetZonesSet = new Set(this.options.acceptedTargetZones);
this.dnd = new DragAndDropObserver(this.element, {
onDragEnter: () => {
var _a, _b, _c;
(_c = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a)) === null || _c === void 0 ? void 0 : _c.getElements();
},
onDragOver: (e) => {
var _a, _b, _c, _d, _e, _f, _g;
Droptarget.ACTUAL_TARGET = this;
const overrideTarget = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
if (this._acceptedTargetZonesSet.size === 0) {
if (overrideTarget) {
return;
}
this.removeDropTarget();
return;
}
const target = (_e = (_d = (_c = this.options).getOverlayOutline) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : this.element;
const width = target.offsetWidth;
const height = target.offsetHeight;
if (width === 0 || height === 0) {
return; // avoid div!0
}
const rect = e.currentTarget.getBoundingClientRect();
const x = ((_f = e.clientX) !== null && _f !== void 0 ? _f : 0) - rect.left;
const y = ((_g = e.clientY) !== null && _g !== void 0 ? _g : 0) - rect.top;
const quadrant = this.calculateQuadrant(this._acceptedTargetZonesSet, x, y, width, height);
/**
* If the event has already been used by another DropTarget instance
* then don't show a second drop target, only one target should be
* active at any one time
*/
if (this.isAlreadyUsed(e) || quadrant === null) {
// no drop target should be displayed
this.removeDropTarget();
return;
}
if (!this.options.canDisplayOverlay(e, quadrant)) {
if (overrideTarget) {
return;
}
this.removeDropTarget();
return;
}
const willShowOverlayEvent = new WillShowOverlayEvent({
nativeEvent: e,
position: quadrant,
});
/**
* Provide an opportunity to prevent the overlay appearing and in turn
* any dnd behaviours
*/
this._onWillShowOverlay.fire(willShowOverlayEvent);
if (willShowOverlayEvent.defaultPrevented) {
this.removeDropTarget();
return;
}
this.markAsUsed(e);
if (overrideTarget) {
//
}
else if (!this.targetElement) {
const els = createOverlayElements();
this.targetElement = els.dropzone;
this.overlayElement = els.selection;
this._state = 'center';
target.classList.add('dv-drop-target');
target.append(this.targetElement);
}
this.toggleClasses(quadrant, width, height);
this._state = quadrant;
},
onDragLeave: () => {
var _a, _b;
const target = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
if (target) {
return;
}
this.removeDropTarget();
},
onDragEnd: (e) => {
var _a, _b;
const target = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
if (target && Droptarget.ACTUAL_TARGET === this) {
if (this._state) {
// only stop the propagation of the event if we are dealing with it
// which is only when the target has state
e.stopPropagation();
this._onDrop.fire({
position: this._state,
nativeEvent: e,
});
}
}
this.removeDropTarget();
target === null || target === void 0 ? void 0 : target.clear();
},
onDrop: (e) => {
var _a, _b, _c;
e.preventDefault();
const state = this._state;
this.removeDropTarget();
(_c = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a)) === null || _c === void 0 ? void 0 : _c.clear();
if (state) {
// only stop the propagation of the event if we are dealing with it
// which is only when the target has state
e.stopPropagation();
this._onDrop.fire({ position: state, nativeEvent: e });
}
},
});
this.addDisposables(this._onDrop, this._onWillShowOverlay, this.dnd);
}
setTargetZones(acceptedTargetZones) {
this._acceptedTargetZonesSet = new Set(acceptedTargetZones);
}
setOverlayModel(model) {
this.options.overlayModel = model;
}
dispose() {
this.removeDropTarget();
super.dispose();
}
/**
* Add a property to the event object for other potential listeners to check
*/
markAsUsed(event) {
event[Droptarget.USED_EVENT_ID] = true;
}
/**
* Check is the event has already been used by another instance of DropTarget
*/
isAlreadyUsed(event) {
const value = event[Droptarget.USED_EVENT_ID];
return typeof value === 'boolean' && value;
}
toggleClasses(quadrant, width, height) {
var _a, _b, _c, _d, _e;
const target = (_b = (_a = this.options).getOverrideTarget) === null || _b === void 0 ? void 0 : _b.call(_a);
if (target) {
const outlineEl = (_e = (_d = (_c = this.options).getOverlayOutline) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : this.element;
renderAnchoredOverlay({
outlineElement: outlineEl,
targetModel: target,
quadrant,
width,
height,
overlayModel: this.options.overlayModel,
className: this.options.className,
});
return;
}
if (!this.overlayElement) {
return;
}
renderInPlaceOverlay(this.overlayElement, quadrant, width, height, this.options.overlayModel);
}
calculateQuadrant(overlayType, x, y, width, height) {
var _a, _b;
const activationSizeOptions = (_b = (_a = this.options.overlayModel) === null || _a === void 0 ? void 0 : _a.activationSize) !== null && _b !== void 0 ? _b : DEFAULT_ACTIVATION_SIZE;
const isPercentage = activationSizeOptions.type === 'percentage';
if (isPercentage) {
return calculateQuadrantAsPercentage(overlayType, x, y, width, height, activationSizeOptions.value);
}
return calculateQuadrantAsPixels(overlayType, x, y, width, height, activationSizeOptions.value);
}
removeDropTarget() {
var _a;
if (this.targetElement) {
this._state = undefined;
(_a = this.targetElement.parentElement) === null || _a === void 0 ? void 0 : _a.classList.remove('dv-drop-target');
this.targetElement.remove();
this.targetElement = undefined;
this.overlayElement = undefined;
}
}
}
Droptarget.USED_EVENT_ID = '__dockview_droptarget_event_is_used__';
export function calculateQuadrantAsPercentage(overlayType, x, y, width, height, threshold) {
const xp = (100 * x) / width;
const yp = (100 * y) / height;
if (overlayType.has('left') && xp < threshold) {
return 'left';
}
if (overlayType.has('right') && xp > 100 - threshold) {
return 'right';
}
if (overlayType.has('top') && yp < threshold) {
return 'top';
}
if (overlayType.has('bottom') && yp > 100 - threshold) {
return 'bottom';
}
if (!overlayType.has('center')) {
return null;
}
return 'center';
}
export function calculateQuadrantAsPixels(overlayType, x, y, width, height, threshold) {
if (overlayType.has('left') && x < threshold) {
return 'left';
}
if (overlayType.has('right') && x > width - threshold) {
return 'right';
}
if (overlayType.has('top') && y < threshold) {
return 'top';
}
if (overlayType.has('bottom') && y > height - threshold) {
return 'bottom';
}
if (!overlayType.has('center')) {
return null;
}
return 'center';
}