Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
462a690
[docs] Update react-window package
sai6855 Oct 9, 2025
557b03f
fix code
sai6855 Oct 9, 2025
de4dc11
Merge branch 'master' into react-window-v2
ZeeshanTamboli Oct 13, 2025
8170133
Merge branch 'master' into react-window-v2
sai6855 Oct 13, 2025
3cef218
Merge branch 'react-window-v2' of https://github.com/sai6855/material…
sai6855 Oct 13, 2025
0d0c1f0
fix-pnpm-loc
sai6855 Oct 13, 2025
a94694c
remove margin,padding
sai6855 Oct 13, 2025
ef84603
Merge branch 'master' into react-window-v2
ZeeshanTamboli Oct 14, 2025
082464d
pnpm dedupe
ZeeshanTamboli Oct 14, 2025
478696e
zeeshan review
sai6855 Oct 15, 2025
aa6947b
Merge branch 'react-window-v2' of https://github.com/sai6855/material…
sai6855 Oct 15, 2025
44a902c
Merge branch 'master' into react-window-v2
sai6855 Oct 15, 2025
97ac177
update-version
sai6855 Oct 23, 2025
89b8747
fix
sai6855 Oct 23, 2025
56cb266
Merge branch 'master' into react-window-v2
sai6855 Oct 23, 2025
96d0e30
Merge branch 'master' into react-window-v2
ZeeshanTamboli Oct 24, 2025
767f49c
fix NaN error
sai6855 Oct 24, 2025
6a5fae9
Merge branch 'react-window-v2' of https://github.com/sai6855/material…
sai6855 Oct 24, 2025
c524561
fix
sai6855 Oct 24, 2025
b052040
Update next-env.d.ts
sai6855 Oct 24, 2025
5b9b40a
fix joy
sai6855 Oct 26, 2025
af380ce
Merge branch 'react-window-v2' of https://github.com/sai6855/material…
sai6855 Oct 26, 2025
a32bf95
fix
sai6855 Oct 27, 2025
0c38609
fix
sai6855 Oct 27, 2025
f7d5a25
fix
sai6855 Oct 27, 2025
85eb361
fix
sai6855 Oct 27, 2025
229a274
Merge branch 'master' into react-window-v2
ZeeshanTamboli Oct 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 81 additions & 43 deletions docs/data/joy/components/autocomplete/Virtualize.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { FixedSizeList } from 'react-window';
import { List } from 'react-window';
import { Popper } from '@mui/base/Popper';
import Autocomplete from '@mui/joy/Autocomplete';
import AutocompleteListbox from '@mui/joy/AutocompleteListbox';
import AutocompleteOption from '@mui/joy/AutocompleteOption';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import ListSubheader from '@mui/joy/ListSubheader';
import AutocompleteListbox from '@mui/joy/AutocompleteListbox';

const LISTBOX_PADDING = 6; // px

Expand All @@ -16,7 +16,7 @@ function renderRow(props) {
const dataSet = data[index];
const inlineStyle = {
...style,
top: style.top + LISTBOX_PADDING,
top: (style.top ?? 0) + LISTBOX_PADDING,
};

if (dataSet.hasOwnProperty('group')) {
Expand All @@ -34,37 +34,26 @@ function renderRow(props) {
);
}

const OuterElementContext = React.createContext({});

const OuterElementType = React.forwardRef((props, ref) => {
const outerProps = React.useContext(OuterElementContext);
return (
<AutocompleteListbox
{...props}
{...outerProps}
component="div"
ref={ref}
sx={{
'& ul': {
padding: 0,
margin: 0,
flexShrink: 0,
},
}}
/>
);
});

// Adapter for react-window

const ListboxComponent = React.forwardRef(function ListboxComponent(props, ref) {
const { children, anchorEl, open, modifiers, ...other } = props;
const { children, anchorEl, open, modifiers, internalListRef, ...other } = props;
const itemData = [];
const optionIndexMap = {};

if (children && Array.isArray(children) && children[0]) {
children[0].forEach((item) => {
if (item) {
itemData.push(item);
itemData.push(...(item.children || []));
}
});
}

children[0].forEach((item) => {
if (item) {
itemData.push(item);
itemData.push(...(item.children || []));
// Build the index map after flattening
itemData.forEach((item, index) => {
if (Array.isArray(item) && item[1]) {
// Option item: [props, optionValue]
optionIndexMap[item[1]] = index;
}
});

Expand All @@ -73,27 +62,53 @@ const ListboxComponent = React.forwardRef(function ListboxComponent(props, ref)

return (
<Popper ref={ref} anchorEl={anchorEl} open={open} modifiers={modifiers}>
<OuterElementContext.Provider value={other}>
<FixedSizeList
itemData={itemData}
height={itemSize * 8}
width="100%"
outerElementType={OuterElementType}
innerElementType="ul"
itemSize={itemSize}
<AutocompleteListbox
{...other}
component="div"
sx={{
'& ul': {
padding: 0,
margin: 0,
flexShrink: 0,
},
maxHeight: '100%',
}}
>
<List
listRef={(api) => {
// Store both the API and the map in the ref
if (internalListRef) {
internalListRef.current = { api, optionIndexMap };
}
}}
rowCount={itemCount}
rowHeight={itemSize}
rowComponent={renderRow}
rowProps={{ data: itemData }}
style={{
height: itemSize * 8,
width: '100%',
}}
overscanCount={5}
itemCount={itemCount}
>
{renderRow}
</FixedSizeList>
</OuterElementContext.Provider>
tagName="ul"
/>
</AutocompleteListbox>
</Popper>
);
});

ListboxComponent.propTypes = {
anchorEl: PropTypes.any.isRequired,
children: PropTypes.node,
internalListRef: PropTypes.shape({
current: PropTypes.shape({
api: PropTypes.shape({
element: PropTypes.object,
scrollToRow: PropTypes.func.isRequired,
}),
optionIndexMap: PropTypes.object.isRequired,
}).isRequired,
}).isRequired,
modifiers: PropTypes.array.isRequired,
open: PropTypes.bool.isRequired,
};
Expand All @@ -115,6 +130,23 @@ const OPTIONS = Array.from(new Array(10000))
.sort((a, b) => a.toUpperCase().localeCompare(b.toUpperCase()));

export default function Virtualize() {
// Ref to store both the List API and the option index map
const internalListRef = React.useRef({
api: null,
optionIndexMap: {},
});

// Handle keyboard navigation by scrolling to highlighted option
const handleHighlightChange = (event, option) => {
if (option && internalListRef.current) {
const { api, optionIndexMap } = internalListRef.current;
const index = optionIndexMap[option];
if (index !== undefined && api) {
api.scrollToRow({ index, align: 'auto' });
}
}
};

return (
<FormControl id="virtualize-demo">
<FormLabel>10,000 options</FormLabel>
Expand All @@ -125,11 +157,17 @@ export default function Virtualize() {
slots={{
listbox: ListboxComponent,
}}
slotProps={{
listbox: {
internalListRef,
},
}}
options={OPTIONS}
groupBy={(option) => option[0].toUpperCase()}
renderOption={(props, option) => [props, option]}
// TODO: Post React 18 update - validate this conversion, look like a hidden bug
renderGroup={(params) => params}
onHighlightChange={handleHighlightChange}
/>
</FormControl>
);
Expand Down
136 changes: 90 additions & 46 deletions docs/data/joy/components/autocomplete/Virtualize.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import * as React from 'react';
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { List, RowComponentProps, ListImperativeAPI } from 'react-window';
import { Popper } from '@mui/base/Popper';
import Autocomplete from '@mui/joy/Autocomplete';
import AutocompleteListbox from '@mui/joy/AutocompleteListbox';
import AutocompleteOption from '@mui/joy/AutocompleteOption';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import ListSubheader from '@mui/joy/ListSubheader';
import AutocompleteListbox, {
AutocompleteListboxProps,
} from '@mui/joy/AutocompleteListbox';

const LISTBOX_PADDING = 6; // px

function renderRow(props: ListChildComponentProps) {
function renderRow(props: RowComponentProps & { data: any }) {
const { data, index, style } = props;
const dataSet = data[index];
const inlineStyle = {
...style,
top: (style.top as number) + LISTBOX_PADDING,
top: ((style.top as number) ?? 0) + LISTBOX_PADDING,
};

if (dataSet.hasOwnProperty('group')) {
Expand All @@ -33,44 +35,40 @@ function renderRow(props: ListChildComponentProps) {
);
}

const OuterElementContext = React.createContext({});

const OuterElementType = React.forwardRef<HTMLDivElement>((props, ref) => {
const outerProps = React.useContext(OuterElementContext);
return (
<AutocompleteListbox
{...props}
{...outerProps}
component="div"
ref={ref}
sx={{
'& ul': {
padding: 0,
margin: 0,
flexShrink: 0,
},
}}
/>
);
});

// Adapter for react-window
const ListboxComponent = React.forwardRef<
HTMLDivElement,
{
anchorEl: any;
open: boolean;
modifiers: any[];
} & React.HTMLAttributes<HTMLElement>
internalListRef: React.MutableRefObject<{
api: ListImperativeAPI | null;
optionIndexMap: Record<string, number>;
}>;
} & React.HTMLAttributes<HTMLElement> &
AutocompleteListboxProps
>(function ListboxComponent(props, ref) {
const { children, anchorEl, open, modifiers, ...other } = props;
const { children, anchorEl, open, modifiers, internalListRef, ...other } = props;
const itemData: Array<any> = [];
(
children as [Array<{ children: Array<React.ReactElement<any>> | undefined }>]
)[0].forEach((item) => {
if (item) {
itemData.push(item);
itemData.push(...(item.children || []));
const optionIndexMap: Record<string, number> = {};

if (children && Array.isArray(children) && children[0]) {
(
children as [Array<{ children: Array<React.ReactElement<any>> | undefined }>]
)[0].forEach((item) => {
if (item) {
itemData.push(item);
itemData.push(...(item.children || []));
}
});
}

// Build the index map after flattening
itemData.forEach((item, index) => {
if (Array.isArray(item) && item[1]) {
// Option item: [props, optionValue]
optionIndexMap[item[1]] = index;
}
});

Expand All @@ -79,20 +77,37 @@ const ListboxComponent = React.forwardRef<

return (
<Popper ref={ref} anchorEl={anchorEl} open={open} modifiers={modifiers}>
<OuterElementContext.Provider value={other}>
<FixedSizeList
itemData={itemData}
height={itemSize * 8}
width="100%"
outerElementType={OuterElementType}
innerElementType="ul"
itemSize={itemSize}
<AutocompleteListbox
{...other}
component="div"
sx={{
'& ul': {
padding: 0,
margin: 0,
flexShrink: 0,
},
maxHeight: '100%',
}}
>
<List
listRef={(api) => {
// Store both the API and the map in the ref
if (internalListRef) {
internalListRef.current = { api, optionIndexMap };
}
}}
rowCount={itemCount}
rowHeight={itemSize}
rowComponent={renderRow}
rowProps={{ data: itemData }}
style={{
height: itemSize * 8,
width: '100%',
}}
overscanCount={5}
itemCount={itemCount}
>
{renderRow}
</FixedSizeList>
</OuterElementContext.Provider>
tagName="ul"
/>
</AutocompleteListbox>
</Popper>
);
});
Expand All @@ -114,6 +129,29 @@ const OPTIONS = Array.from(new Array(10000))
.sort((a, b) => a.toUpperCase().localeCompare(b.toUpperCase()));

export default function Virtualize() {
// Ref to store both the List API and the option index map
const internalListRef = React.useRef<{
api: ListImperativeAPI | null;
optionIndexMap: Record<string, number>;
}>({
api: null,
optionIndexMap: {},
});

// Handle keyboard navigation by scrolling to highlighted option
const handleHighlightChange = (
event: React.SyntheticEvent,
option: string | null,
) => {
if (option && internalListRef.current) {
const { api, optionIndexMap } = internalListRef.current;
const index = optionIndexMap[option];
if (index !== undefined && api) {
api.scrollToRow({ index, align: 'auto' });
}
}
};

return (
<FormControl id="virtualize-demo">
<FormLabel>10,000 options</FormLabel>
Expand All @@ -124,11 +162,17 @@ export default function Virtualize() {
slots={{
listbox: ListboxComponent,
}}
slotProps={{
listbox: {
internalListRef,
} as any,
}}
options={OPTIONS}
groupBy={(option) => option[0].toUpperCase()}
renderOption={(props, option) => [props, option] as React.ReactNode}
// TODO: Post React 18 update - validate this conversion, look like a hidden bug
renderGroup={(params) => params as unknown as React.ReactNode}
onHighlightChange={handleHighlightChange}
/>
</FormControl>
);
Expand Down
Loading
Loading