Defer initial message scroll until list renders
This commit is contained in:
@@ -27,24 +27,45 @@ interface MessageBlockListProps {
|
|||||||
onContentRendered?: () => void
|
onContentRendered?: () => void
|
||||||
setBottomSentinel: (element: HTMLDivElement | null) => void
|
setBottomSentinel: (element: HTMLDivElement | null) => void
|
||||||
suspendMeasurements?: () => boolean
|
suspendMeasurements?: () => boolean
|
||||||
|
onInitialRenderComplete?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MessageBlockList(props: MessageBlockListProps) {
|
export default function MessageBlockList(props: MessageBlockListProps) {
|
||||||
|
const totalMessages = () => props.messageIds().length
|
||||||
|
let renderedCount = 0
|
||||||
|
let initialRenderReported = false
|
||||||
|
const handleBlockRendered = () => {
|
||||||
|
if (initialRenderReported) return
|
||||||
|
renderedCount += 1
|
||||||
|
if (renderedCount >= totalMessages() && totalMessages() > 0) {
|
||||||
|
initialRenderReported = true
|
||||||
|
renderedCount = 0
|
||||||
|
props.onInitialRenderComplete?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (props.loading) {
|
||||||
|
renderedCount = 0
|
||||||
|
initialRenderReported = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Index each={props.messageIds()}>
|
<Index each={props.messageIds()}>
|
||||||
{(messageId) => {
|
{(messageId) => {
|
||||||
return (
|
return (
|
||||||
<VirtualItem
|
<VirtualItem
|
||||||
id={getMessageAnchorId(messageId())}
|
id={getMessageAnchorId(messageId())}
|
||||||
cacheKey={messageId()}
|
cacheKey={messageId()}
|
||||||
scrollContainer={props.scrollContainer}
|
scrollContainer={props.scrollContainer}
|
||||||
threshold={VIRTUAL_ITEM_MARGIN_PX}
|
threshold={VIRTUAL_ITEM_MARGIN_PX}
|
||||||
placeholderClass="message-stream-placeholder"
|
placeholderClass="message-stream-placeholder"
|
||||||
virtualizationEnabled={() => !props.loading}
|
virtualizationEnabled={() => !props.loading}
|
||||||
suspendMeasurements={props.suspendMeasurements}
|
suspendMeasurements={props.suspendMeasurements}
|
||||||
>
|
onMeasured={handleBlockRendered}
|
||||||
|
>
|
||||||
<MessageBlock
|
<MessageBlock
|
||||||
messageId={messageId()}
|
messageId={messageId()}
|
||||||
instanceId={props.instanceId}
|
instanceId={props.instanceId}
|
||||||
|
|||||||
@@ -162,6 +162,9 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
let pendingActiveScroll = false
|
let pendingActiveScroll = false
|
||||||
let scrollToBottomFrame: number | null = null
|
let scrollToBottomFrame: number | null = null
|
||||||
let scrollToBottomDelayedFrame: number | null = null
|
let scrollToBottomDelayedFrame: number | null = null
|
||||||
|
let pendingInitialScroll = true
|
||||||
|
|
||||||
|
const [initialRenderComplete, setInitialRenderComplete] = createSignal(false)
|
||||||
|
|
||||||
function markUserScrollIntent() {
|
function markUserScrollIntent() {
|
||||||
const now = typeof performance !== "undefined" ? performance.now() : Date.now()
|
const now = typeof performance !== "undefined" ? performance.now() : Date.now()
|
||||||
@@ -368,11 +371,18 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleContentRendered() {
|
function handleContentRendered() {
|
||||||
|
if (props.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
scheduleAnchorScroll()
|
scheduleAnchorScroll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleInitialRenderComplete() {
|
||||||
|
setInitialRenderComplete(true)
|
||||||
|
}
|
||||||
|
|
||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
|
|
||||||
if (!containerRef) return
|
if (!containerRef) return
|
||||||
if (pendingScrollFrame !== null) {
|
if (pendingScrollFrame !== null) {
|
||||||
cancelAnimationFrame(pendingScrollFrame)
|
cancelAnimationFrame(pendingScrollFrame)
|
||||||
@@ -412,12 +422,26 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
lastActiveState = active
|
lastActiveState = active
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const loading = Boolean(props.loading)
|
||||||
|
if (loading) {
|
||||||
|
pendingInitialScroll = true
|
||||||
|
setInitialRenderComplete(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pendingInitialScroll && initialRenderComplete()) {
|
||||||
|
pendingInitialScroll = false
|
||||||
|
requestScrollToBottom(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!props.onQuoteSelection) {
|
if (!props.onQuoteSelection) {
|
||||||
clearQuoteSelection()
|
clearQuoteSelection()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (typeof document === "undefined") return
|
if (typeof document === "undefined") return
|
||||||
const handleSelectionChange = () => updateQuoteSelectionFromSelection()
|
const handleSelectionChange = () => updateQuoteSelectionFromSelection()
|
||||||
@@ -647,6 +671,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
onContentRendered={handleContentRendered}
|
onContentRendered={handleContentRendered}
|
||||||
setBottomSentinel={setBottomSentinel}
|
setBottomSentinel={setBottomSentinel}
|
||||||
suspendMeasurements={() => props.isActive === false}
|
suspendMeasurements={() => props.isActive === false}
|
||||||
|
onInitialRenderComplete={handleInitialRenderComplete}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user