dockview-core
Version:
Zero dependency layout manager supporting tabs, groups, grids and splitviews for vanilla TypeScript
149 lines (148 loc) • 6.38 kB
JavaScript
import { addDisposableListener } from '../events';
import { CompositeDisposable, MutableDisposable, } from '../lifecycle';
import { Droptarget } from './droptarget';
import { addGhostImage } from './ghost';
import { PointerDropTarget } from './pointer/pointerDropTarget';
import { PointerDragSource } from './pointer/pointerDragSource';
import { PointerGhost } from './pointer/pointerGhost';
import { disableIframePointEvents } from '../dom';
/**
* HTML5 drag source. Listens for the native `dragstart` event, calls
* `getData` to populate transfer, optionally renders the ghost via
* `setDragImage`, fires `onDragStart` / `onDragEnd`, and tears down the
* transfer disposer after `dragend`.
*/
class Html5DragSource extends CompositeDisposable {
constructor(el, opts) {
super();
this.el = el;
this.opts = opts;
this._dataDisposable = new MutableDisposable();
this._pointerEventsDisposable = new MutableDisposable();
this._disabled = !!opts.disabled;
this.addDisposables(this._dataDisposable, this._pointerEventsDisposable, addDisposableListener(this.el, 'dragstart', (event) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
if (event.defaultPrevented ||
this._disabled ||
((_b = (_a = this.opts).isCancelled) === null || _b === void 0 ? void 0 : _b.call(_a, event))) {
event.preventDefault();
return;
}
// Iframes capture pointermove once the cursor enters them,
// which freezes drag tracking from the parent window's
// POV. Shield the source's owning document so popout-window
// drags shield the popout, not the main window.
const iframes = disableIframePointEvents((_c = this.el.ownerDocument) !== null && _c !== void 0 ? _c : document);
this._pointerEventsDisposable.value = {
dispose: () => iframes.release(),
};
this.el.classList.add('dv-dragged');
setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
this._dataDisposable.value = this.opts.getData(event);
const ghost = (_e = (_d = this.opts).createGhost) === null || _e === void 0 ? void 0 : _e.call(_d, event);
if (ghost && event.dataTransfer) {
addGhostImage(event.dataTransfer, ghost.element, {
x: (_f = ghost.offsetX) !== null && _f !== void 0 ? _f : 0,
y: (_g = ghost.offsetY) !== null && _g !== void 0 ? _g : 0,
});
if (ghost.dispose) {
// addGhostImage removes the element from the DOM on
// the next tick; dispose the framework renderer on
// the same schedule.
const disposeGhost = ghost.dispose;
setTimeout(() => disposeGhost(), 0);
}
}
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
// Some third-party DnD libs (e.g. react-dnd) cancel the
// dragstart when `dataTransfer.types` is empty.
if (event.dataTransfer.items.length === 0) {
event.dataTransfer.setData('text/plain', '');
}
}
(_j = (_h = this.opts).onDragStart) === null || _j === void 0 ? void 0 : _j.call(_h, event);
}), addDisposableListener(this.el, 'dragend', (event) => {
var _a, _b;
this._pointerEventsDisposable.dispose();
// Defer disposal so drop handlers can still read the
// transfer payload before it clears.
setTimeout(() => this._dataDisposable.dispose(), 0);
(_b = (_a = this.opts).onDragEnd) === null || _b === void 0 ? void 0 : _b.call(_a, event);
}));
}
setDisabled(value) {
this._disabled = value;
}
setTouchOnly(_) {
// No-op — HTML5 path can't filter by pointer type.
}
cancelPending() {
// No-op — HTML5 has no pre-arm phase to cancel.
}
}
class Html5DragBackend {
constructor() {
this.kind = 'html5';
}
createDropTarget(element, options) {
return new Droptarget(element, options);
}
createDragSource(element, options) {
return new Html5DragSource(element, options);
}
}
class PointerDragBackend {
constructor() {
this.kind = 'pointer';
}
createDropTarget(element, options) {
return new PointerDropTarget(element, options);
}
createDragSource(element, options) {
const pointerCreateGhost = options.createGhost
? (event) => {
const spec = options.createGhost(event);
if (!spec) {
return undefined;
}
const ghost = new PointerGhost({
element: spec.element,
initialX: event.clientX,
initialY: event.clientY,
offsetX: spec.offsetX,
offsetY: spec.offsetY,
owner: element,
});
if (spec.dispose) {
const baseDispose = ghost.dispose.bind(ghost);
const disposeSpec = spec.dispose;
ghost.dispose = () => {
baseDispose();
disposeSpec();
};
}
return ghost;
}
: undefined;
const source = new PointerDragSource(element, {
getData: options.getData,
isCancelled: options.isCancelled,
onDragStart: options.onDragStart,
onDragEnd: options.onDragEnd
? (event) => options.onDragEnd(event.pointerEvent)
: undefined,
createGhost: pointerCreateGhost,
touchOnly: options.touchOnly,
touchInitiationDelay: options.touchInitiationDelay,
pressTolerance: options.pressTolerance,
threshold: options.threshold,
});
if (options.disabled) {
source.setDisabled(true);
}
return source;
}
}
export const html5Backend = new Html5DragBackend();
export const pointerBackend = new PointerDragBackend();