import { invariant } from '../../invariant'; import { warning } from '../../dev-warning'; import getBodyElement from '../get-body-element'; interface Overflow { overflowX: string; overflowY: string; } const isEqual = (base: string) => (value: string): boolean => base === value; const isScroll = isEqual('scroll'); const isAuto = isEqual('auto'); const isVisible = isEqual('visible'); const isEither = (overflow: Overflow, fn: (value: string) => boolean) => fn(overflow.overflowX) || fn(overflow.overflowY); const isBoth = (overflow: Overflow, fn: (value: string) => boolean) => fn(overflow.overflowX) && fn(overflow.overflowY); const isElementScrollable = (el: Element): boolean => { const style: CSSStyleDeclaration = window.getComputedStyle(el); const overflow: Overflow = { overflowX: style.overflowX, overflowY: style.overflowY, }; return isEither(overflow, isScroll) || isEither(overflow, isAuto); }; // Special case for a body element // Playground: https://codepen.io/alexreardon/pen/ZmyLgX?editors=1111 const isBodyScrollable = (): boolean => { // Because we always return false for now, we can skip any actual processing in production if (process.env.NODE_ENV === 'production') { return false; } const body: HTMLBodyElement = getBodyElement(); const html: HTMLElement | null = document.documentElement; invariant(html); // 1. The `body` has `overflow-[x|y]: auto | scroll` if (!isElementScrollable(body)) { return false; } const htmlStyle: CSSStyleDeclaration = window.getComputedStyle(html); const htmlOverflow: Overflow = { overflowX: htmlStyle.overflowX, overflowY: htmlStyle.overflowY, }; if (isBoth(htmlOverflow, isVisible)) { return false; } warning(` We have detected that your element might be a scroll container. We have found no reliable way of detecting whether the element is a scroll container. Under most circumstances a scroll bar will be on the element (document.documentElement) Because we cannot determine if the is a scroll container, and generally it is not one, we will be treating the as *not* a scroll container More information: https://github.com/hello-pangea/dnd/blob/main/docs/guides/how-we-detect-scroll-containers.md `); return false; }; const getClosestScrollable = (el?: HTMLElement | null): HTMLElement | null => { // cannot do anything else! if (el == null) { return null; } // not allowing us to go higher then body if (el === document.body) { return isBodyScrollable() ? el : null; } // Should never get here, but just being safe if (el === document.documentElement) { return null; } if (!isElementScrollable(el)) { // keep recursing return getClosestScrollable(el.parentElement); } // success! return el; }; export default getClosestScrollable;