Skip to content

Commit

Permalink
Correct TS types for custom components
Browse files Browse the repository at this point in the history
Fixes #1181
  • Loading branch information
petyosi committed Feb 8, 2025
1 parent cb0ad08 commit dc2fad9
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changeset/lovely-countries-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-virtuoso": patch
---

correct TS types for custom component, context is always passed
2 changes: 1 addition & 1 deletion packages/react-virtuoso/src/TableVirtuoso.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ const TableRoot: React.FC<TableRootProps> = /*#__PURE__*/ React.memo(function Ta
) : null

return (
<TheScroller {...props}>
<TheScroller {...props} {...contextPropIfNotDomElement(TheScroller, context)}>
<TheViewport>
<TheTable style={{ borderSpacing: 0, overflowAnchor: 'none' }} {...contextPropIfNotDomElement(TheTable, context)}>
{theHead}
Expand Down
8 changes: 5 additions & 3 deletions packages/react-virtuoso/src/Virtuoso.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import useIsomorphicLayoutEffect from './hooks/useIsomorphicLayoutEffect'
import useScrollTop from './hooks/useScrollTop'
import useSize from './hooks/useSize'
import useWindowViewportRectRef from './hooks/useWindowViewportRect'
import { Components, ComputeItemKey, GroupContent, GroupItemContent, ItemContent, ListRootProps } from './interfaces'
import { Components, ComputeItemKey, ContextProp, GroupContent, GroupItemContent, ItemContent, ListRootProps } from './interfaces'
import { listSystem } from './listSystem'
import { systemToComponent } from './react-urx'
import * as u from './urx'
Expand Down Expand Up @@ -236,8 +236,9 @@ const topItemListStyle: React.CSSProperties = {
zIndex: 1,
}

export function contextPropIfNotDomElement(element: unknown, context: unknown) {
export function contextPropIfNotDomElement<C>(element: unknown, context: C): ContextProp<C> {
if (typeof element === 'string') {
// @ts-expect-error lie the type system, we don't want to return anything for dom elements
return undefined
}
return { context }
Expand Down Expand Up @@ -446,10 +447,11 @@ const ListRoot: React.FC<ListRootProps> = /*#__PURE__*/ React.memo(function Virt
const useWindowScroll = useEmitterValue('useWindowScroll')
const showTopList = useEmitterValue('topItemsIndexes').length > 0
const customScrollParent = useEmitterValue('customScrollParent')
const context = useEmitterValue('context')
const TheScroller = customScrollParent || useWindowScroll ? WindowScroller : Scroller
const TheViewport = customScrollParent || useWindowScroll ? WindowViewport : Viewport
return (
<TheScroller {...props}>
<TheScroller {...props} {...contextPropIfNotDomElement(TheScroller, context)}>
{showTopList && (
<TopItemListContainer>
<Items showTopList={true} />
Expand Down
3 changes: 2 additions & 1 deletion packages/react-virtuoso/src/VirtuosoGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,10 @@ const GridRoot: React.FC<GridRootProps> = /*#__PURE__*/ React.memo(function Grid
const customScrollParent = useEmitterValue('customScrollParent')
const TheScroller = customScrollParent || useWindowScroll ? WindowScroller : Scroller
const TheViewport = customScrollParent || useWindowScroll ? WindowViewport : Viewport
const context = useEmitterValue('context')

return (
<TheScroller {...props}>
<TheScroller {...props} {...contextPropIfNotDomElement(TheScroller, context)}>
<TheViewport>
<Header />
<GridItems />
Expand Down
52 changes: 28 additions & 24 deletions packages/react-virtuoso/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,55 +12,59 @@ export interface CalculateViewLocationParams {
viewportTop: number
}

export interface ContextProp<C> {
context: C
}

/**
* Customize the Virtuoso rendering by passing a set of custom components.
*/
export interface Components<Data = unknown, Context = unknown> {
/**
* Set to render a custom UI when the list is empty.
*/
EmptyPlaceholder?: React.ComponentType<{ context?: Context }>
EmptyPlaceholder?: React.ComponentType<ContextProp<Context>>
/**
* Set to render a component at the bottom of the list.
*/
Footer?: React.ComponentType<{ context?: Context }>
Footer?: React.ComponentType<ContextProp<Context>>
/**
* Set to customize the group item wrapping element. Use only if you would like to render list from elements different than a `div`.
*/
Group?: React.ComponentType<GroupProps & { context?: Context }>
Group?: React.ComponentType<GroupProps & ContextProp<Context>>
/**
* Set to render a component at the top of the list.
*
* The header remains above the top items and does not remain sticky.
*/
Header?: React.ComponentType<{ context?: Context }>
Header?: React.ComponentType<ContextProp<Context>>

/**
* Set to customize the item wrapping element. Use only if you would like to render list from elements different than a `div`.
*/
Item?: React.ComponentType<ItemProps<Data> & { context?: Context }>
Item?: React.ComponentType<ItemProps<Data> & ContextProp<Context>>

/**
* Set to customize the items wrapper. Use only if you would like to render list from elements different than a `div`.
*/
List?: React.ComponentType<ListProps & { context?: Context }>
List?: React.ComponentType<ListProps & ContextProp<Context>>

/**
* Set to customize the outermost scrollable element. This should not be necessary in general,
* as the component passes its HTML attribute props to it.
*/
Scroller?: React.ComponentType<ScrollerProps & { context?: Context }>
Scroller?: React.ComponentType<ScrollerProps & ContextProp<Context>>

/**
* Set to render an item placeholder when the user scrolls fast. See the `scrollSeek` property for more details.
*/
ScrollSeekPlaceholder?: React.ComponentType<ScrollSeekPlaceholderProps & { context?: Context }>
ScrollSeekPlaceholder?: React.ComponentType<ScrollSeekPlaceholderProps & ContextProp<Context>>

/**
* Set to customize the top list item wrapping element. Use if you would like to render list from elements different than a `div`
* or you want to set a custom z-index for the sticky position.
*/
TopItemList?: React.ComponentType<TopItemListProps & { context?: Context }>
TopItemList?: React.ComponentType<TopItemListProps & ContextProp<Context>>
}

export type ComputeItemKey<D, C> = (index: number, item: D, context: C) => React.Key
Expand Down Expand Up @@ -95,36 +99,36 @@ export interface GridComponents<Context = any> {
/**
* Set to render a component at the bottom of the list.
*/
Footer?: React.ComponentType<{ context?: Context }>
Footer?: React.ComponentType<ContextProp<Context>>

/**
* Set to render a component at the top of the list.
*
* The header remains above the top items and does not remain sticky.
*/
Header?: React.ComponentType<{ context?: Context }>
Header?: React.ComponentType<ContextProp<Context>>

/**
* Set to customize the item wrapping element. Use only if you would like to render list from elements different than a `div`.
*/
Item?: React.ComponentType<GridItemProps & { context?: Context }>
Item?: React.ComponentType<GridItemProps & ContextProp<Context>>

/**
* Set to customize the items wrapper. Use only if you would like to render list from elements different than a `div`.
*/
List?: React.ComponentType<GridListProps & { context?: Context }>
List?: React.ComponentType<GridListProps & ContextProp<Context>>

/**
* Set to customize the outermost scrollable element. This should not be necessary in general,
* as the component passes its HTML attribute props to it.
*/
Scroller?: React.ComponentType<ScrollerProps & { context?: Context }>
Scroller?: React.ComponentType<ScrollerProps & ContextProp<Context>>

/**
* Set to render an item placeholder when the user scrolls fast.
* See the `scrollSeekConfiguration` property for more details.
*/
ScrollSeekPlaceholder?: React.ComponentType<GridScrollSeekPlaceholderProps & { context?: Context }>
ScrollSeekPlaceholder?: React.ComponentType<GridScrollSeekPlaceholderProps & ContextProp<Context>>
}

export type GridComputeItemKey<D, C> = (index: number, item: D, context: C) => React.Key
Expand Down Expand Up @@ -351,54 +355,54 @@ export interface TableComponents<Data = unknown, Context = unknown> {
/**
* Set to render a custom UI when the list is empty.
*/
EmptyPlaceholder?: React.ComponentType<{ context?: Context }>
EmptyPlaceholder?: React.ComponentType<ContextProp<Context>>

/**
* Set to render an empty item placeholder.
*/
FillerRow?: React.ComponentType<FillerRowProps & { context?: Context }>
FillerRow?: React.ComponentType<FillerRowProps & ContextProp<Context>>

/**
* Set to customize the outermost scrollable element. This should not be necessary in general,
* as the component passes its HTML attribute props to it.
*/
Scroller?: React.ComponentType<ScrollerProps & { context?: Context }>
Scroller?: React.ComponentType<ScrollerProps & ContextProp<Context>>

/**
* Set to render an item placeholder when the user scrolls fast. See the `scrollSeek` property for more details.
*/
ScrollSeekPlaceholder?: React.ComponentType<ScrollSeekPlaceholderProps & { context?: Context }>
ScrollSeekPlaceholder?: React.ComponentType<ScrollSeekPlaceholderProps & ContextProp<Context>>

/**
* Set to customize the wrapping `table` element.
*
*/
Table?: React.ComponentType<TableProps & { context?: Context }>
Table?: React.ComponentType<TableProps & ContextProp<Context>>

/**
* Set to customize the items wrapper. Default is `tbody`.
*/
TableBody?: React.ComponentType<TableBodyProps & { context?: Context }>
TableBody?: React.ComponentType<TableBodyProps & ContextProp<Context>>

/**
* Set to render a fixed footer at the bottom of the table (`tfoot`). use [[fixedFooterContent]] to set the contents
*/
TableFoot?: React.ComponentType<
Pick<React.ComponentProps<'tfoot'>, 'children' | 'style'> & React.RefAttributes<HTMLTableSectionElement> & { context?: Context }
Pick<React.ComponentProps<'tfoot'>, 'children' | 'style'> & React.RefAttributes<HTMLTableSectionElement> & ContextProp<Context>
>

/**
* Set to render a fixed header at the top of the table (`thead`). use [[fixedHeaderContent]] to set the contents
*
*/
TableHead?: React.ComponentType<
Pick<React.ComponentProps<'thead'>, 'children' | 'style'> & React.RefAttributes<HTMLTableSectionElement> & { context?: Context }
Pick<React.ComponentProps<'thead'>, 'children' | 'style'> & React.RefAttributes<HTMLTableSectionElement> & ContextProp<Context>
>

/**
* Set to customize the item wrapping element. Default is `tr`.
*/
TableRow?: React.ComponentType<ItemProps<Data> & { context?: Context }>
TableRow?: React.ComponentType<ItemProps<Data> & ContextProp<Context>>
}

export type TableProps = Pick<React.ComponentProps<'table'>, 'children' | 'style'>
Expand Down

0 comments on commit dc2fad9

Please sign in to comment.