UNPKG

dockview-core

Version:

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

149 lines (148 loc) 6.38 kB
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();