11import * as React from 'react' ;
2- import { FixedSizeList , ListChildComponentProps } from 'react-window' ;
2+ import { List , RowComponentProps , ListImperativeAPI } from 'react-window' ;
33import { Popper } from '@mui/base/Popper' ;
44import Autocomplete from '@mui/joy/Autocomplete' ;
5- import AutocompleteListbox from '@mui/joy/AutocompleteListbox' ;
65import AutocompleteOption from '@mui/joy/AutocompleteOption' ;
76import FormControl from '@mui/joy/FormControl' ;
87import FormLabel from '@mui/joy/FormLabel' ;
98import ListSubheader from '@mui/joy/ListSubheader' ;
9+ import AutocompleteListbox , {
10+ AutocompleteListboxProps ,
11+ } from '@mui/joy/AutocompleteListbox' ;
1012
1113const LISTBOX_PADDING = 6 ; // px
1214
13- function renderRow ( props : ListChildComponentProps ) {
15+ function renderRow ( props : RowComponentProps & { data : any } ) {
1416 const { data, index, style } = props ;
1517 const dataSet = data [ index ] ;
1618 const inlineStyle = {
1719 ...style ,
18- top : ( style . top as number ) + LISTBOX_PADDING ,
20+ top : ( ( style . top as number ) ?? 0 ) + LISTBOX_PADDING ,
1921 } ;
2022
2123 if ( dataSet . hasOwnProperty ( 'group' ) ) {
@@ -33,44 +35,40 @@ function renderRow(props: ListChildComponentProps) {
3335 ) ;
3436}
3537
36- const OuterElementContext = React . createContext ( { } ) ;
37-
38- const OuterElementType = React . forwardRef < HTMLDivElement > ( ( props , ref ) => {
39- const outerProps = React . useContext ( OuterElementContext ) ;
40- return (
41- < AutocompleteListbox
42- { ...props }
43- { ...outerProps }
44- component = "div"
45- ref = { ref }
46- sx = { {
47- '& ul' : {
48- padding : 0 ,
49- margin : 0 ,
50- flexShrink : 0 ,
51- } ,
52- } }
53- />
54- ) ;
55- } ) ;
56-
5738// Adapter for react-window
5839const ListboxComponent = React . forwardRef <
5940 HTMLDivElement ,
6041 {
6142 anchorEl : any ;
6243 open : boolean ;
6344 modifiers : any [ ] ;
64- } & React . HTMLAttributes < HTMLElement >
45+ internalListRef : React . MutableRefObject < {
46+ api : ListImperativeAPI | null ;
47+ optionIndexMap : Record < string , number > ;
48+ } > ;
49+ } & React . HTMLAttributes < HTMLElement > &
50+ AutocompleteListboxProps
6551> ( function ListboxComponent ( props , ref ) {
66- const { children, anchorEl, open, modifiers, ...other } = props ;
52+ const { children, anchorEl, open, modifiers, internalListRef , ...other } = props ;
6753 const itemData : Array < any > = [ ] ;
68- (
69- children as [ Array < { children : Array < React . ReactElement < any > > | undefined } > ]
70- ) [ 0 ] . forEach ( ( item ) => {
71- if ( item ) {
72- itemData . push ( item ) ;
73- itemData . push ( ...( item . children || [ ] ) ) ;
54+ const optionIndexMap : Record < string , number > = { } ;
55+
56+ if ( children && Array . isArray ( children ) && children [ 0 ] ) {
57+ (
58+ children as [ Array < { children : Array < React . ReactElement < any > > | undefined } > ]
59+ ) [ 0 ] . forEach ( ( item ) => {
60+ if ( item ) {
61+ itemData . push ( item ) ;
62+ itemData . push ( ...( item . children || [ ] ) ) ;
63+ }
64+ } ) ;
65+ }
66+
67+ // Build the index map after flattening
68+ itemData . forEach ( ( item , index ) => {
69+ if ( Array . isArray ( item ) && item [ 1 ] ) {
70+ // Option item: [props, optionValue]
71+ optionIndexMap [ item [ 1 ] ] = index ;
7472 }
7573 } ) ;
7674
@@ -79,20 +77,37 @@ const ListboxComponent = React.forwardRef<
7977
8078 return (
8179 < Popper ref = { ref } anchorEl = { anchorEl } open = { open } modifiers = { modifiers } >
82- < OuterElementContext . Provider value = { other } >
83- < FixedSizeList
84- itemData = { itemData }
85- height = { itemSize * 8 }
86- width = "100%"
87- outerElementType = { OuterElementType }
88- innerElementType = "ul"
89- itemSize = { itemSize }
80+ < AutocompleteListbox
81+ { ...other }
82+ component = "div"
83+ sx = { {
84+ '& ul' : {
85+ padding : 0 ,
86+ margin : 0 ,
87+ flexShrink : 0 ,
88+ } ,
89+ maxHeight : '100%' ,
90+ } }
91+ >
92+ < List
93+ listRef = { ( api ) => {
94+ // Store both the API and the map in the ref
95+ if ( internalListRef ) {
96+ internalListRef . current = { api, optionIndexMap } ;
97+ }
98+ } }
99+ rowCount = { itemCount }
100+ rowHeight = { itemSize }
101+ rowComponent = { renderRow }
102+ rowProps = { { data : itemData } }
103+ style = { {
104+ height : itemSize * 8 ,
105+ width : '100%' ,
106+ } }
90107 overscanCount = { 5 }
91- itemCount = { itemCount }
92- >
93- { renderRow }
94- </ FixedSizeList >
95- </ OuterElementContext . Provider >
108+ tagName = "ul"
109+ />
110+ </ AutocompleteListbox >
96111 </ Popper >
97112 ) ;
98113} ) ;
@@ -114,6 +129,29 @@ const OPTIONS = Array.from(new Array(10000))
114129 . sort ( ( a , b ) => a . toUpperCase ( ) . localeCompare ( b . toUpperCase ( ) ) ) ;
115130
116131export default function Virtualize ( ) {
132+ // Ref to store both the List API and the option index map
133+ const internalListRef = React . useRef < {
134+ api : ListImperativeAPI | null ;
135+ optionIndexMap : Record < string , number > ;
136+ } > ( {
137+ api : null ,
138+ optionIndexMap : { } ,
139+ } ) ;
140+
141+ // Handle keyboard navigation by scrolling to highlighted option
142+ const handleHighlightChange = (
143+ event : React . SyntheticEvent ,
144+ option : string | null ,
145+ ) => {
146+ if ( option && internalListRef . current ) {
147+ const { api, optionIndexMap } = internalListRef . current ;
148+ const index = optionIndexMap [ option ] ;
149+ if ( index !== undefined && api ) {
150+ api . scrollToRow ( { index, align : 'auto' } ) ;
151+ }
152+ }
153+ } ;
154+
117155 return (
118156 < FormControl id = "virtualize-demo" >
119157 < FormLabel > 10,000 options</ FormLabel >
@@ -124,11 +162,17 @@ export default function Virtualize() {
124162 slots = { {
125163 listbox : ListboxComponent ,
126164 } }
165+ slotProps = { {
166+ listbox : {
167+ internalListRef,
168+ } as any ,
169+ } }
127170 options = { OPTIONS }
128171 groupBy = { ( option ) => option [ 0 ] . toUpperCase ( ) }
129172 renderOption = { ( props , option ) => [ props , option ] as React . ReactNode }
130173 // TODO: Post React 18 update - validate this conversion, look like a hidden bug
131174 renderGroup = { ( params ) => params as unknown as React . ReactNode }
175+ onHighlightChange = { handleHighlightChange }
132176 />
133177 </ FormControl >
134178 ) ;
0 commit comments