{
  "version": 3,
  "sources": ["../../src/hooks/use-infinite-scroll.ts"],
  "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport {\n\tuseCallback,\n\tuseEffect,\n\tuseLayoutEffect,\n\tuseRef,\n} from '@wordpress/element';\nimport { throttle } from '@wordpress/compose';\n\n/**\n * Internal dependencies\n */\nimport type { View } from '../types';\n\n/**\n * Captures an anchor element for scroll position preservation.\n * Finds the element closest to the center of the viewport and stores its position.\n *\n * @param container        The scrollable container element.\n * @param anchorElementRef Ref to store the anchor element data.\n * @param direction        The scroll direction ('up' or 'down').\n * @return Whether an anchor element was successfully captured.\n */\nfunction captureAnchorElement(\n\tcontainer: HTMLElement,\n\tanchorElementRef: React.MutableRefObject< {\n\t\tposinset: number;\n\t\tviewportOffset: number;\n\t\tdirection: 'up' | 'down' | null;\n\t} | null >,\n\tdirection: 'up' | 'down'\n): boolean {\n\t// Find a visible element to use as anchor - prefer one in the middle of the viewport\n\tconst containerRect = container.getBoundingClientRect();\n\tconst centerY = containerRect.top + containerRect.height / 2;\n\n\t// Query all items with aria-posinset and find the one closest to center\n\tconst items = Array.from( container.querySelectorAll( '[aria-posinset]' ) );\n\n\tif ( items.length === 0 ) {\n\t\treturn false;\n\t}\n\n\t// Find the item closest to the center of the viewport\n\tconst bestAnchor = items.reduce( ( best, item ) => {\n\t\tconst itemRect = item.getBoundingClientRect();\n\t\tconst itemCenterY = itemRect.top + itemRect.height / 2;\n\t\tconst distance = Math.abs( itemCenterY - centerY );\n\n\t\tconst bestRect = best.getBoundingClientRect();\n\t\tconst bestCenterY = bestRect.top + bestRect.height / 2;\n\t\tconst bestDistance = Math.abs( bestCenterY - centerY );\n\n\t\treturn distance < bestDistance ? item : best;\n\t} );\n\n\tconst posinset = Number( bestAnchor.getAttribute( 'aria-posinset' ) );\n\tconst anchorRect = bestAnchor.getBoundingClientRect();\n\tanchorElementRef.current = {\n\t\tposinset,\n\t\tviewportOffset: anchorRect.top - containerRect.top,\n\t\tdirection,\n\t};\n\treturn true;\n}\n\ntype UseInfiniteScrollProps = {\n\tview: View;\n\tonChangeView: ( view: View ) => void;\n\tisLoading: boolean;\n\tpaginationInfo: {\n\t\ttotalItems: number;\n\t\ttotalPages: number;\n\t};\n\tcontainerRef: React.MutableRefObject< HTMLDivElement | null >;\n\tsetVisibleEntries?: React.Dispatch< React.SetStateAction< number[] > >;\n};\n\ntype UseInfiniteScrollResult = {\n\tintersectionObserver?: IntersectionObserver | null;\n};\n\nexport function useInfiniteScroll( {\n\tview,\n\tonChangeView,\n\tisLoading,\n\tpaginationInfo,\n\tcontainerRef,\n\tsetVisibleEntries,\n}: UseInfiniteScrollProps ): UseInfiniteScrollResult {\n\t// Track an anchor element for scroll position preservation\n\t// This approach is robust even when items are added/removed from both ends simultaneously\n\tconst anchorElementRef = useRef< {\n\t\tposinset: number;\n\t\tviewportOffset: number;\n\t\tdirection: 'up' | 'down' | null;\n\t} | null >( null );\n\tconst viewRef = useRef( view );\n\tconst isLoadingRef = useRef( isLoading );\n\tconst onChangeViewRef = useRef( onChangeView );\n\tconst totalItemsRef = useRef( paginationInfo.totalItems );\n\n\tuseLayoutEffect( () => {\n\t\tviewRef.current = view;\n\t\tisLoadingRef.current = isLoading;\n\t\tonChangeViewRef.current = onChangeView;\n\t\ttotalItemsRef.current = paginationInfo.totalItems;\n\t}, [ view, isLoading, onChangeView, paginationInfo.totalItems ] );\n\n\tconst intersectionObserverCallback: IntersectionObserverCallback =\n\t\tuseCallback(\n\t\t\t( entries: IntersectionObserverEntry[] ) => {\n\t\t\t\t// Calculate new visible entries outside of setState\n\t\t\t\tif ( ! setVisibleEntries ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetVisibleEntries( ( prev: number[] ) => {\n\t\t\t\t\tconst newVisibleEntries = new Set( prev );\n\t\t\t\t\tlet hasChanged = false;\n\n\t\t\t\t\tentries.forEach( ( entry ) => {\n\t\t\t\t\t\tconst posInSet = Number(\n\t\t\t\t\t\t\tentry.target?.attributes?.getNamedItem(\n\t\t\t\t\t\t\t\t'aria-posinset'\n\t\t\t\t\t\t\t)?.value\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif ( isNaN( posInSet ) ) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( entry.isIntersecting ) {\n\t\t\t\t\t\t\tif ( ! newVisibleEntries.has( posInSet ) ) {\n\t\t\t\t\t\t\t\tnewVisibleEntries.add( posInSet );\n\t\t\t\t\t\t\t\thasChanged = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if ( newVisibleEntries.has( posInSet ) ) {\n\t\t\t\t\t\t\tnewVisibleEntries.delete( posInSet );\n\t\t\t\t\t\t\thasChanged = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t} );\n\n\t\t\t\t\t// Only return new array if something actually changed\n\t\t\t\t\treturn hasChanged\n\t\t\t\t\t\t? Array.from( newVisibleEntries ).sort()\n\t\t\t\t\t\t: prev;\n\t\t\t\t} );\n\t\t\t},\n\t\t\t[ setVisibleEntries ]\n\t\t);\n\n\t// Preserve scroll position when items are added or removed during infinite scroll\n\t// Uses anchor element approach: find the same element after render and restore its viewport position\n\tuseLayoutEffect( () => {\n\t\tconst container = containerRef.current;\n\t\tconst anchor = anchorElementRef.current;\n\n\t\tif (\n\t\t\t! container ||\n\t\t\t! view.infiniteScrollEnabled ||\n\t\t\t! anchor ||\n\t\t\tisLoading\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Find the anchor element by its posinset\n\t\tconst anchorElement = container.querySelector(\n\t\t\t`[aria-posinset=\"${ anchor.posinset }\"]`\n\t\t);\n\n\t\tif ( anchorElement ) {\n\t\t\tconst containerRect = container.getBoundingClientRect();\n\t\t\tconst anchorRect = anchorElement.getBoundingClientRect();\n\t\t\tconst currentOffset = anchorRect.top - containerRect.top;\n\n\t\t\t// Calculate how much the anchor has moved and adjust scroll to compensate\n\t\t\tconst scrollAdjustment = currentOffset - anchor.viewportOffset;\n\n\t\t\tif ( Math.abs( scrollAdjustment ) > 1 ) {\n\t\t\t\tcontainer.scrollTop += scrollAdjustment;\n\t\t\t}\n\t\t}\n\n\t\t// Reset the anchor state now that we've adjusted\n\t\tanchorElementRef.current = null;\n\t}, [ containerRef, isLoading, view.infiniteScrollEnabled ] );\n\n\t// Create and expose a shared IntersectionObserver for provider-level reuse.\n\tconst intersectionObserverRef = useRef< IntersectionObserver | null >(\n\t\tnull\n\t);\n\tuseEffect( () => {\n\t\tif ( ! view.infiniteScrollEnabled || ! intersectionObserverCallback ) {\n\t\t\tif ( intersectionObserverRef.current ) {\n\t\t\t\tintersectionObserverRef.current.disconnect();\n\t\t\t\tintersectionObserverRef.current = null;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tintersectionObserverRef.current = new IntersectionObserver(\n\t\t\tintersectionObserverCallback,\n\t\t\t{ root: null, rootMargin: '0px', threshold: 0.1 }\n\t\t);\n\n\t\treturn () => {\n\t\t\tif ( intersectionObserverRef.current ) {\n\t\t\t\tintersectionObserverRef.current.disconnect();\n\t\t\t\tintersectionObserverRef.current = null;\n\t\t\t}\n\t\t};\n\t}, [ view.infiniteScrollEnabled, intersectionObserverCallback ] );\n\n\t// Attach scroll event listener for infinite scroll\n\tuseEffect( () => {\n\t\tif ( ! view.infiniteScrollEnabled || ! containerRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet lastScrollTop = 0;\n\t\t// Use larger thresholds to trigger loading earlier during fast scrolling\n\t\tconst BOTTOM_THRESHOLD = 600; // px from bottom to trigger load\n\t\tconst TOP_THRESHOLD = 800; // px from top to trigger load\n\n\t\tconst handleScroll = throttle( ( event: unknown ) => {\n\t\t\tconst currentView = viewRef.current;\n\t\t\tconst totalItems = totalItemsRef.current;\n\t\t\tconst target = ( event as Event ).target as HTMLElement;\n\t\t\tconst scrollTop = target.scrollTop;\n\t\t\tconst scrollHeight = target.scrollHeight;\n\t\t\tconst clientHeight = target.clientHeight;\n\n\t\t\t// Determine scroll direction\n\t\t\tconst scrollDirection = scrollTop > lastScrollTop ? 'down' : 'up';\n\t\t\tlastScrollTop = scrollTop;\n\n\t\t\t// Don't trigger if already loading\n\t\t\tif ( isLoadingRef.current ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst currentStartPosition = currentView.startPosition || 1;\n\t\t\tconst batchSize = currentView.perPage || 10;\n\t\t\tconst currentEndPosition = Math.min(\n\t\t\t\tcurrentStartPosition + batchSize,\n\t\t\t\ttotalItems\n\t\t\t);\n\n\t\t\t// Check if user has scrolled near the bottom\n\t\t\tif (\n\t\t\t\tscrollDirection === 'down' &&\n\t\t\t\tscrollTop + clientHeight >= scrollHeight - BOTTOM_THRESHOLD\n\t\t\t) {\n\t\t\t\t// Check if there's more data to load\n\t\t\t\tif ( currentEndPosition < totalItems ) {\n\t\t\t\t\tconst newStartPosition = currentEndPosition;\n\n\t\t\t\t\t// Capture anchor element for scroll position preservation\n\t\t\t\t\tcaptureAnchorElement( target, anchorElementRef, 'down' );\n\n\t\t\t\t\tonChangeViewRef.current( {\n\t\t\t\t\t\t...currentView,\n\t\t\t\t\t\tstartPosition: newStartPosition,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check if user has scrolled near the top\n\t\t\tif ( scrollDirection === 'up' && scrollTop <= TOP_THRESHOLD ) {\n\t\t\t\t// Check if there's more data to load\n\t\t\t\tif ( currentStartPosition > 1 ) {\n\t\t\t\t\t// Round to 1 if we're close to the beginning to avoid tiny batches\n\t\t\t\t\tconst calculatedStartPosition =\n\t\t\t\t\t\tcurrentStartPosition - batchSize;\n\t\t\t\t\tconst newStartPosition =\n\t\t\t\t\t\tcalculatedStartPosition < 6\n\t\t\t\t\t\t\t? 1\n\t\t\t\t\t\t\t: calculatedStartPosition;\n\n\t\t\t\t\t// Capture anchor element for scroll position preservation\n\t\t\t\t\tcaptureAnchorElement( target, anchorElementRef, 'up' );\n\n\t\t\t\t\tonChangeViewRef.current( {\n\t\t\t\t\t\t...currentView,\n\t\t\t\t\t\tstartPosition: newStartPosition,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t}, 50 ); // Faster throttle (50ms) for better response to fast scrolling\n\n\t\tconst container = containerRef.current;\n\t\tcontainer.addEventListener( 'scroll', handleScroll );\n\n\t\treturn () => {\n\t\t\tcontainer.removeEventListener( 'scroll', handleScroll );\n\t\t\thandleScroll.cancel(); // Cancel any pending throttled calls\n\t\t};\n\t}, [ containerRef, view.infiniteScrollEnabled ] );\n\n\treturn {\n\t\tintersectionObserver: intersectionObserverRef.current,\n\t};\n}\n"],
  "mappings": "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",
  "names": []
}
