UNPKG

es-grid-template

Version:

es-grid-template

185 lines (180 loc) 6.36 kB
import getNodeName from '@popperjs/core/lib/dom-utils/getNodeName'; import getOffsetParent from '@popperjs/core/lib/dom-utils/getOffsetParent'; import getParentNode from '@popperjs/core/lib/dom-utils/getParentNode'; import getWindow from '@popperjs/core/lib/dom-utils/getWindow'; import getWindowScroll from '@popperjs/core/lib/dom-utils/getWindowScroll'; import { isHTMLElement } from '@popperjs/core/lib/dom-utils/instanceOf'; import isScrollParent from '@popperjs/core/lib/dom-utils/isScrollParent'; import ResizeObserver from 'resize-observer-polyfill'; import { fromEvent, merge, Observable } from 'rxjs'; import * as op from 'rxjs/operators'; import { shallowEqual } from "../utils"; function isWindow(arg) { return arg.toString() === '[object Window]'; } function isBody(arg) { return getNodeName(arg) === 'body'; } function isHtml(arg) { return getNodeName(arg) === 'html'; } function isHtmlOrBody(arg) { return isHtml(arg) || isBody(arg); } // Accumulate the sum of scrollTop or scrollLeft for all elements from start (child) to stop (ancestor) // Note: start and stop are INCLUSIVE; both their scroll offsets are included function accumulateScrollOffset(start, stop, scrollOffsetKey) { let result = 0; let elem = start; while (elem != null) { result += elem[scrollOffsetKey]; if (elem === stop || isWindow(stop) && isHtmlOrBody(elem)) { break; } elem = elem.parentElement; } if (isWindow(stop)) { result += getWindowScroll(elem)[scrollOffsetKey]; } return result; } /** * Get the layout size and relative position of `target` relative to `base`. * Note: this function accounts for scroll offsets. */ function getRelativeLayoutRect(base, target) { if (isWindow(target) || isHtmlOrBody(target)) { return { left: 0, right: window.innerWidth, top: 0, bottom: window.innerHeight }; } let deltaX = 0; let deltaY = 0; let elem = target; while (elem != null && elem != base) { deltaY += elem.offsetTop; deltaX += elem.offsetLeft; const offsetParent = getOffsetParent(elem); deltaY -= accumulateScrollOffset(elem.parentElement, offsetParent, 'scrollTop'); deltaX -= accumulateScrollOffset(elem.parentElement, offsetParent, 'scrollLeft'); if (isWindow(offsetParent)) { break; } deltaY += offsetParent.clientTop; deltaX += offsetParent.clientLeft; elem = offsetParent; } return { top: deltaY, bottom: deltaY + target.offsetHeight, left: deltaX, right: deltaX + target.offsetWidth }; } function findCommonOffsetAncestor(target, scrollParent) { if (isWindow(scrollParent)) { return scrollParent; } const offsetParents = listOffsetParents(target); if (offsetParents.includes(scrollParent)) { return scrollParent; } return getOffsetParent(scrollParent); } // List all offset parents above the target element function listOffsetParents(target) { const result = []; let elem = target; while (true) { if (isWindow(elem)) { break; } elem = getOffsetParent(elem); result.push(elem); } return result; } function fromScrollEvent(element) { return fromEvent(element, 'scroll', { passive: true }); } function fromResizeEvent(element) { if (isWindow(element)) { return fromEvent(element, 'resize', { passive: true }); } return new Observable(subscriber => { const resizeObserver = new ResizeObserver(entries => { subscriber.next(entries); }); resizeObserver.observe(element); return () => { resizeObserver.disconnect(); }; }); } function getScrollParent(elem) { // @ts-ignore if (['html', 'body', '#document'].includes(getNodeName(elem))) { return getWindow(elem); } if (isHTMLElement(elem) && isScrollParent(elem)) { return elem; } return getScrollParent(getParentNode(elem)); } // Get the size and position of the visible area of the target // relative to its scroll parent export function getRichVisibleRectsStream(target, structureMayChange$, virtualDebugLabel) { return structureMayChange$.pipe(op.startWith('init'), op.map(() => { // The first scroll parent of `target`; we consider this where virtual scrolling occurs. // Virtual scrolling ignores scrolls from ancestors above this element. const scrollParent = getScrollParent(target); // Common offset ancestor of `target` and `scrollParent`, used as reference for layout calculations const commonOffsetAncestor = findCommonOffsetAncestor(target, scrollParent); return { scrollParent, commonOffsetAncestor }; }), op.distinctUntilChanged(shallowEqual), op.tap(structure => { if (virtualDebugLabel) { console.log(`%c[ali-react-table STRUCTURE ${virtualDebugLabel}]`, 'color: #4f9052; font-weight: bold', '\ntarget:', target, '\nscrollParent:', structure.scrollParent, '\ncommonOffsetAncestor:', structure.commonOffsetAncestor); } }), op.switchMap(({ scrollParent, commonOffsetAncestor }) => { const events$ = merge(fromScrollEvent(scrollParent), fromResizeEvent(scrollParent), fromResizeEvent(target)); return events$.pipe(op.map(event => ({ targetRect: getRelativeLayoutRect(commonOffsetAncestor, target), scrollParentRect: getRelativeLayoutRect(commonOffsetAncestor, scrollParent), event })), op.map(({ event, scrollParentRect, targetRect }) => ({ event, targetRect, scrollParentRect, offsetY: Math.max(0, scrollParentRect.top - targetRect.top), // Table horizontal scrolling always occurs within the table, so offsetX does not need to be computed here // offsetX: Math.max(0, scrollParentRect.left - targetRect.left), clipRect: { left: Math.max(targetRect.left, scrollParentRect.left), top: Math.max(targetRect.top, scrollParentRect.top), right: Math.min(targetRect.right, scrollParentRect.right), bottom: Math.min(targetRect.bottom, scrollParentRect.bottom) } }))); }), op.tap(rects => { if (virtualDebugLabel) { console.log(`%c[ali-react-table RECTS ${virtualDebugLabel}]`, 'color: #4f9052; font-weight: bold', '\noffsetY:', rects.offsetY, '\ntargetRect:', rects.targetRect, '\nscrollParentRect:', rects.scrollParentRect, '\nclipRect:', rects.clipRect, '\nevent:', rects.event); } })); }