es-grid-template
Version:
es-grid-template
185 lines (180 loc) • 6.36 kB
JavaScript
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);
}
}));
}