UNPKG

dockview-core

Version:

Zero dependency layout manager supporting tabs, grids and splitviews

162 lines (161 loc) 6.94 kB
import { DragAndDropObserver } from '../dnd/dnd'; import { getDomNodePagePosition, toggleClass } from '../dom'; import { CompositeDisposable, Disposable, MutableDisposable, } from '../lifecycle'; function createFocusableElement() { const element = document.createElement('div'); element.tabIndex = -1; return element; } export class OverlayRenderContainer extends CompositeDisposable { constructor(element, accessor) { super(); this.element = element; this.accessor = accessor; this.map = {}; this._disposed = false; this.addDisposables(Disposable.from(() => { for (const value of Object.values(this.map)) { value.disposable.dispose(); value.destroy.dispose(); } this._disposed = true; })); } detatch(panel) { if (this.map[panel.api.id]) { const { disposable, destroy } = this.map[panel.api.id]; disposable.dispose(); destroy.dispose(); delete this.map[panel.api.id]; return true; } return false; } attach(options) { const { panel, referenceContainer } = options; if (!this.map[panel.api.id]) { const element = createFocusableElement(); element.className = 'dv-render-overlay'; this.map[panel.api.id] = { panel, disposable: Disposable.NONE, destroy: Disposable.NONE, element, }; } const focusContainer = this.map[panel.api.id].element; if (panel.view.content.element.parentElement !== focusContainer) { focusContainer.appendChild(panel.view.content.element); } if (focusContainer.parentElement !== this.element) { this.element.appendChild(focusContainer); } const resize = () => { // TODO propagate position to avoid getDomNodePagePosition calls, possible performance bottleneck? const box = getDomNodePagePosition(referenceContainer.element); const box2 = getDomNodePagePosition(this.element); focusContainer.style.left = `${box.left - box2.left}px`; focusContainer.style.top = `${box.top - box2.top}px`; focusContainer.style.width = `${box.width}px`; focusContainer.style.height = `${box.height}px`; toggleClass(focusContainer, 'dv-render-overlay-float', panel.group.api.location.type === 'floating'); }; const visibilityChanged = () => { if (panel.api.isVisible) { resize(); } focusContainer.style.display = panel.api.isVisible ? '' : 'none'; }; const observerDisposable = new MutableDisposable(); const correctLayerPosition = () => { if (panel.api.location.type === 'floating') { queueMicrotask(() => { const floatingGroup = this.accessor.floatingGroups.find((group) => group.group === panel.api.group); if (!floatingGroup) { return; } const element = floatingGroup.overlay.element; const update = () => { const level = Number(element.getAttribute('aria-level')); focusContainer.style.zIndex = `calc(var(--dv-overlay-z-index, 999) + ${level * 2 + 1})`; }; const observer = new MutationObserver(() => { update(); }); observerDisposable.value = Disposable.from(() => observer.disconnect()); observer.observe(element, { attributeFilter: ['aria-level'], attributes: true, }); update(); }); } else { focusContainer.style.zIndex = ''; // reset the z-index, perhaps CSS will take over here } }; const disposable = new CompositeDisposable(observerDisposable, /** * since container is positioned absoutely we must explicitly forward * the dnd events for the expect behaviours to continue to occur in terms of dnd * * the dnd observer does not need to be conditional on whether the panel is visible since * non-visible panels are 'display: none' and in such case the dnd observer will not fire. */ new DragAndDropObserver(focusContainer, { onDragEnd: (e) => { referenceContainer.dropTarget.dnd.onDragEnd(e); }, onDragEnter: (e) => { referenceContainer.dropTarget.dnd.onDragEnter(e); }, onDragLeave: (e) => { referenceContainer.dropTarget.dnd.onDragLeave(e); }, onDrop: (e) => { referenceContainer.dropTarget.dnd.onDrop(e); }, onDragOver: (e) => { referenceContainer.dropTarget.dnd.onDragOver(e); }, }), panel.api.onDidVisibilityChange(() => { /** * Control the visibility of the content, however even when not visible (display: none) * the content is still maintained within the DOM hence DOM specific attributes * such as scroll position are maintained when next made visible. */ visibilityChanged(); }), panel.api.onDidDimensionsChange(() => { if (!panel.api.isVisible) { return; } resize(); }), panel.api.onDidLocationChange(() => { correctLayerPosition(); })); this.map[panel.api.id].destroy = Disposable.from(() => { var _a; if (panel.view.content.element.parentElement === focusContainer) { focusContainer.removeChild(panel.view.content.element); } (_a = focusContainer.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(focusContainer); }); correctLayerPosition(); queueMicrotask(() => { if (this.isDisposed) { return; } /** * wait until everything has finished in the current stack-frame call before * calling the first resize as other size-altering events may still occur before * the end of the stack-frame. */ visibilityChanged(); }); // dispose of logic asoccciated with previous reference-container this.map[panel.api.id].disposable.dispose(); // and reset the disposable to the active reference-container this.map[panel.api.id].disposable = disposable; return focusContainer; } }