Skip to content

Commit c9ee11d

Browse files
committed
account for loaders in base collection filter
1 parent 7e14ffa commit c9ee11d

File tree

3 files changed

+100
-9
lines changed

3 files changed

+100
-9
lines changed

packages/@react-aria/collections/src/BaseCollection.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,11 +296,16 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
296296
newCollection.firstKey = clonedNode.key;
297297
}
298298

299-
if (lastNode != null && (lastNode.type !== 'section' && lastNode.type !== 'separator') && lastNode.parentKey === clonedNode.parentKey) {
300-
lastNode.nextKey = clonedNode.key;
301-
clonedNode.prevKey = lastNode.key;
302-
} else {
303-
clonedNode.prevKey = null;
299+
if (lastNode != null) {
300+
if (
301+
(lastNode.type !== 'section' && lastNode.type !== 'separator' && lastNode.parentKey === clonedNode.parentKey) ||
302+
(clonedNode.type === 'loader')
303+
) {
304+
lastNode.nextKey = clonedNode.key;
305+
clonedNode.prevKey = lastNode.key;
306+
} else {
307+
clonedNode.prevKey = null;
308+
}
304309
}
305310

306311
clonedNode.nextKey = null;
@@ -338,7 +343,9 @@ function shouldKeepNode<T>(node: Node<T>, filterFn: (nodeValue: string) => boole
338343
} else {
339344
return false;
340345
}
341-
} else if (node.type === 'header') {
346+
} else if (node.type === 'header' || node.type === 'loader') {
347+
// TODO what about tree multiple loaders? Should a loader still be preserved if its parent row is filtered out
348+
// Actually how should a tree structure be filtered? Do levels no longer matter or does it filter at each level?
342349
return true;
343350
} else {
344351
return filterFn(node.textValue);

packages/react-aria-components/stories/Autocomplete.stories.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import React from 'react';
1717
import styles from '../example/index.css';
1818
import {useAsyncList, useListData, useTreeData} from 'react-stately';
1919
import {useFilter} from 'react-aria';
20+
import { MyListBoxLoaderIndicator, renderEmptyState } from './ListBox.stories';
2021

2122
export default {
2223
title: 'React Aria Components',
@@ -849,3 +850,85 @@ export const AutocompleteSelect = () => (
849850
</Popover>
850851
</Select>
851852
);
853+
854+
interface Character {
855+
name: string,
856+
height: number,
857+
mass: number,
858+
birth_year: number
859+
}
860+
861+
862+
export const AutocompleteWithAsyncListBox = (args) => {
863+
let list = useAsyncList<Character>({
864+
async load({signal, cursor, filterText}) {
865+
if (cursor) {
866+
cursor = cursor.replace(/^http:\/\//i, 'https://');
867+
}
868+
869+
await new Promise(resolve => setTimeout(resolve, args.delay));
870+
let res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
871+
let json = await res.json();
872+
return {
873+
items: json.results,
874+
cursor: json.next
875+
};
876+
}
877+
});
878+
879+
return (
880+
<AutocompleteWrapper>
881+
<div>
882+
<TextField autoFocus data-testid="autocomplete-example">
883+
<Label style={{display: 'block'}}>Test</Label>
884+
<Input />
885+
<Text style={{display: 'block'}} slot="description">Please select an option below.</Text>
886+
</TextField>
887+
<Virtualizer
888+
layout={ListLayout}
889+
layoutOptions={{
890+
rowHeight: 50,
891+
padding: 4,
892+
loaderHeight: 30
893+
}}>
894+
<ListBox
895+
{...args}
896+
style={{
897+
height: 400,
898+
width: 100,
899+
border: '1px solid gray',
900+
background: 'lightgray',
901+
overflow: 'auto',
902+
padding: 'unset',
903+
display: 'flex'
904+
}}
905+
aria-label="async virtualized listbox"
906+
renderEmptyState={() => renderEmptyState({isLoading: list.isLoading})}>
907+
<Collection items={list.items}>
908+
{(item: Character) => (
909+
<MyListBoxItem
910+
style={{
911+
backgroundColor: 'lightgrey',
912+
border: '1px solid black',
913+
boxSizing: 'border-box',
914+
height: '100%',
915+
width: '100%'
916+
}}
917+
id={item.name}>
918+
{item.name}
919+
</MyListBoxItem>
920+
)}
921+
</Collection>
922+
<MyListBoxLoaderIndicator isLoading={list.loadingState === 'loadingMore'} onLoadMore={list.loadMore} />
923+
</ListBox>
924+
</Virtualizer>
925+
</div>
926+
</AutocompleteWrapper>
927+
);
928+
};
929+
930+
AutocompleteWithAsyncListBox.story = {
931+
args: {
932+
delay: 50
933+
}
934+
};

packages/react-aria-components/stories/ListBox.stories.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {UNSTABLE_ListBoxLoadingSentinel} from '../src/ListBox';
2020
import {useAsyncList, useListData} from 'react-stately';
2121

2222
export default {
23-
title: 'React Aria Components'
23+
title: 'React Aria Components',
24+
excludeStories: ['MyListBoxLoaderIndicator', 'renderEmptyState']
2425
};
2526

2627
export const ListBoxExample = (args) => (
@@ -435,7 +436,7 @@ export function VirtualizedListBoxWaterfall({minSize = 80, maxSize = 100}) {
435436
);
436437
}
437438

438-
let renderEmptyState = ({isLoading}) => {
439+
export let renderEmptyState = ({isLoading}) => {
439440
return (
440441
<div style={{height: 30, width: '100%'}}>
441442
{isLoading ? <LoadingSpinner style={{height: 20, width: 20, transform: 'translate(-50%, -50%)'}} /> : 'No results'}
@@ -450,7 +451,7 @@ interface Character {
450451
birth_year: number
451452
}
452453

453-
const MyListBoxLoaderIndicator = (props) => {
454+
export const MyListBoxLoaderIndicator = (props) => {
454455
let {orientation, ...otherProps} = props;
455456
return (
456457
<UNSTABLE_ListBoxLoadingSentinel

0 commit comments

Comments
 (0)