Message replies in chat (#10903)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,15 @@ export type ListMethods = {
|
||||
scrollToTop: () => void
|
||||
scrollToOffset: (options: {animated: boolean; offset: number}) => void
|
||||
scrollToEnd: (options?: {animated?: boolean}) => void
|
||||
// Signature kept compatible with FlatList's scrollToIndex (the native
|
||||
// ListMethods type) so callers stay platform-agnostic. viewOffset is
|
||||
// accepted for parity but not currently used by the web implementation.
|
||||
scrollToIndex: (params: {
|
||||
animated?: boolean | null
|
||||
index: number
|
||||
viewOffset?: number
|
||||
viewPosition?: number
|
||||
}) => void
|
||||
}
|
||||
export type ListProps<ItemT> = Omit<
|
||||
FlatListProps<ItemT>,
|
||||
@@ -216,6 +225,22 @@ function ListImpl<ItemT>(
|
||||
}, [disableFullWindowScroll])
|
||||
|
||||
const nativeRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Registry of item index -> row DOM node. The list renders header/footer and
|
||||
// visibility-detector siblings too, so we can't index into the container's
|
||||
// children directly; each Row registers its own node here keyed by index.
|
||||
const rowNodesRef = useRef<Map<number, HTMLElement>>(new Map())
|
||||
const registerRowNode = useCallback(
|
||||
(index: number, node: HTMLElement | null) => {
|
||||
if (node) {
|
||||
rowNodesRef.current.set(index, node)
|
||||
} else {
|
||||
rowNodesRef.current.delete(index)
|
||||
}
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
@@ -239,6 +264,17 @@ function ListImpl<ItemT>(
|
||||
behavior: animated ? 'smooth' : 'instant',
|
||||
})
|
||||
},
|
||||
|
||||
scrollToIndex({animated = true, index}) {
|
||||
const node = rowNodesRef.current.get(index)
|
||||
// scrollIntoView with block: 'center' roughly matches the caller's
|
||||
// viewPosition of 0.3 - not exact, but close enough and it respects
|
||||
// whichever element is the scroll container (window or nativeRef).
|
||||
node?.scrollIntoView({
|
||||
block: 'center',
|
||||
behavior: animated ? 'smooth' : 'instant',
|
||||
})
|
||||
},
|
||||
}),
|
||||
[getScrollableNode],
|
||||
)
|
||||
@@ -392,6 +428,7 @@ function ListImpl<ItemT>(
|
||||
renderItem={renderItem}
|
||||
extraData={extraData}
|
||||
onItemSeen={onItemSeen}
|
||||
registerRowNode={registerRowNode}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
@@ -470,6 +507,7 @@ let Row = function RowImpl<ItemT>({
|
||||
renderItem,
|
||||
extraData: _unused,
|
||||
onItemSeen,
|
||||
registerRowNode,
|
||||
}: {
|
||||
item: ItemT
|
||||
index: number
|
||||
@@ -479,6 +517,7 @@ let Row = function RowImpl<ItemT>({
|
||||
| ((info: ListRenderItemInfo<ItemT>) => React.ReactNode)
|
||||
extraData: unknown
|
||||
onItemSeen: ((item: ItemT) => void) | undefined
|
||||
registerRowNode: (index: number, node: HTMLElement | null) => void
|
||||
}): React.ReactNode {
|
||||
const rowRef = useRef(null)
|
||||
const intersectionTimeout = useRef<ReturnType<typeof setTimeout> | undefined>(
|
||||
@@ -529,6 +568,15 @@ let Row = function RowImpl<ItemT>({
|
||||
}
|
||||
}, [handleIntersection, onItemSeen])
|
||||
|
||||
// Register this row's DOM node so the list can scroll to it by index.
|
||||
useEffect(() => {
|
||||
const node: HTMLElement | null = rowRef.current
|
||||
registerRowNode(index, node)
|
||||
return () => {
|
||||
registerRowNode(index, null)
|
||||
}
|
||||
}, [index, registerRowNode])
|
||||
|
||||
if (!renderItem) {
|
||||
return null
|
||||
}
|
||||
@@ -552,6 +600,7 @@ Row = memo(Row) as <ItemT>(props: {
|
||||
| ((info: ListRenderItemInfo<ItemT>) => React.ReactNode)
|
||||
extraData: unknown
|
||||
onItemSeen: ((item: ItemT) => void) | undefined
|
||||
registerRowNode: (index: number, node: HTMLElement | null) => void
|
||||
}) => React.ReactNode
|
||||
|
||||
let Visibility = ({
|
||||
|
||||
Reference in New Issue
Block a user