UNPKG

dockview-core

Version:

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

193 lines (192 loc) 7.86 kB
// Two render paths: in-place (dropzone appended to drop element) and // anchored (overlay rendered into an external anchor container). import { toggleClass } from '../dom'; import { clamp } from '../math'; const DEFAULT_SIZE = { value: 50, type: 'percentage' }; const SMALL_WIDTH_BOUNDARY = 100; const SMALL_HEIGHT_BOUNDARY = 100; export function createOverlayElements() { const dropzone = document.createElement('div'); dropzone.className = 'dv-drop-target-dropzone'; const selection = document.createElement('div'); selection.className = 'dv-drop-target-selection'; dropzone.appendChild(selection); return { dropzone, selection }; } function computeOverlayShape(quadrant, width, height, overlayModel) { var _a, _b, _c; const smallWidthBoundary = (_a = overlayModel === null || overlayModel === void 0 ? void 0 : overlayModel.smallWidthBoundary) !== null && _a !== void 0 ? _a : SMALL_WIDTH_BOUNDARY; const smallHeightBoundary = (_b = overlayModel === null || overlayModel === void 0 ? void 0 : overlayModel.smallHeightBoundary) !== null && _b !== void 0 ? _b : SMALL_HEIGHT_BOUNDARY; const isSmallX = width < smallWidthBoundary; const isSmallY = height < smallHeightBoundary; const isLeft = quadrant === 'left'; const isRight = quadrant === 'right'; const isTop = quadrant === 'top'; const isBottom = quadrant === 'bottom'; const rightClass = !isSmallX && isRight; const leftClass = !isSmallX && isLeft; const topClass = !isSmallY && isTop; const bottomClass = !isSmallY && isBottom; let size = 1; const sizeOptions = (_c = overlayModel === null || overlayModel === void 0 ? void 0 : overlayModel.size) !== null && _c !== void 0 ? _c : DEFAULT_SIZE; if (sizeOptions.type === 'percentage') { size = clamp(sizeOptions.value, 0, 100) / 100; } else { if (rightClass || leftClass) { size = clamp(0, sizeOptions.value, width) / width; } if (topClass || bottomClass) { size = clamp(0, sizeOptions.value, height) / height; } } return { isSmallX, isSmallY, isLeft, isRight, isTop, isBottom, rightClass, leftClass, topClass, bottomClass, size, }; } export function renderInPlaceOverlay(overlay, quadrant, width, height, overlayModel) { const shape = computeOverlayShape(quadrant, width, height, overlayModel); const { rightClass, leftClass, topClass, bottomClass, size } = shape; const box = { top: '0px', left: '0px', width: '100%', height: '100%' }; if (rightClass) { box.left = `${100 * (1 - size)}%`; box.width = `${100 * size}%`; } else if (leftClass) { box.width = `${100 * size}%`; } else if (topClass) { box.height = `${100 * size}%`; } else if (bottomClass) { box.top = `${100 * (1 - size)}%`; box.height = `${100 * size}%`; } if (shape.isSmallX && shape.isLeft) { box.width = '4px'; } if (shape.isSmallX && shape.isRight) { box.left = `${width - 4}px`; box.width = '4px'; } if (shape.isSmallY && shape.isTop) { box.height = '4px'; } if (shape.isSmallY && shape.isBottom) { box.top = `${height - 4}px`; box.height = '4px'; } overlay.style.top = box.top; overlay.style.left = box.left; overlay.style.width = box.width; overlay.style.height = box.height; overlay.style.visibility = 'visible'; if (!overlay.style.transform || overlay.style.transform === '') { overlay.style.transform = 'translate3d(0, 0, 0)'; } const isLine = (shape.isSmallX && (shape.isLeft || shape.isRight)) || (shape.isSmallY && (shape.isTop || shape.isBottom)); toggleClass(overlay, 'dv-drop-target-small-vertical', shape.isSmallY); toggleClass(overlay, 'dv-drop-target-small-horizontal', shape.isSmallX); toggleClass(overlay, 'dv-drop-target-selection-line', isLine); toggleClass(overlay, 'dv-drop-target-left', shape.isLeft); toggleClass(overlay, 'dv-drop-target-right', shape.isRight); toggleClass(overlay, 'dv-drop-target-top', shape.isTop); toggleClass(overlay, 'dv-drop-target-bottom', shape.isBottom); toggleClass(overlay, 'dv-drop-target-center', quadrant === 'center'); } function checkAnchoredBoundsChanged(overlay, bounds) { const topPx = `${Math.round(bounds.top)}px`; const leftPx = `${Math.round(bounds.left)}px`; const widthPx = `${Math.round(bounds.width)}px`; const heightPx = `${Math.round(bounds.height)}px`; return (overlay.style.top !== topPx || overlay.style.left !== leftPx || overlay.style.width !== widthPx || overlay.style.height !== heightPx); } function applyAnchoredBounds(overlay, bounds) { overlay.style.top = `${Math.round(bounds.top)}px`; overlay.style.left = `${Math.round(bounds.left)}px`; overlay.style.width = `${Math.round(bounds.width)}px`; overlay.style.height = `${Math.round(bounds.height)}px`; overlay.style.visibility = 'visible'; if (!overlay.style.transform || overlay.style.transform === '') { overlay.style.transform = 'translate3d(0, 0, 0)'; } } /** `boundsChanged: false` lets callers skip redundant work on tight drag loops. */ export function renderAnchoredOverlay(args) { const shape = computeOverlayShape(args.quadrant, args.width, args.height, args.overlayModel); const { rightClass, leftClass, topClass, bottomClass, size } = shape; const elBox = args.outlineElement.getBoundingClientRect(); const ta = args.targetModel.getElements(undefined, args.outlineElement); const el = ta.root; const overlay = ta.overlay; const bigbox = el.getBoundingClientRect(); const rootTop = elBox.top - bigbox.top; const rootLeft = elBox.left - bigbox.left; const box = { top: rootTop, left: rootLeft, width: args.width, height: args.height, }; if (rightClass) { box.left = rootLeft + args.width * (1 - size); box.width = args.width * size; } else if (leftClass) { box.width = args.width * size; } else if (topClass) { box.height = args.height * size; } else if (bottomClass) { box.top = rootTop + args.height * (1 - size); box.height = args.height * size; } if (shape.isSmallX && shape.isLeft) { box.width = 4; } if (shape.isSmallX && shape.isRight) { box.left = rootLeft + args.width - 4; box.width = 4; } if (shape.isSmallY && shape.isTop) { box.height = 4; } if (shape.isSmallY && shape.isBottom) { box.top = rootTop + args.height - 4; box.height = 4; } if (!checkAnchoredBoundsChanged(overlay, box)) { return { boundsChanged: false, targetChanged: ta.changed }; } applyAnchoredBounds(overlay, box); overlay.className = `dv-drop-target-anchor${args.className ? ` ${args.className}` : ''}`; toggleClass(overlay, 'dv-drop-target-left', shape.isLeft); toggleClass(overlay, 'dv-drop-target-right', shape.isRight); toggleClass(overlay, 'dv-drop-target-top', shape.isTop); toggleClass(overlay, 'dv-drop-target-bottom', shape.isBottom); toggleClass(overlay, 'dv-drop-target-anchor-line', (shape.isSmallX && (shape.isLeft || shape.isRight)) || (shape.isSmallY && (shape.isTop || shape.isBottom))); toggleClass(overlay, 'dv-drop-target-center', args.quadrant === 'center'); if (ta.changed) { toggleClass(overlay, 'dv-drop-target-anchor-container-changed', true); setTimeout(() => { toggleClass(overlay, 'dv-drop-target-anchor-container-changed', false); }, 10); } return { boundsChanged: true, targetChanged: ta.changed }; }