diff --git a/packages/@react-stately/grid/src/useGridState.ts b/packages/@react-stately/grid/src/useGridState.ts index f716f9bb9bd..0c47a1db0c1 100644 --- a/packages/@react-stately/grid/src/useGridState.ts +++ b/packages/@react-stately/grid/src/useGridState.ts @@ -1,7 +1,7 @@ import {getChildNodes, getFirstItem, getLastItem} from '@react-stately/collections'; import {GridCollection, GridNode} from '@react-types/grid'; import {Key} from '@react-types/shared'; -import {MultipleSelectionStateProps, SelectionManager, useMultipleSelectionState} from '@react-stately/selection'; +import {MultipleSelectionState, MultipleSelectionStateProps, SelectionManager, useMultipleSelectionState} from '@react-stately/selection'; import {useEffect, useMemo, useRef} from 'react'; export interface GridState> { @@ -17,7 +17,9 @@ export interface GridState> { export interface GridStateOptions> extends MultipleSelectionStateProps { collection: C, disabledKeys?: Iterable, - focusMode?: 'row' | 'cell' + focusMode?: 'row' | 'cell', + /** @private - do not use unless you know what you're doing. */ + UNSAFE_selectionState?: MultipleSelectionState } /** @@ -25,7 +27,8 @@ export interface GridStateOptions> extends Multip */ export function useGridState>(props: GridStateOptions): GridState { let {collection, focusMode} = props; - let selectionState = useMultipleSelectionState(props); + // eslint-disable-next-line react-hooks/rules-of-hooks + let selectionState = props.UNSAFE_selectionState || useMultipleSelectionState(props); let disabledKeys = useMemo(() => props.disabledKeys ? new Set(props.disabledKeys) : new Set() , [props.disabledKeys]); diff --git a/packages/@react-stately/table/src/useTableState.ts b/packages/@react-stately/table/src/useTableState.ts index 7d6dbbef713..ca2448702b1 100644 --- a/packages/@react-stately/table/src/useTableState.ts +++ b/packages/@react-stately/table/src/useTableState.ts @@ -13,7 +13,7 @@ import {GridState, useGridState} from '@react-stately/grid'; import {TableCollection as ITableCollection, TableBodyProps, TableHeaderProps} from '@react-types/table'; import {Key, Node, SelectionMode, Sortable, SortDescriptor, SortDirection} from '@react-types/shared'; -import {MultipleSelectionStateProps} from '@react-stately/selection'; +import {MultipleSelectionState, MultipleSelectionStateProps} from '@react-stately/selection'; import {ReactElement, useCallback, useMemo, useState} from 'react'; import {TableCollection} from './TableCollection'; import {useCollection} from '@react-stately/collections'; @@ -52,7 +52,9 @@ export interface TableStateProps extends MultipleSelectionStateProps, Sortabl /** Whether the row drag button should be displayed. * @private */ - showDragButtons?: boolean + showDragButtons?: boolean, + /** @private - do not use unless you know what you're doing. */ + UNSAFE_selectionState?: MultipleSelectionState } const OPPOSITE_SORT_DIRECTION = { diff --git a/packages/react-aria-components/src/Breadcrumbs.tsx b/packages/react-aria-components/src/Breadcrumbs.tsx index 01394840d18..197ec8498ff 100644 --- a/packages/react-aria-components/src/Breadcrumbs.tsx +++ b/packages/react-aria-components/src/Breadcrumbs.tsx @@ -10,13 +10,13 @@ * governing permissions and limitations under the License. */ import {AriaBreadcrumbsProps} from 'react-aria'; -import {Collection, Node} from 'react-stately'; -import {CollectionProps, CollectionRendererContext, createLeafComponent, useCollection} from './Collection'; +import {Collection, CollectionBuilder, CollectionProps, CollectionRendererContext, createLeafComponent} from './Collection'; import {ContextValue, forwardRefType, RenderProps, SlotProps, StyleProps, useContextProps, useRenderProps, useSlottedContext} from './utils'; import {filterDOMProps} from '@react-aria/utils'; import {Key} from '@react-types/shared'; import {LinkContext} from './Link'; -import React, {createContext, ForwardedRef, forwardRef, ReactNode, RefObject, useContext} from 'react'; +import {Node} from 'react-stately'; +import React, {createContext, ForwardedRef, forwardRef, ReactNode, useContext} from 'react'; export interface BreadcrumbsProps extends Omit, 'disabledKeys'>, AriaBreadcrumbsProps, StyleProps, SlotProps { /** Whether the breadcrumbs are disabled. */ @@ -29,36 +29,23 @@ export const BreadcrumbsContext = createContext(props: BreadcrumbsProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, BreadcrumbsContext); - let {portal, collection} = useCollection(props); - - // Render the portal first so that we have the collection by the time we render the DOM in SSR - return ( - <> - {portal} - - - ); -} - -interface BreadcrumbsInnerProps { - props: BreadcrumbsProps, - collection: Collection>, - breadcrumbsRef: RefObject -} - -function BreadcrumbsInner({props, collection, breadcrumbsRef: ref}: BreadcrumbsInnerProps) { let {CollectionRoot} = useContext(CollectionRendererContext); + return ( -
    - - - -
+ }> + {collection => ( +
    + + + +
+ )} +
); } diff --git a/packages/react-aria-components/src/Collection.tsx b/packages/react-aria-components/src/Collection.tsx index 9ab8098f883..aca86dd6c09 100644 --- a/packages/react-aria-components/src/Collection.tsx +++ b/packages/react-aria-components/src/Collection.tsx @@ -11,10 +11,10 @@ */ import {CollectionBase, DropTargetDelegate, Key, LayoutDelegate} from '@react-types/shared'; import {createPortal} from 'react-dom'; -import {forwardRefType, StyleProps} from './utils'; +import {forwardRefType, Hidden, StyleProps} from './utils'; import {Collection as ICollection, Node, SelectionBehavior, SelectionMode, SectionProps as SharedSectionProps} from 'react-stately'; import {mergeProps, useIsSSR} from 'react-aria'; -import React, {cloneElement, createContext, ForwardedRef, forwardRef, HTMLAttributes, JSX, ReactElement, ReactNode, RefObject, useCallback, useContext, useMemo, useRef} from 'react'; +import React, {cloneElement, createContext, ForwardedRef, forwardRef, HTMLAttributes, JSX, ReactElement, ReactNode, RefObject, useCallback, useContext, useMemo, useRef, useState} from 'react'; import {useLayoutEffect} from '@react-aria/utils'; import {useSyncExternalStore as useSyncExternalStoreShim} from 'use-sync-external-store/shim/index.js'; @@ -274,7 +274,7 @@ class BaseNode { * A mutable element node in the fake DOM tree. It owns an immutable * Collection Node which is copied on write. */ -export class ElementNode extends BaseNode { +class ElementNode extends BaseNode { nodeType = 8; // COMMENT_NODE (we'd use ELEMENT_NODE but React DevTools will fail to get its dimensions) node: NodeValue; private _index: number = 0; @@ -503,7 +503,7 @@ export class BaseCollection implements ICollection> { * A mutable Document in the fake DOM. It owns an immutable Collection instance, * which is lazily copied on write during updates. */ -export class Document = BaseCollection> extends BaseNode { +class Document = BaseCollection> extends BaseNode { nodeType = 11; // DOCUMENT_FRAGMENT_NODE ownerDocument = this; dirtyNodes: Set> = new Set(); @@ -709,16 +709,41 @@ export function useCollectionChildren(props: CachedChildrenOpt } const ShallowRenderContext = createContext(false); +const CollectionDocumentContext = createContext> | null>(null); -interface CollectionResult { - portal: ReactNode, - collection: C +export interface CollectionBuilderProps> { + content: ReactNode, + children: (collection: C) => ReactNode, + createCollection?: () => C } -export function useCollection>(props: CollectionProps, initialCollection?: C): CollectionResult { - let {collection, document} = useCollectionDocument(initialCollection); - let portal = useCollectionPortal(props, document); - return {portal, collection}; +export function CollectionBuilder>(props: CollectionBuilderProps) { + // If a document was provided above us, we're already in a hidden tree. Just render the content. + let doc = useContext(CollectionDocumentContext); + if (doc) { + return props.content; + } + + // Otherwise, render a hidden copy of the children so that we can build the collection before constructing the state. + // This should always come before the real DOM content so we have built the collection by the time it renders during SSR. + + // This is fine. CollectionDocumentContext never changes after mounting. + // eslint-disable-next-line react-hooks/rules-of-hooks + let {collection, document} = useCollectionDocument(props.createCollection); + return ( + <> + + + {props.content} + + + + + ); +} + +function CollectionInner({collection, render}) { + return render(collection); } interface CollectionDocumentResult> { @@ -747,10 +772,10 @@ const useSyncExternalStore = typeof React['useSyncExternalStore'] === 'function' ? React['useSyncExternalStore'] : useSyncExternalStoreFallback; -export function useCollectionDocument>(initialCollection?: C): CollectionDocumentResult { +function useCollectionDocument>(createCollection?: () => C): CollectionDocumentResult { // The document instance is mutable, and should never change between renders. // useSyncExternalStore is used to subscribe to updates, which vends immutable Collection objects. - let document = useMemo(() => new Document(initialCollection || new BaseCollection() as C), [initialCollection]); + let [document] = useState(() => new Document(createCollection?.() || new BaseCollection() as C)); let subscribe = useCallback((fn: () => void) => document.subscribe(fn), [document]); let getSnapshot = useCallback(() => { let collection = document.getCollection(); @@ -779,27 +804,6 @@ export function useCollectionDocument | null>(null); -export const CollectionDocumentContext = createContext> | null>(null); - -export function useCollectionPortal>(props: CollectionProps, document?: Document): ReactNode { - let ctx = useContext(CollectionDocumentContext); - let doc = document ?? ctx!; - let children = useCollectionChildren(props); - let wrappedChildren = useMemo(() => ( - - {children} - - ), [children]); - // During SSR, we render the content directly, and append nodes to the document during render. - // The collection children return null so that nothing is actually rendered into the HTML. - return useIsSSR() - ? {wrappedChildren} - : createPortal(wrappedChildren, doc as unknown as Element); -} - -export function CollectionPortal(props: CollectionProps) { - return <>{useCollectionPortal(props)}; -} export interface ItemRenderProps { /** @@ -919,7 +923,30 @@ export function Collection(props: CollectionProps): JSX.Ele let ctx = useContext(CollectionContext)!; props = mergeProps(ctx, props); props.dependencies = (ctx?.dependencies || []).concat(props.dependencies); - return <>{useCollectionChildren(props)}; + let children = useCollectionChildren(props); + + let doc = useContext(CollectionDocumentContext); + if (doc) { + return {children}; + } + + return <>{children}; +} + +function CollectionRoot({children}) { + let doc = useContext(CollectionDocumentContext); + let wrappedChildren = useMemo(() => ( + + + {children} + + + ), [children]); + // During SSR, we render the content directly, and append nodes to the document during render. + // The collection children return null so that nothing is actually rendered into the HTML. + return useIsSSR() + ? {wrappedChildren} + : createPortal(wrappedChildren, doc as unknown as Element); } export function createLeafComponent(type: string, render: (props: P, ref: ForwardedRef) => JSX.Element): (props: P & React.RefAttributes) => React.ReactElement | null; @@ -976,7 +1003,7 @@ export interface CollectionRenderer { CollectionBranch: React.ComponentType } -const DefaultCollectionRenderer: CollectionRenderer = { +export const DefaultCollectionRenderer: CollectionRenderer = { CollectionRoot({collection}) { return useCachedChildren({ items: collection, diff --git a/packages/react-aria-components/src/ComboBox.tsx b/packages/react-aria-components/src/ComboBox.tsx index 32b09b658e2..fc8bbd8ee08 100644 --- a/packages/react-aria-components/src/ComboBox.tsx +++ b/packages/react-aria-components/src/ComboBox.tsx @@ -12,8 +12,8 @@ import {AriaComboBoxProps, useComboBox, useFilter} from 'react-aria'; import {ButtonContext} from './Button'; import {Collection, ComboBoxState, Node, useComboBoxState} from 'react-stately'; -import {CollectionDocumentContext, useCollectionDocument} from './Collection'; -import {ContextValue, forwardRefType, Hidden, Provider, RACValidation, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot, useSlottedContext} from './utils'; +import {CollectionBuilder} from './Collection'; +import {ContextValue, forwardRefType, Provider, RACValidation, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot, useSlottedContext} from './utils'; import {FieldErrorContext} from './FieldError'; import {filterDOMProps, useResizeObserver} from '@react-aria/utils'; import {FormContext} from './Form'; @@ -67,35 +67,25 @@ export const ComboBoxStateContext = createContext | null>(nul function ComboBox(props: ComboBoxProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, ComboBoxContext); - let {collection, document} = useCollectionDocument(); let {children, isDisabled = false, isInvalid = false, isRequired = false} = props; - children = useMemo(() => ( - typeof children === 'function' - ? children({ - isOpen: false, - isDisabled, - isInvalid, - isRequired, - defaultChildren: null - }) - : children - ), [children, isDisabled, isInvalid, isRequired]); + let content = useMemo(() => ( + + {typeof children === 'function' + ? children({ + isOpen: false, + isDisabled, + isInvalid, + isRequired, + defaultChildren: null + }) + : children} + + ), [children, isDisabled, isInvalid, isRequired, props.items, props.defaultItems]); return ( - <> - {/* Render a hidden copy of the children so that we can build the collection even when the popover is not open. - * This should always come before the real DOM content so we have built the collection by the time it renders during SSR. */} - - - {children} - - - - + + {collection => } + ); } diff --git a/packages/react-aria-components/src/GridList.tsx b/packages/react-aria-components/src/GridList.tsx index de962dc883b..862eb82a15b 100644 --- a/packages/react-aria-components/src/GridList.tsx +++ b/packages/react-aria-components/src/GridList.tsx @@ -12,10 +12,10 @@ import {AriaGridListProps, DraggableItemResult, DragPreviewRenderer, DropIndicatorAria, DroppableCollectionResult, FocusScope, ListKeyboardDelegate, mergeProps, useCollator, useFocusRing, useGridList, useGridListItem, useGridListSelectionCheckbox, useHover, useLocale, useVisuallyHidden} from 'react-aria'; import {ButtonContext} from './Button'; import {CheckboxContext} from './RSPContexts'; -import {Collection, DraggableCollectionState, DroppableCollectionState, ListState, Node, SelectionBehavior, useListState} from 'react-stately'; -import {CollectionProps, CollectionRendererContext, createLeafComponent, ItemRenderProps, useCollection} from './Collection'; +import {Collection, CollectionBuilder, CollectionProps, CollectionRendererContext, createLeafComponent, ItemRenderProps} from './Collection'; import {ContextValue, DEFAULT_SLOT, forwardRefType, Provider, RenderProps, ScrollableProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps} from './utils'; import {DragAndDropContext, DragAndDropHooks, DropIndicator, DropIndicatorContext, DropIndicatorProps} from './useDragAndDrop'; +import {DraggableCollectionState, DroppableCollectionState, Collection as ICollection, ListState, Node, SelectionBehavior, useListState} from 'react-stately'; import {filterDOMProps, useObjectRef} from '@react-aria/utils'; import {HoverEvents, Key, LinkDOMProps} from '@react-types/shared'; import {ListStateContext} from './ListBox'; @@ -74,18 +74,17 @@ export const GridListContext = createContext, HT function GridList(props: GridListProps, ref: ForwardedRef) { // Render the portal first so that we have the collection by the time we render the DOM in SSR. [props, ref] = useContextProps(props, ref, GridListContext); - let {collection, portal} = useCollection(props); + return ( - <> - {portal} - - + }> + {collection => } + ); } interface GridListInnerProps { props: GridListProps, - collection: Collection>, + collection: ICollection>, gridListRef: RefObject } diff --git a/packages/react-aria-components/src/ListBox.tsx b/packages/react-aria-components/src/ListBox.tsx index 26b8b7de3e5..835d50e34e1 100644 --- a/packages/react-aria-components/src/ListBox.tsx +++ b/packages/react-aria-components/src/ListBox.tsx @@ -11,8 +11,8 @@ */ import {AriaListBoxOptions, AriaListBoxProps, DraggableItemResult, DragPreviewRenderer, DroppableCollectionResult, DroppableItemResult, FocusScope, ListKeyboardDelegate, mergeProps, useCollator, useFocusRing, useHover, useListBox, useListBoxSection, useLocale, useOption} from 'react-aria'; -import {CollectionDocumentContext, CollectionPortal, CollectionProps, CollectionRendererContext, createLeafComponent, ItemRenderProps, SectionContext, SectionProps, useCollection} from './Collection'; -import {ContextValue, forwardRefType, HiddenContext, Provider, RenderProps, ScrollableProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlot} from './utils'; +import {Collection, CollectionBuilder, CollectionProps, CollectionRendererContext, createLeafComponent, ItemRenderProps, SectionContext, SectionProps} from './Collection'; +import {ContextValue, forwardRefType, Provider, RenderProps, ScrollableProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlot} from './utils'; import {DragAndDropContext, DragAndDropHooks, DropIndicator, DropIndicatorContext, DropIndicatorProps} from './useDragAndDrop'; import {DraggableCollectionState, DroppableCollectionState, ListState, Node, Orientation, SelectionBehavior, useListState} from 'react-stately'; import {filterDOMProps, useObjectRef} from '@react-aria/utils'; @@ -79,9 +79,7 @@ export const ListStateContext = createContext | null>(null); function ListBox(props: ListBoxProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, ListBoxContext); - let isHidden = useContext(HiddenContext); let state = useContext(ListStateContext); - let document = useContext(CollectionDocumentContext); // The structure of ListBox is a bit strange because it needs to work inside other components like ComboBox and Select. // Those components render two copies of their children so that the collection can be built even when the popover is closed. @@ -89,27 +87,21 @@ function ListBox(props: ListBoxProps, ref: ForwardedRef; - } - if (state) { - return isHidden ? null : ; + return ; } - return ; + return ( + }> + {collection => } + + ); } -function StandaloneListBox({props, listBoxRef}) { - let {portal, collection} = useCollection(props); +function StandaloneListBox({props, listBoxRef, collection}) { props = {...props, collection, children: null, items: null}; let state = useListState(props); - return ( - <> - {portal} - - - ); + return ; } /** diff --git a/packages/react-aria-components/src/Menu.tsx b/packages/react-aria-components/src/Menu.tsx index 1841653a04d..b500934ef4b 100644 --- a/packages/react-aria-components/src/Menu.tsx +++ b/packages/react-aria-components/src/Menu.tsx @@ -12,8 +12,8 @@ import {AriaMenuProps, FocusScope, mergeProps, useFocusRing, useMenu, useMenuItem, useMenuSection, useMenuTrigger} from 'react-aria'; -import {BaseCollection, CollectionProps, CollectionRendererContext, createBranchComponent, createLeafComponent, ItemRenderProps, SectionContext, SectionProps, useCollection} from './Collection'; -import {MenuTriggerProps as BaseMenuTriggerProps, Node, TreeState, useMenuTriggerState, useTreeState} from 'react-stately'; +import {MenuTriggerProps as BaseMenuTriggerProps, Collection as ICollection, Node, TreeState, useMenuTriggerState, useTreeState} from 'react-stately'; +import {Collection, CollectionBuilder, CollectionProps, CollectionRendererContext, createBranchComponent, createLeafComponent, ItemRenderProps, SectionContext, SectionProps} from './Collection'; import {ContextValue, forwardRefType, Provider, RenderProps, ScrollableProps, SlotProps, StyleProps, useContextProps, useRenderProps, useSlot, useSlottedContext} from './utils'; import {filterDOMProps, useObjectRef, useResizeObserver} from '@react-aria/utils'; import {HeaderContext} from './Header'; @@ -148,20 +148,18 @@ export interface MenuProps extends Omit, 'children'>, Collec function Menu(props: MenuProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, MenuContext); - let {portal, collection} = useCollection(props); // Delay rendering the actual menu until we have the collection so that auto focus works properly. return ( - <> - {collection.size > 0 && } - {portal} - + }> + {collection => collection.size > 0 && } + ); } interface MenuInnerProps { props: MenuProps, - collection: BaseCollection, + collection: ICollection>, menuRef: RefObject } diff --git a/packages/react-aria-components/src/Select.tsx b/packages/react-aria-components/src/Select.tsx index 62d10f38bea..54791acfb1c 100644 --- a/packages/react-aria-components/src/Select.tsx +++ b/packages/react-aria-components/src/Select.tsx @@ -12,8 +12,9 @@ import {AriaSelectProps, HiddenSelect, useFocusRing, useLocalizedStringFormatter, useSelect} from 'react-aria'; import {ButtonContext} from './Button'; -import {CollectionDocumentContext, ItemRenderProps, useCollectionDocument} from './Collection'; -import {ContextValue, forwardRefType, Hidden, Provider, RACValidation, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot, useSlottedContext} from './utils'; +import {Collection, Node, SelectState, useSelectState} from 'react-stately'; +import {CollectionBuilder, ItemRenderProps} from './Collection'; +import {ContextValue, forwardRefType, Provider, RACValidation, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot, useSlottedContext} from './utils'; import {FieldErrorContext} from './FieldError'; import {filterDOMProps, useResizeObserver} from '@react-aria/utils'; import {FormContext} from './Form'; @@ -24,7 +25,6 @@ import {ListBoxContext, ListStateContext} from './ListBox'; import {OverlayTriggerStateContext} from './Dialog'; import {PopoverContext} from './Popover'; import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes, ReactNode, useCallback, useContext, useMemo, useRef, useState} from 'react'; -import {SelectState, useSelectState} from 'react-stately'; import {TextContext} from './Text'; export interface SelectRenderProps { @@ -67,9 +67,37 @@ export const SelectStateContext = createContext | null>(nul function Select(props: SelectProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, SelectContext); + let {children, isDisabled = false, isInvalid = false, isRequired = false} = props; + let content = useMemo(() => ( + typeof children === 'function' + ? children({ + isOpen: false, + isDisabled, + isInvalid, + isRequired, + isFocused: false, + isFocusVisible: false, + defaultChildren: null + }) + : children + ), [children, isDisabled, isInvalid, isRequired]); + + return ( + + {collection => } + + ); +} + +interface SelectInnerProps { + props: SelectProps, + selectRef: ForwardedRef, + collection: Collection> +} + +function SelectInner({props, selectRef: ref, collection}: SelectInnerProps) { let {validationBehavior: formValidationBehavior} = useSlottedContext(FormContext) || {}; let validationBehavior = props.validationBehavior ?? formValidationBehavior ?? 'native'; - let {collection, document} = useCollectionDocument(); let state = useSelectState({ ...props, collection, @@ -129,63 +157,49 @@ function Select(props: SelectProps, ref: ForwardedRef - {/* Render a hidden copy of the children so that we can build the collection even when the popover is not open. - * This should always come before the real DOM content so we have built the collection by the time it renders during SSR. */} - - - {renderProps.children} - - - -
- - - + +
+ + ); } diff --git a/packages/react-aria-components/src/Table.tsx b/packages/react-aria-components/src/Table.tsx index aa5ea5b07f9..053cc40b83a 100644 --- a/packages/react-aria-components/src/Table.tsx +++ b/packages/react-aria-components/src/Table.tsx @@ -1,11 +1,11 @@ import {AriaLabelingProps, HoverEvents, Key, LinkDOMProps} from '@react-types/shared'; -import {BaseCollection, CollectionContext, CollectionProps, CollectionRendererContext, createBranchComponent, createLeafComponent, ItemRenderProps, NodeValue, useCachedChildren, useCollection, useCollectionChildren} from './Collection'; +import {BaseCollection, Collection, CollectionBuilder, CollectionContext, CollectionProps, CollectionRendererContext, createBranchComponent, createLeafComponent, ItemRenderProps, NodeValue, useCachedChildren, useCollectionChildren} from './Collection'; import {buildHeaderRows, TableColumnResizeState} from '@react-stately/table'; import {ButtonContext} from './Button'; import {CheckboxContext} from './RSPContexts'; import {ColumnSize, ColumnStaticSize, TableCollection as ITableCollection, TableProps as SharedTableProps} from '@react-types/table'; import {ContextValue, DEFAULT_SLOT, DOMProps, Provider, RenderProps, ScrollableProps, SlotProps, StyleProps, StyleRenderProps, useContextProps, useRenderProps} from './utils'; -import {DisabledBehavior, DraggableCollectionState, DroppableCollectionState, Node, SelectionBehavior, SelectionMode, SortDirection, TableState, useTableColumnResizeState, useTableState} from 'react-stately'; +import {DisabledBehavior, DraggableCollectionState, DroppableCollectionState, MultipleSelectionState, Node, SelectionBehavior, SelectionMode, SortDirection, TableState, useMultipleSelectionState, useTableColumnResizeState, useTableState} from 'react-stately'; import {DragAndDropContext, DragAndDropHooks, DropIndicator, DropIndicatorContext, DropIndicatorProps} from './useDragAndDrop'; import {DraggableItemResult, DragPreviewRenderer, DropIndicatorAria, DroppableCollectionResult, FocusScope, ListKeyboardDelegate, mergeProps, useFocusRing, useHover, useLocale, useLocalizedStringFormatter, useTable, useTableCell, useTableColumnHeader, useTableColumnResize, useTableHeaderRow, useTableRow, useTableRowGroup, useTableSelectAllCheckbox, useTableSelectionCheckbox, useVisuallyHidden} from 'react-aria'; import {filterDOMProps, isScrollable, mergeRefs, useLayoutEffect, useObjectRef, useResizeObserver} from '@react-aria/utils'; @@ -317,14 +317,47 @@ export interface TableProps extends Omit, 'children'>, Sty function Table(props: TableProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, TableContext); + + // Separate selection state so we have access to it from collection components via useTableOptions. + let selectionState = useMultipleSelectionState(props); + let {selectionBehavior, selectionMode, disallowEmptySelection} = selectionState; + let hasDragHooks = !!props.dragAndDropHooks?.useDraggableCollectionState; + let ctx = useMemo(() => ({ + selectionBehavior: selectionMode === 'none' ? null : selectionBehavior, + selectionMode, + disallowEmptySelection, + allowsDragging: hasDragHooks + }), [selectionBehavior, selectionMode, disallowEmptySelection, hasDragHooks]); + + let content = ( + + + + ); + + return ( + new TableCollection()}> + {collection => } + + ); +} + +interface TableInnerProps { + props: TableProps, + forwardedRef: ForwardedRef, + selectionState: MultipleSelectionState, + collection: ITableCollection> +} + + +function TableInner({props, forwardedRef: ref, selectionState, collection}: TableInnerProps) { let tableContainerContext = useContext(ResizableTableContainerContext); ref = useObjectRef(useMemo(() => mergeRefs(ref, tableContainerContext?.tableRef), [ref, tableContainerContext?.tableRef])); - let initialCollection = useMemo(() => new TableCollection(), []); - let {portal, collection} = useCollection(props, initialCollection); let state = useTableState({ ...props, collection, - children: undefined + children: undefined, + UNSAFE_selectionState: selectionState }); let {isVirtualized, layoutDelegate, dropTargetDelegate: ctxDropTargetDelegate, CollectionRoot} = useContext(CollectionRendererContext); @@ -407,14 +440,6 @@ function Table(props: TableProps, ref: ForwardedRef) { let isListDraggable = !!(hasDragHooks && !dragState?.isDisabled); - let {selectionBehavior, selectionMode, disallowEmptySelection} = state.selectionManager; - let ctx = useMemo(() => ({ - selectionBehavior: selectionMode === 'none' ? null : selectionBehavior, - selectionMode, - disallowEmptySelection, - allowsDragging: hasDragHooks - }), [selectionBehavior, selectionMode, disallowEmptySelection, hasDragHooks]); - let style = renderProps.style; let layoutState: TableColumnResizeState | null = null; if (tableContainerContext) { @@ -433,36 +458,31 @@ function Table(props: TableProps, ref: ForwardedRef) { let ElementType = useElementType('table'); return ( - <> - - {portal} - - - - - - - - {dragPreview} - - + + + + + + + {dragPreview} + ); } diff --git a/packages/react-aria-components/src/Tabs.tsx b/packages/react-aria-components/src/Tabs.tsx index 3c3d272b235..b4eaecc9370 100644 --- a/packages/react-aria-components/src/Tabs.tsx +++ b/packages/react-aria-components/src/Tabs.tsx @@ -12,10 +12,10 @@ import {AriaLabelingProps, Key, LinkDOMProps} from '@react-types/shared'; import {AriaTabListProps, AriaTabPanelProps, mergeProps, Orientation, useFocusRing, useHover, useTab, useTabList, useTabPanel} from 'react-aria'; -import {Collection, Node, TabListState, useTabListState} from 'react-stately'; -import {CollectionDocumentContext, CollectionPortal, CollectionProps, CollectionRendererContext, createLeafComponent, useCollectionDocument} from './Collection'; -import {ContextValue, createHideableComponent, forwardRefType, Hidden, Provider, RenderProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlottedContext} from './utils'; +import {Collection, CollectionBuilder, CollectionProps, CollectionRendererContext, createLeafComponent} from './Collection'; +import {ContextValue, createHideableComponent, forwardRefType, Provider, RenderProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlottedContext} from './utils'; import {filterDOMProps, useObjectRef} from '@react-aria/utils'; +import {Collection as ICollection, Node, TabListState, useTabListState} from 'react-stately'; import React, {createContext, ForwardedRef, forwardRef, JSX, RefObject, useContext, useMemo} from 'react'; export interface TabsProps extends Omit, 'items' | 'children'>, RenderProps, SlotProps {} @@ -119,7 +119,6 @@ export const TabListStateContext = createContext | null>(nu function Tabs(props: TabsProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, TabsContext); - let {collection, document} = useCollectionDocument(); let {children, orientation = 'horizontal'} = props; children = useMemo(() => ( typeof children === 'function' @@ -128,22 +127,15 @@ function Tabs(props: TabsProps, ref: ForwardedRef) { ), [children, orientation]); return ( - <> - {/* Render a hidden copy of the children so that we can build the collection before constructing the state. - * This should always come before the real DOM content so we have built the collection by the time it renders during SSR. */} - - - {children} - - - - + + {collection => } + ); } interface TabsInnerProps { props: TabsProps, - collection: Collection>, + collection: ICollection>, tabsRef: RefObject } @@ -195,10 +187,10 @@ const _Tabs = /*#__PURE__*/ (forwardRef as forwardRefType)(Tabs); export {_Tabs as Tabs}; function TabList(props: TabListProps, ref: ForwardedRef): JSX.Element { - let document = useContext(CollectionDocumentContext); - return document - ? - : ; + let state = useContext(TabListStateContext); + return state + ? + : ; } interface TabListInnerProps { diff --git a/packages/react-aria-components/src/TagGroup.tsx b/packages/react-aria-components/src/TagGroup.tsx index c388c9b9f10..1152d2c2854 100644 --- a/packages/react-aria-components/src/TagGroup.tsx +++ b/packages/react-aria-components/src/TagGroup.tsx @@ -12,14 +12,14 @@ import {AriaTagGroupProps, useFocusRing, useHover, useTag, useTagGroup} from 'react-aria'; import {ButtonContext} from './Button'; -import {CollectionDocumentContext, CollectionProps, CollectionRendererContext, createLeafComponent, ItemRenderProps, useCollectionDocument, useCollectionPortal} from './Collection'; +import {Collection, CollectionBuilder, CollectionProps, CollectionRendererContext, createLeafComponent, ItemRenderProps} from './Collection'; import {ContextValue, DOMProps, forwardRefType, Provider, RenderProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlot} from './utils'; import {filterDOMProps, mergeProps, useObjectRef} from '@react-aria/utils'; import {HoverEvents, Key, LinkDOMProps} from '@react-types/shared'; import {LabelContext} from './Label'; import {ListState, Node, useListState} from 'react-stately'; import {ListStateContext} from './ListBox'; -import React, {createContext, ForwardedRef, forwardRef, ReactNode, useContext, useEffect, useRef} from 'react'; +import React, {createContext, ForwardedRef, forwardRef, JSX, ReactNode, useContext, useEffect, useRef} from 'react'; import {TextContext} from './Text'; export interface TagGroupProps extends Omit, 'children' | 'items' | 'label' | 'description' | 'errorMessage' | 'keyboardDelegate'>, DOMProps, SlotProps {} @@ -56,9 +56,22 @@ export const TagListContext = createContext, HTML function TagGroup(props: TagGroupProps, ref: ForwardedRef) { [props, ref] = useContextProps(props, ref, TagGroupContext); + return ( + + {collection => } + + ); +} + +interface TagGroupInnerProps { + props: TagGroupProps, + forwardedRef: ForwardedRef, + collection +} + +function TagGroupInner({props, forwardedRef: ref, collection}: TagGroupInnerProps) { let tagListRef = useRef(null); let [labelRef, label] = useSlot(); - let {collection, document} = useCollectionDocument(); let state = useListState({ ...props, children: undefined, @@ -91,7 +104,6 @@ function TagGroup(props: TagGroupProps, ref: ForwardedRef) { [LabelContext, {...labelProps, elementType: 'span', ref: labelRef}], [TagListContext, {...gridProps, ref: tagListRef}], [ListStateContext, state], - [CollectionDocumentContext, document], [TextContext, { slots: { description: descriptionProps, @@ -111,15 +123,11 @@ function TagGroup(props: TagGroupProps, ref: ForwardedRef) { const _TagGroup = /*#__PURE__*/ (forwardRef as forwardRefType)(TagGroup); export {_TagGroup as TagGroup}; -function TagList(props: TagListProps, forwardedRef: ForwardedRef) { - // Render the portal first so that we have the collection by the time we render the DOM in SSR. - let portal = useCollectionPortal(props); - return ( - <> - {portal} - - - ); +function TagList(props: TagListProps, ref: ForwardedRef): JSX.Element { + let state = useContext(ListStateContext); + return state + ? + : ; } interface TagListInnerProps { diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index 222ad23a3bd..a667c8b2d8a 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -11,9 +11,9 @@ */ import {AriaTreeGridListProps, useTreeGridList, useTreeGridListItem} from '@react-aria/tree'; -import {BaseCollection, CollectionProps, CollectionRendererContext, createBranchComponent, createLeafComponent, ItemRenderProps, NodeValue, useCachedChildren, useCollection} from './Collection'; import {ButtonContext} from './Button'; import {CheckboxContext} from './RSPContexts'; +import {Collection, CollectionBuilder, CollectionProps, CollectionRendererContext, createBranchComponent, createLeafComponent, ItemRenderProps, NodeValue, useCachedChildren} from './Collection'; import {ContextValue, DEFAULT_SLOT, forwardRefType, Provider, RenderProps, ScrollableProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps} from './utils'; import {DisabledBehavior, Expandable, HoverEvents, Key, LinkDOMProps} from '@react-types/shared'; import {filterDOMProps, useObjectRef} from '@react-aria/utils'; @@ -136,19 +136,17 @@ export const UNSTABLE_TreeStateContext = createContext | null>(nu function Tree(props: TreeProps, ref: ForwardedRef) { // Render the portal first so that we have the collection by the time we render the DOM in SSR. [props, ref] = useContextProps(props, ref, UNSTABLE_TreeContext); - let {collection, portal} = useCollection(props); return ( - <> - {portal} - - + }> + {collection => } + ); } interface TreeInnerProps { props: TreeProps, - collection: BaseCollection, + collection: ICollection, treeRef: RefObject } diff --git a/packages/react-aria-components/src/index.ts b/packages/react-aria-components/src/index.ts index 54f6a0bf9f3..87f9ac15ffb 100644 --- a/packages/react-aria-components/src/index.ts +++ b/packages/react-aria-components/src/index.ts @@ -42,7 +42,7 @@ export {Group, GroupContext} from './Group'; export {Header, HeaderContext} from './Header'; export {Heading} from './Heading'; export {Input, InputContext} from './Input'; -export {Section, Collection, CollectionRendererContext} from './Collection'; +export {Section, Collection, createLeafComponent as UNSTABLE_createLeafComponent, createBranchComponent as UNSTABLE_createBranchComponent, CollectionBuilder as UNSTABLE_CollectionBuilder, CollectionRendererContext as UNSTABLE_CollectionRendererContext, DefaultCollectionRenderer as UNSTABLE_DefaultCollectionRenderer} from './Collection'; export {Keyboard, KeyboardContext} from './Keyboard'; export {Label, LabelContext} from './Label'; export {Link, LinkContext} from './Link'; @@ -61,7 +61,7 @@ export {Separator, SeparatorContext} from './Separator'; export {Slider, SliderOutput, SliderTrack, SliderThumb, SliderContext, SliderOutputContext, SliderTrackContext, SliderStateContext} from './Slider'; export {Switch, SwitchContext} from './Switch'; export {Table, Row, Cell, Column, ColumnResizer, TableHeader, TableBody, TableContext, ResizableTableContainer, useTableOptions, TableStateContext, TableColumnResizeStateContext} from './Table'; -export {TableLayout} from './TableLayout'; +export {TableLayout as UNSTABLE_TableLayout} from './TableLayout'; export {Tabs, TabList, TabPanel, Tab, TabsContext, TabListStateContext} from './Tabs'; export {TagGroup, TagGroupContext, TagList, TagListContext, Tag} from './TagGroup'; export {Text, TextContext} from './Text'; @@ -72,11 +72,11 @@ export {Toolbar, ToolbarContext} from './Toolbar'; export {TooltipTrigger, Tooltip, TooltipTriggerStateContext, TooltipContext} from './Tooltip'; export {UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeContext, UNSTABLE_TreeItemContent, UNSTABLE_TreeStateContext} from './Tree'; export {useDragAndDrop, DropIndicator, DropIndicatorContext, DragAndDropContext} from './useDragAndDrop'; -export {Virtualizer} from './Virtualizer'; +export {Virtualizer as UNSTABLE_Virtualizer} from './Virtualizer'; export {DIRECTORY_DRAG_TYPE, isDirectoryDropItem, isFileDropItem, isTextDropItem, SSRProvider, RouterProvider, I18nProvider, useLocale} from 'react-aria'; export {FormValidationContext} from 'react-stately'; export {parseColor, getColorChannels} from '@react-stately/color'; -export {ListLayout} from '@react-stately/layout'; +export {ListLayout as UNSTABLE_ListLayout} from '@react-stately/layout'; export type {BreadcrumbsProps, BreadcrumbProps, BreadcrumbRenderProps} from './Breadcrumbs'; export type {ButtonProps, ButtonRenderProps} from './Button'; diff --git a/packages/react-aria-components/stories/GridList.stories.tsx b/packages/react-aria-components/stories/GridList.stories.tsx index c06ba282795..7c10dc07dcb 100644 --- a/packages/react-aria-components/stories/GridList.stories.tsx +++ b/packages/react-aria-components/stories/GridList.stories.tsx @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import {Button, Checkbox, CheckboxProps, GridList, GridListItem, GridListItemProps, ListLayout, Virtualizer} from 'react-aria-components'; +import {Button, Checkbox, CheckboxProps, GridList, GridListItem, GridListItemProps, UNSTABLE_ListLayout as ListLayout, UNSTABLE_Virtualizer as Virtualizer} from 'react-aria-components'; import {classNames} from '@react-spectrum/utils'; import {GridLayout} from '@react-spectrum/card'; import React, {useMemo} from 'react'; diff --git a/packages/react-aria-components/stories/ListBox.stories.tsx b/packages/react-aria-components/stories/ListBox.stories.tsx index e0d83ad89c5..b5fe4ec14a0 100644 --- a/packages/react-aria-components/stories/ListBox.stories.tsx +++ b/packages/react-aria-components/stories/ListBox.stories.tsx @@ -13,7 +13,7 @@ import {action} from '@storybook/addon-actions'; import {Collection} from '../src/Collection'; import {GridLayout} from '@react-spectrum/card'; -import {Header, ListBox, ListBoxItem, ListBoxProps, ListLayout, Section, Separator, Text, useDragAndDrop, Virtualizer} from 'react-aria-components'; +import {Header, ListBox, ListBoxItem, ListBoxProps, UNSTABLE_ListLayout as ListLayout, Section, Separator, Text, useDragAndDrop, UNSTABLE_Virtualizer as Virtualizer} from 'react-aria-components'; import {MyListBoxItem} from './utils'; import React, {useMemo} from 'react'; import {Size} from '@react-stately/virtualizer'; diff --git a/packages/react-aria-components/stories/Select.stories.tsx b/packages/react-aria-components/stories/Select.stories.tsx index 739749126b1..1917210e3d5 100644 --- a/packages/react-aria-components/stories/Select.stories.tsx +++ b/packages/react-aria-components/stories/Select.stories.tsx @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import {Button, Label, ListBox, ListLayout, OverlayArrow, Popover, Select, SelectValue, Virtualizer} from 'react-aria-components'; +import {Button, Label, ListBox, UNSTABLE_ListLayout as ListLayout, OverlayArrow, Popover, Select, SelectValue, UNSTABLE_Virtualizer as Virtualizer} from 'react-aria-components'; import {MyListBoxItem} from './utils'; import React from 'react'; import styles from '../example/index.css'; diff --git a/packages/react-aria-components/stories/Table.stories.tsx b/packages/react-aria-components/stories/Table.stories.tsx index 32180002ebc..24b1f4baf27 100644 --- a/packages/react-aria-components/stories/Table.stories.tsx +++ b/packages/react-aria-components/stories/Table.stories.tsx @@ -11,7 +11,7 @@ */ import {action} from '@storybook/addon-actions'; -import {Button, Cell, Checkbox, CheckboxProps, Column, ColumnProps, ColumnResizer, Dialog, DialogTrigger, Heading, Menu, MenuTrigger, Modal, ModalOverlay, Popover, ResizableTableContainer, Row, Table, TableBody, TableHeader, TableLayout, useDragAndDrop, Virtualizer} from 'react-aria-components'; +import {Button, Cell, Checkbox, CheckboxProps, Column, ColumnProps, ColumnResizer, Dialog, DialogTrigger, Heading, Menu, MenuTrigger, Modal, ModalOverlay, Popover, ResizableTableContainer, Row, Table, TableBody, TableHeader, UNSTABLE_TableLayout as TableLayout, useDragAndDrop, UNSTABLE_Virtualizer as Virtualizer} from 'react-aria-components'; import {isTextDropItem} from 'react-aria'; import {MyMenuItem} from './utils'; import React, {useMemo} from 'react'; diff --git a/packages/react-aria-components/stories/Tree.stories.tsx b/packages/react-aria-components/stories/Tree.stories.tsx index 285aae433da..5b7df3b29a5 100644 --- a/packages/react-aria-components/stories/Tree.stories.tsx +++ b/packages/react-aria-components/stories/Tree.stories.tsx @@ -11,7 +11,7 @@ */ import {action} from '@storybook/addon-actions'; -import {Button, Checkbox, CheckboxProps, Collection, ListLayout, Menu, MenuTrigger, Popover, Text, TreeItemProps, TreeProps, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeItemContent, Virtualizer} from 'react-aria-components'; +import {Button, Checkbox, CheckboxProps, Collection, UNSTABLE_ListLayout as ListLayout, Menu, MenuTrigger, Popover, Text, TreeItemProps, TreeProps, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeItemContent, UNSTABLE_Virtualizer as Virtualizer} from 'react-aria-components'; import {classNames} from '@react-spectrum/utils'; import {MyMenuItem} from './utils'; import React, {ReactNode, useMemo} from 'react'; diff --git a/packages/react-aria-components/test/Collection.test.js b/packages/react-aria-components/test/Collection.test.js index 9ebc4a8b6f6..9d4d757f868 100644 --- a/packages/react-aria-components/test/Collection.test.js +++ b/packages/react-aria-components/test/Collection.test.js @@ -1,19 +1,15 @@ +import {Collection, CollectionBuilder} from '../src/Collection'; import {ListBoxItem} from '../src/ListBox'; import React from 'react'; import {render} from '@testing-library/react'; -import {useCollection} from '../src/Collection'; - - -const CollectionTest = (props) => { - const result = useCollection(props); - props.spyCollection.current = result.collection; - return <>{result.portal}; -}; const renderItems = (items, spyCollection) => ( - - {items.map((item) => )} - + {items.map((item) => )}}> + {collection => { + spyCollection.current = collection; + return null; + }} + ); describe('Collection', () => { diff --git a/packages/react-aria-components/test/GridList.test.js b/packages/react-aria-components/test/GridList.test.js index 0d9489ec16e..96b93d2adde 100644 --- a/packages/react-aria-components/test/GridList.test.js +++ b/packages/react-aria-components/test/GridList.test.js @@ -18,10 +18,10 @@ import { GridList, GridListContext, GridListItem, - ListLayout, + UNSTABLE_ListLayout as ListLayout, RouterProvider, useDragAndDrop, - Virtualizer + UNSTABLE_Virtualizer as Virtualizer } from '../'; import React from 'react'; import userEvent from '@testing-library/user-event'; diff --git a/packages/react-aria-components/test/ListBox.test.js b/packages/react-aria-components/test/ListBox.test.js index 3ec3c5811c7..2397b054718 100644 --- a/packages/react-aria-components/test/ListBox.test.js +++ b/packages/react-aria-components/test/ListBox.test.js @@ -18,11 +18,13 @@ import { Header, Heading, ListBox, ListBoxContext, - ListBoxItem, ListLayout, Modal, + ListBoxItem, + UNSTABLE_ListLayout as ListLayout, + Modal, Section, Text, useDragAndDrop, - Virtualizer + UNSTABLE_Virtualizer as Virtualizer } from '../'; import React, {useState} from 'react'; import userEvent from '@testing-library/user-event'; diff --git a/packages/react-aria-components/test/Table.test.js b/packages/react-aria-components/test/Table.test.js index 21a371d0436..04f283fdcea 100644 --- a/packages/react-aria-components/test/Table.test.js +++ b/packages/react-aria-components/test/Table.test.js @@ -10,8 +10,8 @@ * governing permissions and limitations under the License. */ -import {act, fireEvent, mockClickDefault, pointerMap, render, within} from '@react-spectrum/test-utils-internal'; -import {Button, Cell, Checkbox, Collection, Column, ColumnResizer, DropIndicator, ResizableTableContainer, Row, Table, TableBody, TableHeader, TableLayout, useDragAndDrop, useTableOptions, Virtualizer} from '../'; +import {act, fireEvent, installPointerEvent, mockClickDefault, pointerMap, render, triggerLongPress, within} from '@react-spectrum/test-utils-internal'; +import {Button, Cell, Checkbox, Collection, Column, ColumnResizer, DropIndicator, ResizableTableContainer, Row, Table, TableBody, TableHeader, UNSTABLE_TableLayout as TableLayout, useDragAndDrop, useTableOptions, UNSTABLE_Virtualizer as Virtualizer} from '../'; import React, {useMemo, useState} from 'react'; import {resizingTests} from '@react-aria/table/test/tableResizingTests'; import {setInteractionModality} from '@react-aria/interactions'; @@ -1256,4 +1256,32 @@ describe('Table', () => { }); }); }); + + describe('with pointer events', () => { + installPointerEvent(); + + it('should show checkboxes on long press', async () => { + let {getAllByRole} = renderTable({ + tableProps: { + selectionMode: 'multiple', + selectionBehavior: 'replace', + onRowAction: () => {} + } + }); + + for (let row of getAllByRole('row')) { + let checkbox = within(row).queryByRole('checkbox'); + expect(checkbox).toBeNull(); + } + + let row = getAllByRole('row')[1]; + triggerLongPress(row); + expect(row).toHaveAttribute('aria-selected', 'true'); + + for (let row of getAllByRole('row')) { + let checkbox = within(row).queryByRole('checkbox'); + expect(checkbox).toBeInTheDocument(); + } + }); + }); }); diff --git a/packages/react-aria-components/test/TagGroup.ssr.test.js b/packages/react-aria-components/test/TagGroup.ssr.test.js index 485f36b017b..c35740bdab4 100644 --- a/packages/react-aria-components/test/TagGroup.ssr.test.js +++ b/packages/react-aria-components/test/TagGroup.ssr.test.js @@ -42,6 +42,7 @@ describe('TagGroup SSR', function () { // Assert that server rendered stuff into the HTML. let options = screen.getAllByRole('row'); expect(options.map(o => o.textContent)).toEqual(['Left', 'Middle', 'Right']); + expect(screen.getByRole('grid')).toBeInTheDocument(); }); // Assert that hydrated UI matches what we expect. diff --git a/packages/react-aria-components/test/Tree.test.tsx b/packages/react-aria-components/test/Tree.test.tsx index 264d80a7028..012100bb224 100644 --- a/packages/react-aria-components/test/Tree.test.tsx +++ b/packages/react-aria-components/test/Tree.test.tsx @@ -11,7 +11,7 @@ */ import {act, fireEvent, mockClickDefault, pointerMap, render, within} from '@react-spectrum/test-utils-internal'; -import {Button, Checkbox, Collection, ListLayout, Text, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeItemContent, Virtualizer} from '../'; +import {Button, Checkbox, Collection, UNSTABLE_ListLayout as ListLayout, Text, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeItemContent, UNSTABLE_Virtualizer as Virtualizer} from '../'; import React from 'react'; import userEvent from '@testing-library/user-event';