export function calculatePosition(parentRect, childRect, alignments) { const { parentH, parentV, childH, childV } = alignments const baseLeft = parentRect[parentH] const baseTop = parentRect[parentV] const left = childH === 'left' ? baseLeft : baseLeft - childRect.width const top = childV === 'top' ? baseTop : baseTop - childRect.height return updateRect(childRect, { left, top }) } export function alignChild(parent, child, container, alignments, containment) { const parentRect = parent.getBoundingClientRect() let childRect = child.getBoundingClientRect() const containerRect = container.getBoundingClientRect() childRect = calculatePosition(parentRect, childRect, alignments) const overflows = getOverflows(containerRect, childRect) if (containment.invertH && overflows.h) { alignments = invertH(alignments) } if (containment.invertV && overflows.v) { alignments = invertV(alignments) } childRect = calculatePosition(parentRect, childRect, alignments) if (containment.containInto) { childRect = containInto(containerRect, childRect) } child.style.top = childRect.top child.style.left = childRect.left } const alignMapping = { top: 'bottom', bottom: 'top', left: 'right', right: 'left', } function invertH(alignments) { const parentH = alignMapping[alignments.parentH] const childH = alignMapping[alignments.childH] return { ...alignments, parentH, childH } } function invertV(alignments) { const parentV = alignMapping[alignments.parentV] const childV = alignMapping[alignments.childV] return { ...alignments, parentV, childV } } /** * Sets new top and left property of a rectangle * @param {Object} rect * @param {Object} updates */ export function updateRect(rect, { left, top }) { const { width, height } = rect top = top || rect.top left = left || rect.left const right = left + width const bottom = top + height return { top, bottom, left, right, width, height } } export function getOverflows(parentRect, childRect) { const top = childRect.top < parentRect.top const bottom = childRect.bottom > parentRect.bottom const left = childRect.left < parentRect.left const right = childRect.right > parentRect.right const h = left || right const v = top || bottom const any = h || v return { top, bottom, left, right, h, v, any } } export function containInto(parent, child) { let top = child.top top = Math.min(top, parent.bottom - child.height) top = Math.max(top, parent.top) let left = child.left left = Math.min(left, parent.right - child.width) left = Math.max(left, parent.left) return updateRect(child, { top, left }) }