UNPKG

dockview-core

Version:

Zero dependency layout manager supporting tabs, groups, grids and splitviews for vanilla TypeScript

289 lines (288 loc) 11.3 kB
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'; }