diff --git a/packages/ui/src/components/message-block-list.tsx b/packages/ui/src/components/message-block-list.tsx index 8e0c8159..4ea4d828 100644 --- a/packages/ui/src/components/message-block-list.tsx +++ b/packages/ui/src/components/message-block-list.tsx @@ -8,11 +8,9 @@ export function getMessageAnchorId(messageId: string) { } const VIRTUAL_ITEM_MARGIN_PX = 800 -const ESTIMATED_MESSAGE_HEIGHT = 320 -const INITIAL_FORCE_MIN_ITEMS = 12 -const INITIAL_FORCE_OVERSCAN = 6 interface MessageBlockListProps { + instanceId: string sessionId: string store: () => InstanceMessageStore @@ -31,50 +29,10 @@ interface MessageBlockListProps { } export default function MessageBlockList(props: MessageBlockListProps) { - const [initialForceActive, setInitialForceActive] = createSignal(true) - const [initialForceInitialized, setInitialForceInitialized] = createSignal(false) - const [initialForceStartIndex, setInitialForceStartIndex] = createSignal(0) - const [, setInitialForceRemaining] = createSignal(0) - - createEffect(() => { - props.instanceId - props.sessionId - setInitialForceActive(true) - setInitialForceInitialized(false) - setInitialForceStartIndex(0) - setInitialForceRemaining(0) - }) - - createEffect(() => { - if (!initialForceActive() || initialForceInitialized()) return - const ids = props.messageIds() - if (ids.length === 0) return - const viewportHeight = props.scrollContainer()?.clientHeight ?? (typeof window !== "undefined" ? window.innerHeight : 800) - const estimatedCount = Math.min( - ids.length, - Math.max(INITIAL_FORCE_MIN_ITEMS, Math.ceil(viewportHeight / ESTIMATED_MESSAGE_HEIGHT) + INITIAL_FORCE_OVERSCAN), - ) - setInitialForceStartIndex(Math.max(0, ids.length - estimatedCount)) - setInitialForceRemaining(estimatedCount) - setInitialForceInitialized(true) - }) - return ( <> {(messageId) => { - const messageIndex = () => props.messageIndexMap().get(messageId()) ?? 0 - const forceVisible = () => initialForceActive() && messageIndex() >= initialForceStartIndex() - const handleMeasured = () => { - if (!forceVisible()) return - setInitialForceRemaining((value) => { - const next = value > 0 ? value - 1 : 0 - if (next === 0) { - setInitialForceActive(false) - } - return next - }) - } return ( !props.loading} - forceVisible={forceVisible} - onMeasured={handleMeasured} > () const DEFAULT_MARGIN_PX = 600 @@ -133,8 +133,14 @@ export default function VirtualItem(props: VirtualItemProps) { }) } const virtualizationEnabled = () => (props.virtualizationEnabled ? props.virtualizationEnabled() : true) + const shouldHideContent = createMemo(() => { + if (props.forceVisible?.()) return false + if (!virtualizationEnabled()) return false + return !isIntersecting() + }) + + let wrapperRef: HTMLDivElement | undefined - let wrapperRef: HTMLDivElement | undefined let contentRef: HTMLDivElement | undefined let resizeObserver: ResizeObserver | undefined let intersectionCleanup: (() => void) | undefined @@ -213,6 +219,7 @@ export default function VirtualItem(props: VirtualItemProps) { contentRef = element ?? undefined if (contentRef) { queueMicrotask(() => { + if (shouldHideContent()) return updateMeasuredHeight() setupResizeObserver() }) @@ -220,9 +227,21 @@ export default function VirtualItem(props: VirtualItemProps) { cleanupResizeObserver() } } - + + createEffect(() => { + if (shouldHideContent()) { + cleanupResizeObserver() + } else if (contentRef) { + queueMicrotask(() => { + updateMeasuredHeight() + setupResizeObserver() + }) + } + }) + createEffect(() => { const key = props.cacheKey + const cached = sizeCache.get(key) if (cached !== undefined) { setMeasuredHeight(cached) @@ -238,13 +257,8 @@ export default function VirtualItem(props: VirtualItemProps) { refreshIntersectionObserver(root ?? null) }) - const shouldHideContent = createMemo(() => { - if (props.forceVisible?.()) return false - if (!virtualizationEnabled()) return false - return !isIntersecting() - }) - const placeholderHeight = createMemo(() => { + const seenHeight = measuredHeight() if (seenHeight > 0) { return seenHeight @@ -267,6 +281,14 @@ export default function VirtualItem(props: VirtualItemProps) { return classes.filter(Boolean).join(" ") } const placeholderClass = () => ["virtual-item-placeholder", props.placeholderClass].filter(Boolean).join(" ") + const lazyContent = createMemo(() => { + if (shouldHideContent()) return null + if (import.meta.env?.DEV) { + console.debug("rendering virtual item", props.cacheKey) + } + return resolved() + }) + return (
@@ -278,7 +300,7 @@ export default function VirtualItem(props: VirtualItemProps) { }} >
- {resolved()} + {lazyContent()}