@dynamic-scroll/core 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useHeightMap.ts","../src/hooks/usePositions.ts","../src/hooks/useGroupPositions.ts","../src/utils/binarySearch.ts","../src/hooks/useScrollState.ts","../src/components/Measure.tsx","../src/components/VirtualScroll.tsx","../src/components/InitialMeasure.tsx","../src/DynamicScroll.tsx","../src/components/StickyGroupHeader.tsx"],"names":["useRef","useState","useCallback","useMemo","useEffect","jsx","memo","useLayoutEffect","flushSync","useImperativeHandle","jsxs","forwardRef","images","Fragment"],"mappings":"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","file":"index.js","sourcesContent":["import { useCallback, useRef, useState } from \"react\";\nimport type { VirtualScrollItem } from \"../types\";\n\ninterface UseHeightMapParams<T extends VirtualScrollItem> {\n items: T[];\n estimatedItemSize?: number;\n}\n\ninterface UseHeightMapReturn {\n heightMapRef: React.RefObject<Map<string, number>>;\n isAllMeasured: boolean;\n unmeasuredIds: string[];\n onItemMeasured: (id: string, height: number) => void;\n onHeightChange: (id: string, height: number) => void;\n version: number;\n}\n\n/**\n * 아이템 높이 맵을 관리하는 훅.\n *\n * - heightMap은 useRef<Map>으로 저장\n * - InitialMeasure: pendingIds Set으로 미측정 추적, 0이 되면 1번 리렌더\n * - ResizeObserver: rAF 배치\n */\nexport function useHeightMap<T extends VirtualScrollItem>({\n items,\n estimatedItemSize,\n}: UseHeightMapParams<T>): UseHeightMapReturn {\n const heightMapRef = useRef<Map<string, number>>(new Map());\n const pendingIdsRef = useRef<Set<string>>(new Set());\n const batchRafRef = useRef<number | null>(null);\n const [version, setVersion] = useState(0);\n\n // 미측정 아이템 계산 + pendingIds를 현재 items 기준으로 동기화\n const unmeasuredIds: string[] = [];\n const currentIds = new Set<string>();\n\n if (estimatedItemSize === undefined) {\n for (const item of items) {\n currentIds.add(item.id);\n if (!heightMapRef.current.has(item.id)) {\n unmeasuredIds.push(item.id);\n }\n }\n // items에서 사라진 id를 pendingIds에서 제거 (아이템 교체 시 stale pending 방지)\n for (const id of pendingIdsRef.current) {\n if (!currentIds.has(id)) {\n pendingIdsRef.current.delete(id);\n }\n }\n // 새 unmeasured를 pending에 추가\n for (const id of unmeasuredIds) {\n pendingIdsRef.current.add(id);\n }\n }\n\n const isAllMeasured =\n estimatedItemSize !== undefined ||\n (items.length > 0 && unmeasuredIds.length === 0);\n\n const onItemMeasured = useCallback((id: string, height: number) => {\n heightMapRef.current.set(id, height);\n pendingIdsRef.current.delete(id);\n\n if (pendingIdsRef.current.size === 0) {\n setVersion((v) => v + 1);\n }\n }, []);\n\n const onHeightChange = useCallback((id: string, height: number) => {\n const cur = heightMapRef.current.get(id);\n if (cur === height) return;\n\n heightMapRef.current.set(id, height);\n\n if (batchRafRef.current === null) {\n batchRafRef.current = requestAnimationFrame(() => {\n batchRafRef.current = null;\n setVersion((v) => v + 1);\n });\n }\n }, []);\n\n return {\n heightMapRef,\n isAllMeasured,\n unmeasuredIds,\n onItemMeasured,\n onHeightChange,\n version,\n };\n}\n","import { useMemo } from \"react\";\nimport type { VirtualScrollItem } from \"../types\";\n\ninterface UsePositionsParams<T extends VirtualScrollItem> {\n /** 아이템 배열 */\n items: T[];\n /** 높이 맵 ref (useHeightMap에서 제공) */\n heightMapRef: React.RefObject<Map<string, number>>;\n /** heightMap 변경 시 재계산 트리거용 버전 (useHeightMap에서 제공) */\n version: number;\n /** 추정 아이템 높이 (미측정 아이템의 fallback) */\n estimatedItemSize?: number;\n}\n\ninterface UsePositionsReturn {\n /** 각 아이템의 누적 top 위치 배열. positions[i] = i번째 아이템의 top. positions[length] = totalHeight */\n childPositions: number[];\n /** 전체 컨텐츠 높이 */\n totalHeight: number;\n}\n\n/**\n * heightMapRef + version을 기반으로 childPositions를 계산하는 훅.\n *\n * - heightMapRef는 useHeightMap에서 관리하는 ref를 그대로 받는다\n * - version이 변경될 때마다 useMemo가 재계산된다\n * - 미측정 아이템은 estimatedItemSize 또는 0으로 처리\n */\nexport function usePositions<T extends VirtualScrollItem>({\n items,\n heightMapRef,\n version,\n estimatedItemSize,\n}: UsePositionsParams<T>): UsePositionsReturn {\n const childPositions = useMemo(() => {\n const positions = [0];\n for (let i = 0; i < items.length; i++) {\n const height =\n heightMapRef.current.get(items[i].id) ?? estimatedItemSize ?? 0;\n positions.push(positions[i] + height);\n }\n return positions;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- version은 heightMap 변경의 프록시\n }, [items, version, estimatedItemSize]);\n\n const totalHeight =\n childPositions.length > 1\n ? childPositions[childPositions.length - 1]\n : 0;\n\n return { childPositions, totalHeight };\n}\n","import { useMemo } from \"react\";\nimport type { VirtualScrollItem } from \"../types\";\n\ninterface GroupInfo {\n /** 그룹 키 → 해당 그룹의 전체 높이 */\n heightByGroup: Map<string, number>;\n /** 그룹 키 → 해당 그룹 아래 모든 그룹의 누적 높이 (sticky 헤더 height 제한용) */\n cumulativeHeightByGroup: Map<string, number>;\n /** 인덱스 → 해당 아이템이 속한 그룹 키 */\n groupKeyByIndex: string[];\n}\n\n/**\n * 그룹별 높이 정보를 계산하는 훅.\n *\n * sticky group header의 height 제한에 사용된다.\n * cumulativeHeightByGroup은 최신 그룹부터 0, 이전 그룹일수록 아래 그룹들의 누적 높이.\n *\n * @param items - 아이템 배열\n * @param groupBy - 아이템을 그룹핑할 키를 반환하는 함수\n * @param heightMapRef - 높이 맵 ref\n * @param version - heightMap 변경 트리거용 버전\n */\nexport function useGroupPositions<T extends VirtualScrollItem>(\n items: T[],\n groupBy: ((item: T) => string) | undefined,\n heightMapRef: React.RefObject<Map<string, number>>,\n version: number,\n): GroupInfo | null {\n return useMemo(() => {\n if (!groupBy || items.length === 0) return null;\n\n const heightByGroup = new Map<string, number>();\n const groupKeyByIndex: string[] = [];\n const groupOrder: string[] = [];\n\n for (let i = 0; i < items.length; i++) {\n const key = groupBy(items[i]);\n groupKeyByIndex.push(key);\n\n const itemHeight = heightMapRef.current.get(items[i].id) ?? 0;\n const current = heightByGroup.get(key) ?? 0;\n heightByGroup.set(key, current + itemHeight);\n\n if (!groupOrder.includes(key)) {\n groupOrder.push(key);\n }\n }\n\n // 누적 높이 계산 (마지막 그룹 = 0, 이전 그룹 = 아래 그룹들의 합)\n const cumulativeHeightByGroup = new Map<string, number>();\n let cumulative = 0;\n\n for (let i = groupOrder.length - 1; i >= 0; i--) {\n cumulativeHeightByGroup.set(groupOrder[i], cumulative);\n cumulative += heightByGroup.get(groupOrder[i]) ?? 0;\n }\n\n return { heightByGroup, cumulativeHeightByGroup, groupKeyByIndex };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [items, groupBy, version]);\n}\n","/**\n * 이진 탐색으로 scrollTop에 해당하는 첫 번째 가시 노드 인덱스를 찾는다.\n *\n * nodePositions[i]는 i번째 아이템의 top 위치 (누적합 배열).\n * 노드의 중앙점(nodeCenter)과 scrollTop을 비교하여\n * 현재 뷰포트 최상단에 위치한 노드를 O(log n)으로 찾는다.\n *\n * @param scrollTop - 현재 스크롤 위치\n * @param nodePositions - 누적 위치 배열 (길이: itemCount + 1)\n * @param itemCount - 전체 아이템 수\n * @returns 첫 번째 가시 노드의 인덱스\n */\nexport function generateStartNodeIndex(\n scrollTop: number,\n nodePositions: number[],\n itemCount: number,\n): number {\n let startRange = 0;\n let endRange = itemCount - 1;\n\n if (endRange < 0) return 0;\n if (scrollTop <= 0) return 0;\n\n while (endRange !== startRange) {\n const middle = Math.floor((endRange - startRange) / 2 + startRange);\n const nodeCenter =\n (nodePositions[middle] + nodePositions[middle + 1]) / 2;\n\n if (nodeCenter <= scrollTop && nodePositions[middle + 1] > scrollTop) {\n return middle;\n }\n\n if (middle === startRange) {\n return endRange;\n }\n\n if (nodeCenter <= scrollTop) {\n startRange = middle;\n } else {\n endRange = middle;\n }\n }\n\n return startRange;\n}\n\n/**\n * 이진 탐색으로 뷰포트를 넘어서는 마지막 가시 노드 인덱스를 찾는다.\n *\n * 기존 구현은 선형 탐색 O(k)였으나, 이진 탐색 O(log n)으로 개선.\n * startNodeIndex의 다음 노드 위치를 기준으로 viewportHeight를 더한\n * 경계값을 초과하는 첫 번째 노드를 찾는다.\n *\n * @param nodePositions - 누적 위치 배열\n * @param startNodeIndex - 첫 번째 가시 노드 인덱스 (renderAhead 미적용)\n * @param itemCount - 전체 아이템 수\n * @param viewportHeight - 뷰포트 높이\n * @returns 마지막 가시 노드의 인덱스\n */\nexport function generateEndNodeIndex(\n nodePositions: number[],\n startNodeIndex: number,\n itemCount: number,\n viewportHeight: number,\n): number {\n if (itemCount === 0) return 0;\n\n const basePosition = nodePositions[startNodeIndex + 1] ?? nodePositions[startNodeIndex];\n const targetPosition = basePosition + viewportHeight;\n\n let low = startNodeIndex;\n let high = itemCount - 1;\n\n // 마지막 아이템도 뷰포트 안이면 바로 반환\n if (nodePositions[high] <= targetPosition) {\n return high;\n }\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n\n if (nodePositions[mid] <= targetPosition) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n return low;\n}\n\n/**\n * 이진 탐색으로 타겟 아이템을 뷰포트 중앙에 배치하기 위한 시작 노드 인덱스를 찾는다.\n *\n * 타겟 아이템의 중심이 뷰포트 중앙(viewportHeight / 2)에 오도록 하는\n * scrollTop 위치의 첫 번째 가시 노드를 찾는다.\n *\n * @param nodePositions - 누적 위치 배열\n * @param targetRowHeight - 타겟 아이템의 높이\n * @param targetRowIndex - 타겟 아이템의 인덱스\n * @param viewportHeight - 뷰포트 높이\n * @returns 시작 노드 인덱스\n */\nexport function generateCenteredStartNodeIndex(\n nodePositions: number[],\n targetRowHeight: number,\n targetRowIndex: number,\n viewportHeight: number,\n): number {\n if (!nodePositions || nodePositions.length === 0) return 0;\n\n const totalHeight = nodePositions[targetRowIndex] + targetRowHeight;\n if (totalHeight <= viewportHeight) return 0;\n\n const viewHalf = Math.floor(viewportHeight / 2);\n\n let start = 0;\n let end = targetRowIndex;\n let answer = 0;\n let closestDistance = Infinity;\n\n while (start <= end) {\n const middle = Math.floor((end - start) / 2 + start);\n const middleHeight =\n nodePositions[targetRowIndex] - nodePositions[middle] + targetRowHeight;\n\n const distance = Math.abs(middleHeight - viewHalf);\n if (distance < closestDistance) {\n closestDistance = distance;\n answer = middle;\n }\n\n if (viewHalf < middleHeight) {\n start = middle + 1;\n } else {\n end = middle - 1;\n }\n }\n\n return answer;\n}\n","import { useMemo } from \"react\";\nimport type { ScrollState } from \"../types\";\nimport {\n generateStartNodeIndex,\n generateEndNodeIndex,\n} from \"../utils/binarySearch\";\n\ninterface UseScrollStateParams {\n /** 현재 스크롤 위치 */\n scrollTop: number;\n /** 전체 아이템 수 */\n itemCount: number;\n /** 뷰포트 전후로 추가 렌더링할 아이템 수 (기본값: 8) */\n overscanCount?: number;\n /** 뷰포트 높이 */\n viewportHeight: number;\n /** 각 아이템의 누적 top 위치 배열 */\n childPositions: number[];\n}\n\n/**\n * scrollTop과 childPositions를 기반으로 가시 영역의 노드 범위를 계산하는 훅.\n *\n * - generateStartNodeIndex: 이진탐색 O(log n)으로 시작 노드 계산\n * - generateEndNodeIndex: 이진탐색 O(log n)으로 끝 노드 계산\n * - overscanCount만큼 전후로 버퍼를 추가하여 스크롤 시 빈 영역 방지\n */\nexport function useScrollState({\n scrollTop,\n itemCount,\n overscanCount = 8,\n viewportHeight,\n childPositions,\n}: UseScrollStateParams): ScrollState {\n const firstVisibleNode = useMemo(\n () => generateStartNodeIndex(scrollTop, childPositions, itemCount),\n [scrollTop, childPositions, itemCount],\n );\n\n const lastVisibleNode = useMemo(\n () =>\n generateEndNodeIndex(\n childPositions,\n firstVisibleNode,\n itemCount,\n viewportHeight,\n ),\n [childPositions, firstVisibleNode, itemCount, viewportHeight],\n );\n\n const startNode = Math.max(0, firstVisibleNode - overscanCount);\n const endNode = Math.min(itemCount - 1, lastVisibleNode + overscanCount);\n const visibleNodeCount = Math.max(0, endNode - startNode + 1);\n\n return {\n firstVisibleNode,\n lastVisibleNode,\n startNode,\n endNode,\n visibleNodeCount,\n };\n}\n","import { memo, useEffect, useRef, useState } from \"react\";\n\ninterface MeasureProps {\n children: React.ReactNode;\n /** 아이템 고유 식별자 */\n itemId: string;\n /** absolute top 위치 (px) */\n position: number;\n /** 높이 변경 시 호출되는 콜백 */\n onHeightChange: (id: string, height: number) => void;\n /** 사전 측정된 높이 (InitialMeasure에서 측정한 값). 깜빡임 방지용 높이 잠금에 사용 */\n knownHeight?: number;\n}\n\n/**\n * 가상 스크롤 내 각 아이템을 감싸는 컴포넌트.\n *\n * 높이 잠금 메커니즘:\n * 1. knownHeight가 있으면 height style로 잠금 (이미지 재로드 중 깜빡임 방지)\n * 2. MutationObserver로 내부 DOM 변경 감지 (이미지 로드 등)\n * 3. 변경 감지 → height style 제거 (자연 리플로우)\n * 4. ResizeObserver로 새 높이 감지 → onHeightChange 호출\n */\nfunction MeasureInner({\n children,\n itemId,\n position,\n onHeightChange,\n knownHeight,\n}: MeasureProps) {\n const ref = useRef<HTMLDivElement>(null);\n const prevHeightRef = useRef<number>(knownHeight ?? 0);\n const [heightLocked, setHeightLocked] = useState(!!knownHeight);\n\n // --- ResizeObserver: 높이 변경 감지 ---\n useEffect(() => {\n const node = ref.current;\n if (!node) return;\n\n const measure = () => {\n const newHeight = Math.ceil(node.offsetHeight);\n // 언마운트 시 ResizeObserver가 0을 감지 → heightMap 오염 방지\n if (newHeight === 0 || prevHeightRef.current === newHeight) return;\n const oldHeight = prevHeightRef.current;\n prevHeightRef.current = newHeight;\n console.log(`[Measure] ${itemId} height changed: ${oldHeight} → ${newHeight} (locked: ${heightLocked}, knownHeight: ${knownHeight})`);\n onHeightChange(itemId, newHeight);\n };\n\n const resizeObserver = new ResizeObserver(measure);\n resizeObserver.observe(node);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [itemId, onHeightChange]);\n\n // --- MutationObserver: 내부 DOM 변경 시 높이 잠금 해제 ---\n useEffect(() => {\n if (!heightLocked) return;\n const node = ref.current;\n if (!node) return;\n\n const mutationObserver = new MutationObserver(() => {\n console.log(`[Measure] ${itemId} mutation detected → unlocking height (was ${knownHeight}px)`);\n setHeightLocked(false);\n });\n\n mutationObserver.observe(node, {\n childList: true,\n subtree: false,\n });\n\n return () => {\n mutationObserver.disconnect();\n };\n }, [heightLocked]);\n\n return (\n <div\n data-dynamic-scroll-item={itemId}\n style={{\n position: \"absolute\",\n top: position,\n left: 0,\n width: \"100%\",\n ...(heightLocked && knownHeight ? { height: knownHeight, overflow: \"hidden\" } : {}),\n }}\n ref={ref}\n >\n {children}\n </div>\n );\n}\n\nexport const Measure = memo(MeasureInner);\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { flushSync } from \"react-dom\";\nimport type {\n DynamicScrollHandle,\n InitialScrollPosition,\n ScrollAlign,\n VirtualScrollItem,\n VirtualScrollProps,\n} from \"../types\";\nimport { useScrollState } from \"../hooks/useScrollState\";\nimport { Measure } from \"./Measure\";\n\nconst DEFAULT_OVERSCAN = 8;\n\nfunction VirtualScrollInner<T extends VirtualScrollItem>(\n {\n items,\n renderItem,\n childPositions,\n totalHeight,\n heightMapRef,\n onHeightChange,\n overscanCount = DEFAULT_OVERSCAN,\n onStartReached,\n onEndReached,\n threshold = 0,\n onAtBottomChange,\n syncScrollUpdates = false,\n className,\n style,\n isMeasuring = false,\n loadingComponent,\n bottomLoadingComponent,\n initialScrollPosition = \"bottom\",\n groupInfo,\n renderGroupHeader,\n }: VirtualScrollProps<T>,\n ref: React.ForwardedRef<DynamicScrollHandle>,\n) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [scrollTop, setScrollTop] = useState(0);\n const [viewportHeight, setViewportHeight] = useState(0);\n\n // --- refs (리렌더 없이 최신값 추적) ---\n const animationFrameRef = useRef<number | null>(null);\n const isAtBottomRef = useRef(true);\n const mountedRef = useRef(false);\n // 프로그래매틱 스크롤 후 reach 감지/stick-to-bottom 일시 차단\n const programmaticScrollRef = useRef(false);\n\n // --- 양방향 무한 스크롤: 로딩 가드 ---\n // backward(prepend): scrollHeight 보존 → 측정 완료 후 scrollTop 보정\n const backwardLoadingRef = useRef(false);\n const prevScrollHeightRef = useRef(0);\n // forward(append): 로딩 중 stick-to-bottom 차단 → 측정 완료 후 해제\n const forwardLoadingRef = useRef(false);\n\n // --- viewportHeight 초기화 ---\n useLayoutEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n setViewportHeight(el.clientHeight);\n }, []);\n\n // --- 최초 마운트: initialScrollPosition에 따라 스크롤 위치 설정 ---\n useLayoutEffect(() => {\n const el = containerRef.current;\n if (!el || mountedRef.current || totalHeight <= 0) return;\n\n mountedRef.current = true;\n\n if (initialScrollPosition === \"top\") {\n el.scrollTop = 0;\n setScrollTop(0);\n isAtBottomRef.current = false;\n onAtBottomChange?.(false);\n } else if (initialScrollPosition === \"bottom\") {\n el.scrollTop = el.scrollHeight;\n setScrollTop(el.scrollTop);\n isAtBottomRef.current = true;\n onAtBottomChange?.(true);\n } else {\n // { index, align } — 특정 아이템 위치로 이동 (예: 마지막 읽은 메시지)\n const { index, align = \"center\" } = initialScrollPosition;\n if (index >= 0 && index < items.length) {\n const itemTop = childPositions[index];\n const itemHeight = heightMapRef.current.get(items[index].id) ?? 0;\n\n let target: number;\n if (align === \"start\") {\n target = itemTop;\n } else if (align === \"end\") {\n target = itemTop + itemHeight - el.clientHeight;\n } else {\n target = itemTop + itemHeight / 2 - el.clientHeight / 2;\n }\n el.scrollTop = Math.max(0, target);\n setScrollTop(el.scrollTop);\n isAtBottomRef.current = false;\n onAtBottomChange?.(false);\n }\n }\n }, [totalHeight, onAtBottomChange, initialScrollPosition, items, childPositions, heightMapRef]);\n\n const { startNode, endNode } = useScrollState({\n scrollTop,\n itemCount: items.length,\n overscanCount,\n viewportHeight,\n childPositions,\n });\n\n // --- onScroll ---\n const onScroll = useCallback(\n (e: Event) => {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n animationFrameRef.current = requestAnimationFrame(() => {\n animationFrameRef.current = null;\n const target = e.target as HTMLDivElement;\n const newScrollTop = target.scrollTop;\n\n if (syncScrollUpdates) {\n flushSync(() => setScrollTop(newScrollTop));\n } else {\n setScrollTop(newScrollTop);\n }\n\n // 하단 판정 (프로그래매틱 스크롤 중에는 건너뜀)\n if (programmaticScrollRef.current) return;\n const atBottom =\n target.scrollHeight - newScrollTop - target.clientHeight <= 1;\n if (isAtBottomRef.current !== atBottom) {\n isAtBottomRef.current = atBottom;\n onAtBottomChange?.(atBottom);\n }\n });\n },\n [syncScrollUpdates, onAtBottomChange],\n );\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n el.addEventListener(\"scroll\", onScroll);\n return () => el.removeEventListener(\"scroll\", onScroll);\n }, [onScroll]);\n\n // --- Imperative API ---\n const scrollToBottom = useCallback(\n (behavior: ScrollBehavior = \"auto\") => {\n const el = containerRef.current;\n if (!el || items.length === 0) return;\n el.scrollTo({ top: el.scrollHeight, left: 0, behavior });\n isAtBottomRef.current = true;\n onAtBottomChange?.(true);\n },\n [items.length, onAtBottomChange],\n );\n\n const scrollToItem = useCallback(\n (index: number, align: ScrollAlign = \"center\") => {\n const el = containerRef.current;\n if (!el || index < 0 || index >= items.length) {\n console.warn('[scrollToItem] SKIP', { index, itemsLength: items.length, hasEl: !!el });\n return;\n }\n\n const itemTop = childPositions[index];\n const itemHeight = heightMapRef.current.get(items[index].id) ?? 0;\n\n let target: number;\n if (align === \"start\") {\n target = itemTop;\n } else if (align === \"end\") {\n target = itemTop + itemHeight - el.clientHeight;\n } else {\n // center: 아이템 중심이 뷰포트 중앙에 오도록\n target = itemTop + itemHeight / 2 - el.clientHeight / 2;\n }\n const maxScrollTop = el.scrollHeight - el.clientHeight;\n const finalTarget = Math.max(0, Math.min(target, maxScrollTop));\n console.log('[scrollToItem]', {\n index,\n align,\n itemId: items[index].id,\n itemTop,\n itemHeight,\n target,\n finalTarget,\n viewportHeight: el.clientHeight,\n scrollHeight: el.scrollHeight,\n totalHeight,\n maxScrollTop,\n });\n // 프로그래매틱 이동: stick-to-bottom/reach 일시 차단\n programmaticScrollRef.current = true;\n isAtBottomRef.current = false;\n el.scrollTo({ top: finalTarget, left: 0, behavior: \"auto\" });\n // 다음 rAF에서 해제 (onScroll의 rAF 이후)\n requestAnimationFrame(() => {\n programmaticScrollRef.current = false;\n });\n },\n [childPositions, items, heightMapRef],\n );\n\n useImperativeHandle(ref, () => ({\n scrollToItem,\n scrollToBottom,\n scrollToOffset: (offset: number, behavior: ScrollBehavior = \"auto\") => {\n containerRef.current?.scrollTo({ top: offset, left: 0, behavior });\n },\n getScrollOffset: () => containerRef.current?.scrollTop ?? 0,\n }));\n\n // --- totalHeight 변경 시: 스크롤 위치 보정 ---\n //\n // prepend/append 후 측정이 완료되면 totalHeight가 변경된다.\n // 이 시점에 각 방향에 맞는 스크롤 보정을 수행한다.\n //\n // 방향 | 가드 ref | 보정 방식\n // ----------|----------------------|----------------------------------\n // backward | prevScrollHeightRef | scrollTop += (새 scrollHeight - 이전 scrollHeight)\n // forward | forwardLoadingRef | stick-to-bottom 차단 (현재 위치 유지)\n // 없음 | isAtBottomRef | stick-to-bottom (하단 고정)\n //\n useLayoutEffect(() => {\n if (!mountedRef.current) return;\n const el = containerRef.current;\n if (!el) return;\n\n // backward(prepend): 측정 완료 후 scrollTop 보정으로 위치 보존\n if (prevScrollHeightRef.current > 0 && !isMeasuring) {\n const diff = el.scrollHeight - prevScrollHeightRef.current;\n console.log('[totalHeight] backward adjust', { diff, prevScrollHeight: prevScrollHeightRef.current, newScrollHeight: el.scrollHeight });\n if (diff > 0) {\n el.scrollTop += diff;\n setScrollTop(el.scrollTop);\n }\n prevScrollHeightRef.current = 0;\n backwardLoadingRef.current = false;\n return;\n }\n\n // forward(append): isMeasuring 중에도 flag 유지 → 측정 완료 후 해제\n if (forwardLoadingRef.current) {\n if (!isMeasuring) {\n forwardLoadingRef.current = false;\n }\n return;\n }\n\n // stick-to-bottom: 하단에 있으면 하단 유지 (이미지 로드, 새 메시지 등)\n if (isAtBottomRef.current && !isMeasuring) {\n console.log('[totalHeight] stick-to-bottom', { scrollHeight: el.scrollHeight });\n el.scrollTop = el.scrollHeight;\n }\n }, [totalHeight, isMeasuring]);\n\n // --- 상단/하단 도달 감지 ---\n //\n // scrollTop 변경 시 상단/하단 도달을 판단하고 데이터 로드를 트리거한다.\n // 각 방향은 독립적인 가드 ref로 중복 호출을 방지한다.\n // 가드 ref는 여기서 설정하고, totalHeight effect에서 해제한다.\n //\n useLayoutEffect(() => {\n const el = containerRef.current;\n if (!el || !mountedRef.current || programmaticScrollRef.current) return;\n\n const actualScrollTop = el.scrollTop;\n\n // 상단 도달 → backward loading\n if (\n actualScrollTop <= threshold &&\n onStartReached &&\n !backwardLoadingRef.current &&\n prevScrollHeightRef.current === 0\n ) {\n console.log('[reach] START reached', { actualScrollTop, threshold, scrollHeight: el.scrollHeight, hasCallback: !!onStartReached });\n backwardLoadingRef.current = true;\n prevScrollHeightRef.current = el.scrollHeight;\n Promise.resolve(onStartReached()).catch(() => {\n prevScrollHeightRef.current = 0;\n backwardLoadingRef.current = false;\n });\n return;\n }\n\n // 하단 도달 → forward loading\n const distFromBottom = el.scrollHeight - actualScrollTop - el.clientHeight;\n if (\n distFromBottom <= threshold &&\n onEndReached &&\n !forwardLoadingRef.current\n ) {\n console.log('[reach] END reached', { distFromBottom, threshold, actualScrollTop, scrollHeight: el.scrollHeight, clientHeight: el.clientHeight });\n forwardLoadingRef.current = true;\n Promise.resolve(onEndReached()).catch(() => {\n forwardLoadingRef.current = false;\n });\n }\n }, [scrollTop, threshold, onStartReached, onEndReached]);\n\n // --- 렌더링 ---\n const visibleChildren = useMemo(() => {\n if (viewportHeight === 0) return null;\n const result: React.ReactNode[] = [];\n for (let i = startNode; i <= endNode && i < items.length; i++) {\n const item = items[i];\n result.push(\n <Measure\n key={item.id}\n itemId={item.id}\n position={childPositions[i]}\n onHeightChange={onHeightChange}\n knownHeight={heightMapRef.current.get(item.id)}\n >\n {renderItem(item, i)}\n </Measure>,\n );\n }\n return result;\n }, [startNode, endNode, items, childPositions, renderItem, onHeightChange, viewportHeight]);\n\n // --- GroupWrapper 렌더링 (sticky 헤더용) ---\n const groupWrappers = useMemo(() => {\n if (!groupInfo || !renderGroupHeader || viewportHeight === 0) return null;\n\n // 가시 영역에 걸친 그룹만 렌더\n const renderedGroups = new Set<string>();\n const result: React.ReactNode[] = [];\n\n for (let i = startNode; i <= endNode && i < items.length; i++) {\n const groupKey = groupInfo.groupKeyByIndex[i];\n if (!groupKey || renderedGroups.has(groupKey)) continue;\n renderedGroups.add(groupKey);\n\n const groupTop = groupInfo.groupStartPositions.get(groupKey) ?? 0;\n const groupHeight = groupInfo.heightByGroup.get(groupKey) ?? 0;\n\n result.push(\n <div\n key={`__group_${groupKey}`}\n style={{\n position: \"absolute\",\n top: groupTop,\n left: 0,\n width: \"100%\",\n height: groupHeight,\n pointerEvents: \"none\",\n zIndex: 1,\n }}\n >\n <div\n style={{\n position: \"sticky\",\n top: 0,\n pointerEvents: \"auto\",\n }}\n >\n {renderGroupHeader(groupKey)}\n </div>\n </div>,\n );\n }\n return result;\n }, [groupInfo, renderGroupHeader, startNode, endNode, items, viewportHeight]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ overflow: \"auto\", position: \"relative\", ...style }}\n >\n {(backwardLoadingRef.current || isMeasuring) && loadingComponent}\n <div style={{ position: \"relative\", width: \"100%\", height: totalHeight }}>\n {groupWrappers}\n {visibleChildren}\n </div>\n {forwardLoadingRef.current && bottomLoadingComponent}\n </div>\n );\n}\n\nexport const VirtualScroll = forwardRef(VirtualScrollInner) as <\n T extends VirtualScrollItem,\n>(\n props: VirtualScrollProps<T> & { ref?: React.Ref<DynamicScrollHandle> },\n) => React.ReactElement | null;\n","import { useEffect, useRef } from \"react\";\n\ninterface InitialMeasureProps {\n children: React.ReactNode;\n itemId: string;\n onMeasured: (id: string, height: number) => void;\n}\n\nconst IMAGE_LOAD_TIMEOUT = 5000;\n\n/**\n * 사전 높이 측정용 컴포넌트.\n * visibility: hidden으로 렌더링하여 실제 높이를 측정한다.\n * 이미지가 있으면 onload를 대기한다.\n */\nexport function InitialMeasure({\n children,\n itemId,\n onMeasured,\n}: InitialMeasureProps) {\n const ref = useRef<HTMLDivElement>(null);\n const reportedRef = useRef(false);\n\n useEffect(() => {\n const node = ref.current;\n if (!node || reportedRef.current) return;\n\n const report = (reason: string) => {\n if (reportedRef.current) return;\n reportedRef.current = true;\n const height = Math.ceil(node.offsetHeight);\n const images = node.querySelectorAll(\"img\");\n const imgInfo = Array.from(images).map((img) => ({\n complete: img.complete,\n naturalHeight: img.naturalHeight,\n offsetHeight: img.offsetHeight,\n src: img.src.slice(-40),\n }));\n console.log(`[InitialMeasure] ${itemId} → ${height}px (${reason})`, imgInfo.length > 0 ? imgInfo : \"no images\");\n onMeasured(itemId, Math.max(height, 1));\n };\n\n // 이미지 체크\n const images = node.querySelectorAll(\"img\");\n const pending: HTMLImageElement[] = [];\n images.forEach((img) => {\n if (!img.complete) pending.push(img);\n });\n\n if (pending.length === 0) {\n queueMicrotask(() => report(images.length > 0 ? \"images already complete\" : \"no images\"));\n return;\n }\n\n console.log(`[InitialMeasure] ${itemId} waiting for ${pending.length} image(s)`);\n\n let remaining = pending.length;\n const onSettled = () => {\n remaining--;\n if (remaining <= 0) report(\"image load/error\");\n };\n\n pending.forEach((img) => {\n img.addEventListener(\"load\", onSettled, { once: true });\n img.addEventListener(\"error\", onSettled, { once: true });\n });\n\n const timeout = setTimeout(() => report(\"timeout\"), IMAGE_LOAD_TIMEOUT);\n\n return () => {\n clearTimeout(timeout);\n pending.forEach((img) => {\n img.removeEventListener(\"load\", onSettled);\n img.removeEventListener(\"error\", onSettled);\n });\n };\n }, [itemId, onMeasured]);\n\n return (\n <div\n ref={ref}\n data-dynamic-scroll-measure={itemId}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n visibility: \"hidden\",\n pointerEvents: \"none\",\n }}\n >\n {children}\n </div>\n );\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n} from \"react\";\nimport type {\n DynamicScrollHandle,\n DynamicScrollProps,\n VirtualScrollItem,\n} from \"./types\";\n\n/** 그룹 separator를 위한 내부 아이템 타입 */\ninterface GroupSeparatorItem extends VirtualScrollItem {\n __isSeparator: true;\n __groupKey: string;\n}\nimport { useHeightMap } from \"./hooks/useHeightMap\";\nimport { usePositions } from \"./hooks/usePositions\";\nimport { useGroupPositions } from \"./hooks/useGroupPositions\";\nimport { VirtualScroll } from \"./components/VirtualScroll\";\nimport { InitialMeasure } from \"./components/InitialMeasure\";\n\n/**\n * DynamicScroll - 최상위 가상 스크롤 컴포넌트.\n *\n * 핵심 흐름:\n * 1. 최초: 모든 아이템을 InitialMeasure로 측정 → isAllMeasured 게이트\n * 2. 이후: VirtualScroll을 유지한 채 새 아이템만 백그라운드 측정\n * - 측정 중에는 estimatedItemSize 또는 0으로 임시 배치\n * - 측정 완료되면 positions 재계산으로 자연스럽게 반영\n * - VirtualScroll이 절대 언마운트되지 않으므로 스크롤 위치 보존\n */\nfunction DynamicScrollInner<T extends VirtualScrollItem>(\n {\n items,\n renderItem,\n overscanCount,\n estimatedItemSize,\n onStartReached,\n onEndReached,\n threshold,\n onAtBottomChange,\n syncScrollUpdates,\n className,\n style,\n groupBy,\n renderGroupHeader,\n renderGroupSeparator,\n loadingComponent,\n bottomLoadingComponent,\n initialScrollPosition = \"bottom\",\n onMeasurementComplete,\n initialLoadingComponent,\n }: DynamicScrollProps<T>,\n ref: React.ForwardedRef<DynamicScrollHandle>,\n) {\n // --- 그룹 separator 삽입 ---\n // groupBy + renderGroupSeparator가 있으면, 각 그룹 시작 전에 separator 아이템을 삽입\n // separator는 일반 아이템처럼 높이 측정됨 (heightMap/childPositions에 반영)\n const itemsWithSeparators = useMemo(() => {\n if (!groupBy || !renderGroupSeparator) return items;\n\n const result: (T | GroupSeparatorItem)[] = [];\n let prevGroup: string | null = null;\n\n for (let i = 0; i < items.length; i++) {\n const currentGroup = groupBy(items[i]);\n if (currentGroup !== prevGroup) {\n result.push({\n id: `__separator_${currentGroup}`,\n __isSeparator: true,\n __groupKey: currentGroup,\n } as GroupSeparatorItem);\n prevGroup = currentGroup;\n }\n result.push(items[i]);\n }\n return result;\n }, [items, groupBy, renderGroupSeparator]);\n\n // renderItem을 래핑하여 separator 아이템은 수평선으로 렌더\n const wrappedRenderItem = useMemo(() => {\n if (!groupBy || !renderGroupSeparator) return renderItem;\n\n return (item: T | GroupSeparatorItem, _index: number) => {\n if (\"__isSeparator\" in item && item.__isSeparator) {\n return renderGroupSeparator((item as GroupSeparatorItem).__groupKey);\n }\n return renderItem(item as T, _index);\n };\n }, [groupBy, renderGroupSeparator, renderItem]) as (item: T, index: number) => React.ReactNode;\n\n // separator 포함된 아이템 목록으로 높이 측정\n const allItems = itemsWithSeparators as T[];\n\n const {\n heightMapRef,\n isAllMeasured,\n unmeasuredIds,\n onItemMeasured,\n onHeightChange,\n version,\n } = useHeightMap({ items: allItems, estimatedItemSize });\n\n // 초기 측정 완료 여부를 한 번만 추적\n const hasEverMeasuredRef = useRef(false);\n if (isAllMeasured && !hasEverMeasuredRef.current) {\n hasEverMeasuredRef.current = true;\n console.log('[DynamicScroll] initial measurement complete', { itemCount: allItems.length, unmeasuredCount: unmeasuredIds.length });\n }\n\n // 현재 측정 중인지 (초기 이후 새 아이템 추가 시)\n const isMeasuring = !isAllMeasured && hasEverMeasuredRef.current;\n\n // --- 스크롤 명령 큐잉 ---\n const innerRef = useRef<DynamicScrollHandle>(null);\n const pendingScrollRef = useRef<(() => void) | null>(null);\n const isMeasuringRef = useRef(isMeasuring);\n isMeasuringRef.current = isMeasuring;\n\n const wasMeasuringRef = useRef(false);\n useEffect(() => {\n if (isMeasuring) {\n wasMeasuringRef.current = true;\n console.log('[DynamicScroll] measuring started', { unmeasuredCount: unmeasuredIds.length, totalItems: allItems.length });\n } else if (wasMeasuringRef.current) {\n wasMeasuringRef.current = false;\n console.log('[DynamicScroll] measuring complete', { hasPendingScroll: !!pendingScrollRef.current });\n if (pendingScrollRef.current) {\n pendingScrollRef.current();\n pendingScrollRef.current = null;\n }\n onMeasurementComplete?.();\n }\n }, [isMeasuring, onMeasurementComplete, unmeasuredIds.length, allItems.length]);\n\n /**\n * 외부 index (원본 items 기준) → 내부 index (separator 포함 배열 기준) 변환.\n * separator가 없으면 그대로 반환.\n */\n const toInternalIndex = useCallback(\n (externalIndex: number): number => {\n if (!groupBy || !renderGroupSeparator) return externalIndex;\n // separator는 각 그룹 시작 전에 1개씩 삽입되므로,\n // 외부 index까지의 그룹 전환 횟수만큼 offset 추가\n let separatorCount = 0;\n let prevGroup: string | null = null;\n for (let i = 0; i <= externalIndex && i < items.length; i++) {\n const group = groupBy(items[i]);\n if (group !== prevGroup) {\n separatorCount++;\n prevGroup = group;\n }\n }\n return externalIndex + separatorCount;\n },\n [items, groupBy, renderGroupSeparator],\n );\n\n useImperativeHandle(ref, () => ({\n scrollToItem: (index, align) => {\n const internalIdx = toInternalIndex(index);\n console.log('[DynamicScroll.scrollToItem]', { externalIndex: index, internalIndex: internalIdx, align, isMeasuring: isMeasuringRef.current });\n const action = () => innerRef.current?.scrollToItem(internalIdx, align);\n isMeasuringRef.current ? (pendingScrollRef.current = action) : action();\n },\n scrollToBottom: (behavior) => {\n console.log('[DynamicScroll.scrollToBottom]', { behavior, isMeasuring: isMeasuringRef.current, queued: isMeasuringRef.current });\n const action = () => innerRef.current?.scrollToBottom(behavior);\n isMeasuringRef.current ? (pendingScrollRef.current = action) : action();\n },\n scrollToOffset: (offset, behavior) => {\n const action = () => innerRef.current?.scrollToOffset(offset, behavior);\n isMeasuringRef.current ? (pendingScrollRef.current = action) : action();\n },\n getScrollOffset: () => innerRef.current?.getScrollOffset() ?? 0,\n }));\n\n // 측정 중에는 이전에 확정된 아이템 목록을 유지 (높이 0 아이템이 VirtualScroll에 노출되는 것 방지)\n const stableItemsRef = useRef(allItems);\n if (!isMeasuring) {\n stableItemsRef.current = allItems;\n }\n const stableItems = isMeasuring ? stableItemsRef.current : allItems;\n\n const { childPositions, totalHeight } = usePositions({\n items: stableItems,\n heightMapRef,\n version,\n estimatedItemSize,\n });\n\n // separator도 같은 그룹에 속하도록 groupBy 래핑\n const groupByWithSeparator = useMemo(() => {\n if (!groupBy) return undefined;\n return (item: T) => {\n if (\"__isSeparator\" in item && (item as unknown as GroupSeparatorItem).__isSeparator) {\n return (item as unknown as GroupSeparatorItem).__groupKey;\n }\n return groupBy(item);\n };\n }, [groupBy]);\n\n const groupInfo = useGroupPositions(stableItems, groupByWithSeparator, heightMapRef, version);\n\n // VirtualScroll에 전달할 groupInfo: groupStartPositions 추가\n const virtualScrollGroupInfo = useMemo(() => {\n if (!groupInfo || childPositions.length === 0) return null;\n\n const groupStartPositions = new Map<string, number>();\n let prevGroup: string | null = null;\n for (let i = 0; i < groupInfo.groupKeyByIndex.length; i++) {\n const group = groupInfo.groupKeyByIndex[i];\n if (group !== prevGroup) {\n groupStartPositions.set(group, childPositions[i] ?? 0);\n prevGroup = group;\n }\n }\n\n return {\n heightByGroup: groupInfo.heightByGroup,\n groupKeyByIndex: groupInfo.groupKeyByIndex,\n groupStartPositions,\n };\n }, [groupInfo, childPositions]);\n\n // 초기 측정이 완료되지 않았으면 측정 영역 + 로딩 컴포넌트 렌더링\n if (!hasEverMeasuredRef.current) {\n return (\n <div\n className={className}\n style={{\n overflow: \"hidden\",\n position: \"relative\",\n ...style,\n }}\n >\n {initialLoadingComponent}\n {unmeasuredIds.map((id) => {\n const index = allItems.findIndex((item) => item.id === id);\n if (index === -1) return null;\n return (\n <InitialMeasure key={id} itemId={id} onMeasured={onItemMeasured}>\n {wrappedRenderItem(allItems[index], index)}\n </InitialMeasure>\n );\n })}\n </div>\n );\n }\n\n return (\n <>\n {/* 새 아이템 백그라운드 측정 (VirtualScroll 유지한 채) */}\n {isMeasuring && (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n overflow: \"hidden\",\n height: 0,\n visibility: \"hidden\",\n pointerEvents: \"none\",\n }}\n >\n {unmeasuredIds.map((id) => {\n const index = allItems.findIndex((item) => item.id === id);\n if (index === -1) return null;\n return (\n <InitialMeasure key={id} itemId={id} onMeasured={onItemMeasured}>\n {wrappedRenderItem(allItems[index], index)}\n </InitialMeasure>\n );\n })}\n </div>\n )}\n <VirtualScroll\n ref={innerRef}\n items={stableItems}\n renderItem={wrappedRenderItem}\n childPositions={childPositions}\n totalHeight={totalHeight}\n heightMapRef={heightMapRef}\n onHeightChange={onHeightChange}\n overscanCount={overscanCount}\n onStartReached={isMeasuring ? undefined : onStartReached}\n onEndReached={isMeasuring ? undefined : onEndReached}\n threshold={threshold}\n onAtBottomChange={onAtBottomChange}\n syncScrollUpdates={syncScrollUpdates}\n className={className}\n style={style}\n isMeasuring={isMeasuring}\n loadingComponent={loadingComponent}\n bottomLoadingComponent={bottomLoadingComponent}\n initialScrollPosition={\n typeof initialScrollPosition === \"object\" && \"index\" in initialScrollPosition\n ? { ...initialScrollPosition, index: toInternalIndex(initialScrollPosition.index) }\n : initialScrollPosition\n }\n groupInfo={virtualScrollGroupInfo}\n renderGroupHeader={renderGroupHeader}\n />\n </>\n );\n}\n\nexport const DynamicScroll = forwardRef(DynamicScrollInner) as <\n T extends VirtualScrollItem,\n>(\n props: DynamicScrollProps<T> & { ref?: React.Ref<DynamicScrollHandle> },\n) => React.ReactElement | null;\n","import type { ReactNode } from \"react\";\n\ninterface StickyGroupHeaderProps {\n /** 현재 표시할 그룹 키 */\n groupKey: string;\n /** 그룹 헤더 렌더링 함수 */\n renderGroupHeader: (groupKey: string) => ReactNode;\n /** 전체 컨텐츠 높이 */\n totalHeight: number;\n /** 해당 그룹 아래 모든 그룹의 누적 높이 */\n cumulativeHeight: number;\n /** 상단 오프셋 (px) */\n topOffset?: number;\n}\n\n/**\n * Sticky Group Header 컴포넌트.\n *\n * 가상 스크롤에서 CSS position:sticky가 동작하지 않는 문제를 해결한다.\n * (absolute positioned 아이템들과 sticky는 호환되지 않음)\n *\n * 구현 방식:\n * - position: sticky + top: 0으로 고정\n * - height를 totalHeight - cumulativeHeight로 제한하여\n * 해당 그룹 영역이 끝나면 자연스럽게 밀려 올라가는 push-up 효과 구현\n * - z-index로 아이템들 위에 표시\n */\nexport function StickyGroupHeader({\n groupKey,\n renderGroupHeader,\n totalHeight,\n cumulativeHeight,\n topOffset = 0,\n}: StickyGroupHeaderProps) {\n return (\n <div\n data-dynamic-scroll-group-header={groupKey}\n style={{\n position: \"sticky\",\n top: topOffset,\n zIndex: 1,\n height: totalHeight - cumulativeHeight,\n pointerEvents: \"none\",\n }}\n >\n <div style={{ pointerEvents: \"auto\" }}>\n {renderGroupHeader(groupKey)}\n </div>\n </div>\n );\n}\n"]}