Skip to content

Commit fb2c3f0

Browse files
authored
Merge branch 'main' into s2-datepicker
2 parents 9353a75 + 8326f90 commit fb2c3f0

40 files changed

+1567
-245
lines changed

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,10 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
7979
private firstKey: Key | null = null;
8080
private lastKey: Key | null = null;
8181
private frozen = false;
82+
private itemCount: number = 0;
8283

8384
get size(): number {
84-
return this.keyMap.size;
85+
return this.itemCount;
8586
}
8687

8788
getKeys(): IterableIterator<Key> {
@@ -184,6 +185,7 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
184185
collection.keyMap = new Map(this.keyMap);
185186
collection.firstKey = this.firstKey;
186187
collection.lastKey = this.lastKey;
188+
collection.itemCount = this.itemCount;
187189
return collection;
188190
}
189191

@@ -192,6 +194,10 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
192194
throw new Error('Cannot add a node to a frozen collection');
193195
}
194196

197+
if (node.type === 'item' && this.keyMap.get(node.key) == null) {
198+
this.itemCount++;
199+
}
200+
195201
this.keyMap.set(node.key, node);
196202
}
197203

@@ -200,6 +206,11 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
200206
throw new Error('Cannot remove a node to a frozen collection');
201207
}
202208

209+
let node = this.keyMap.get(key);
210+
if (node != null && node.type === 'item') {
211+
this.itemCount--;
212+
}
213+
203214
this.keyMap.delete(key);
204215
}
205216

packages/@react-aria/gridlist/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
"@react-aria/interactions": "^3.25.3",
3333
"@react-aria/selection": "^3.24.3",
3434
"@react-aria/utils": "^3.29.1",
35-
"@react-stately/collections": "^3.12.5",
3635
"@react-stately/list": "^3.12.3",
3736
"@react-stately/tree": "^3.9.0",
3837
"@react-types/shared": "^3.30.0",

packages/@react-aria/gridlist/src/useGridListItem.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import {chain, getScrollParent, mergeProps, scrollIntoViewport, useSlotId, useSyntheticLinkProps} from '@react-aria/utils';
1414
import {DOMAttributes, FocusableElement, Key, RefObject, Node as RSNode} from '@react-types/shared';
1515
import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus';
16-
import {getLastItem} from '@react-stately/collections';
1716
import {getRowId, listMap} from './utils';
1817
import {HTMLAttributes, KeyboardEvent as ReactKeyboardEvent, useRef} from 'react';
1918
import {isFocusVisible} from '@react-aria/interactions';
@@ -104,13 +103,14 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt
104103
if (node.level > 0 && node?.parentKey != null) {
105104
let parent = state.collection.getItem(node.parentKey);
106105
if (parent) {
107-
// siblings must exist because our original node exists, same with lastItem
106+
// siblings must exist because our original node exists
108107
let siblings = state.collection.getChildren?.(parent.key)!;
109-
setSize = getLastItem(siblings)!.index + 1;
108+
setSize = [...siblings].filter(row => row.type === 'item').length;
110109
}
111110
} else {
112-
setSize = ([...state.collection].filter(row => row.level === 0).at(-1)?.index ?? 0) + 1;
111+
setSize = [...state.collection].filter(row => row.level === 0 && row.type === 'item').length;
113112
}
113+
114114
treeGridRowProps = {
115115
'aria-expanded': isExpanded,
116116
'aria-level': node.level + 1,

packages/@react-aria/utils/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export {useEffectEvent} from './useEffectEvent';
4545
export {useDeepMemo} from './useDeepMemo';
4646
export {useFormReset} from './useFormReset';
4747
export {useLoadMore} from './useLoadMore';
48-
export {UNSTABLE_useLoadMoreSentinel} from './useLoadMoreSentinel';
48+
export {useLoadMoreSentinel} from './useLoadMoreSentinel';
4949
export {inertValue} from './inertValue';
5050
export {CLEAR_FOCUS_EVENT, FOCUS_EVENT} from './constants';
5151
export {isCtrlKeyPressed} from './keyboard';

packages/@react-aria/utils/src/useLoadMoreSentinel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface LoadMoreSentinelProps extends Omit<AsyncLoadable, 'isLoading'>
2828
scrollOffset?: number
2929
}
3030

31-
export function UNSTABLE_useLoadMoreSentinel(props: LoadMoreSentinelProps, ref: RefObject<HTMLElement | null>): void {
31+
export function useLoadMoreSentinel(props: LoadMoreSentinelProps, ref: RefObject<HTMLElement | null>): void {
3232
let {collection, onLoadMore, scrollOffset = 1} = props;
3333

3434
let sentinelObserver = useRef<IntersectionObserver>(null);

packages/@react-spectrum/autocomplete/test/SearchAutocomplete.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,7 +1844,7 @@ describe('SearchAutocomplete', function () {
18441844
expect(() => within(tray).getByText('No results')).toThrow();
18451845
});
18461846

1847-
it.skip('user can select options by pressing them', async function () {
1847+
it('user can select options by pressing them', async function () {
18481848
let {getByRole, getByText, getByTestId} = renderSearchAutocomplete();
18491849
let button = getByRole('button');
18501850

@@ -1892,7 +1892,7 @@ describe('SearchAutocomplete', function () {
18921892
expect(items[1]).toHaveAttribute('aria-selected', 'true');
18931893
});
18941894

1895-
it.skip('user can select options by focusing them and hitting enter', async function () {
1895+
it('user can select options by focusing them and hitting enter', async function () {
18961896
let {getByRole, getByText, getByTestId} = renderSearchAutocomplete();
18971897
let button = getByRole('button');
18981898

packages/@react-spectrum/s2/src/CardView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import {
1616
ContextValue,
1717
GridLayout,
1818
GridListItem,
19+
GridListLoadMoreItem,
1920
GridListProps,
2021
Size,
21-
UNSTABLE_GridListLoadingSentinel,
2222
Virtualizer,
2323
WaterfallLayout
2424
} from 'react-aria-components';
@@ -246,7 +246,7 @@ export const CardView = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
246246

247247
let renderer;
248248
let cardLoadingSentinel = (
249-
<UNSTABLE_GridListLoadingSentinel
249+
<GridListLoadMoreItem
250250
onLoadMore={onLoadMore} />
251251
);
252252

packages/@react-spectrum/s2/src/ComboBox.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ import {
2424
ListBox,
2525
ListBoxItem,
2626
ListBoxItemProps,
27+
ListBoxLoadMoreItem,
2728
ListBoxProps,
2829
ListLayout,
2930
ListStateContext,
3031
Provider,
3132
SectionProps,
32-
UNSTABLE_ListBoxLoadingSentinel,
3333
Virtualizer
3434
} from 'react-aria-components';
3535
import {AsyncLoadable, GlobalDOMAttributes, HelpTextProps, LoadingState, SpectrumLabelableProps} from '@react-types/shared';
@@ -542,7 +542,7 @@ const ComboboxInner = forwardRef(function ComboboxInner(props: ComboBoxProps<any
542542

543543
let renderer;
544544
let listBoxLoadingCircle = (
545-
<UNSTABLE_ListBoxLoadingSentinel
545+
<ListBoxLoadMoreItem
546546
// Only show the spinner in the list when loading more
547547
isLoading={loadingState === 'loadingMore'}
548548
onLoadMore={onLoadMore}
@@ -553,7 +553,7 @@ const ComboboxInner = forwardRef(function ComboboxInner(props: ComboBoxProps<any
553553
styles={progressCircleStyles({size})}
554554
// Same loading string as table
555555
aria-label={stringFormatter.format('table.loadingMore')} />
556-
</UNSTABLE_ListBoxLoadingSentinel>
556+
</ListBoxLoadMoreItem>
557557
);
558558

559559
if (typeof children === 'function') {

packages/@react-spectrum/s2/src/Picker.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import {
2323
ListBox,
2424
ListBoxItem,
2525
ListBoxItemProps,
26+
ListBoxLoadMoreItem,
2627
ListBoxProps,
2728
ListLayout,
2829
Provider,
2930
SectionProps,
3031
SelectValue,
31-
UNSTABLE_ListBoxLoadingSentinel,
3232
Virtualizer
3333
} from 'react-aria-components';
3434
import {AsyncLoadable, FocusableRef, FocusableRefValue, GlobalDOMAttributes, HelpTextProps, LoadingState, PressEvent, RefObject, SpectrumLabelableProps} from '@react-types/shared';
@@ -307,12 +307,12 @@ export const Picker = /*#__PURE__*/ (forwardRef as forwardRefType)(function Pick
307307
let spinnerId = useSlotId([showButtonSpinner]);
308308

309309
let listBoxLoadingCircle = (
310-
<UNSTABLE_ListBoxLoadingSentinel
310+
<ListBoxLoadMoreItem
311311
className={loadingWrapperStyles}
312312
isLoading={loadingState === 'loadingMore'}
313313
onLoadMore={onLoadMore}>
314314
<PickerProgressCircle size={size} aria-label={stringFormatter.format('table.loadingMore')} />
315-
</UNSTABLE_ListBoxLoadingSentinel>
315+
</ListBoxLoadMoreItem>
316316
);
317317

318318
if (typeof children === 'function' && items) {

packages/@react-spectrum/s2/src/TableView.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ import {
3838
RowRenderProps,
3939
TableBodyRenderProps,
4040
TableLayout,
41+
TableLoadMoreItem,
4142
TableRenderProps,
42-
UNSTABLE_TableLoadingSentinel,
4343
useSlottedContext,
4444
useTableOptions,
4545
Virtualizer
@@ -196,11 +196,10 @@ export class S2TableLayout<T> extends TableLayout<T> {
196196
if (!header) {
197197
return [];
198198
}
199-
let {children, layoutInfo} = body;
199+
let {layoutInfo} = body;
200200
// TableLayout's buildCollection always sets the body width to the max width between the header width, but
201201
// we want the body to be sticky and only as wide as the table so it is always in view if loading/empty
202-
// TODO: we may want to adjust RAC layouts to do something simlar? Current users of RAC table will probably run into something similar
203-
let isEmptyOrLoading = children?.length === 0 || (children?.length === 1 && children[0].layoutInfo.type === 'loader');
202+
let isEmptyOrLoading = this.virtualizer?.collection.size === 0;
204203
if (isEmptyOrLoading) {
205204
layoutInfo.rect.width = this.virtualizer!.visibleRect.width - 80;
206205
}
@@ -219,19 +218,19 @@ export class S2TableLayout<T> extends TableLayout<T> {
219218
// If performing first load or empty, the body will be sticky so we don't want to apply sticky to the loader, otherwise it will
220219
// affect the positioning of the empty state renderer
221220
let collection = this.virtualizer!.collection;
222-
let isEmptyOrLoading = collection?.size === 0 || (collection.size === 1 && collection.getItem(collection.getFirstKey()!)!.type === 'loader');
221+
let isEmptyOrLoading = collection?.size === 0;
223222
layoutInfo.isSticky = !isEmptyOrLoading;
224223
return layoutNode;
225224
}
226225

227226
// y is the height of the headers
228227
protected buildBody(y: number): LayoutNode {
229228
let layoutNode = super.buildBody(y);
230-
let {children, layoutInfo} = layoutNode;
229+
let {layoutInfo} = layoutNode;
231230
// Needs overflow for sticky loader
232231
layoutInfo.allowOverflow = true;
233232
// If loading or empty, we'll want the body to be sticky and centered
234-
let isEmptyOrLoading = children?.length === 0 || (children?.length === 1 && children[0].layoutInfo.type === 'loader');
233+
let isEmptyOrLoading = this.virtualizer?.collection.size === 0;
235234
if (isEmptyOrLoading) {
236235
layoutInfo.rect = new Rect(40, 40, this.virtualizer!.visibleRect.width - 80, this.virtualizer!.visibleRect.height - 80);
237236
layoutInfo.isSticky = true;
@@ -390,13 +389,13 @@ export const TableBody = /*#__PURE__*/ (forwardRef as forwardRefType)(function T
390389
// This is because we don't distinguish between loadingMore and loading in the layout, resulting in a different rect being used to build the body. Perhaps can be considered as a user error
391390
// if they pass loadingMore without having any other items in the table. Alternatively, could update the layout so it knows the current loading state.
392391
let loadMoreSpinner = (
393-
<UNSTABLE_TableLoadingSentinel isLoading={loadingState === 'loadingMore'} onLoadMore={onLoadMore} className={style({height: 'full', width: 'full'})}>
392+
<TableLoadMoreItem isLoading={loadingState === 'loadingMore'} onLoadMore={onLoadMore} className={style({height: 'full', width: 'full'})}>
394393
<div className={centeredWrapper}>
395394
<ProgressCircle
396395
isIndeterminate
397396
aria-label={stringFormatter.format('table.loadingMore')} />
398397
</div>
399-
</UNSTABLE_TableLoadingSentinel>
398+
</TableLoadMoreItem>
400399
);
401400

402401
// If the user is rendering their rows in dynamic fashion, wrap their render function in Collection so we can inject

0 commit comments

Comments
 (0)