@@ -24,7 +24,6 @@ import React, {createContext, ForwardedRef, forwardRef, JSX, memo, ReactNode, us
24
24
import { SeparatorContext } from './Separator' ;
25
25
import { TextContext } from './Text' ;
26
26
import { UNSTABLE_InternalAutocompleteContext } from './Autocomplete' ;
27
- import { SelectionManager } from '@react-stately/selection' ;
28
27
29
28
export interface ListBoxRenderProps {
30
29
/**
@@ -79,22 +78,24 @@ export interface ListBoxProps<T> extends Omit<AriaListBoxProps<T>, 'children' |
79
78
}
80
79
81
80
export const ListBoxContext = createContext < ContextValue < ListBoxProps < any > , HTMLDivElement > > ( null ) ;
82
- export const ListStateContext = createContext < ListState < any > | null > ( null ) ;
81
+ export const ListStateContext = createContext < [ ListState < any > , Key | null ] | null > ( null ) ;
83
82
84
83
/**
85
84
* A listbox displays a list of options and allows a user to select one or more of them.
86
85
*/
87
86
export const ListBox = /*#__PURE__*/ ( forwardRef as forwardRefType ) ( function ListBox < T extends object > ( props : ListBoxProps < T > , ref : ForwardedRef < HTMLDivElement > ) {
88
87
[ props , ref ] = useContextProps ( props , ref , ListBoxContext ) ;
89
- let state = useContext ( ListStateContext ) ;
88
+ let context = useContext ( ListStateContext ) ;
90
89
91
90
// The structure of ListBox is a bit strange because it needs to work inside other components like ComboBox and Select.
92
91
// Those components render two copies of their children so that the collection can be built even when the popover is closed.
93
92
// The first copy sends a collection document via context which we render the collection portal into.
94
93
// The second copy sends a ListState object via context which we use to render the ListBox without rebuilding the state.
95
94
// Otherwise, we have a standalone ListBox, so we need to create a collection and state ourselves.
96
95
97
- if ( state ) {
96
+ if ( context ) {
97
+ let [ state ] = context ;
98
+
98
99
return < ListBoxInner state = { state } props = { props } listBoxRef = { ref } /> ;
99
100
}
100
101
@@ -249,7 +250,7 @@ function ListBoxInner<T extends object>({state: inputState, props, listBoxRef}:
249
250
< Provider
250
251
values = { [
251
252
[ ListBoxContext , props ] ,
252
- [ ListStateContext , state ] ,
253
+ [ ListStateContext , [ state , state . selectionManager . focusedKey ] ] ,
253
254
[ DragAndDropContext , useMemo ( ( ) => ( { dragAndDropHooks, dragState, dropState} ) , [ dragAndDropHooks , dragState , dropState ] ) ] ,
254
255
[ SeparatorContext , { elementType : 'div' } ] ,
255
256
[ DropIndicatorContext , { render : ListBoxDropIndicatorWrapper } ] ,
@@ -271,7 +272,7 @@ function ListBoxInner<T extends object>({state: inputState, props, listBoxRef}:
271
272
export interface ListBoxSectionProps < T > extends SectionProps < T > { }
272
273
273
274
function ListBoxSectionInner < T extends object > ( props : ListBoxSectionProps < T > , ref : ForwardedRef < HTMLElement > , section : Node < T > , className = 'react-aria-ListBoxSection' ) {
274
- let state = useContext ( ListStateContext ) ! ;
275
+ let [ state ] = useContext ( ListStateContext ) ! ;
275
276
let { dragAndDropHooks, dropState} = useContext ( DragAndDropContext ) ! ;
276
277
let { CollectionBranch} = useContext ( CollectionRendererContext ) ;
277
278
let [ headingRef , heading ] = useSlot ( ) ;
@@ -332,22 +333,24 @@ export interface ListBoxItemProps<T = object> extends RenderProps<ListBoxItemRen
332
333
*/
333
334
export const ListBoxItem = /*#__PURE__*/ createLeafComponent ( 'item' , function ListBoxItem < T extends object > ( props : ListBoxItemProps < T > , forwardedRef : ForwardedRef < HTMLDivElement > , item : Node < T > ) {
334
335
let ref = useObjectRef < any > ( forwardedRef ) ;
335
- let state = useContext ( ListStateContext ) ! ;
336
+ let [ state , focusedKey ] = useContext ( ListStateContext ) ! ;
337
+
338
+ // ListBoxItemInner is memoized so that a focus change does not re-render all items in the ListBox.
339
+ // The data-focused attribute tells React which list boxes are affected by a focus change. It does not actually
340
+ // get passed through to the component - it could be named anything, as long as it changes so React knows to re-render.
341
+ return < ListBoxItemInner data-focused = { item . key === focusedKey } props = { props } state = { state } passRef = { ref } item = { item } /> ;
342
+ } ) ;
336
343
344
+ const ListBoxItemInner = memo ( function ListBoxItemInner < T extends object > ( { props, item, state, passRef} : { props : ListBoxItemProps < T > , state : ListState < T > , item : Node < T > , passRef : React . MutableRefObject < any > } ) {
345
+ const ref = passRef ;
346
+
347
+ let { dragAndDropHooks, dragState, dropState} = useContext ( DragAndDropContext ) ! ;
337
348
let options = useOption (
338
349
{ key : item . key , 'aria-label' : props ?. [ 'aria-label' ] } ,
339
350
state ,
340
351
ref
341
352
) ;
342
353
343
- return < ListBoxItemInner selectionManager = { state . selectionManager } options = { options } focused = { item . key === state . focusedKey } passRef = { ref } props = { props } item = { item } />
344
- } ) ;
345
-
346
- const ListBoxItemInner = memo ( function ListBoxItemInner < T extends object > ( { props, item, selectionManager, options, focused, passRef} :
347
- { options : OptionAria , props : ListBoxItemProps < T > , focused : boolean , selectionManager : SelectionManager , item : Node < T > , passRef : React . MutableRefObject < any > } ) {
348
- const ref = passRef ;
349
-
350
- let { dragAndDropHooks, dragState, dropState} = useContext ( DragAndDropContext ) ! ;
351
354
let { optionProps, labelProps, descriptionProps, ...states } = options ;
352
355
353
356
let { hoverProps, isHovered} = useHover ( {
@@ -378,8 +381,8 @@ const ListBoxItemInner = memo(function ListBoxItemInner<T extends object>({props
378
381
values : {
379
382
...states ,
380
383
isHovered,
381
- selectionMode : selectionManager . selectionMode ,
382
- selectionBehavior : selectionManager . selectionBehavior ,
384
+ selectionMode : state . selectionManager . selectionMode ,
385
+ selectionBehavior : state . selectionManager . selectionBehavior ,
383
386
allowsDragging : ! ! dragState ,
384
387
isDragging,
385
388
isDropTarget : droppableItem ?. isDropTarget
@@ -408,7 +411,7 @@ const ListBoxItemInner = memo(function ListBoxItemInner<T extends object>({props
408
411
data-pressed = { states . isPressed || undefined }
409
412
data-dragging = { isDragging || undefined }
410
413
data-drop-target = { droppableItem ?. isDropTarget || undefined }
411
- data-selection-mode = { selectionManager . selectionMode === 'none' ? undefined : selectionManager . selectionMode } >
414
+ data-selection-mode = { state . selectionManager . selectionMode === 'none' ? undefined : state . selectionManager . selectionMode } >
412
415
< Provider
413
416
values = { [
414
417
[ TextContext , {
0 commit comments