{"version":3,"file":"index.js","sources":["../src/hooks/useIsomorphicLayoutEffect.ts","../src/loggerSystem.ts","../src/hooks/useSize.ts","../src/hooks/useChangedChildSizes.ts","../src/utils/correctItemSize.ts","../src/utils/approximatelyEqual.ts","../src/hooks/useScrollTop.ts","../src/domIOSystem.ts","../src/AATree.ts","../src/utils/binaryArraySearch.ts","../src/recalcSystem.ts","../src/sizeSystem.ts","../src/scrollToIndexSystem.ts","../src/stateFlagsSystem.ts","../src/propsReadySystem.ts","../src/initialTopMostItemIndexSystem.ts","../src/followOutputSystem.ts","../src/groupedListSystem.ts","../src/comparators.tsx","../src/sizeRangeSystem.ts","../src/listStateSystem.ts","../src/initialItemCountSystem.ts","../src/scrollSeekSystem.ts","../src/topItemCountSystem.ts","../src/totalListHeightSystem.ts","../src/utils/simpleMemoize.ts","../src/upwardScrollFixSystem.ts","../src/initialScrollTopSystem.ts","../src/alignToBottomSystem.ts","../src/windowScrollerSystem.ts","../src/scrollIntoViewSystem.ts","../src/listSystem.ts","../src/utils/positionStickyCssValue.ts","../src/hooks/useWindowViewportRect.ts","../src/utils/context.ts","../src/List.tsx","../src/gridSystem.ts","../src/Grid.tsx","../src/Table.tsx","../src/components.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect } from 'react'\n\nconst useIsomorphicLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect\n\nexport default useIsomorphicLayoutEffect\n","import * as u from '@virtuoso.dev/urx'\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\ndeclare namespace globalThis {\n let VIRTUOSO_LOG_LEVEL: LogLevel | undefined\n}\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\ndeclare namespace window {\n let VIRTUOSO_LOG_LEVEL: LogLevel | undefined\n}\n\nexport enum LogLevel {\n DEBUG,\n INFO,\n WARN,\n ERROR,\n}\nexport interface LogMessage {\n level: LogLevel\n message: any\n label: string\n}\n\nexport type Log = (label: string, message: any, level?: LogLevel) => void\n\nconst CONSOLE_METHOD_MAP = {\n [LogLevel.DEBUG]: 'debug',\n [LogLevel.INFO]: 'log',\n [LogLevel.WARN]: 'warn',\n [LogLevel.ERROR]: 'error',\n} as const\n\nconst getGlobalThis = () => (typeof globalThis === 'undefined' ? window : globalThis)\n\nexport const loggerSystem = u.system(\n () => {\n const logLevel = u.statefulStream(LogLevel.ERROR)\n const log = u.statefulStream((label: string, message: any, level: LogLevel = LogLevel.INFO) => {\n const currentLevel = getGlobalThis()['VIRTUOSO_LOG_LEVEL'] ?? u.getValue(logLevel)\n if (level >= currentLevel) {\n // eslint-disable-next-line no-console\n console[CONSOLE_METHOD_MAP[level]](\n '%creact-virtuoso: %c%s %o',\n 'color: #0253b3; font-weight: bold',\n 'color: initial',\n label,\n message\n )\n }\n })\n\n return {\n log,\n logLevel,\n }\n },\n [],\n { singleton: true }\n)\n","import { useRef } from 'react'\n\nexport type CallbackRefParam = HTMLElement | null\n\nexport function useSizeWithElRef(callback: (e: HTMLElement) => void, enabled = true) {\n const ref = useRef(null)\n\n let callbackRef = (_el: CallbackRefParam) => {\n void 0\n }\n\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {\n const element = entries[0].target as HTMLElement\n if (element.offsetParent !== null) {\n callback(element)\n }\n })\n\n callbackRef = (elRef: CallbackRefParam) => {\n if (elRef && enabled) {\n observer.observe(elRef)\n ref.current = elRef\n } else {\n if (ref.current) {\n observer.unobserve(ref.current)\n }\n ref.current = null\n }\n }\n }\n\n return { ref, callbackRef }\n}\n\nexport default function useSize(callback: (e: HTMLElement) => void, enabled = true) {\n return useSizeWithElRef(callback, enabled).callbackRef\n}\n","import { Log, LogLevel } from '../loggerSystem'\nimport { SizeRange } from '../sizeSystem'\nimport { useSizeWithElRef } from './useSize'\nimport { SizeFunction, ScrollContainerState } from '../interfaces'\nexport default function useChangedListContentsSizes(\n callback: (ranges: SizeRange[]) => void,\n itemSize: SizeFunction,\n enabled: boolean,\n scrollContainerStateCallback: (state: ScrollContainerState) => void,\n log: Log,\n gap?: (gap: number) => void,\n customScrollParent?: HTMLElement\n) {\n return useSizeWithElRef((el: HTMLElement) => {\n const ranges = getChangedChildSizes(el.children, itemSize, 'offsetHeight', log)\n let scrollableElement = el.parentElement!\n\n while (!scrollableElement.dataset['virtuosoScroller']) {\n scrollableElement = scrollableElement.parentElement!\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const windowScrolling = (scrollableElement.firstElementChild! as HTMLDivElement).dataset['viewportType']! === 'window'\n\n const scrollTop = customScrollParent\n ? customScrollParent.scrollTop\n : windowScrolling\n ? window.pageYOffset || document.documentElement.scrollTop\n : scrollableElement.scrollTop\n\n const scrollHeight = customScrollParent\n ? customScrollParent.scrollHeight\n : windowScrolling\n ? document.documentElement.scrollHeight\n : scrollableElement.scrollHeight\n\n const viewportHeight = customScrollParent\n ? customScrollParent.offsetHeight\n : windowScrolling\n ? window.innerHeight\n : scrollableElement.offsetHeight\n\n scrollContainerStateCallback({\n scrollTop: Math.max(scrollTop, 0),\n scrollHeight,\n viewportHeight,\n })\n\n gap?.(resolveGapValue('row-gap', getComputedStyle(el).rowGap, log))\n\n if (ranges !== null) {\n callback(ranges)\n }\n }, enabled)\n}\n\nfunction getChangedChildSizes(children: HTMLCollection, itemSize: SizeFunction, field: 'offsetHeight' | 'offsetWidth', log: Log) {\n const length = children.length\n\n if (length === 0) {\n return null\n }\n\n const results: SizeRange[] = []\n\n for (let i = 0; i < length; i++) {\n const child = children.item(i) as HTMLElement\n\n if (!child || child.dataset.index === undefined) {\n continue\n }\n\n const index = parseInt(child.dataset.index!)\n const knownSize = parseFloat(child.dataset.knownSize!)\n const size = itemSize(child, field)\n\n if (size === 0) {\n log('Zero-sized element, this should not happen', { child }, LogLevel.ERROR)\n }\n\n if (size === knownSize) {\n continue\n }\n\n const lastResult = results[results.length - 1]\n if (results.length === 0 || lastResult.size !== size || lastResult.endIndex !== index - 1) {\n results.push({ startIndex: index, endIndex: index, size })\n } else {\n results[results.length - 1].endIndex++\n }\n }\n\n return results\n}\n\nfunction resolveGapValue(property: string, value: string | undefined, log: Log) {\n if (value !== 'normal' && !value?.endsWith('px')) {\n log(`${property} was not resolved to pixel value correctly`, value, LogLevel.WARN)\n }\n if (value === 'normal') {\n return 0\n }\n return parseInt(value ?? '0', 10)\n}\n","export function correctItemSize(el: HTMLElement, dimension: 'height' | 'width') {\n return Math.round(el.getBoundingClientRect()[dimension])\n}\n","export function approximatelyEqual(num1: number, num2: number) {\n return Math.abs(num1 - num2) < 1.01\n}\n","import { useRef, useCallback, useEffect } from 'react'\nimport * as u from '@virtuoso.dev/urx'\nimport { correctItemSize } from '../utils/correctItemSize'\nimport { ScrollContainerState } from '../interfaces'\nimport { flushSync } from 'react-dom'\nimport { approximatelyEqual } from '../utils/approximatelyEqual'\n\nexport type ScrollerRef = Window | HTMLElement | null\n\nexport default function useScrollTop(\n scrollContainerStateCallback: (state: ScrollContainerState) => void,\n smoothScrollTargetReached: (yes: true) => void,\n scrollerElement: any,\n scrollerRefCallback: (ref: ScrollerRef) => void = u.noop,\n customScrollParent?: HTMLElement\n) {\n const scrollerRef = useRef(null)\n const scrollTopTarget = useRef(null)\n const timeoutRef = useRef | null>(null)\n const shouldFlushSync = useRef(false)\n\n const handler = useCallback(\n (ev: Event) => {\n const el = ev.target as HTMLElement\n const windowScroll = (el as any) === window || (el as any) === document\n const scrollTop = windowScroll ? window.pageYOffset || document.documentElement.scrollTop : el.scrollTop\n const scrollHeight = windowScroll ? document.documentElement.scrollHeight : el.scrollHeight\n const viewportHeight = windowScroll ? window.innerHeight : el.offsetHeight\n\n const call = () => {\n scrollContainerStateCallback({\n scrollTop: Math.max(scrollTop, 0),\n scrollHeight,\n viewportHeight,\n })\n }\n\n if (shouldFlushSync.current) {\n flushSync(call)\n } else {\n call()\n }\n shouldFlushSync.current = false\n\n if (scrollTopTarget.current !== null) {\n if (scrollTop === scrollTopTarget.current || scrollTop <= 0 || scrollTop === scrollHeight - viewportHeight) {\n scrollTopTarget.current = null\n smoothScrollTargetReached(true)\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current)\n timeoutRef.current = null\n }\n }\n }\n },\n [scrollContainerStateCallback, smoothScrollTargetReached]\n )\n\n useEffect(() => {\n const localRef = customScrollParent ? customScrollParent : scrollerRef.current!\n\n scrollerRefCallback(customScrollParent ? customScrollParent : scrollerRef.current)\n handler({ target: localRef } as unknown as Event)\n localRef.addEventListener('scroll', handler, { passive: true })\n\n return () => {\n scrollerRefCallback(null)\n localRef.removeEventListener('scroll', handler)\n }\n }, [scrollerRef, handler, scrollerElement, scrollerRefCallback, customScrollParent])\n\n function scrollToCallback(location: ScrollToOptions) {\n const scrollerElement = scrollerRef.current\n if (!scrollerElement || ('offsetHeight' in scrollerElement && scrollerElement.offsetHeight === 0)) {\n return\n }\n\n const isSmooth = location.behavior === 'smooth'\n\n let offsetHeight: number\n let scrollHeight: number\n let scrollTop: number\n\n if (scrollerElement === window) {\n // this is not a mistake\n scrollHeight = Math.max(correctItemSize(document.documentElement, 'height'), document.documentElement.scrollHeight)\n offsetHeight = window.innerHeight\n scrollTop = document.documentElement.scrollTop\n } else {\n scrollHeight = (scrollerElement as HTMLElement).scrollHeight\n offsetHeight = correctItemSize(scrollerElement as HTMLElement, 'height')\n scrollTop = (scrollerElement as HTMLElement).scrollTop\n }\n\n const maxScrollTop = scrollHeight - offsetHeight\n location.top = Math.ceil(Math.max(Math.min(maxScrollTop, location.top!), 0))\n\n // avoid system hanging because the DOM never called back\n // with the scrollTop\n // scroller is already at this location\n if (approximatelyEqual(offsetHeight, scrollHeight) || location.top === scrollTop) {\n scrollContainerStateCallback({ scrollTop, scrollHeight, viewportHeight: offsetHeight })\n if (isSmooth) {\n smoothScrollTargetReached(true)\n }\n return\n }\n\n if (isSmooth) {\n scrollTopTarget.current = location.top\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current)\n }\n\n timeoutRef.current = setTimeout(() => {\n timeoutRef.current = null\n scrollTopTarget.current = null\n smoothScrollTargetReached(true)\n }, 1000)\n } else {\n scrollTopTarget.current = null\n }\n\n scrollerElement.scrollTo(location)\n }\n\n function scrollByCallback(location: ScrollToOptions) {\n shouldFlushSync.current = true\n scrollerRef.current!.scrollBy(location)\n }\n\n return { scrollerRef, scrollByCallback, scrollToCallback }\n}\n","import * as u from '@virtuoso.dev/urx'\nimport { ScrollContainerState } from './interfaces'\n\nexport const domIOSystem = u.system(\n () => {\n const scrollContainerState = u.stream()\n const scrollTop = u.stream()\n const deviation = u.statefulStream(0)\n const smoothScrollTargetReached = u.stream()\n const statefulScrollTop = u.statefulStream(0)\n const viewportHeight = u.stream()\n const scrollHeight = u.stream()\n const headerHeight = u.statefulStream(0)\n const fixedHeaderHeight = u.statefulStream(0)\n const fixedFooterHeight = u.statefulStream(0)\n const footerHeight = u.statefulStream(0)\n const scrollTo = u.stream()\n const scrollBy = u.stream()\n const scrollingInProgress = u.statefulStream(false)\n // bogus, has no effect\n const react18ConcurrentRendering = u.statefulStream(false)\n\n u.connect(\n u.pipe(\n scrollContainerState,\n u.map(({ scrollTop }) => scrollTop)\n ),\n scrollTop\n )\n\n u.connect(\n u.pipe(\n scrollContainerState,\n u.map(({ scrollHeight }) => scrollHeight)\n ),\n scrollHeight\n )\n\n u.connect(scrollTop, statefulScrollTop)\n\n return {\n // input\n scrollContainerState,\n scrollTop,\n viewportHeight,\n headerHeight,\n fixedHeaderHeight,\n fixedFooterHeight,\n footerHeight,\n scrollHeight,\n smoothScrollTargetReached,\n react18ConcurrentRendering,\n\n // signals\n scrollTo,\n scrollBy,\n\n // state\n statefulScrollTop,\n deviation,\n scrollingInProgress,\n }\n },\n [],\n { singleton: true }\n)\n","interface NilNode {\n lvl: 0\n}\n\nconst NIL_NODE: NilNode = { lvl: 0 }\n\ninterface NodeData {\n k: number\n v: T\n}\n\ninterface NonNilAANode {\n k: number\n v: T\n lvl: number\n l: NonNilAANode | NilNode\n r: NonNilAANode | NilNode\n}\n\nexport interface Range {\n start: number\n end: number\n value: T\n}\n\nexport type AANode = NilNode | NonNilAANode\n\nfunction newAANode(k: number, v: T, lvl: number, l: AANode = NIL_NODE, r: AANode = NIL_NODE): NonNilAANode {\n return { k, v, lvl, l, r }\n}\n\nexport function empty(node: AANode): node is NilNode {\n return node === NIL_NODE\n}\n\nexport function newTree(): AANode {\n return NIL_NODE\n}\n\nexport function remove(node: AANode, key: number): AANode {\n if (empty(node)) return NIL_NODE\n\n const { k, l, r } = node\n\n if (key === k) {\n if (empty(l)) {\n return r\n } else if (empty(r)) {\n return l\n } else {\n const [lastKey, lastValue] = last(l)\n return adjust(clone(node, { k: lastKey, v: lastValue, l: deleteLast(l) }))\n }\n } else if (key < k) {\n return adjust(clone(node, { l: remove(l, key) }))\n } else {\n return adjust(clone(node, { r: remove(r, key) }))\n }\n}\n\nexport function find(node: AANode, key: number): T | undefined {\n if (empty(node)) {\n return\n }\n\n if (key === node.k) {\n return node.v\n } else if (key < node.k) {\n return find(node.l, key)\n } else {\n return find(node.r, key)\n }\n}\n\nexport function findMaxKeyValue(node: AANode, value: number, field: 'k' | 'v' = 'k'): [number, T | undefined] {\n if (empty(node)) {\n return [-Infinity, undefined]\n }\n\n if (node[field] === value) {\n return [node.k, node.v]\n }\n\n if (node[field] < value) {\n const r = findMaxKeyValue(node.r, value, field)\n if (r[0] === -Infinity) {\n return [node.k, node.v]\n } else {\n return r\n }\n }\n\n return findMaxKeyValue(node.l, value, field)\n}\n\nexport function insert(node: AANode, k: number, v: T): NonNilAANode {\n if (empty(node)) {\n return newAANode(k, v, 1)\n }\n if (k === node.k) {\n return clone(node, { k, v })\n } else if (k < node.k) {\n return rebalance(clone(node, { l: insert(node.l, k, v) }))\n } else {\n return rebalance(clone(node, { r: insert(node.r, k, v) }))\n }\n}\n\nexport function walkWithin(node: AANode, start: number, end: number): NodeData[] {\n if (empty(node)) {\n return []\n }\n\n const { k, v, l, r } = node\n let result: NodeData[] = []\n if (k > start) {\n result = result.concat(walkWithin(l, start, end))\n }\n\n if (k >= start && k <= end) {\n result.push({ k, v })\n }\n\n if (k <= end) {\n result = result.concat(walkWithin(r, start, end))\n }\n\n return result\n}\n\nexport function walk(node: AANode): NodeData[] {\n if (empty(node)) {\n return []\n }\n\n return [...walk(node.l), { k: node.k, v: node.v }, ...walk(node.r)]\n}\n\nfunction last(node: NonNilAANode): [number, T] {\n return empty(node.r) ? [node.k, node.v] : last(node.r)\n}\n\nfunction deleteLast(node: NonNilAANode): AANode {\n return empty(node.r) ? node.l : adjust(clone(node, { r: deleteLast(node.r) }))\n}\n\nfunction clone(node: NonNilAANode, args: Partial>): NonNilAANode {\n return newAANode(\n args.k !== undefined ? args.k : node.k,\n args.v !== undefined ? args.v : node.v,\n args.lvl !== undefined ? args.lvl : node.lvl,\n args.l !== undefined ? args.l : node.l,\n args.r !== undefined ? args.r : node.r\n )\n}\n\nfunction isSingle(node: AANode) {\n return empty(node) || node.lvl > node.r.lvl\n}\n\nfunction rebalance(node: NonNilAANode): NonNilAANode {\n return split(skew(node))\n}\n\nfunction adjust(node: NonNilAANode): NonNilAANode {\n const { l, r, lvl } = node\n if (r.lvl >= lvl - 1 && l.lvl >= lvl - 1) {\n return node\n } else if (lvl > r.lvl + 1) {\n if (isSingle(l)) {\n return skew(clone(node, { lvl: lvl - 1 }))\n } else {\n if (!empty(l) && !empty(l.r)) {\n return clone(l.r, {\n l: clone(l, { r: l.r.l }),\n r: clone(node, {\n l: l.r.r,\n lvl: lvl - 1,\n }),\n lvl: lvl,\n })\n } else {\n throw new Error('Unexpected empty nodes')\n }\n }\n } else {\n if (isSingle(node)) {\n return split(clone(node, { lvl: lvl - 1 }))\n } else {\n if (!empty(r) && !empty(r.l)) {\n const rl = r.l\n const rlvl = isSingle(rl) ? r.lvl - 1 : r.lvl\n\n return clone(rl, {\n l: clone(node, {\n r: rl.l,\n lvl: lvl - 1,\n }),\n r: split(clone(r, { l: rl.r, lvl: rlvl })),\n lvl: rl.lvl + 1,\n })\n } else {\n throw new Error('Unexpected empty nodes')\n }\n }\n }\n}\n\nexport function keys(node: AANode): number[] {\n if (empty(node)) {\n return []\n }\n return [...keys(node.l), node.k, ...keys(node.r)]\n}\n\nexport function ranges(node: AANode): Range[] {\n return toRanges(walk(node))\n}\n\nexport function rangesWithin(node: AANode, startIndex: number, endIndex: number): Range[] {\n if (empty(node)) {\n return []\n }\n const adjustedStart = findMaxKeyValue(node, startIndex)[0]\n return toRanges(walkWithin(node, adjustedStart, endIndex))\n}\n\nexport function arrayToRanges(\n items: T[],\n parser: (item: T) => { index: number; value: V }\n): Array<{ start: number; end: number; value: V }> {\n const length = items.length\n if (length === 0) {\n return []\n }\n\n let { index: start, value } = parser(items[0])\n\n const result = []\n\n for (let i = 1; i < length; i++) {\n const { index: nextIndex, value: nextValue } = parser(items[i])\n result.push({ start, end: nextIndex - 1, value })\n\n start = nextIndex\n value = nextValue\n }\n\n result.push({ start, end: Infinity, value })\n return result\n}\n\nfunction toRanges(nodes: NodeData[]): Range[] {\n return arrayToRanges(nodes, ({ k: index, v: value }) => ({ index, value }))\n}\n\nfunction split(node: NonNilAANode): NonNilAANode {\n const { r, lvl } = node\n\n return !empty(r) && !empty(r.r) && r.lvl === lvl && r.r.lvl === lvl ? clone(r, { l: clone(node, { r: r.l }), lvl: lvl + 1 }) : node\n}\n\nfunction skew(node: NonNilAANode): NonNilAANode {\n const { l } = node\n\n return !empty(l) && l.lvl === node.lvl ? clone(l, { r: clone(node, { l: l.r }) }) : node\n}\n","export type Comparator = {\n (item: T, value: number): -1 | 0 | 1\n}\n\nexport function findIndexOfClosestSmallerOrEqual(items: T[], value: number, comparator: Comparator, start = 0): number {\n let end = items.length - 1\n\n while (start <= end) {\n const index = Math.floor((start + end) / 2)\n const item = items[index]\n const match = comparator(item, value)\n if (match === 0) {\n return index\n }\n\n if (match === -1) {\n if (end - start < 2) {\n return index - 1\n }\n end = index - 1\n } else {\n if (end === start) {\n return index\n }\n start = index + 1\n }\n }\n\n throw new Error(`Failed binary finding record in array - ${items.join(',')}, searched for ${value}`)\n}\n\nexport function findClosestSmallerOrEqual(items: T[], value: number, comparator: Comparator): T {\n return items[findIndexOfClosestSmallerOrEqual(items, value, comparator)]\n}\n\nexport function findRange(items: T[], startValue: number, endValue: number, comparator: Comparator): T[] {\n const startIndex = findIndexOfClosestSmallerOrEqual(items, startValue, comparator)\n const endIndex = findIndexOfClosestSmallerOrEqual(items, endValue, comparator, startIndex)\n return items.slice(startIndex, endIndex + 1)\n}\n","import * as u from '@virtuoso.dev/urx'\n\nexport const recalcSystem = u.system(\n () => {\n const recalcInProgress = u.statefulStream(false)\n return { recalcInProgress }\n },\n [],\n { singleton: true }\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { arrayToRanges, AANode, empty, findMaxKeyValue, insert, newTree, Range, rangesWithin, remove, walk } from './AATree'\nimport * as arrayBinarySearch from './utils/binaryArraySearch'\nimport { correctItemSize } from './utils/correctItemSize'\nimport { loggerSystem, Log, LogLevel } from './loggerSystem'\nimport { recalcSystem } from './recalcSystem'\nimport { SizeFunction } from './interfaces'\n\nexport interface SizeRange {\n startIndex: number\n endIndex: number\n size: number\n}\n\nexport type Data = readonly unknown[] | undefined\n\nfunction rangeIncludes(refRange: SizeRange) {\n const { size, startIndex, endIndex } = refRange\n return (range: Range) => {\n return range.start === startIndex && (range.end === endIndex || range.end === Infinity) && range.value === size\n }\n}\n\nexport function insertRanges(sizeTree: AANode, ranges: SizeRange[]) {\n let syncStart = empty(sizeTree) ? 0 : Infinity\n\n for (const range of ranges) {\n const { size, startIndex, endIndex } = range\n syncStart = Math.min(syncStart, startIndex)\n\n if (empty(sizeTree)) {\n sizeTree = insert(sizeTree, 0, size)\n continue\n }\n\n // extend the range in both directions, so that we can get adjacent neighbours.\n // if the previous / next ones have the same value as the one we are about to insert,\n // we 'merge' them.\n const overlappingRanges = rangesWithin(sizeTree, startIndex - 1, endIndex + 1)\n\n if (overlappingRanges.some(rangeIncludes(range))) {\n continue\n }\n\n let firstPassDone = false\n let shouldInsert = false\n for (const { start: rangeStart, end: rangeEnd, value: rangeValue } of overlappingRanges) {\n // previous range\n if (!firstPassDone) {\n shouldInsert = rangeValue !== size\n firstPassDone = true\n } else {\n // remove the range if it starts within the new range OR if\n // it has the same value as it, in order to perform a merge\n if (endIndex >= rangeStart || size === rangeValue) {\n sizeTree = remove(sizeTree, rangeStart)\n }\n }\n\n // next range\n if (rangeEnd > endIndex && endIndex >= rangeStart) {\n if (rangeValue !== size) {\n sizeTree = insert(sizeTree, endIndex + 1, rangeValue)\n }\n }\n }\n\n if (shouldInsert) {\n sizeTree = insert(sizeTree, startIndex, size)\n }\n }\n return [sizeTree, syncStart] as const\n}\n\nexport interface OffsetPoint {\n offset: number\n size: number\n index: number\n}\n\nexport interface SizeState {\n sizeTree: AANode\n offsetTree: Array\n groupOffsetTree: AANode\n lastIndex: number\n lastOffset: number\n lastSize: number\n groupIndices: number[]\n}\n\nexport function initialSizeState(): SizeState {\n return {\n offsetTree: [],\n sizeTree: newTree(),\n groupOffsetTree: newTree(),\n lastIndex: 0,\n lastOffset: 0,\n lastSize: 0,\n groupIndices: [],\n }\n}\n\nexport function indexComparator({ index: itemIndex }: OffsetPoint, index: number) {\n return index === itemIndex ? 0 : index < itemIndex ? -1 : 1\n}\n\nexport function offsetComparator({ offset: itemOffset }: OffsetPoint, offset: number) {\n return offset === itemOffset ? 0 : offset < itemOffset ? -1 : 1\n}\n\nfunction offsetPointParser(point: OffsetPoint) {\n return { index: point.index, value: point }\n}\n\nexport function rangesWithinOffsets(\n tree: Array,\n startOffset: number,\n endOffset: number,\n minStartIndex = 0\n): Array<{\n start: number\n end: number\n value: {\n size: number\n offset: number\n index: number\n }\n}> {\n if (minStartIndex > 0) {\n startOffset = Math.max(startOffset, arrayBinarySearch.findClosestSmallerOrEqual(tree, minStartIndex, indexComparator).offset)\n }\n\n return arrayToRanges(arrayBinarySearch.findRange(tree, startOffset, endOffset, offsetComparator), offsetPointParser)\n}\n\nfunction createOffsetTree(prevOffsetTree: OffsetPoint[], syncStart: number, sizeTree: AANode, gap: number) {\n let offsetTree = prevOffsetTree\n let prevIndex = 0\n let prevSize = 0\n\n let prevOffset = 0\n let startIndex = 0\n\n if (syncStart !== 0) {\n startIndex = arrayBinarySearch.findIndexOfClosestSmallerOrEqual(offsetTree, syncStart - 1, indexComparator)\n const offsetInfo = offsetTree[startIndex]\n prevOffset = offsetInfo.offset\n const kv = findMaxKeyValue(sizeTree, syncStart - 1)\n prevIndex = kv[0]\n prevSize = kv[1]!\n\n if (offsetTree.length && offsetTree[startIndex].size === findMaxKeyValue(sizeTree, syncStart)[1]) {\n startIndex -= 1\n }\n\n offsetTree = offsetTree.slice(0, startIndex + 1)\n } else {\n offsetTree = []\n }\n\n for (const { start: startIndex, value } of rangesWithin(sizeTree, syncStart, Infinity)) {\n const indexOffset = startIndex - prevIndex\n const aOffset = indexOffset * prevSize + prevOffset + indexOffset * gap\n offsetTree.push({\n offset: aOffset,\n size: value,\n index: startIndex,\n })\n prevIndex = startIndex\n prevOffset = aOffset\n prevSize = value\n }\n\n return {\n offsetTree,\n lastIndex: prevIndex,\n lastOffset: prevOffset,\n lastSize: prevSize,\n }\n}\n\nexport function sizeStateReducer(state: SizeState, [ranges, groupIndices, log, gap]: [SizeRange[], number[], Log, number]) {\n if (ranges.length > 0) {\n log('received item sizes', ranges, LogLevel.DEBUG)\n }\n const sizeTree = state.sizeTree\n let newSizeTree: AANode = sizeTree\n let syncStart = 0\n\n // We receive probe item results from a group probe,\n // which should always pass an item and a group\n // the results contain two ranges, which we consider to mean that groups and items have different size\n if (groupIndices.length > 0 && empty(sizeTree) && ranges.length === 2) {\n const groupSize = ranges[0].size\n const itemSize = ranges[1].size\n newSizeTree = groupIndices.reduce((tree, groupIndex) => {\n return insert(insert(tree, groupIndex, groupSize), groupIndex + 1, itemSize)\n }, newSizeTree)\n } else {\n ;[newSizeTree, syncStart] = insertRanges(newSizeTree, ranges)\n }\n\n if (newSizeTree === sizeTree) {\n return state\n }\n\n const { offsetTree: newOffsetTree, lastIndex, lastSize, lastOffset } = createOffsetTree(state.offsetTree, syncStart, newSizeTree, gap)\n\n return {\n sizeTree: newSizeTree,\n offsetTree: newOffsetTree,\n lastIndex,\n lastOffset,\n lastSize,\n groupOffsetTree: groupIndices.reduce((tree, index) => {\n return insert(tree, index, offsetOf(index, newOffsetTree, gap))\n }, newTree()),\n groupIndices,\n }\n}\n\nexport function offsetOf(index: number, tree: Array, gap: number) {\n if (tree.length === 0) {\n return 0\n }\n\n const { offset, index: startIndex, size } = arrayBinarySearch.findClosestSmallerOrEqual(tree, index, indexComparator)\n const itemCount = index - startIndex\n const top = size * itemCount + (itemCount - 1) * gap + offset\n return top > 0 ? top + gap : top\n}\n\nexport type FlatOrGroupedLocation = { index: number | 'LAST' } | { groupIndex: number }\n\nexport function isGroupLocation(location: FlatOrGroupedLocation): location is { groupIndex: number } {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n return typeof (location as any).groupIndex !== 'undefined'\n}\n\nexport function originalIndexFromLocation(location: FlatOrGroupedLocation, sizes: SizeState, lastIndex: number) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n if (isGroupLocation(location)) {\n // return the index of the first item below the index\n return sizes.groupIndices[location.groupIndex] + 1\n } else {\n const numericIndex = location.index === 'LAST' ? lastIndex : location.index\n let result = originalIndexFromItemIndex(numericIndex, sizes)\n result = Math.max(0, result, Math.min(lastIndex, result))\n return result\n }\n}\n\nexport function originalIndexFromItemIndex(itemIndex: number, sizes: SizeState) {\n if (!hasGroups(sizes)) {\n return itemIndex\n }\n\n let groupOffset = 0\n while (sizes.groupIndices[groupOffset] <= itemIndex + groupOffset) {\n groupOffset++\n }\n // we find the real item index, offsetting it by the number of group items before it\n return itemIndex + groupOffset\n}\n\nexport function hasGroups(sizes: SizeState) {\n return !empty(sizes.groupOffsetTree)\n}\n\ntype OptionalNumber = number | undefined\n\nconst SIZE_MAP = {\n offsetHeight: 'height',\n offsetWidth: 'width',\n} as const\n\nexport const sizeSystem = u.system(\n ([{ log }, { recalcInProgress }]) => {\n const sizeRanges = u.stream()\n const totalCount = u.stream()\n const statefulTotalCount = u.statefulStreamFromEmitter(totalCount, 0)\n const unshiftWith = u.stream()\n const shiftWith = u.stream()\n const firstItemIndex = u.statefulStream(0)\n const groupIndices = u.statefulStream([] as number[])\n const fixedItemSize = u.statefulStream(undefined)\n const defaultItemSize = u.statefulStream(undefined)\n const itemSize = u.statefulStream((el, field) => correctItemSize(el, SIZE_MAP[field]))\n const data = u.statefulStream(undefined)\n const gap = u.statefulStream(0)\n const initial = initialSizeState()\n\n const sizes = u.statefulStreamFromEmitter(\n u.pipe(sizeRanges, u.withLatestFrom(groupIndices, log, gap), u.scan(sizeStateReducer, initial), u.distinctUntilChanged()),\n initial\n )\n\n u.connect(\n u.pipe(\n groupIndices,\n u.filter((indexes) => indexes.length > 0),\n u.withLatestFrom(sizes, gap),\n u.map(([groupIndices, sizes, gap]) => {\n const groupOffsetTree = groupIndices.reduce((tree, index, idx) => {\n return insert(tree, index, offsetOf(index, sizes.offsetTree, gap) || idx)\n }, newTree())\n\n return {\n ...sizes,\n groupIndices,\n groupOffsetTree,\n }\n })\n ),\n sizes\n )\n\n // decreasing the total count should remove any existing entries\n // beyond the last index - do this by publishing the default size as a range over them.\n u.connect(\n u.pipe(\n totalCount,\n u.withLatestFrom(sizes),\n u.filter(([totalCount, { lastIndex }]) => {\n return totalCount < lastIndex\n }),\n u.map(([totalCount, { lastIndex, lastSize }]) => {\n return [\n {\n startIndex: totalCount,\n endIndex: lastIndex,\n size: lastSize,\n },\n ] as SizeRange[]\n })\n ),\n sizeRanges\n )\n\n u.connect(fixedItemSize, defaultItemSize)\n\n const trackItemSizes = u.statefulStreamFromEmitter(\n u.pipe(\n fixedItemSize,\n u.map((size) => size === undefined)\n ),\n true\n )\n\n u.connect(\n u.pipe(\n defaultItemSize,\n u.filter((value) => {\n return value !== undefined && empty(u.getValue(sizes).sizeTree)\n }),\n u.map((size) => [{ startIndex: 0, endIndex: 0, size }] as SizeRange[])\n ),\n sizeRanges\n )\n\n const listRefresh = u.streamFromEmitter(\n u.pipe(\n sizeRanges,\n u.withLatestFrom(sizes),\n u.scan(\n ({ sizes: oldSizes }, [_, newSizes]) => {\n return {\n changed: newSizes !== oldSizes,\n sizes: newSizes,\n }\n },\n { changed: false, sizes: initial }\n ),\n u.map((value) => value.changed)\n )\n )\n\n u.subscribe(\n u.pipe(\n firstItemIndex,\n u.scan(\n (prev, next) => {\n return { diff: prev.prev - next, prev: next }\n },\n { diff: 0, prev: 0 }\n ),\n u.map((val) => val.diff)\n ),\n (offset) => {\n if (offset > 0) {\n u.publish(recalcInProgress, true)\n u.publish(unshiftWith, offset)\n } else if (offset < 0) {\n u.publish(shiftWith, offset)\n }\n }\n )\n\n u.subscribe(u.pipe(firstItemIndex, u.withLatestFrom(log)), ([index, log]) => {\n if (index < 0) {\n log(\n \"`firstItemIndex` prop should not be set to less than zero. If you don't know the total count, just use a very high value\",\n { firstItemIndex },\n LogLevel.ERROR\n )\n }\n })\n\n // Capture the current list top item before the sizes get refreshed\n const beforeUnshiftWith = u.streamFromEmitter(unshiftWith)\n\n u.connect(\n u.pipe(\n unshiftWith,\n u.withLatestFrom(sizes),\n u.map(([unshiftWith, sizes]) => {\n if (sizes.groupIndices.length > 0) {\n throw new Error('Virtuoso: prepending items does not work with groups')\n }\n\n return walk(sizes.sizeTree).reduce(\n (acc, { k: index, v: size }) => {\n return {\n ranges: [...acc.ranges, { startIndex: acc.prevIndex, endIndex: index + unshiftWith - 1, size: acc.prevSize }],\n prevIndex: index + unshiftWith,\n prevSize: size,\n }\n },\n {\n ranges: [] as SizeRange[],\n prevIndex: 0,\n prevSize: sizes.lastSize,\n }\n ).ranges\n })\n ),\n sizeRanges\n )\n\n const shiftWithOffset = u.streamFromEmitter(\n u.pipe(\n shiftWith,\n u.withLatestFrom(sizes, gap),\n u.map(([shiftWith, { offsetTree }, gap]) => {\n const newFirstItemIndex = -shiftWith\n return offsetOf(newFirstItemIndex, offsetTree, gap)\n })\n )\n )\n\n u.connect(\n u.pipe(\n shiftWith,\n u.withLatestFrom(sizes, gap),\n u.map(([shiftWith, sizes, gap]) => {\n if (sizes.groupIndices.length > 0) {\n throw new Error('Virtuoso: shifting items does not work with groups')\n }\n\n const newSizeTree = walk(sizes.sizeTree).reduce((acc, { k, v }) => {\n return insert(acc, Math.max(0, k + shiftWith), v)\n }, newTree())\n\n return {\n ...sizes,\n sizeTree: newSizeTree,\n ...createOffsetTree(sizes.offsetTree, 0, newSizeTree, gap),\n }\n })\n ),\n sizes\n )\n\n return {\n // input\n data,\n totalCount,\n sizeRanges,\n groupIndices,\n defaultItemSize,\n fixedItemSize,\n unshiftWith,\n shiftWith,\n shiftWithOffset,\n beforeUnshiftWith,\n firstItemIndex,\n gap,\n\n // output\n sizes,\n listRefresh,\n statefulTotalCount,\n trackItemSizes,\n itemSize,\n }\n },\n u.tup(loggerSystem, recalcSystem),\n { singleton: true }\n)\n","/* eslint-disable @typescript-eslint/no-unsafe-call */\nimport * as u from '@virtuoso.dev/urx'\nimport { findMaxKeyValue } from './AATree'\nimport { domIOSystem } from './domIOSystem'\nimport { offsetOf, originalIndexFromLocation, sizeSystem } from './sizeSystem'\nimport { IndexLocationWithAlign } from './interfaces'\nimport { loggerSystem, LogLevel } from './loggerSystem'\n\nexport type IndexLocation = number | IndexLocationWithAlign\n\nconst SUPPORTS_SCROLL_TO_OPTIONS = typeof document !== 'undefined' && 'scrollBehavior' in document.documentElement.style\n\nexport function normalizeIndexLocation(location: IndexLocation) {\n const result: IndexLocationWithAlign = typeof location === 'number' ? { index: location } : location\n\n if (!result.align) {\n result.align = 'start'\n }\n if (!result.behavior || !SUPPORTS_SCROLL_TO_OPTIONS) {\n result.behavior = 'auto'\n }\n if (!result.offset) {\n result.offset = 0\n }\n\n return result\n}\n\nexport const scrollToIndexSystem = u.system(\n ([\n { sizes, totalCount, listRefresh, gap },\n {\n scrollingInProgress,\n viewportHeight,\n scrollTo,\n smoothScrollTargetReached,\n headerHeight,\n footerHeight,\n fixedHeaderHeight,\n fixedFooterHeight,\n },\n { log },\n ]) => {\n const scrollToIndex = u.stream()\n const topListHeight = u.statefulStream(0)\n\n let unsubscribeNextListRefresh: any = null\n let cleartTimeoutRef: ReturnType | null = null\n let unsubscribeListRefresh: any = null\n\n function cleanup() {\n if (unsubscribeNextListRefresh) {\n unsubscribeNextListRefresh()\n unsubscribeNextListRefresh = null\n }\n\n if (unsubscribeListRefresh) {\n unsubscribeListRefresh()\n unsubscribeListRefresh = null\n }\n\n if (cleartTimeoutRef) {\n clearTimeout(cleartTimeoutRef)\n cleartTimeoutRef = null\n }\n u.publish(scrollingInProgress, false)\n }\n\n u.connect(\n u.pipe(\n scrollToIndex,\n u.withLatestFrom(sizes, viewportHeight, totalCount, topListHeight, headerHeight, footerHeight, log),\n u.withLatestFrom(gap, fixedHeaderHeight, fixedFooterHeight),\n u.map(\n ([\n [location, sizes, viewportHeight, totalCount, topListHeight, headerHeight, footerHeight, log],\n gap,\n fixedHeaderHeight,\n fixedFooterHeight,\n ]) => {\n const normalLocation = normalizeIndexLocation(location)\n const { align, behavior, offset } = normalLocation\n const lastIndex = totalCount - 1\n\n const index = originalIndexFromLocation(normalLocation, sizes, lastIndex)\n\n let top = offsetOf(index, sizes.offsetTree, gap) + headerHeight\n if (align === 'end') {\n top += fixedHeaderHeight + findMaxKeyValue(sizes.sizeTree, index)[1]! - viewportHeight + fixedFooterHeight\n if (index === lastIndex) {\n top += footerHeight\n }\n } else if (align === 'center') {\n top += (fixedHeaderHeight + findMaxKeyValue(sizes.sizeTree, index)[1]! - viewportHeight + fixedFooterHeight) / 2\n } else {\n top -= topListHeight\n }\n\n if (offset) {\n top += offset\n }\n\n const retry = (listChanged: boolean) => {\n cleanup()\n if (listChanged) {\n log('retrying to scroll to', { location }, LogLevel.DEBUG)\n u.publish(scrollToIndex, location)\n } else {\n log('list did not change, scroll successful', {}, LogLevel.DEBUG)\n }\n }\n\n cleanup()\n\n if (behavior === 'smooth') {\n let listChanged = false\n unsubscribeListRefresh = u.subscribe(listRefresh, (changed) => {\n listChanged = listChanged || changed\n })\n\n unsubscribeNextListRefresh = u.handleNext(smoothScrollTargetReached, () => {\n retry(listChanged)\n })\n } else {\n unsubscribeNextListRefresh = u.handleNext(u.pipe(listRefresh, watchChangesFor(150)), retry)\n }\n\n // if the scroll jump is too small, the list won't get rerendered.\n // clean this listener\n cleartTimeoutRef = setTimeout(() => {\n cleanup()\n }, 1200)\n\n u.publish(scrollingInProgress, true)\n log('scrolling from index to', { index, top, behavior }, LogLevel.DEBUG)\n return { top, behavior }\n }\n )\n ),\n scrollTo\n )\n\n return {\n scrollToIndex,\n topListHeight,\n }\n },\n u.tup(sizeSystem, domIOSystem, loggerSystem),\n { singleton: true }\n)\n\nfunction watchChangesFor(limit: number): u.Operator {\n return (done) => {\n const timeoutRef = setTimeout(() => {\n done(false)\n }, limit)\n return (value) => {\n if (value) {\n done(true)\n clearTimeout(timeoutRef)\n }\n }\n }\n}\n","/* eslint-disable @typescript-eslint/no-unsafe-argument */\nimport * as u from '@virtuoso.dev/urx'\nimport { domIOSystem } from './domIOSystem'\nimport { approximatelyEqual } from './utils/approximatelyEqual'\n\nexport const UP = 'up' as const\nexport const DOWN = 'down' as const\nexport const NONE = 'none' as const\nexport type ScrollDirection = typeof UP | typeof DOWN | typeof NONE\n\nexport interface ListBottomInfo {\n bottom: number\n offsetBottom: number\n}\n\nexport interface AtBottomParams {\n offsetBottom: number\n scrollTop: number\n viewportHeight: number\n scrollHeight: number\n}\n\nexport type NotAtBottomReason =\n | 'SIZE_INCREASED'\n | 'NOT_SHOWING_LAST_ITEM'\n | 'VIEWPORT_HEIGHT_DECREASING'\n | 'SCROLLING_UPWARDS'\n | 'NOT_FULLY_SCROLLED_TO_LAST_ITEM_BOTTOM'\n\nexport type AtBottomReason = 'SIZE_DECREASED' | 'SCROLLED_DOWN'\n\nexport type AtBottomState =\n | {\n atBottom: false\n notAtBottomBecause: NotAtBottomReason\n state: AtBottomParams\n }\n | {\n atBottom: true\n state: AtBottomParams\n atBottomBecause: AtBottomReason\n scrollTopDelta: number\n }\n\nconst INITIAL_BOTTOM_STATE = {\n atBottom: false,\n notAtBottomBecause: 'NOT_SHOWING_LAST_ITEM',\n state: {\n offsetBottom: 0,\n scrollTop: 0,\n viewportHeight: 0,\n scrollHeight: 0,\n },\n} as AtBottomState\n\nconst DEFAULT_AT_TOP_THRESHOLD = 0\n\nexport const stateFlagsSystem = u.system(([{ scrollContainerState, scrollTop, viewportHeight, headerHeight, footerHeight, scrollBy }]) => {\n const isAtBottom = u.statefulStream(false)\n const isAtTop = u.statefulStream(true)\n const atBottomStateChange = u.stream()\n const atTopStateChange = u.stream()\n const atBottomThreshold = u.statefulStream(4)\n const atTopThreshold = u.statefulStream(DEFAULT_AT_TOP_THRESHOLD)\n\n // skip 1 to avoid an initial on/off flick\n const isScrolling = u.statefulStreamFromEmitter(\n u.pipe(\n u.merge(u.pipe(u.duc(scrollTop), u.skip(1), u.mapTo(true)), u.pipe(u.duc(scrollTop), u.skip(1), u.mapTo(false), u.debounceTime(100))),\n u.distinctUntilChanged()\n ),\n false\n )\n\n const isScrollingBy = u.statefulStreamFromEmitter(\n u.pipe(u.merge(u.pipe(scrollBy, u.mapTo(true)), u.pipe(scrollBy, u.mapTo(false), u.debounceTime(200))), u.distinctUntilChanged()),\n false\n )\n\n // u.subscribe(isScrollingBy, (isScrollingBy) => console.log({ isScrollingBy }))\n\n u.connect(\n u.pipe(\n u.combineLatest(u.duc(scrollTop), u.duc(atTopThreshold)),\n u.map(([top, atTopThreshold]) => top <= atTopThreshold),\n u.distinctUntilChanged()\n ),\n isAtTop\n )\n\n u.connect(u.pipe(isAtTop, u.throttleTime(50)), atTopStateChange)\n\n const atBottomState = u.streamFromEmitter(\n u.pipe(\n u.combineLatest(scrollContainerState, u.duc(viewportHeight), u.duc(headerHeight), u.duc(footerHeight), u.duc(atBottomThreshold)),\n u.scan((current, [{ scrollTop, scrollHeight }, viewportHeight, _headerHeight, _footerHeight, atBottomThreshold]) => {\n const isAtBottom = scrollTop + viewportHeight - scrollHeight > -atBottomThreshold\n const state = {\n viewportHeight,\n scrollTop,\n scrollHeight,\n }\n\n if (isAtBottom) {\n let atBottomBecause: 'SIZE_DECREASED' | 'SCROLLED_DOWN'\n let scrollTopDelta: number\n if (scrollTop > current.state.scrollTop) {\n atBottomBecause = 'SCROLLED_DOWN'\n scrollTopDelta = current.state.scrollTop - scrollTop\n } else {\n atBottomBecause = 'SIZE_DECREASED'\n scrollTopDelta = current.state.scrollTop - scrollTop || (current as { scrollTopDelta: number }).scrollTopDelta\n }\n return {\n atBottom: true,\n state,\n atBottomBecause,\n scrollTopDelta,\n } as AtBottomState\n }\n\n let notAtBottomBecause: NotAtBottomReason\n\n if (state.scrollHeight > current.state.scrollHeight) {\n notAtBottomBecause = 'SIZE_INCREASED'\n } else if (viewportHeight < current.state.viewportHeight) {\n notAtBottomBecause = 'VIEWPORT_HEIGHT_DECREASING'\n } else if (scrollTop < current.state.scrollTop) {\n notAtBottomBecause = 'SCROLLING_UPWARDS'\n } else {\n notAtBottomBecause = 'NOT_FULLY_SCROLLED_TO_LAST_ITEM_BOTTOM'\n }\n\n return {\n atBottom: false,\n notAtBottomBecause,\n state,\n } as AtBottomState\n }, INITIAL_BOTTOM_STATE),\n u.distinctUntilChanged((prev, next) => {\n return prev && prev.atBottom === next.atBottom\n })\n )\n )\n\n const lastJumpDueToItemResize = u.statefulStreamFromEmitter(\n u.pipe(\n scrollContainerState,\n u.scan(\n (current, { scrollTop, scrollHeight, viewportHeight }) => {\n if (!approximatelyEqual(current.scrollHeight, scrollHeight)) {\n const atBottom = scrollHeight - (scrollTop + viewportHeight) < 1\n\n if (current.scrollTop !== scrollTop && atBottom) {\n return {\n scrollHeight,\n scrollTop,\n jump: current.scrollTop - scrollTop,\n changed: true,\n }\n } else {\n return {\n scrollHeight,\n scrollTop,\n jump: 0,\n changed: true,\n }\n }\n } else {\n return {\n scrollTop,\n scrollHeight,\n jump: 0,\n changed: false,\n }\n }\n },\n { scrollHeight: 0, jump: 0, scrollTop: 0, changed: false }\n ),\n u.filter((value) => value.changed),\n u.map((value) => value.jump)\n ),\n 0\n )\n\n u.connect(\n u.pipe(\n atBottomState,\n u.map((state) => state.atBottom)\n ),\n isAtBottom\n )\n\n u.connect(u.pipe(isAtBottom, u.throttleTime(50)), atBottomStateChange)\n\n const scrollDirection = u.statefulStream(DOWN)\n\n u.connect(\n u.pipe(\n scrollContainerState,\n u.map(({ scrollTop }) => scrollTop),\n u.distinctUntilChanged(),\n u.scan(\n (acc, scrollTop) => {\n // if things change while compensating for items, ignore,\n // but store the new scrollTop\n if (u.getValue(isScrollingBy)) {\n return { direction: acc.direction, prevScrollTop: scrollTop }\n }\n\n return { direction: scrollTop < acc.prevScrollTop ? UP : DOWN, prevScrollTop: scrollTop }\n },\n { direction: DOWN, prevScrollTop: 0 } as { direction: ScrollDirection; prevScrollTop: number }\n ),\n u.map((value) => value.direction)\n ),\n scrollDirection\n )\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n u.connect(u.pipe(scrollContainerState, u.throttleTime(50), u.mapTo(NONE)), scrollDirection)\n\n const scrollVelocity = u.statefulStream(0)\n\n u.connect(\n u.pipe(\n isScrolling,\n u.filter((value) => !value),\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n u.mapTo(0)\n ),\n scrollVelocity\n )\n\n u.connect(\n u.pipe(\n scrollTop,\n u.throttleTime(100),\n u.withLatestFrom(isScrolling),\n u.filter(([_, isScrolling]) => !!isScrolling),\n u.scan(([_, prev], [next]) => [prev, next], [0, 0]),\n u.map(([prev, next]) => next - prev)\n ),\n scrollVelocity\n )\n\n return {\n isScrolling,\n isAtTop,\n isAtBottom,\n atBottomState,\n atTopStateChange,\n atBottomStateChange,\n scrollDirection,\n atBottomThreshold,\n atTopThreshold,\n scrollVelocity,\n lastJumpDueToItemResize,\n }\n}, u.tup(domIOSystem))\n","import * as u from '@virtuoso.dev/urx'\nimport { loggerSystem, LogLevel } from './loggerSystem'\n\nexport const propsReadySystem = u.system(\n ([{ log }]) => {\n const propsReady = u.statefulStream(false)\n\n const didMount = u.streamFromEmitter(\n u.pipe(\n propsReady,\n u.filter((ready) => ready),\n u.distinctUntilChanged()\n )\n )\n u.subscribe(propsReady, (value) => {\n value && u.getValue(log)('props updated', {}, LogLevel.DEBUG)\n })\n\n return { propsReady, didMount }\n },\n u.tup(loggerSystem),\n { singleton: true }\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { empty } from './AATree'\nimport { sizeSystem } from './sizeSystem'\nimport { domIOSystem } from './domIOSystem'\nimport { scrollToIndexSystem } from './scrollToIndexSystem'\nimport { propsReadySystem } from './propsReadySystem'\nimport { FlatIndexLocationWithAlign } from './interfaces'\n\nexport function getInitialTopMostItemIndexNumber(location: number | FlatIndexLocationWithAlign, totalCount: number): number {\n const lastIndex = totalCount - 1\n const index = typeof location === 'number' ? location : location.index === 'LAST' ? lastIndex : location.index\n return index\n}\n\nexport const initialTopMostItemIndexSystem = u.system(\n ([{ sizes, listRefresh, defaultItemSize }, { scrollTop }, { scrollToIndex }, { didMount }]) => {\n const scrolledToInitialItem = u.statefulStream(true)\n const initialTopMostItemIndex = u.statefulStream(0)\n\n u.connect(\n u.pipe(\n didMount,\n u.withLatestFrom(initialTopMostItemIndex),\n u.filter(([_, location]) => !!location),\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n u.mapTo(false)\n ),\n scrolledToInitialItem\n )\n\n u.subscribe(\n u.pipe(\n u.combineLatest(listRefresh, didMount),\n u.withLatestFrom(scrolledToInitialItem, sizes, defaultItemSize),\n u.filter(([[, didMount], scrolledToInitialItem, { sizeTree }, defaultItemSize]) => {\n return didMount && (!empty(sizeTree) || defaultItemSize !== undefined) && !scrolledToInitialItem\n }),\n u.withLatestFrom(initialTopMostItemIndex)\n ),\n ([, initialTopMostItemIndex]) => {\n setTimeout(() => {\n u.handleNext(scrollTop, () => {\n u.publish(scrolledToInitialItem, true)\n })\n u.publish(scrollToIndex, initialTopMostItemIndex)\n })\n }\n )\n\n return {\n scrolledToInitialItem,\n initialTopMostItemIndex,\n }\n },\n u.tup(sizeSystem, domIOSystem, scrollToIndexSystem, propsReadySystem),\n { singleton: true }\n)\n","/* eslint-disable @typescript-eslint/no-unsafe-call */\nimport * as u from '@virtuoso.dev/urx'\nimport { scrollToIndexSystem } from './scrollToIndexSystem'\nimport { sizeSystem } from './sizeSystem'\nimport { stateFlagsSystem } from './stateFlagsSystem'\nimport { initialTopMostItemIndexSystem } from './initialTopMostItemIndexSystem'\nimport { FollowOutput, FollowOutputScalarType } from './interfaces'\nimport { propsReadySystem } from './propsReadySystem'\nimport { loggerSystem, LogLevel } from './loggerSystem'\nimport { domIOSystem } from './domIOSystem'\n\nfunction normalizeFollowOutput(follow: FollowOutputScalarType): FollowOutputScalarType {\n if (!follow) {\n return false\n }\n return follow === 'smooth' ? 'smooth' : 'auto'\n}\n\nconst behaviorFromFollowOutput = (follow: FollowOutput, isAtBottom: boolean) => {\n if (typeof follow === 'function') {\n return normalizeFollowOutput(follow(isAtBottom))\n }\n return isAtBottom && normalizeFollowOutput(follow)\n}\n\nexport const followOutputSystem = u.system(\n ([\n { totalCount, listRefresh },\n { isAtBottom, atBottomState },\n { scrollToIndex },\n { scrolledToInitialItem },\n { propsReady, didMount },\n { log },\n { scrollingInProgress },\n ]) => {\n const followOutput = u.statefulStream(false)\n const autoscrollToBottom = u.stream()\n let pendingScrollHandle: any = null\n\n function scrollToBottom(followOutputBehavior: FollowOutputScalarType) {\n u.publish(scrollToIndex, {\n index: 'LAST',\n align: 'end',\n behavior: followOutputBehavior,\n })\n }\n\n u.subscribe(\n u.pipe(\n u.combineLatest(u.pipe(u.duc(totalCount), u.skip(1)), didMount),\n u.withLatestFrom(u.duc(followOutput), isAtBottom, scrolledToInitialItem, scrollingInProgress),\n u.map(([[totalCount, didMount], followOutput, isAtBottom, scrolledToInitialItem, scrollingInProgress]) => {\n let shouldFollow = didMount && scrolledToInitialItem\n let followOutputBehavior: FollowOutputScalarType = 'auto'\n\n if (shouldFollow) {\n // if scrolling to index is in progress,\n // assume that a previous followOutput response is going\n followOutputBehavior = behaviorFromFollowOutput(followOutput, isAtBottom || scrollingInProgress)\n shouldFollow = shouldFollow && !!followOutputBehavior\n }\n\n return { totalCount, shouldFollow, followOutputBehavior }\n }),\n u.filter(({ shouldFollow }) => shouldFollow)\n ),\n ({ totalCount, followOutputBehavior }) => {\n if (pendingScrollHandle) {\n pendingScrollHandle()\n pendingScrollHandle = null\n }\n\n pendingScrollHandle = u.handleNext(listRefresh, () => {\n u.getValue(log)('following output to ', { totalCount }, LogLevel.DEBUG)\n scrollToBottom(followOutputBehavior)\n pendingScrollHandle = null\n })\n }\n )\n\n function trapNextSizeIncrease(followOutput: boolean) {\n const cancel = u.handleNext(atBottomState, (state) => {\n if (followOutput && !state.atBottom && state.notAtBottomBecause === 'SIZE_INCREASED' && !pendingScrollHandle) {\n u.getValue(log)('scrolling to bottom due to increased size', {}, LogLevel.DEBUG)\n scrollToBottom('auto')\n }\n })\n setTimeout(cancel, 100)\n }\n\n u.subscribe(\n u.pipe(\n u.combineLatest(u.duc(followOutput), totalCount, propsReady),\n u.filter(([follow, , ready]) => follow && ready),\n u.scan(\n ({ value }, [, next]) => {\n return { refreshed: value === next, value: next }\n },\n { refreshed: false, value: 0 }\n ),\n u.filter(({ refreshed }) => refreshed),\n u.withLatestFrom(followOutput, totalCount)\n ),\n ([, followOutput]) => {\n trapNextSizeIncrease(followOutput !== false)\n }\n )\n\n u.subscribe(autoscrollToBottom, () => {\n trapNextSizeIncrease(u.getValue(followOutput) !== false)\n })\n\n u.subscribe(u.combineLatest(u.duc(followOutput), atBottomState), ([followOutput, state]) => {\n if (followOutput && !state.atBottom && state.notAtBottomBecause === 'VIEWPORT_HEIGHT_DECREASING') {\n scrollToBottom('auto')\n }\n })\n\n return { followOutput, autoscrollToBottom }\n },\n u.tup(sizeSystem, stateFlagsSystem, scrollToIndexSystem, initialTopMostItemIndexSystem, propsReadySystem, loggerSystem, domIOSystem)\n)\n","import * as u from '@virtuoso.dev/urx'\n\nimport { findMaxKeyValue } from './AATree'\nimport { domIOSystem } from './domIOSystem'\nimport { sizeSystem, hasGroups } from './sizeSystem'\nexport interface GroupIndexesAndCount {\n totalCount: number\n groupIndices: number[]\n}\n\nexport function groupCountsToIndicesAndCount(counts: number[]) {\n return counts.reduce(\n (acc, groupCount) => {\n acc.groupIndices.push(acc.totalCount)\n acc.totalCount += groupCount + 1\n return acc\n },\n {\n totalCount: 0,\n groupIndices: [],\n } as GroupIndexesAndCount\n )\n}\n\nexport const groupedListSystem = u.system(([{ totalCount, groupIndices, sizes }, { scrollTop, headerHeight }]) => {\n const groupCounts = u.stream()\n const topItemsIndexes = u.stream<[number]>()\n const groupIndicesAndCount = u.streamFromEmitter(u.pipe(groupCounts, u.map(groupCountsToIndicesAndCount)))\n u.connect(\n u.pipe(\n groupIndicesAndCount,\n u.map((value) => value.totalCount)\n ),\n totalCount\n )\n u.connect(\n u.pipe(\n groupIndicesAndCount,\n u.map((value) => value.groupIndices)\n ),\n groupIndices\n )\n\n u.connect(\n u.pipe(\n u.combineLatest(scrollTop, sizes, headerHeight),\n u.filter(([_, sizes]) => hasGroups(sizes)),\n u.map(([scrollTop, state, headerHeight]) => findMaxKeyValue(state.groupOffsetTree, Math.max(scrollTop - headerHeight, 0), 'v')[0]),\n u.distinctUntilChanged(),\n u.map((index) => [index])\n ),\n topItemsIndexes\n )\n\n return { groupCounts, topItemsIndexes }\n}, u.tup(sizeSystem, domIOSystem))\n","import { ListRange } from './interfaces'\n\nexport function tupleComparator(prev: [T1, T2] | undefined, current: [T1, T2]) {\n return !!(prev && prev[0] === current[0] && prev[1] === current[1])\n}\n\nexport function rangeComparator(prev: ListRange | undefined, next: ListRange) {\n return !!(prev && prev.startIndex === next.startIndex && prev.endIndex === next.endIndex)\n}\n","import * as u from '@virtuoso.dev/urx'\nimport { domIOSystem } from './domIOSystem'\nimport { UP, DOWN, ScrollDirection } from './stateFlagsSystem'\nimport { tupleComparator } from './comparators'\n\nexport type NumberTuple = [number, number]\nexport type Overscan = number | { main: number; reverse: number }\nexport const TOP = 'top' as const\nexport const BOTTOM = 'bottom' as const\nexport const NONE = 'none' as const\nexport type ListEnd = typeof TOP | typeof BOTTOM\nexport type ViewportIncrease = number | { [k in ListEnd]?: number }\nexport type ChangeDirection = typeof UP | typeof DOWN | typeof NONE\n\nexport function getOverscan(overscan: Overscan, end: ListEnd, direction: ScrollDirection) {\n if (typeof overscan === 'number') {\n return (direction === UP && end === TOP) || (direction === DOWN && end === BOTTOM) ? overscan : 0\n } else {\n if (direction === UP) {\n return end === TOP ? overscan.main : overscan.reverse\n } else {\n return end === BOTTOM ? overscan.main : overscan.reverse\n }\n }\n}\n\nfunction getViewportIncrease(value: ViewportIncrease, end: ListEnd) {\n return typeof value === 'number' ? value : value[end] || 0\n}\n\nexport const sizeRangeSystem = u.system(\n ([{ scrollTop, viewportHeight, deviation, headerHeight, fixedHeaderHeight }]) => {\n const listBoundary = u.stream()\n const topListHeight = u.statefulStream(0)\n const increaseViewportBy = u.statefulStream(0)\n const overscan = u.statefulStream(0)\n\n const visibleRange = u.statefulStreamFromEmitter(\n u.pipe(\n u.combineLatest(\n u.duc(scrollTop),\n u.duc(viewportHeight),\n u.duc(headerHeight),\n u.duc(listBoundary, tupleComparator),\n u.duc(overscan),\n u.duc(topListHeight),\n u.duc(fixedHeaderHeight),\n u.duc(deviation),\n u.duc(increaseViewportBy)\n ),\n u.map(\n ([\n scrollTop,\n viewportHeight,\n headerHeight,\n [listTop, listBottom],\n overscan,\n topListHeight,\n fixedHeaderHeight,\n deviation,\n increaseViewportBy,\n ]) => {\n const top = scrollTop - deviation\n const stickyHeaderHeight = topListHeight + fixedHeaderHeight\n const headerVisible = Math.max(headerHeight - top, 0)\n let direction: ChangeDirection = NONE\n const topViewportAddition = getViewportIncrease(increaseViewportBy, TOP)\n const bottomViewportAddition = getViewportIncrease(increaseViewportBy, BOTTOM)\n\n listTop -= deviation\n listTop += headerHeight + fixedHeaderHeight\n listBottom += headerHeight + fixedHeaderHeight\n listBottom -= deviation\n\n // console.log({ listTop, scrollTop, stickyHeaderHeight, topViewportAddition })\n if (listTop > scrollTop + stickyHeaderHeight - topViewportAddition) {\n direction = UP\n }\n\n if (listBottom < scrollTop - headerVisible + viewportHeight + bottomViewportAddition) {\n direction = DOWN\n }\n\n if (direction !== NONE) {\n return [\n Math.max(top - headerHeight - getOverscan(overscan, TOP, direction) - topViewportAddition, 0),\n top -\n headerVisible -\n fixedHeaderHeight +\n viewportHeight +\n getOverscan(overscan, BOTTOM, direction) +\n bottomViewportAddition,\n ] as NumberTuple\n }\n\n return null\n }\n ),\n u.filter((value) => value != null) as u.Operator,\n u.distinctUntilChanged(tupleComparator)\n ),\n [0, 0]\n ) as unknown as u.StatefulStream\n\n return {\n // input\n listBoundary,\n overscan,\n topListHeight,\n increaseViewportBy,\n\n // output\n visibleRange,\n }\n },\n u.tup(domIOSystem),\n { singleton: true }\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { empty, findMaxKeyValue, Range, rangesWithin } from './AATree'\nimport { groupedListSystem } from './groupedListSystem'\nimport { getInitialTopMostItemIndexNumber, initialTopMostItemIndexSystem } from './initialTopMostItemIndexSystem'\nimport { Item, ListItem, ListRange } from './interfaces'\nimport { propsReadySystem } from './propsReadySystem'\nimport { scrollToIndexSystem } from './scrollToIndexSystem'\nimport { sizeRangeSystem } from './sizeRangeSystem'\nimport { Data, originalIndexFromItemIndex, SizeState, sizeSystem, hasGroups, rangesWithinOffsets } from './sizeSystem'\nimport { stateFlagsSystem } from './stateFlagsSystem'\nimport { rangeComparator, tupleComparator } from './comparators'\nimport { recalcSystem } from './recalcSystem'\n\nexport type ListItems = ListItem[]\nexport interface TopListState {\n items: ListItems\n listHeight: number\n}\n\nexport interface ListState {\n items: ListItems\n topItems: ListItems\n topListHeight: number\n offsetTop: number\n offsetBottom: number\n top: number\n bottom: number\n totalCount: number\n firstItemIndex: number\n}\n\nfunction probeItemSet(index: number, sizes: SizeState, data: Data) {\n if (hasGroups(sizes)) {\n const itemIndex = originalIndexFromItemIndex(index, sizes)\n const groupIndex = findMaxKeyValue(sizes.groupOffsetTree, itemIndex)[0]\n\n return [\n { index: groupIndex, size: 0, offset: 0 },\n { index: itemIndex, size: 0, offset: 0, data: data && data[0] },\n ]\n }\n return [{ index, size: 0, offset: 0, data: data && data[0] }]\n}\n\nconst EMPTY_LIST_STATE: ListState = {\n items: [] as ListItems,\n topItems: [] as ListItems,\n offsetTop: 0,\n offsetBottom: 0,\n top: 0,\n bottom: 0,\n topListHeight: 0,\n totalCount: 0,\n firstItemIndex: 0,\n}\n\nfunction transposeItems(items: Item[], sizes: SizeState, firstItemIndex: number): ListItems {\n if (items.length === 0) {\n return []\n }\n\n if (!hasGroups(sizes)) {\n return items.map((item) => ({ ...item, index: item.index + firstItemIndex, originalIndex: item.index }))\n }\n\n const startIndex = items[0].index\n const endIndex = items[items.length - 1].index\n\n const transposedItems = [] as ListItems\n const groupRanges = rangesWithin(sizes.groupOffsetTree, startIndex, endIndex)\n let currentRange: Range | undefined = undefined\n let currentGroupIndex = 0\n\n for (const item of items) {\n if (!currentRange || currentRange.end < item.index) {\n currentRange = groupRanges.shift()!\n currentGroupIndex = sizes.groupIndices.indexOf(currentRange.start)\n }\n\n let transposedItem: { type: 'group'; index: number } | { index: number; groupIndex: number }\n\n if (item.index === currentRange.start) {\n transposedItem = {\n type: 'group' as const,\n index: currentGroupIndex,\n }\n } else {\n transposedItem = {\n index: item.index - (currentGroupIndex + 1) + firstItemIndex,\n groupIndex: currentGroupIndex,\n }\n }\n\n transposedItems.push({\n ...transposedItem,\n size: item.size,\n offset: item.offset,\n originalIndex: item.index,\n data: item.data,\n })\n }\n\n return transposedItems\n}\n\nexport function buildListState(\n items: Item[],\n topItems: Item[],\n totalCount: number,\n gap: number,\n sizes: SizeState,\n firstItemIndex: number\n): ListState {\n const { lastSize, lastOffset, lastIndex } = sizes\n let offsetTop = 0\n let bottom = 0\n\n if (items.length > 0) {\n offsetTop = items[0].offset\n const lastItem = items[items.length - 1]\n bottom = lastItem.offset + lastItem.size\n }\n\n const itemCount = totalCount - lastIndex\n const total = lastOffset + itemCount * lastSize + (itemCount - 1) * gap\n const top = offsetTop\n const offsetBottom = total - bottom\n\n return {\n items: transposeItems(items, sizes, firstItemIndex),\n topItems: transposeItems(topItems, sizes, firstItemIndex),\n topListHeight: topItems.reduce((height, item) => item.size + height, 0),\n offsetTop,\n offsetBottom,\n top,\n bottom,\n totalCount,\n firstItemIndex,\n }\n}\n\nexport const listStateSystem = u.system(\n ([\n { sizes, totalCount, data, firstItemIndex, gap },\n groupedListSystem,\n { visibleRange, listBoundary, topListHeight: rangeTopListHeight },\n { scrolledToInitialItem, initialTopMostItemIndex },\n { topListHeight },\n stateFlags,\n { didMount },\n { recalcInProgress },\n ]) => {\n const topItemsIndexes = u.statefulStream>([])\n const itemsRendered = u.stream()\n\n u.connect(groupedListSystem.topItemsIndexes, topItemsIndexes)\n\n const listState = u.statefulStreamFromEmitter(\n u.pipe(\n u.combineLatest(\n didMount,\n recalcInProgress,\n u.duc(visibleRange, tupleComparator),\n u.duc(totalCount),\n u.duc(sizes),\n u.duc(initialTopMostItemIndex),\n scrolledToInitialItem,\n u.duc(topItemsIndexes),\n u.duc(firstItemIndex),\n u.duc(gap),\n data\n ),\n u.filter(([mount, recalcInProgress]) => {\n return mount && !recalcInProgress\n }),\n u.map(\n ([\n ,\n ,\n [startOffset, endOffset],\n totalCount,\n sizes,\n initialTopMostItemIndex,\n scrolledToInitialItem,\n topItemsIndexes,\n firstItemIndex,\n gap,\n data,\n ]) => {\n const sizesValue = sizes\n const { sizeTree, offsetTree } = sizesValue\n\n if (totalCount === 0 || (startOffset === 0 && endOffset === 0)) {\n return { ...EMPTY_LIST_STATE, totalCount }\n }\n\n if (empty(sizeTree)) {\n return buildListState(\n probeItemSet(getInitialTopMostItemIndexNumber(initialTopMostItemIndex, totalCount), sizesValue, data),\n [],\n totalCount,\n gap,\n sizesValue,\n firstItemIndex\n )\n }\n\n const topItems = [] as Item[]\n\n if (topItemsIndexes.length > 0) {\n const startIndex = topItemsIndexes[0]\n const endIndex = topItemsIndexes[topItemsIndexes.length - 1]\n let offset = 0\n for (const range of rangesWithin(sizeTree, startIndex, endIndex)) {\n const size = range.value\n const rangeStartIndex = Math.max(range.start, startIndex)\n const rangeEndIndex = Math.min(range.end, endIndex)\n for (let i = rangeStartIndex; i <= rangeEndIndex; i++) {\n topItems.push({ index: i, size, offset: offset, data: data && data[i] })\n offset += size\n }\n }\n }\n\n // If the list hasn't scrolled to the initial item because the initial item was set,\n // render empty list.\n //\n // This is a condition to be evaluated past the probe check, do not merge\n // with the totalCount check above\n if (!scrolledToInitialItem) {\n return buildListState([], topItems, totalCount, gap, sizesValue, firstItemIndex)\n }\n\n const minStartIndex = topItemsIndexes.length > 0 ? topItemsIndexes[topItemsIndexes.length - 1] + 1 : 0\n\n const offsetPointRanges = rangesWithinOffsets(offsetTree, startOffset, endOffset, minStartIndex)\n if (offsetPointRanges.length === 0) {\n return null\n }\n\n const maxIndex = totalCount - 1\n\n const items = u.tap([] as Item[], (result) => {\n for (const range of offsetPointRanges) {\n const point = range.value\n let offset = point.offset\n let rangeStartIndex = range.start\n const size = point.size\n\n if (point.offset < startOffset) {\n rangeStartIndex += Math.floor((startOffset - point.offset + gap) / (size + gap))\n const itemCount = rangeStartIndex - range.start\n offset += itemCount * size + itemCount * gap\n }\n\n if (rangeStartIndex < minStartIndex) {\n offset += (minStartIndex - rangeStartIndex) * size\n rangeStartIndex = minStartIndex\n }\n\n const endIndex = Math.min(range.end, maxIndex)\n\n for (let i = rangeStartIndex; i <= endIndex; i++) {\n if (offset >= endOffset) {\n break\n }\n\n result.push({ index: i, size, offset: offset, data: data && data[i] })\n offset += size + gap\n }\n }\n })\n\n return buildListState(items, topItems, totalCount, gap, sizesValue, firstItemIndex)\n }\n ),\n //@ts-expect-error filter needs to be fixed\n u.filter((value) => value !== null),\n u.distinctUntilChanged()\n ),\n EMPTY_LIST_STATE\n )\n\n u.connect(\n u.pipe(\n data,\n u.filter((data) => data !== undefined),\n u.map((data) => data!.length)\n ),\n totalCount\n )\n\n u.connect(\n u.pipe(\n listState,\n u.map((value) => value.topListHeight)\n ),\n topListHeight\n )\n u.connect(topListHeight, rangeTopListHeight)\n\n u.connect(\n u.pipe(\n listState,\n u.map((state) => [state.top, state.bottom])\n ),\n listBoundary\n )\n\n u.connect(\n u.pipe(\n listState,\n u.map((state) => state.items)\n ),\n itemsRendered\n )\n\n const endReached = u.streamFromEmitter(\n u.pipe(\n listState,\n u.filter(({ items }) => items.length > 0),\n u.withLatestFrom(totalCount, data),\n u.filter(([{ items }, totalCount]) => items[items.length - 1].originalIndex === totalCount - 1),\n u.map(([, totalCount, data]) => [totalCount - 1, data] as [number, unknown[]]),\n u.distinctUntilChanged(tupleComparator),\n u.map(([count]) => count)\n )\n )\n\n const startReached = u.streamFromEmitter(\n u.pipe(\n listState,\n u.throttleTime(200),\n u.filter(({ items, topItems }) => {\n return items.length > 0 && items[0].originalIndex === topItems.length\n }),\n u.map(({ items }) => items[0].index),\n u.distinctUntilChanged()\n )\n )\n\n const rangeChanged = u.streamFromEmitter(\n u.pipe(\n listState,\n u.filter(({ items }) => items.length > 0),\n u.map(({ items }) => {\n let startIndex = 0\n let endIndex = items.length - 1\n\n while (items[startIndex].type === 'group' && startIndex < endIndex) {\n startIndex++\n }\n\n while (items[endIndex].type === 'group' && endIndex > startIndex) {\n endIndex--\n }\n\n return {\n startIndex: items[startIndex].index,\n endIndex: items[endIndex].index,\n } as ListRange\n }),\n u.distinctUntilChanged(rangeComparator)\n )\n )\n\n return { listState, topItemsIndexes, endReached, startReached, rangeChanged, itemsRendered, ...stateFlags }\n },\n u.tup(\n sizeSystem,\n groupedListSystem,\n sizeRangeSystem,\n initialTopMostItemIndexSystem,\n scrollToIndexSystem,\n stateFlagsSystem,\n propsReadySystem,\n recalcSystem\n ),\n { singleton: true }\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { listStateSystem, buildListState } from './listStateSystem'\nimport { sizeSystem } from './sizeSystem'\nimport { propsReadySystem } from './propsReadySystem'\n\nexport const initialItemCountSystem = u.system(\n ([{ sizes, firstItemIndex, data, gap }, { listState }, { didMount }]) => {\n const initialItemCount = u.statefulStream(0)\n\n u.connect(\n u.pipe(\n didMount,\n u.withLatestFrom(initialItemCount),\n u.filter(([, count]) => count !== 0),\n u.withLatestFrom(sizes, firstItemIndex, gap, data),\n u.map(([[, count], sizes, firstItemIndex, gap, data = []]) => {\n let includedGroupsCount = 0\n if (sizes.groupIndices.length > 0) {\n for (const index of sizes.groupIndices) {\n if (index - includedGroupsCount >= count) {\n break\n }\n includedGroupsCount++\n }\n }\n const adjustedCount = count + includedGroupsCount\n const items = Array.from({ length: adjustedCount }).map((_, index) => ({ index, size: 0, offset: 0, data: data[index] }))\n return buildListState(items, [], adjustedCount, gap, sizes, firstItemIndex)\n })\n ),\n listState\n )\n\n return { initialItemCount }\n },\n u.tup(sizeSystem, listStateSystem, propsReadySystem),\n { singleton: true }\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { ListRange } from './interfaces'\nimport { stateFlagsSystem } from './stateFlagsSystem'\nimport { ScrollSeekConfiguration } from './interfaces'\n\nexport const scrollSeekSystem = u.system(\n ([{ scrollVelocity }]) => {\n const isSeeking = u.statefulStream(false)\n const rangeChanged = u.stream()\n const scrollSeekConfiguration = u.statefulStream(false)\n\n u.connect(\n u.pipe(\n scrollVelocity,\n u.withLatestFrom(scrollSeekConfiguration, isSeeking, rangeChanged),\n u.filter(([_, config]) => !!config),\n u.map(([speed, config, isSeeking, range]) => {\n const { exit, enter } = config as ScrollSeekConfiguration\n if (isSeeking) {\n if (exit(speed, range)) {\n return false\n }\n } else {\n if (enter(speed, range)) {\n return true\n }\n }\n return isSeeking\n }),\n u.distinctUntilChanged()\n ),\n isSeeking\n )\n\n u.subscribe(\n u.pipe(u.combineLatest(isSeeking, scrollVelocity, rangeChanged), u.withLatestFrom(scrollSeekConfiguration)),\n ([[isSeeking, velocity, range], config]) => isSeeking && config && config.change && config.change(velocity, range)\n )\n\n return { isSeeking, scrollSeekConfiguration, scrollVelocity, scrollSeekRangeChanged: rangeChanged }\n },\n u.tup(stateFlagsSystem),\n { singleton: true }\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { listStateSystem } from './listStateSystem'\n\nexport const topItemCountSystem = u.system(([{ topItemsIndexes }]) => {\n const topItemCount = u.statefulStream(0)\n\n u.connect(\n u.pipe(\n topItemCount,\n u.filter((length) => length > 0),\n u.map((length) => Array.from({ length }).map((_, index) => index))\n ),\n topItemsIndexes\n )\n return { topItemCount }\n}, u.tup(listStateSystem))\n","import * as u from '@virtuoso.dev/urx'\nimport { listStateSystem } from './listStateSystem'\nimport { domIOSystem } from './domIOSystem'\n\nexport const totalListHeightSystem = u.system(\n ([{ footerHeight, headerHeight, fixedHeaderHeight, fixedFooterHeight }, { listState }]) => {\n const totalListHeightChanged = u.stream()\n const totalListHeight = u.statefulStreamFromEmitter(\n u.pipe(\n u.combineLatest(footerHeight, fixedFooterHeight, headerHeight, fixedHeaderHeight, listState),\n u.map(([footerHeight, fixedFooterHeight, headerHeight, fixedHeaderHeight, listState]) => {\n return footerHeight + fixedFooterHeight + headerHeight + fixedHeaderHeight + listState.offsetBottom + listState.bottom\n })\n ),\n 0\n )\n\n u.connect(u.duc(totalListHeight), totalListHeightChanged)\n\n return { totalListHeight, totalListHeightChanged }\n },\n u.tup(domIOSystem, listStateSystem),\n { singleton: true }\n)\n","export function simpleMemoize any>(func: T): T {\n let called = false\n let result: unknown\n\n return (() => {\n if (!called) {\n called = true\n result = func()\n }\n return result\n }) as T\n}\n","import * as u from '@virtuoso.dev/urx'\nimport { domIOSystem } from './domIOSystem'\nimport { listStateSystem } from './listStateSystem'\nimport { sizeSystem } from './sizeSystem'\nimport { UP, stateFlagsSystem } from './stateFlagsSystem'\nimport { ListItem } from './interfaces'\nimport { loggerSystem, LogLevel } from './loggerSystem'\nimport { simpleMemoize } from './utils/simpleMemoize'\nimport { recalcSystem } from './recalcSystem'\n\nconst isMobileSafari = simpleMemoize(() => {\n return /iP(ad|hone|od).+Version\\/[\\d.]+.*Safari/i.test(navigator.userAgent)\n})\n\ntype UpwardFixState = [number, ListItem[], number, number]\n/**\n * Fixes upward scrolling by calculating and compensation from changed item heights, using scrollBy.\n */\nexport const upwardScrollFixSystem = u.system(\n ([\n { scrollBy, scrollTop, deviation, scrollingInProgress },\n { isScrolling, isAtBottom, scrollDirection, lastJumpDueToItemResize },\n { listState },\n { beforeUnshiftWith, shiftWithOffset, sizes, gap },\n { log },\n { recalcInProgress },\n ]) => {\n const deviationOffset = u.streamFromEmitter(\n u.pipe(\n listState,\n u.withLatestFrom(lastJumpDueToItemResize),\n u.scan(\n ([, prevItems, prevTotalCount, prevTotalHeight], [{ items, totalCount, bottom, offsetBottom }, lastJumpDueToItemResize]) => {\n const totalHeight = bottom + offsetBottom\n\n let newDev = 0\n if (prevTotalCount === totalCount) {\n if (prevItems.length > 0 && items.length > 0) {\n const atStart = items[0].originalIndex === 0 && prevItems[0].originalIndex === 0\n if (!atStart) {\n newDev = totalHeight - prevTotalHeight\n if (newDev !== 0) {\n newDev += lastJumpDueToItemResize\n }\n }\n }\n }\n\n return [newDev, items, totalCount, totalHeight] as UpwardFixState\n },\n [0, [], 0, 0] as UpwardFixState\n ),\n u.filter(([amount]) => amount !== 0),\n u.withLatestFrom(scrollTop, scrollDirection, scrollingInProgress, isAtBottom, log),\n u.filter(([, scrollTop, scrollDirection, scrollingInProgress]) => {\n return !scrollingInProgress && scrollTop !== 0 && scrollDirection === UP\n }),\n u.map(([[amount], , , , , log]) => {\n log('Upward scrolling compensation', { amount }, LogLevel.DEBUG)\n return amount\n })\n )\n )\n\n function scrollByWith(offset: number) {\n if (offset > 0) {\n u.publish(scrollBy, { top: -offset, behavior: 'auto' })\n u.publish(deviation, 0)\n } else {\n u.publish(deviation, 0)\n u.publish(scrollBy, { top: -offset, behavior: 'auto' })\n }\n }\n\n u.subscribe(u.pipe(deviationOffset, u.withLatestFrom(deviation, isScrolling)), ([offset, deviationAmount, isScrolling]) => {\n if (isScrolling && isMobileSafari()) {\n u.publish(deviation, deviationAmount - offset)\n } else {\n scrollByWith(-offset)\n }\n })\n\n // this hack is only necessary for mobile safari which does not support scrollBy while scrolling is in progress.\n // when the browser stops scrolling, restore the position and reset the glitching\n u.subscribe(\n u.pipe(\n u.combineLatest(u.statefulStreamFromEmitter(isScrolling, false), deviation, recalcInProgress),\n u.filter(([is, deviation, recalc]) => !is && !recalc && deviation !== 0),\n u.map(([_, deviation]) => deviation),\n u.throttleTime(1)\n ),\n scrollByWith\n )\n\n u.connect(\n u.pipe(\n shiftWithOffset,\n u.map((offset) => {\n return { top: -offset }\n })\n ),\n scrollBy\n )\n\n u.subscribe(\n u.pipe(\n beforeUnshiftWith,\n u.withLatestFrom(sizes, gap),\n u.map(([offset, { lastSize }, gap]) => offset * lastSize + offset * gap)\n ),\n (offset) => {\n u.publish(deviation, offset)\n requestAnimationFrame(() => {\n u.publish(scrollBy, { top: offset })\n requestAnimationFrame(() => {\n u.publish(deviation, 0)\n u.publish(recalcInProgress, false)\n })\n })\n }\n )\n\n return { deviation }\n },\n u.tup(domIOSystem, stateFlagsSystem, listStateSystem, sizeSystem, loggerSystem, recalcSystem)\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { totalListHeightSystem } from './totalListHeightSystem'\nimport { propsReadySystem } from './propsReadySystem'\nimport { domIOSystem } from './domIOSystem'\n\nexport const initialScrollTopSystem = u.system(\n ([{ totalListHeight }, { didMount }, { scrollTo }]) => {\n const initialScrollTop = u.statefulStream(0)\n\n u.subscribe(\n u.pipe(\n didMount,\n u.withLatestFrom(initialScrollTop),\n u.filter(([, offset]) => offset !== 0),\n u.map(([, offset]) => ({ top: offset }))\n ),\n (location) => {\n u.handleNext(\n u.pipe(\n totalListHeight,\n u.filter((val) => val !== 0)\n ),\n () => {\n setTimeout(() => {\n u.publish(scrollTo, location)\n })\n }\n )\n }\n )\n\n return {\n initialScrollTop,\n }\n },\n u.tup(totalListHeightSystem, propsReadySystem, domIOSystem),\n { singleton: true }\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { totalListHeightSystem } from './totalListHeightSystem'\nimport { domIOSystem } from './domIOSystem'\n\nexport const alignToBottomSystem = u.system(\n ([{ viewportHeight }, { totalListHeight }]) => {\n const alignToBottom = u.statefulStream(false)\n\n const paddingTopAddition = u.statefulStreamFromEmitter(\n u.pipe(\n u.combineLatest(alignToBottom, viewportHeight, totalListHeight),\n u.filter(([enabled]) => enabled),\n u.map(([, viewportHeight, totalListHeight]) => {\n return Math.max(0, viewportHeight - totalListHeight)\n }),\n u.distinctUntilChanged()\n ),\n 0\n )\n\n return { alignToBottom, paddingTopAddition }\n },\n u.tup(domIOSystem, totalListHeightSystem),\n { singleton: true }\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { domIOSystem } from './domIOSystem'\nimport { WindowViewportInfo, ScrollContainerState } from './interfaces'\n\nexport const windowScrollerSystem = u.system(([{ scrollTo, scrollContainerState }]) => {\n const windowScrollContainerState = u.stream()\n const windowViewportRect = u.stream()\n const windowScrollTo = u.stream()\n const useWindowScroll = u.statefulStream(false)\n const customScrollParent = u.statefulStream(undefined)\n\n u.connect(\n u.pipe(\n u.combineLatest(windowScrollContainerState, windowViewportRect),\n u.map(([{ viewportHeight, scrollTop: windowScrollTop, scrollHeight }, { offsetTop }]) => {\n return {\n scrollTop: Math.max(0, windowScrollTop - offsetTop),\n scrollHeight,\n viewportHeight,\n }\n })\n ),\n scrollContainerState\n )\n\n u.connect(\n u.pipe(\n scrollTo,\n u.withLatestFrom(windowViewportRect),\n u.map(([scrollTo, { offsetTop }]) => {\n return {\n ...scrollTo,\n top: scrollTo.top! + offsetTop,\n }\n })\n ),\n windowScrollTo\n )\n\n return {\n // config\n useWindowScroll,\n customScrollParent,\n\n // input\n windowScrollContainerState,\n windowViewportRect,\n\n // signals\n windowScrollTo,\n }\n}, u.tup(domIOSystem))\n","import * as u from '@virtuoso.dev/urx'\nimport { findMaxKeyValue } from './AATree'\nimport { domIOSystem } from './domIOSystem'\nimport { offsetOf, originalIndexFromLocation, sizeSystem } from './sizeSystem'\nimport { loggerSystem } from './loggerSystem'\nimport { scrollToIndexSystem } from './scrollToIndexSystem'\nimport { listStateSystem } from './listStateSystem'\nimport { ScrollIntoViewLocation } from './interfaces'\n\nexport const scrollIntoViewSystem = u.system(\n ([\n { sizes, totalCount, gap },\n { scrollTop, viewportHeight, headerHeight, fixedHeaderHeight, fixedFooterHeight, scrollingInProgress },\n { scrollToIndex },\n ]) => {\n const scrollIntoView = u.stream()\n\n u.connect(\n u.pipe(\n scrollIntoView,\n u.withLatestFrom(sizes, viewportHeight, totalCount, headerHeight, fixedHeaderHeight, fixedFooterHeight, scrollTop),\n u.withLatestFrom(gap),\n u.map(([[viewLocation, sizes, viewportHeight, totalCount, headerHeight, fixedHeaderHeight, fixedFooterHeight, scrollTop], gap]) => {\n const { done, behavior, align, ...rest } = viewLocation\n let location = null\n const actualIndex = originalIndexFromLocation(viewLocation, sizes, totalCount - 1)\n\n const itemTop = offsetOf(actualIndex, sizes.offsetTree, gap) + headerHeight + fixedHeaderHeight\n if (itemTop < scrollTop + fixedHeaderHeight) {\n location = { ...rest, behavior, align: align ?? 'start' }\n } else {\n const itemBottom = itemTop + findMaxKeyValue(sizes.sizeTree, actualIndex)[1]!\n\n if (itemBottom > scrollTop + viewportHeight - fixedFooterHeight) {\n location = { ...rest, behavior, align: align ?? 'end' }\n }\n }\n\n if (location) {\n done &&\n u.handleNext(\n u.pipe(\n scrollingInProgress,\n u.skip(1),\n u.filter((value) => value === false)\n ),\n done\n )\n } else {\n done && done()\n }\n\n return location\n }),\n u.filter((value) => value !== null)\n ),\n scrollToIndex\n )\n\n return {\n scrollIntoView,\n }\n },\n u.tup(sizeSystem, domIOSystem, scrollToIndexSystem, listStateSystem, loggerSystem),\n { singleton: true }\n)\n","import * as u from '@virtuoso.dev/urx'\nimport { domIOSystem } from './domIOSystem'\nimport { followOutputSystem } from './followOutputSystem'\nimport { groupedListSystem } from './groupedListSystem'\nimport { initialItemCountSystem } from './initialItemCountSystem'\nimport { initialTopMostItemIndexSystem } from './initialTopMostItemIndexSystem'\nimport { listStateSystem } from './listStateSystem'\nimport { propsReadySystem } from './propsReadySystem'\nimport { scrollSeekSystem } from './scrollSeekSystem'\nimport { scrollToIndexSystem } from './scrollToIndexSystem'\nimport { sizeRangeSystem } from './sizeRangeSystem'\nimport { sizeSystem } from './sizeSystem'\nimport { topItemCountSystem } from './topItemCountSystem'\nimport { totalListHeightSystem } from './totalListHeightSystem'\nimport { upwardScrollFixSystem } from './upwardScrollFixSystem'\nimport { initialScrollTopSystem } from './initialScrollTopSystem'\nimport { alignToBottomSystem } from './alignToBottomSystem'\nimport { windowScrollerSystem } from './windowScrollerSystem'\nimport { loggerSystem } from './loggerSystem'\nimport { scrollIntoViewSystem } from './scrollIntoViewSystem'\n\n// workaround the growing list of systems below\n// fix this with 4.1 recursive conditional types\nconst featureGroup1System = u.system(\n ([\n sizeRange,\n initialItemCount,\n propsReady,\n scrollSeek,\n totalListHeight,\n initialScrollTopSystem,\n alignToBottom,\n windowScroller,\n scrollIntoView,\n ]) => {\n return {\n ...sizeRange,\n ...initialItemCount,\n ...propsReady,\n ...scrollSeek,\n ...totalListHeight,\n ...initialScrollTopSystem,\n ...alignToBottom,\n ...windowScroller,\n ...scrollIntoView,\n }\n },\n u.tup(\n sizeRangeSystem,\n initialItemCountSystem,\n propsReadySystem,\n scrollSeekSystem,\n totalListHeightSystem,\n initialScrollTopSystem,\n alignToBottomSystem,\n windowScrollerSystem,\n scrollIntoViewSystem\n )\n)\n\nexport const listSystem = u.system(\n ([\n {\n totalCount,\n sizeRanges,\n fixedItemSize,\n defaultItemSize,\n trackItemSizes,\n itemSize,\n data,\n firstItemIndex,\n groupIndices,\n statefulTotalCount,\n gap,\n },\n { initialTopMostItemIndex, scrolledToInitialItem },\n domIO,\n followOutput,\n { listState, topItemsIndexes, ...flags },\n { scrollToIndex },\n _,\n { topItemCount },\n { groupCounts },\n featureGroup1,\n log,\n ]) => {\n u.connect(flags.rangeChanged, featureGroup1.scrollSeekRangeChanged)\n u.connect(\n u.pipe(\n featureGroup1.windowViewportRect,\n u.map((value) => value.visibleHeight)\n ),\n domIO.viewportHeight\n )\n\n return {\n // input\n totalCount,\n data,\n firstItemIndex,\n sizeRanges,\n initialTopMostItemIndex,\n scrolledToInitialItem,\n topItemsIndexes,\n topItemCount,\n groupCounts,\n fixedItemHeight: fixedItemSize,\n defaultItemHeight: defaultItemSize,\n gap,\n ...followOutput,\n\n // output\n statefulTotalCount,\n listState,\n scrollToIndex,\n trackItemSizes,\n itemSize,\n groupIndices,\n\n // exported from stateFlagsSystem\n ...flags,\n // the bag of IO from featureGroup1System\n ...featureGroup1,\n ...domIO,\n ...log,\n }\n },\n u.tup(\n sizeSystem,\n initialTopMostItemIndexSystem,\n domIOSystem,\n followOutputSystem,\n listStateSystem,\n scrollToIndexSystem,\n upwardScrollFixSystem,\n topItemCountSystem,\n groupedListSystem,\n featureGroup1System,\n loggerSystem\n )\n)\n","import { simpleMemoize } from './simpleMemoize'\n\nconst WEBKIT_STICKY = '-webkit-sticky'\nconst STICKY = 'sticky'\n\nexport const positionStickyCssValue = simpleMemoize(() => {\n if (typeof document === 'undefined') {\n return STICKY\n }\n const node = document.createElement('div')\n node.style.position = WEBKIT_STICKY\n return node.style.position === WEBKIT_STICKY ? WEBKIT_STICKY : STICKY\n})\n","import { useEffect, useRef, useCallback } from 'react'\nimport { useSizeWithElRef } from './useSize'\nimport { WindowViewportInfo } from '../interfaces'\n\nexport default function useWindowViewportRectRef(callback: (info: WindowViewportInfo) => void, customScrollParent?: HTMLElement) {\n const viewportInfo = useRef(null)\n\n const calculateInfo = useCallback(\n (element: HTMLElement | null) => {\n if (element === null || !element.offsetParent) {\n return\n }\n const rect = element.getBoundingClientRect()\n const visibleWidth = rect.width\n let visibleHeight: number, offsetTop: number\n\n if (customScrollParent) {\n const customScrollParentRect = customScrollParent.getBoundingClientRect()\n const deltaTop = rect.top - customScrollParentRect.top\n\n visibleHeight = customScrollParentRect.height - Math.max(0, deltaTop)\n offsetTop = deltaTop + customScrollParent.scrollTop\n } else {\n visibleHeight = window.innerHeight - Math.max(0, rect.top)\n offsetTop = rect.top + window.pageYOffset\n }\n\n viewportInfo.current = {\n offsetTop,\n visibleHeight,\n visibleWidth,\n }\n\n callback(viewportInfo.current)\n },\n [callback, customScrollParent]\n )\n\n const { callbackRef, ref } = useSizeWithElRef(calculateInfo)\n\n const scrollAndResizeEventHandler = useCallback(() => {\n calculateInfo(ref.current)\n }, [calculateInfo, ref])\n\n useEffect(() => {\n if (customScrollParent) {\n customScrollParent.addEventListener('scroll', scrollAndResizeEventHandler)\n const observer = new ResizeObserver(scrollAndResizeEventHandler)\n observer.observe(customScrollParent)\n return () => {\n customScrollParent.removeEventListener('scroll', scrollAndResizeEventHandler)\n observer.unobserve(customScrollParent)\n }\n } else {\n window.addEventListener('scroll', scrollAndResizeEventHandler)\n window.addEventListener('resize', scrollAndResizeEventHandler)\n return () => {\n window.removeEventListener('scroll', scrollAndResizeEventHandler)\n window.removeEventListener('resize', scrollAndResizeEventHandler)\n }\n }\n }, [scrollAndResizeEventHandler, customScrollParent])\n\n return callbackRef\n}\n","import * as React from 'react'\n\nexport interface VirtuosoMockContextValue {\n viewportHeight: number\n itemHeight: number\n}\n\nexport const VirtuosoMockContext = React.createContext(undefined)\n","import { RefHandle, systemToComponent } from '@virtuoso.dev/react-urx'\nimport * as u from '@virtuoso.dev/urx'\nimport * as React from 'react'\nimport { ComponentType, createElement, CSSProperties, FC, PropsWithChildren, useContext } from 'react'\nimport useIsomorphicLayoutEffect from './hooks/useIsomorphicLayoutEffect'\nimport useChangedListContentsSizes from './hooks/useChangedChildSizes'\nimport useScrollTop from './hooks/useScrollTop'\nimport useSize from './hooks/useSize'\nimport { Components, ComputeItemKey, GroupContent, GroupItemContent, ItemContent, ListRootProps } from './interfaces'\nimport { listSystem } from './listSystem'\nimport { positionStickyCssValue } from './utils/positionStickyCssValue'\nimport useWindowViewportRectRef from './hooks/useWindowViewportRect'\nimport { correctItemSize } from './utils/correctItemSize'\nimport { VirtuosoMockContext } from './utils/context'\nimport { ScrollerProps } from '.'\n\nexport function identity(value: T) {\n return value\n}\n\nconst listComponentPropsSystem = u.system(() => {\n const itemContent = u.statefulStream | GroupItemContent>((index: number) => `Item ${index}`)\n const context = u.statefulStream(null)\n const groupContent = u.statefulStream((index: number) => `Group ${index}`)\n const components = u.statefulStream>({})\n const computeItemKey = u.statefulStream>(identity)\n const headerFooterTag = u.statefulStream('div')\n const scrollerRef = u.statefulStream<(ref: HTMLElement | Window | null) => void>(u.noop)\n\n const distinctProp = >(propName: K, defaultValue: Components[K] | null | 'div' = null) => {\n return u.statefulStreamFromEmitter(\n u.pipe(\n components,\n u.map((components) => components[propName]),\n u.distinctUntilChanged()\n ),\n defaultValue\n )\n }\n\n return {\n context,\n itemContent,\n groupContent,\n components,\n computeItemKey,\n headerFooterTag,\n scrollerRef,\n FooterComponent: distinctProp('Footer'),\n HeaderComponent: distinctProp('Header'),\n TopItemListComponent: distinctProp('TopItemList'),\n ListComponent: distinctProp('List', 'div'),\n ItemComponent: distinctProp('Item', 'div'),\n GroupComponent: distinctProp('Group', 'div'),\n ScrollerComponent: distinctProp('Scroller', 'div'),\n EmptyPlaceholder: distinctProp('EmptyPlaceholder'),\n ScrollSeekPlaceholder: distinctProp('ScrollSeekPlaceholder'),\n }\n})\n\nexport function addDeprecatedAlias(prop: u.Stream, message: string) {\n const alias = u.stream()\n u.subscribe(alias, () =>\n console.warn(`react-virtuoso: You are using a deprecated property. ${message}`, 'color: red;', 'color: inherit;', 'color: blue;')\n )\n u.connect(alias, prop)\n return alias\n}\n\nconst combinedSystem = u.system(([listSystem, propsSystem]) => {\n const deprecatedProps = {\n item: addDeprecatedAlias(propsSystem.itemContent, 'Rename the %citem%c prop to %citemContent.'),\n group: addDeprecatedAlias(propsSystem.groupContent, 'Rename the %cgroup%c prop to %cgroupContent.'),\n topItems: addDeprecatedAlias(listSystem.topItemCount, 'Rename the %ctopItems%c prop to %ctopItemCount.'),\n itemHeight: addDeprecatedAlias(listSystem.fixedItemHeight, 'Rename the %citemHeight%c prop to %cfixedItemHeight.'),\n scrollingStateChange: addDeprecatedAlias(listSystem.isScrolling, 'Rename the %cscrollingStateChange%c prop to %cisScrolling.'),\n adjustForPrependedItems: u.stream(),\n maxHeightCacheSize: u.stream(),\n footer: u.stream(),\n header: u.stream(),\n HeaderContainer: u.stream(),\n FooterContainer: u.stream(),\n ItemContainer: u.stream(),\n ScrollContainer: u.stream(),\n GroupContainer: u.stream(),\n ListContainer: u.stream(),\n emptyComponent: u.stream(),\n scrollSeek: u.stream(),\n }\n\n u.subscribe(deprecatedProps.adjustForPrependedItems, () => {\n console.warn(\n `react-virtuoso: adjustForPrependedItems is no longer supported. Use the firstItemIndex property instead - https://virtuoso.dev/prepend-items.`,\n 'color: red;',\n 'color: inherit;',\n 'color: blue;'\n )\n })\n\n u.subscribe(deprecatedProps.maxHeightCacheSize, () => {\n console.warn(`react-virtuoso: maxHeightCacheSize is no longer necessary. Setting it has no effect - remove it from your code.`)\n })\n\n u.subscribe(deprecatedProps.HeaderContainer, () => {\n console.warn(\n `react-virtuoso: HeaderContainer is deprecated. Use headerFooterTag if you want to change the wrapper of the header component and pass components.Header to change its contents.`\n )\n })\n\n u.subscribe(deprecatedProps.FooterContainer, () => {\n console.warn(\n `react-virtuoso: FooterContainer is deprecated. Use headerFooterTag if you want to change the wrapper of the footer component and pass components.Footer to change its contents.`\n )\n })\n\n function deprecateComponentProp(stream: u.Stream, componentName: string, propName: string) {\n u.connect(\n u.pipe(\n stream,\n u.withLatestFrom(propsSystem.components),\n u.map(([comp, components]) => {\n console.warn(`react-virtuoso: ${propName} property is deprecated. Pass components.${componentName} instead.`)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n return { ...components, [componentName]: comp }\n })\n ),\n propsSystem.components\n )\n }\n\n u.subscribe(deprecatedProps.scrollSeek, ({ placeholder, ...config }) => {\n console.warn(\n `react-virtuoso: scrollSeek property is deprecated. Pass scrollSeekConfiguration and specify the placeholder in components.ScrollSeekPlaceholder instead.`\n )\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n u.publish(propsSystem.components, { ...u.getValue(propsSystem.components), ScrollSeekPlaceholder: placeholder })\n u.publish(listSystem.scrollSeekConfiguration, config)\n })\n\n deprecateComponentProp(deprecatedProps.footer, 'Footer', 'footer')\n deprecateComponentProp(deprecatedProps.header, 'Header', 'header')\n deprecateComponentProp(deprecatedProps.ItemContainer, 'Item', 'ItemContainer')\n deprecateComponentProp(deprecatedProps.ListContainer, 'List', 'ListContainer')\n deprecateComponentProp(deprecatedProps.ScrollContainer, 'Scroller', 'ScrollContainer')\n deprecateComponentProp(deprecatedProps.emptyComponent, 'EmptyPlaceholder', 'emptyComponent')\n deprecateComponentProp(deprecatedProps.GroupContainer, 'Group', 'GroupContainer')\n\n return { ...listSystem, ...propsSystem, ...deprecatedProps }\n}, u.tup(listSystem, listComponentPropsSystem))\n\nconst DefaultScrollSeekPlaceholder = ({ height }: { height: number }) =>
\n\nconst GROUP_STYLE = { position: positionStickyCssValue(), zIndex: 1, overflowAnchor: 'none' } as const\nconst ITEM_STYLE = { overflowAnchor: 'none' } as const\n\nexport const Items = React.memo(function VirtuosoItems({ showTopList = false }: { showTopList?: boolean }) {\n const listState = useEmitterValue('listState')\n\n const sizeRanges = usePublisher('sizeRanges')\n const useWindowScroll = useEmitterValue('useWindowScroll')\n const customScrollParent = useEmitterValue('customScrollParent')\n const windowScrollContainerStateCallback = usePublisher('windowScrollContainerState')\n const _scrollContainerStateCallback = usePublisher('scrollContainerState')\n const scrollContainerStateCallback =\n customScrollParent || useWindowScroll ? windowScrollContainerStateCallback : _scrollContainerStateCallback\n const itemContent = useEmitterValue('itemContent')\n const context = useEmitterValue('context')\n const groupContent = useEmitterValue('groupContent')\n const trackItemSizes = useEmitterValue('trackItemSizes')\n const itemSize = useEmitterValue('itemSize')\n const log = useEmitterValue('log')\n const listGap = usePublisher('gap')\n\n const { callbackRef } = useChangedListContentsSizes(\n sizeRanges,\n itemSize,\n trackItemSizes,\n showTopList ? u.noop : scrollContainerStateCallback,\n log,\n listGap,\n customScrollParent\n )\n\n const [deviation, setDeviation] = React.useState(0)\n useEmitter('deviation', (value) => {\n if (deviation !== value) {\n // ref.current!.style.marginTop = `${value}px`\n setDeviation(value)\n }\n })\n\n const EmptyPlaceholder = useEmitterValue('EmptyPlaceholder')\n const ScrollSeekPlaceholder = useEmitterValue('ScrollSeekPlaceholder') || DefaultScrollSeekPlaceholder\n const ListComponent = useEmitterValue('ListComponent')!\n const ItemComponent = useEmitterValue('ItemComponent')!\n const GroupComponent = useEmitterValue('GroupComponent')!\n const computeItemKey = useEmitterValue('computeItemKey')\n const isSeeking = useEmitterValue('isSeeking')\n const hasGroups = useEmitterValue('groupIndices').length > 0\n const paddingTopAddition = useEmitterValue('paddingTopAddition')\n\n const containerStyle: CSSProperties = showTopList\n ? {}\n : {\n boxSizing: 'border-box',\n paddingTop: listState.offsetTop + paddingTopAddition,\n paddingBottom: listState.offsetBottom,\n marginTop: deviation,\n }\n\n if (!showTopList && listState.totalCount === 0 && EmptyPlaceholder) {\n return createElement(EmptyPlaceholder, contextPropIfNotDomElement(EmptyPlaceholder, context))\n }\n\n return createElement(\n ListComponent,\n {\n ...contextPropIfNotDomElement(ListComponent, context),\n ref: callbackRef,\n style: containerStyle,\n 'data-test-id': showTopList ? 'virtuoso-top-item-list' : 'virtuoso-item-list',\n },\n (showTopList ? listState.topItems : listState.items).map((item) => {\n const index = item.originalIndex!\n const key = computeItemKey(index + listState.firstItemIndex, item.data, context)\n\n if (isSeeking) {\n return createElement(ScrollSeekPlaceholder, {\n ...contextPropIfNotDomElement(ScrollSeekPlaceholder, context),\n key,\n index: item.index,\n height: item.size,\n type: item.type || 'item',\n ...(item.type === 'group' ? {} : { groupIndex: item.groupIndex }),\n })\n }\n\n if (item.type === 'group') {\n return createElement(\n GroupComponent,\n {\n ...contextPropIfNotDomElement(GroupComponent, context),\n key,\n 'data-index': index,\n 'data-known-size': item.size,\n 'data-item-index': item.index,\n style: GROUP_STYLE,\n },\n groupContent(item.index)\n )\n } else {\n return createElement(\n ItemComponent,\n {\n ...contextPropIfNotDomElement(ItemComponent, context),\n key,\n 'data-index': index,\n 'data-known-size': item.size,\n 'data-item-index': item.index,\n 'data-item-group-index': item.groupIndex,\n style: ITEM_STYLE,\n },\n hasGroups\n ? (itemContent as GroupItemContent)(item.index, item.groupIndex!, item.data, context)\n : (itemContent as ItemContent)(item.index, item.data, context)\n )\n }\n })\n )\n})\n\nexport const scrollerStyle: CSSProperties = {\n height: '100%',\n outline: 'none',\n overflowY: 'auto',\n position: 'relative',\n WebkitOverflowScrolling: 'touch',\n}\n\nexport const viewportStyle: CSSProperties = {\n width: '100%',\n height: '100%',\n position: 'absolute',\n top: 0,\n}\n\nconst topItemListStyle: CSSProperties = {\n width: '100%',\n position: positionStickyCssValue(),\n top: 0,\n}\n\nexport function contextPropIfNotDomElement(element: unknown, context: unknown) {\n if (typeof element === 'string') {\n return undefined\n }\n return { context }\n}\n\nconst Header: FC = React.memo(function VirtuosoHeader() {\n const Header = useEmitterValue('HeaderComponent')\n const headerHeight = usePublisher('headerHeight')\n const headerFooterTag = useEmitterValue('headerFooterTag')\n const ref = useSize((el) => headerHeight(correctItemSize(el, 'height')))\n const context = useEmitterValue('context')\n return Header ? createElement(headerFooterTag, { ref }, createElement(Header, contextPropIfNotDomElement(Header, context))) : null\n})\n\nconst Footer: FC = React.memo(function VirtuosoFooter() {\n const Footer = useEmitterValue('FooterComponent')\n const footerHeight = usePublisher('footerHeight')\n const headerFooterTag = useEmitterValue('headerFooterTag')\n const ref = useSize((el) => footerHeight(correctItemSize(el, 'height')))\n const context = useEmitterValue('context')\n return Footer ? createElement(headerFooterTag, { ref }, createElement(Footer, contextPropIfNotDomElement(Footer, context))) : null\n})\n\nexport interface Hooks {\n usePublisher: typeof usePublisher\n useEmitterValue: typeof useEmitterValue\n useEmitter: typeof useEmitter\n}\n\nexport function buildScroller({ usePublisher, useEmitter, useEmitterValue }: Hooks) {\n const Scroller: ComponentType = React.memo(function VirtuosoScroller({ style, children, ...props }) {\n const scrollContainerStateCallback = usePublisher('scrollContainerState')\n const ScrollerComponent = useEmitterValue('ScrollerComponent')!\n const smoothScrollTargetReached = usePublisher('smoothScrollTargetReached')\n const scrollerRefCallback = useEmitterValue('scrollerRef')\n const context = useEmitterValue('context')\n\n const { scrollerRef, scrollByCallback, scrollToCallback } = useScrollTop(\n scrollContainerStateCallback,\n smoothScrollTargetReached,\n ScrollerComponent,\n scrollerRefCallback\n )\n\n useEmitter('scrollTo', scrollToCallback)\n useEmitter('scrollBy', scrollByCallback)\n return createElement(\n ScrollerComponent,\n {\n ref: scrollerRef as React.MutableRefObject,\n style: { ...scrollerStyle, ...style },\n 'data-test-id': 'virtuoso-scroller',\n 'data-virtuoso-scroller': true,\n tabIndex: 0,\n ...props,\n ...contextPropIfNotDomElement(ScrollerComponent, context),\n },\n children\n )\n })\n return Scroller\n}\n\nexport function buildWindowScroller({ usePublisher, useEmitter, useEmitterValue }: Hooks) {\n const Scroller: Components['Scroller'] = React.memo(function VirtuosoWindowScroller({ style, children, ...props }) {\n const scrollContainerStateCallback = usePublisher('windowScrollContainerState')\n const ScrollerComponent = useEmitterValue('ScrollerComponent')!\n const smoothScrollTargetReached = usePublisher('smoothScrollTargetReached')\n const totalListHeight = useEmitterValue('totalListHeight')\n const deviation = useEmitterValue('deviation')\n const customScrollParent = useEmitterValue('customScrollParent')\n const context = useEmitterValue('context')\n const { scrollerRef, scrollByCallback, scrollToCallback } = useScrollTop(\n scrollContainerStateCallback,\n smoothScrollTargetReached,\n ScrollerComponent,\n u.noop,\n customScrollParent\n )\n\n useIsomorphicLayoutEffect(() => {\n scrollerRef.current = customScrollParent ? customScrollParent : window\n return () => {\n scrollerRef.current = null\n }\n }, [scrollerRef, customScrollParent])\n\n useEmitter('windowScrollTo', scrollToCallback)\n useEmitter('scrollBy', scrollByCallback)\n return createElement(\n ScrollerComponent,\n {\n style: { position: 'relative', ...style, ...(totalListHeight !== 0 ? { height: totalListHeight + deviation } : {}) },\n 'data-virtuoso-scroller': true,\n ...props,\n ...contextPropIfNotDomElement(ScrollerComponent, context),\n },\n children\n )\n })\n return Scroller\n}\n\nconst Viewport: FC> = ({ children }) => {\n const ctx = useContext(VirtuosoMockContext)\n const viewportHeight = usePublisher('viewportHeight')\n const fixedItemHeight = usePublisher('fixedItemHeight')\n const viewportRef = useSize(u.compose(viewportHeight, (el) => correctItemSize(el, 'height')))\n\n React.useEffect(() => {\n if (ctx) {\n viewportHeight(ctx.viewportHeight)\n fixedItemHeight(ctx.itemHeight)\n }\n }, [ctx, viewportHeight, fixedItemHeight])\n\n return (\n
\n {children}\n
\n )\n}\n\nconst WindowViewport: FC> = ({ children }) => {\n const ctx = useContext(VirtuosoMockContext)\n const windowViewportRect = usePublisher('windowViewportRect')\n const fixedItemHeight = usePublisher('fixedItemHeight')\n const customScrollParent = useEmitterValue('customScrollParent')\n const viewportRef = useWindowViewportRectRef(windowViewportRect, customScrollParent)\n\n React.useEffect(() => {\n if (ctx) {\n fixedItemHeight(ctx.itemHeight)\n windowViewportRect({ offsetTop: 0, visibleHeight: ctx.viewportHeight, visibleWidth: 100 })\n }\n }, [ctx, windowViewportRect, fixedItemHeight])\n\n return (\n
\n {children}\n
\n )\n}\n\nconst TopItemListContainer: FC> = ({ children }) => {\n const TopItemList = useEmitterValue('TopItemListComponent')\n const headerHeight = useEmitterValue('headerHeight')\n const style = { ...topItemListStyle, marginTop: `${headerHeight}px` }\n const context = useEmitterValue('context')\n return createElement(TopItemList || 'div', { style, context }, children)\n}\n\nconst ListRoot: FC = React.memo(function VirtuosoRoot(props) {\n const useWindowScroll = useEmitterValue('useWindowScroll')\n const showTopList = useEmitterValue('topItemsIndexes').length > 0\n const customScrollParent = useEmitterValue('customScrollParent')\n const TheScroller = customScrollParent || useWindowScroll ? WindowScroller : Scroller\n const TheViewport = customScrollParent || useWindowScroll ? WindowViewport : Viewport\n return (\n \n \n
\n \n