Skip to content

Commit

Permalink
feat: topItemCount for TableVirtuoso (#1147)
Browse files Browse the repository at this point in the history
* feat: topItemCount for TableVirtuoso

* fix: aggregate topItems and items into ItemsComponent
  • Loading branch information
yuta4j1 authored Oct 15, 2024
1 parent 67bc341 commit 3405b64
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 41 deletions.
102 changes: 62 additions & 40 deletions src/TableVirtuoso.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,70 @@ const DefaultFillerRow = ({ height }: { height: number }) => (

const ITEM_STYLE = { overflowAnchor: 'none' } as const

const Items = /*#__PURE__*/ React.memo(function VirtuosoItems() {
const Items = /*#__PURE__*/ React.memo(function VirtuosoItems({ showTopList = false }: { showTopList?: boolean }) {
const listState = useEmitterValue('listState')
const computeItemKey = useEmitterValue('computeItemKey')
const firstItemIndex = useEmitterValue('firstItemIndex')
const isSeeking = useEmitterValue('isSeeking')
const ScrollSeekPlaceholder = useEmitterValue('ScrollSeekPlaceholder') || DefaultScrollSeekPlaceholder
const context = useEmitterValue('context')
const TableRowComponent = useEmitterValue('TableRowComponent')!
const fixedHeaderHeight = useEmitterValue('fixedHeaderHeight')
const itemContent = useEmitterValue('itemContent')

const topItemOffsets = (showTopList ? listState.topItems : []).reduce<number[]>((acc, item, index) => {
if (index === 0) {
acc.push(item.size)
} else {
acc.push(acc[index - 1] + item.size)
}
return acc
}, [])

const items = (showTopList ? listState.topItems : listState.items).map((item) => {
const index = item.originalIndex!
const key = computeItemKey(index + firstItemIndex, item.data, context)
const offsetTop = showTopList ? (index === 0 ? 0 : topItemOffsets[index - 1]) : 0

if (isSeeking) {
return (
<ScrollSeekPlaceholder
{...contextPropIfNotDomElement(ScrollSeekPlaceholder, context)}
key={key}
index={item.index}
height={item.size}
type={item.type || 'item'}
/>
)
}
return (
<TableRowComponent
{...contextPropIfNotDomElement(TableRowComponent, context)}
{...itemPropIfNotDomElement(TableRowComponent, item.data)}
key={key}
data-index={index}
data-known-size={item.size}
data-item-index={item.index}
style={showTopList ? { overflowAnchor: 'none', position: 'sticky', zIndex: 2, top: fixedHeaderHeight + offsetTop } : ITEM_STYLE}
>
{itemContent(item.index, item.data, context)}
</TableRowComponent>
)
})

return <>{items}</>
})

const TableBody = /*#__PURE__*/ React.memo(function TableVirtuosoBody() {
const listState = useEmitterValue('listState')
const showTopList = useEmitterValue('topItemsIndexes').length > 0
const sizeRanges = usePublisher('sizeRanges')
const useWindowScroll = useEmitterValue('useWindowScroll')
const customScrollParent = useEmitterValue('customScrollParent')
const windowScrollContainerStateCallback = usePublisher('windowScrollContainerState')
const _scrollContainerStateCallback = usePublisher('scrollContainerState')
const scrollContainerStateCallback =
customScrollParent || useWindowScroll ? windowScrollContainerStateCallback : _scrollContainerStateCallback
const itemContent = useEmitterValue('itemContent')
const trackItemSizes = useEmitterValue('trackItemSizes')
const itemSize = useEmitterValue('itemSize')
const log = useEmitterValue('log')
Expand All @@ -113,62 +167,30 @@ const Items = /*#__PURE__*/ React.memo(function VirtuosoItems() {
}
})
const EmptyPlaceholder = useEmitterValue('EmptyPlaceholder')
const ScrollSeekPlaceholder = useEmitterValue('ScrollSeekPlaceholder') || DefaultScrollSeekPlaceholder
const FillerRow = useEmitterValue('FillerRow') || DefaultFillerRow
const TableBodyComponent = useEmitterValue('TableBodyComponent')!
const TableRowComponent = useEmitterValue('TableRowComponent')!
const computeItemKey = useEmitterValue('computeItemKey')
const isSeeking = useEmitterValue('isSeeking')
const paddingTopAddition = useEmitterValue('paddingTopAddition')
const firstItemIndex = useEmitterValue('firstItemIndex')
const statefulTotalCount = useEmitterValue('statefulTotalCount')
const context = useEmitterValue('context')

if (statefulTotalCount === 0 && EmptyPlaceholder) {
return <EmptyPlaceholder {...contextPropIfNotDomElement(EmptyPlaceholder, context)} />
}

const paddingTop = listState.offsetTop + paddingTopAddition + deviation
const topItemsSize = (showTopList ? listState.topItems : []).reduce((acc, item) => acc + item.size, 0)

const paddingTop = listState.offsetTop + paddingTopAddition + deviation - topItemsSize
const paddingBottom = listState.offsetBottom

const paddingTopEl = paddingTop > 0 ? <FillerRow height={paddingTop} key="padding-top" context={context} /> : null

const paddingBottomEl = paddingBottom > 0 ? <FillerRow height={paddingBottom} key="padding-bottom" context={context} /> : null

const items = listState.items.map((item) => {
const index = item.originalIndex!
const key = computeItemKey(index + firstItemIndex, item.data, context)

if (isSeeking) {
return (
<ScrollSeekPlaceholder
{...contextPropIfNotDomElement(ScrollSeekPlaceholder, context)}
key={key}
index={item.index}
height={item.size}
type={item.type || 'item'}
/>
)
}
return (
<TableRowComponent
{...contextPropIfNotDomElement(TableRowComponent, context)}
{...itemPropIfNotDomElement(TableRowComponent, item.data)}
key={key}
data-index={index}
data-known-size={item.size}
data-item-index={item.index}
style={ITEM_STYLE}
>
{itemContent(item.index, item.data, context)}
</TableRowComponent>
)
})

return (
<TableBodyComponent ref={callbackRef} data-testid="virtuoso-item-list" {...contextPropIfNotDomElement(TableBodyComponent, context)}>
{paddingTopEl}
{items}
{showTopList && <Items showTopList={true} />}
<Items />
{paddingBottomEl}
</TableBodyComponent>
)
Expand Down Expand Up @@ -276,7 +298,7 @@ const TableRoot: React.FC<TableRootProps> = /*#__PURE__*/ React.memo(function Ta
<TheViewport>
<TheTable style={{ borderSpacing: 0, overflowAnchor: 'none' }} {...contextPropIfNotDomElement(TheTable, context)}>
{theHead}
<Items key="TableBody" />
<TableBody key="TableBody" />
{theFoot}
</TheTable>
</TheViewport>
Expand Down
7 changes: 6 additions & 1 deletion src/component-interfaces/TableVirtuoso.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
} from '../interfaces'
import type { VirtuosoProps } from './Virtuoso'

export interface TableVirtuosoProps<D, C> extends Omit<VirtuosoProps<D, C>, 'components' | 'headerFooterTag' | 'topItemCount'> {
export interface TableVirtuosoProps<D, C> extends Omit<VirtuosoProps<D, C>, 'components' | 'headerFooterTag'> {
/**
* Use the `components` property for advanced customization of the elements rendered by the table.
*/
Expand All @@ -33,6 +33,11 @@ export interface TableVirtuosoProps<D, C> extends Omit<VirtuosoProps<D, C>, 'com
*/
fixedFooterContent?: FixedFooterContent

/**
* Set the amount of items to remain fixed at the top of the table.
*/
topItemCount?: number

/**
* The total amount of items to be rendered.
*/
Expand Down

0 comments on commit 3405b64

Please sign in to comment.