diff --git a/packages/@react-aria/virtualizer/src/ScrollView.tsx b/packages/@react-aria/virtualizer/src/ScrollView.tsx index 526996f19eb..348abd8b5a2 100644 --- a/packages/@react-aria/virtualizer/src/ScrollView.tsx +++ b/packages/@react-aria/virtualizer/src/ScrollView.tsx @@ -159,16 +159,27 @@ function ScrollView(props: ScrollViewProps, ref: RefObject) { if (scrollDirection === 'horizontal') { style.overflowX = 'auto'; style.overflowY = 'hidden'; - } else if (scrollDirection === 'vertical') { + } else if (scrollDirection === 'vertical' || contentSize.width === state.width) { + // Set overflow-x: hidden if content size is equal to the width of the scroll view. + // This prevents horizontal scrollbars from flickering during resizing due to resize observer + // firing slower than the frame rate, which may cause an infinite re-render loop. style.overflowY = 'auto'; style.overflowX = 'hidden'; } else { style.overflow = 'auto'; } + innerStyle = { + width: contentSize.width, + height: contentSize.height, + pointerEvents: isScrolling ? 'none' : 'auto', + position: 'relative', + ...innerStyle + }; + return (
-
+
{children}
diff --git a/packages/@react-spectrum/table/stories/Table.stories.tsx b/packages/@react-spectrum/table/stories/Table.stories.tsx index 14487e0595f..faf6ae4e7d6 100644 --- a/packages/@react-spectrum/table/stories/Table.stories.tsx +++ b/packages/@react-spectrum/table/stories/Table.stories.tsx @@ -927,8 +927,7 @@ function AsyncLoadingExample(props) { let url = new URL('https://www.reddit.com/r/upliftingnews.json'); if (cursor) { url.searchParams.append('after', cursor); - } - + } let res = await fetch(url.toString(), {signal}); let json = await res.json(); return {items: json.data.children, cursor: json.data.after}; @@ -983,6 +982,153 @@ export const AsyncLoading: TableStory = { name: 'async loading' }; +async function fakeFetch() { + return new Promise( + (resolve) => { + setTimeout(() => resolve({json: async function () { + return {data: {children: [ + {data: {id: 'foo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: 'fooo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: 'foooo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: 'fooooo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: 'foooooo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: 'doo', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: '1', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: '2', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: '3', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: '4', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: '5', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}}, + {data: {id: '6', thumbnail: 'https://i.imgur.com/Z7AzH2c.jpg', ups: '7', title: 'cats', created: Date.now()}} + ]}}; + }}), 1000); + } + ); +} + +export const AsyncLoadingQuarryTest: TableStory = { + args: { + 'aria-label': 'Top news from Reddit', + selectionMode: 'multiple', + height: 400, + width: '100%' + }, + render: (args) => , + name: 'async reload on sort' +}; + +function AsyncLoadingExampleQuarryTest(props) { + let [filters, setFilters] = React.useState({}); + + const rColumns = [ + { + key: 'title', + label: 'Title' + }, + { + key: 'ups', + label: 'Upvotes', + width: 200 + }, + { + key: 'created', + label: 'Created', + width: 350 + } + ]; + interface Post { + id: string, + key: string, + preview?: string, + ups: number, + title: string, + created: string, + url: string + } + + let list = useAsyncList({ + getKey: (item) => item.id, + async load({cursor}) { + const url = new URL('https://www.reddit.com/r/aww.json'); + + if (cursor) { + url.searchParams.append('after', cursor); + } + + const res = await fakeFetch(); + // @ts-ignore + const json = await res.json(); + const items = json.data.children.map((item) => { + return { + id: item.data.id, + preview: item.data.thumbnail, + ups: item.data.ups, + title: item.data.title, + created: new Date(item.data.created * 1000).toISOString() + }; + }); + return { + items, + cursor: json.data.after, + total: 987 + }; + }, + async sort({items, sortDescriptor}) { + return { + items: items.slice().sort((a, b) => { + let cmp = a[sortDescriptor.column] < b[sortDescriptor.column] ? -1 : 1; + + if (sortDescriptor.direction === 'descending') { + cmp *= -1; + } + + return cmp; + }) + + }; + } + }); + + let reloadDeps = [filters]; + + useMountEffect((): void => { + list.reload(); + }, reloadDeps); + + return ( + + + {(column) => ( + + {column.label} + + )} + + + {item => + ( + {key => + key === 'title' + ? {item.title} + : {item[key]} + } + ) + } + + + ); +} + +function useMountEffect(fn: () => void, deps: Array): void { + const mounted = React.useRef(false); + React.useEffect(() => { + if (mounted.current) { + fn(); + } else { + mounted.current = true; + } + }, deps); +} + export const HideHeader: TableStory = { args: { 'aria-label': 'TableView with static contents',