Skip to content

Commit 9d65d5b

Browse files
committed
adding tests, make sure we only apply autocomplete attributes if the wrapped collection is filterable
1 parent 45a39c1 commit 9d65d5b

File tree

10 files changed

+463
-224
lines changed

10 files changed

+463
-224
lines changed

packages/@react-aria/autocomplete/src/useAutocomplete.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {AriaLabelingProps, BaseEvent, DOMProps, Node, RefObject} from '@react-types/shared';
1414
import {AriaTextFieldProps} from '@react-aria/textfield';
1515
import {AutocompleteProps, AutocompleteState} from '@react-stately/autocomplete';
16-
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getOwnerDocument, isCtrlKeyPressed, mergeProps, mergeRefs, useEffectEvent, useEvent, useId, useLabels, useObjectRef} from '@react-aria/utils';
16+
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getOwnerDocument, isCtrlKeyPressed, mergeProps, mergeRefs, useEffectEvent, useEvent, useLabels, useObjectRef, useSlotId} from '@react-aria/utils';
1717
import {dispatchVirtualBlur, dispatchVirtualFocus, getVirtuallyFocusedElement, moveVirtualFocus} from '@react-aria/focus';
1818
import {getInteractionModality} from '@react-aria/interactions';
1919
// @ts-ignore
@@ -28,7 +28,7 @@ export interface CollectionOptions extends DOMProps, AriaLabelingProps {
2828
disallowTypeAhead: boolean
2929
}
3030

31-
// TODO: is in beta so technically could replace textValue with Node if we are comfortable with that
31+
// TODO; For now go with Node here, but maybe pare it down to just the essentials? Value, key, and maybe type?
3232
export interface AriaAutocompleteProps extends AutocompleteProps {
3333
/**
3434
* An optional filter function used to determine if a option should be included in the autocomplete list.
@@ -57,7 +57,6 @@ export interface AutocompleteAria {
5757
collectionProps: CollectionOptions,
5858
/** Ref to attach to the wrapped collection. */
5959
collectionRef: RefObject<HTMLElement | null>,
60-
// TODO: same as above, replace nodeTextValue?
6160
/** A filter function that returns if the provided collection node should be filtered out of the collection. */
6261
filter?: (nodeTextValue: string, node: Node<unknown>) => boolean
6362
}
@@ -76,7 +75,7 @@ export function useAutocomplete(props: AriaAutocompleteOptions, state: Autocompl
7675
disableAutoFocusFirst = false
7776
} = props;
7877

79-
let collectionId = useId();
78+
let collectionId = useSlotId();
8079
let timeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
8180
let delayNextActiveDescendant = useRef(false);
8281
let queuedActiveDescendant = useRef<string | null>(null);
@@ -355,13 +354,19 @@ export function useAutocomplete(props: AriaAutocompleteOptions, state: Autocompl
355354
}
356355
};
357356

358-
return {
359-
textFieldProps: {
360-
value: state.inputValue,
361-
onChange,
357+
// Only apply the autocomplete specific behaviors if the collection component wrapped by it is actually
358+
// being filtered/allows filtering by the Autocomplete.
359+
let textFieldProps = {
360+
value: state.inputValue,
361+
onChange
362+
} as AriaTextFieldProps<HTMLInputElement>;
363+
364+
if (collectionId) {
365+
textFieldProps = {
366+
...textFieldProps,
362367
onKeyDown,
363368
autoComplete: 'off',
364-
'aria-haspopup': 'listbox',
369+
'aria-haspopup': collectionId ? 'listbox' : undefined,
365370
'aria-controls': collectionId,
366371
// TODO: readd proper logic for completionMode = complete (aria-autocomplete: both)
367372
'aria-autocomplete': 'list',
@@ -373,7 +378,11 @@ export function useAutocomplete(props: AriaAutocompleteOptions, state: Autocompl
373378
enterKeyHint: 'go',
374379
onBlur,
375380
onFocus
376-
},
381+
};
382+
}
383+
384+
return {
385+
textFieldProps,
377386
collectionProps: mergeProps(collectionProps, {
378387
shouldUseVirtualFocus,
379388
disallowTypeAhead: true

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
282282
this.frozen = !isSSR;
283283
}
284284

285-
filter(filterFn: FilterFn, newCollection?: BaseCollection<T>): BaseCollection<T> {
285+
filter(filterFn: FilterFn<T>, newCollection?: BaseCollection<T>): BaseCollection<T> {
286286
if (newCollection == null) {
287287
newCollection = new BaseCollection<T>();
288288
}
@@ -294,7 +294,7 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
294294
}
295295
}
296296

297-
function filterChildren<T>(collection: BaseCollection<T>, newCollection: BaseCollection<T>, firstChildKey: Key | null, filterFn: FilterFn): [Key | null, Key | null] {
297+
function filterChildren<T>(collection: BaseCollection<T>, newCollection: BaseCollection<T>, firstChildKey: Key | null, filterFn: FilterFn<T>): [Key | null, Key | null] {
298298
// loop over the siblings for firstChildKey
299299
// create new nodes based on calling node.filter for each child
300300
// if it returns null then don't include it, otherwise update its prev/next keys

packages/@react-aria/collections/src/CollectionBuilder.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ function useSSRCollectionNode<T extends Element>(CollectionNodeClass: Collection
164164
return <CollectionNodeClass.type ref={itemRef}>{children}</CollectionNodeClass.type>;
165165
}
166166

167+
// TODO: have it still accept a string along side a collectionNodeClass, just have it default to a base node class if so
167168
// TODO: check the signature of the CollectionNodeClass here and other places (aka useSSRCollectionNode and branchCompoennt). If I use the generic it complains. Perhaps it should be unknown? Or maybe the definitions in Listbox and stuff shouldn't use a generic?
168169
export function createLeafComponent<T extends object, P extends object, E extends Element>(CollectionNodeClass: CollectionNodeClass<any>, render: (props: P, ref: ForwardedRef<E>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null;
169170
export function createLeafComponent<T extends object, P extends object, E extends Element>(CollectionNodeClass: CollectionNodeClass<any>, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null;

packages/react-aria-components/src/GridList.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ interface GridListInnerProps<T extends object> {
106106
function GridListInner<T extends object>({props, collection, gridListRef: ref}: GridListInnerProps<T>) {
107107
// TODO: for now, don't grab collection ref and collectionProps from the autocomplete, rely on the user tabbing to the gridlist
108108
// figure out if we want to support virtual focus for grids when wrapped in an autocomplete
109-
let {filter} = useContext(UNSTABLE_InternalAutocompleteContext) || {};
109+
let {filter, collectionProps} = useContext(UNSTABLE_InternalAutocompleteContext) || {};
110+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
111+
let {shouldUseVirtualFocus, disallowTypeAhead, ...DOMCollectionProps} = collectionProps || {};
110112
let {dragAndDropHooks, keyboardNavigationBehavior = 'arrow', layout = 'stack'} = props;
111113
let {CollectionRoot, isVirtualized, layoutDelegate, dropTargetDelegate: ctxDropTargetDelegate} = useContext(CollectionRendererContext);
112114
let gridlistState = useListState({
@@ -135,6 +137,7 @@ function GridListInner<T extends object>({props, collection, gridListRef: ref}:
135137

136138
let {gridProps} = useGridList({
137139
...props,
140+
...DOMCollectionProps,
138141
keyboardDelegate,
139142
// Only tab navigation is supported in grid layout.
140143
keyboardNavigationBehavior: layout === 'grid' ? 'tab' : keyboardNavigationBehavior,

packages/react-aria-components/src/Table.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,9 @@ interface TableInnerProps {
375375

376376

377377
function TableInner({props, forwardedRef: ref, selectionState, collection}: TableInnerProps) {
378-
let {filter} = useContext(UNSTABLE_InternalAutocompleteContext) || {};
378+
let {filter, collectionProps} = useContext(UNSTABLE_InternalAutocompleteContext) || {};
379+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
380+
let {shouldUseVirtualFocus, disallowTypeAhead, ...DOMCollectionProps} = collectionProps || {};
379381
let tableContainerContext = useContext(ResizableTableContainerContext);
380382
ref = useObjectRef(useMemo(() => mergeRefs(ref, tableContainerContext?.tableRef), [ref, tableContainerContext?.tableRef]));
381383
let tableState = useTableState({
@@ -390,6 +392,7 @@ function TableInner({props, forwardedRef: ref, selectionState, collection}: Tabl
390392
let {dragAndDropHooks} = props;
391393
let {gridProps} = useTable({
392394
...props,
395+
...DOMCollectionProps,
393396
layoutDelegate,
394397
isVirtualized
395398
}, filteredState, ref);

packages/react-aria-components/src/TagGroup.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ interface TagGroupInnerProps {
7575
}
7676

7777
function TagGroupInner({props, forwardedRef: ref, collection}: TagGroupInnerProps) {
78-
let {filter} = useContext(UNSTABLE_InternalAutocompleteContext) || {};
78+
let {filter, collectionProps} = useContext(UNSTABLE_InternalAutocompleteContext) || {};
79+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
80+
let {shouldUseVirtualFocus, disallowTypeAhead, ...DOMCollectionProps} = collectionProps || {};
7981
let tagListRef = useRef<HTMLDivElement>(null);
8082
let [labelRef, label] = useSlot(
8183
!props['aria-label'] && !props['aria-labelledby']
@@ -99,6 +101,7 @@ function TagGroupInner({props, forwardedRef: ref, collection}: TagGroupInnerProp
99101
} = useTagGroup({
100102
...props,
101103
...domPropOverrides,
104+
...DOMCollectionProps,
102105
label
103106
}, filteredState, tagListRef);
104107

packages/react-aria-components/src/Tree.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {AriaTreeItemOptions, AriaTreeProps, DraggableItemResult, DropIndicatorAria, DropIndicatorProps, DroppableCollectionResult, FocusScope, ListKeyboardDelegate, mergeProps, useCollator, useFocusRing, useGridListSelectionCheckbox, useHover, useId, useLocale, useTree, useTreeItem, useVisuallyHidden} from 'react-aria';
1414
import {ButtonContext} from './Button';
1515
import {CheckboxContext} from './RSPContexts';
16-
import {Collection, CollectionBuilder, CollectionNode, createBranchComponent, createLeafComponent, useCachedChildren} from '@react-aria/collections';
16+
import {Collection, CollectionBuilder, CollectionNode, createBranchComponent, createLeafComponent, FilterLessNode, useCachedChildren} from '@react-aria/collections';
1717
import {CollectionProps, CollectionRendererContext, DefaultCollectionRenderer, ItemRenderProps} from './Collection';
1818
import {ContextValue, DEFAULT_SLOT, Provider, RenderProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps} from './utils';
1919
import {DisabledBehavior, DragPreviewRenderer, Expandable, forwardRefType, GlobalDOMAttributes, HoverEvents, Key, LinkDOMProps, MultipleSelection, PressEvents, RefObject, SelectionMode} from '@react-types/shared';
@@ -448,7 +448,7 @@ export interface TreeItemContentRenderProps extends TreeItemRenderProps {}
448448
// need to do a bunch of check to figure out what is the Content and what are the actual collection elements (aka child rows) of the TreeItem
449449
export interface TreeItemContentProps extends Pick<RenderProps<TreeItemContentRenderProps>, 'children'> {}
450450

451-
class TreeContentNode extends CollectionNode<any> {
451+
class TreeContentNode extends FilterLessNode<any> {
452452
static readonly type = 'content';
453453

454454
constructor(key: Key) {
@@ -491,8 +491,7 @@ export interface TreeItemProps<T = object> extends StyleRenderProps<TreeItemRend
491491
onAction?: () => void
492492
}
493493

494-
// TODO: also might be able to reuse the ItemNode
495-
class TreeItemNode extends CollectionNode<any> {
494+
class TreeItemNode extends FilterLessNode<any> {
496495
static readonly type = 'item';
497496

498497
constructor(key: Key) {
@@ -736,8 +735,7 @@ export interface TreeLoadMoreItemProps extends Omit<LoadMoreSentinelProps, 'coll
736735
isLoading?: boolean
737736
}
738737

739-
// TODO: can reuse this most likely
740-
class TreeLoaderNode extends CollectionNode<any> {
738+
class TreeLoaderNode extends FilterLessNode<any> {
741739
static readonly type = 'loader';
742740

743741
constructor(key: Key) {

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {MyListBoxLoaderIndicator, renderEmptyState} from './ListBox.stories';
1919
import {MyTag} from './TagGroup.stories';
2020
import React from 'react';
2121
import styles from '../example/index.css';
22-
import {TreeExampleStaticRender} from './Tree.stories';
2322
import {useAsyncList, useListData, useTreeData} from 'react-stately';
2423
import {useFilter} from 'react-aria';
2524
import './styles.css';

0 commit comments

Comments
 (0)