{"version":3,"file":"scroller.mjs","names":[],"sources":["../../../../package/common/virtualList/scroller.ts"],"sourcesContent":["import type { VirtualListScrollParent } from './virtualList.types';\n\n/**\n * Whatever carries the list's scroll, and how the list is measured against it.\n *\n * Everything here works in *list space*: offset 0 is the top of the first row,\n * which is the root's `scrollTop` when the list scrolls itself and its distance\n * above the scrollport's top edge when it doesn't. One coordinate system, so the\n * windowing, anchoring and jump math never branch.\n */\n\nexport type Scroller = HTMLElement | Window;\n\nexport function isWindowScroller(scroller: Scroller): scroller is Window {\n  return !('nodeType' in scroller);\n}\n\n/** `'nearest'` is resolved by the caller, which caches the ancestor walk. */\nexport function resolveScroller(\n  scrollParent: Exclude<VirtualListScrollParent, 'nearest'> | undefined,\n): Scroller | null {\n  if (scrollParent == null || typeof window === 'undefined') return null;\n  if (scrollParent === 'window') return window;\n  // Resolved on every commit, so a ref still empty on the first render costs one\n  // frame against the page rather than rendering nothing.\n  if (typeof scrollParent === 'function') return scrollParent() ?? window;\n  return scrollParent;\n}\n\n/** In viewport coordinates, which is what the rects below are compared against. */\nexport function scrollportBounds(scroller: Scroller): { top: number; height: number } {\n  if (isWindowScroller(scroller)) return { top: 0, height: scroller.innerHeight };\n  // `clientTop` is the border, which `getBoundingClientRect` includes and\n  // `clientHeight` doesn't: without it a bordered scroll parent is off by it.\n  return { top: scroller.getBoundingClientRect().top + scroller.clientTop, height: scroller.clientHeight };\n}\n\nexport interface Viewport {\n  /** List-space scroll position, unclamped: negative while the list starts below\n   *  the scrollport, which is what makes a target reachable from either side. */\n  rel: number;\n  /** The slice of the scrollport the list occupies now. */\n  visibleHeight: number;\n  /** The whole scrollport, which is what a scroll target is aimed within. */\n  viewportHeight: number;\n  /** From the list's last row to the end of the scrollport. The browser clamps\n   *  `scrollTop`, so a list that scrolls itself never reports this negative;\n   *  riding a scrollport, it does, for the whole rest of the page. */\n  distanceToEnd: number;\n  width: number;\n}\n\nexport function measureViewport(root: HTMLElement, scroller: Scroller | null): Viewport {\n  const width = root.clientWidth;\n  if (!scroller) {\n    const height = root.clientHeight;\n    return {\n      rel: root.scrollTop,\n      visibleHeight: height,\n      viewportHeight: height,\n      distanceToEnd: root.scrollHeight - root.scrollTop - height,\n      width,\n    };\n  }\n  const rect = root.getBoundingClientRect();\n  const { top, height } = scrollportBounds(scroller);\n  const bottom = top + height;\n  return {\n    rel: top - rect.top,\n    visibleHeight: Math.max(0, Math.min(bottom, rect.bottom) - Math.max(top, rect.top)),\n    viewportHeight: height,\n    distanceToEnd: rect.bottom - bottom,\n    width,\n  };\n}\n"],"mappings":"aAaA,SAAgB,EAAiB,EAAwC,CACvE,MAAO,EAAE,aAAc,EACzB,CAGA,SAAgB,EACd,EACiB,CAMjB,OALI,GAAgB,MAAQ,OAAO,OAAW,IAAoB,KAC9D,IAAiB,SAAiB,OAGlC,OAAO,GAAiB,WAAmB,EAAa,GAAK,OAC1D,CACT,CAGA,SAAgB,EAAiB,EAAqD,CAIpF,OAHI,EAAiB,CAAQ,EAAU,CAAE,IAAK,EAAG,OAAQ,EAAS,WAAY,EAGvE,CAAE,IAAK,EAAS,sBAAsB,CAAC,CAAC,IAAM,EAAS,UAAW,OAAQ,EAAS,YAAa,CACzG,CAiBA,SAAgB,EAAgB,EAAmB,EAAqC,CACtF,IAAM,EAAQ,EAAK,YACnB,GAAI,CAAC,EAAU,CACb,IAAM,EAAS,EAAK,aACpB,MAAO,CACL,IAAK,EAAK,UACV,cAAe,EACf,eAAgB,EAChB,cAAe,EAAK,aAAe,EAAK,UAAY,EACpD,OACF,CACF,CACA,IAAM,EAAO,EAAK,sBAAsB,EAClC,CAAE,MAAK,UAAW,EAAiB,CAAQ,EAC3C,EAAS,EAAM,EACrB,MAAO,CACL,IAAK,EAAM,EAAK,IAChB,cAAe,KAAK,IAAI,EAAG,KAAK,IAAI,EAAQ,EAAK,MAAM,EAAI,KAAK,IAAI,EAAK,EAAK,GAAG,CAAC,EAClF,eAAgB,EAChB,cAAe,EAAK,OAAS,EAC7B,OACF,CACF"}