UNPKG

@jupyterlab/application

Version:
231 lines 8.14 kB
import { DockPanelSvg } from '@jupyterlab/ui-components'; const DEFAULT_NODE_THRESHOLD = 1000; const DEFAULT_TEXT_LENGTH_THRESHOLD = 25000; /** * A dock panel that freezes heavy panel dimensions during handle drags to * reduce layout reflow and improve resize performance. */ export class OptimizedDockPanelSvg extends DockPanelSvg { constructor() { super(...arguments); this._optimizeResize = true; this._isResizeDragActive = false; this._frozenGroups = []; this._refreshTimerId = 0; this._refreshRAFId = 0; this._intervalId = 0; } /** * Handle the DOM events for the dock panel. * * @param event - The DOM event sent to the panel. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events registered for the node. It should * not be called directly by user code. */ handleEvent(event) { if (event.type === 'pointerdown') { this._isResizeDragActive = this._isHandlePointerDown(event); } // Unfreeze before super processes the drag-release events so Lumino // measures natural sizes when it finalises the split position. if (this._frozenGroups.length > 0 && this._isResizeDragActive) { const t = event.type; if (t === 'pointerup' || t === 'pointercancel' || t === 'keydown') { this._isResizeDragActive = false; this._unfreezeElements(); } } super.handleEvent(event); if (!this._optimizeResize) { return; } if (event.type === 'pointerdown' && this._isResizeDragActive) { this._freezeHeavyLeaves(); } else if (event.type === 'pointermove' && this._isResizeDragActive) { this._scheduleRefresh(); } } _isHandlePointerDown(event) { const pointerEvent = event; if (pointerEvent.button !== 0) { return false; } const target = pointerEvent.target; if (!(target instanceof HTMLElement)) { return false; } for (const handle of this.handles()) { if (handle.contains(target)) { return true; } } return false; } /** * Whether resize optimizations are enabled. * * When `true` (the default), panels with heavy DOM content have their * dimensions frozen during a handle drag, avoiding repeated reflows. * Setting this to `false` immediately unfreezes any frozen panels. */ get optimizeResize() { return this._optimizeResize; } set optimizeResize(enabled) { this._optimizeResize = enabled; if (!enabled) { this._unfreezeElements(); } } _freezeHeavyLeaves() { if (this._frozenGroups.length > 0) { return; } const targets = []; for (const child of this.widgets()) { this._collectHeavyWidgets(child, targets); } for (const target of targets) { const head = target.node; const elements = [head]; for (let i = 0; i < head.children.length; i++) { elements.push(head.children[i]); } // Read all rects before writing to avoid layout thrashing. const rects = elements.map(el => el.getBoundingClientRect()); const frozenGroup = elements.map((el, i) => ({ element: el, isHead: i === 0, prevWidth: el.style.width, prevMaxWidth: el.style.maxWidth, prevHeight: el.style.height, prevMaxHeight: el.style.maxHeight })); for (let i = 0; i < frozenGroup.length; i++) { const el = frozenGroup[i].element; const rect = rects[i]; el.style.width = `${rect.width}px`; el.style.maxWidth = `${rect.width}px`; el.style.maxHeight = `${rect.height}px`; if (frozenGroup[i].isHead) { el.style.height = `${rect.height}px`; } } this._frozenGroups.push(frozenGroup); } if (this._frozenGroups.length > 0 && this._intervalId === 0) { this._intervalId = window.setInterval(() => { this._refreshFrozenElements(); }, 3000); } } _scheduleRefresh() { if (this._frozenGroups.length === 0) { return; } if (this._refreshTimerId !== 0) { clearTimeout(this._refreshTimerId); } this._refreshTimerId = window.setTimeout(() => { this._refreshTimerId = 0; this._refreshFrozenElements(); }, 300); } _refreshFrozenElements() { let g = 0; const step = () => { if (g >= this._frozenGroups.length) { this._refreshRAFId = 0; return; } let group = this._frozenGroups[g]; for (let entry of group) { let el = entry.element; el.style.width = ''; el.style.maxWidth = ''; el.style.height = ''; el.style.maxHeight = ''; } this._refreshRAFId = requestAnimationFrame(() => { let rects = group.map(entry => entry.element.getBoundingClientRect()); for (let i = 0; i < group.length; i++) { let entry = group[i]; let rect = rects[i]; let el = entry.element; el.style.width = `${rect.width}px`; el.style.maxWidth = `${rect.width}px`; el.style.maxHeight = `${rect.height}px`; if (entry.isHead) { el.style.height = `${rect.height}px`; } } g++; this._refreshRAFId = requestAnimationFrame(step); }); }; this._refreshRAFId = requestAnimationFrame(step); } _unfreezeElements() { if (this._refreshTimerId !== 0) { clearTimeout(this._refreshTimerId); this._refreshTimerId = 0; } if (this._refreshRAFId !== 0) { cancelAnimationFrame(this._refreshRAFId); this._refreshRAFId = 0; } if (this._intervalId !== 0) { clearInterval(this._intervalId); this._intervalId = 0; } for (let group of this._frozenGroups) { for (let entry of group) { entry.element.style.width = entry.prevWidth; entry.element.style.maxWidth = entry.prevMaxWidth; entry.element.style.height = entry.prevHeight; entry.element.style.maxHeight = entry.prevMaxHeight; } } this._frozenGroups = []; } _collectHeavyWidgets(widget, result) { if (!this._isDOMHeavy(widget.node)) { return; } let layout = widget.layout; if (!layout) { result.push(widget); return; } let anyChildHeavy = false; for (let child of layout) { if (this._isDOMHeavy(child.node)) { anyChildHeavy = true; break; } } if (anyChildHeavy) { for (let child of layout) { this._collectHeavyWidgets(child, result); } } else { result.push(widget); } } _isDOMHeavy(el) { var _a, _b; if (el.querySelectorAll('*').length >= DEFAULT_NODE_THRESHOLD) { return true; } if (((_b = (_a = el.textContent) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) >= DEFAULT_TEXT_LENGTH_THRESHOLD) { return true; } return false; } } //# sourceMappingURL=dockpanel.js.map