@@ -15,12 +15,13 @@ import {AriaLabelingProps, DOMProps, KeyboardDelegate, Selection} from '@react-t
15
15
import { filterDOMProps , mergeProps , useId , useUpdateEffect } from '@react-aria/utils' ;
16
16
import { GridCollection } from '@react-types/grid' ;
17
17
import { GridKeyboardDelegate } from './GridKeyboardDelegate' ;
18
- import { gridKeyboardDelegates } from './utils' ;
18
+ import { gridMap } from './utils' ;
19
19
import { GridState } from '@react-stately/grid' ;
20
20
import { HTMLAttributes , Key , RefObject , useMemo , useRef } from 'react' ;
21
21
// @ts -ignore
22
22
import intlMessages from '../intl/*.json' ;
23
23
import { useCollator , useLocale , useMessageFormatter } from '@react-aria/i18n' ;
24
+ import { useHighlightSelectionDescription } from './useHighlightSelectionDescription' ;
24
25
import { useSelectableCollection } from '@react-aria/selection' ;
25
26
26
27
export interface GridProps extends DOMProps , AriaLabelingProps {
@@ -44,7 +45,11 @@ export interface GridProps extends DOMProps, AriaLabelingProps {
44
45
/**
45
46
* The ref attached to the scrollable body. Used to provided automatic scrolling on item focus for non-virtualized grids.
46
47
*/
47
- scrollRef ?: RefObject < HTMLElement >
48
+ scrollRef ?: RefObject < HTMLElement > ,
49
+ /** Handler that is called when a user performs an action on the row. */
50
+ onRowAction ?: ( key : Key ) => void ,
51
+ /** Handler that is called when a user performs an action on the cell. */
52
+ onCellAction ?: ( key : Key ) => void
48
53
}
49
54
50
55
export interface GridAria {
@@ -65,7 +70,9 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection<
65
70
keyboardDelegate,
66
71
focusMode,
67
72
getRowText = ( key ) => state . collection . getItem ( key ) ?. textValue ,
68
- scrollRef
73
+ scrollRef,
74
+ onRowAction,
75
+ onCellAction
69
76
} = props ;
70
77
let formatMessage = useMessageFormatter ( intlMessages ) ;
71
78
@@ -85,6 +92,7 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection<
85
92
collator,
86
93
focusMode
87
94
} ) , [ keyboardDelegate , state . collection , state . disabledKeys , ref , direction , collator , focusMode ] ) ;
95
+
88
96
let { collectionProps} = useSelectableCollection ( {
89
97
ref,
90
98
selectionManager : state . selectionManager ,
@@ -94,16 +102,25 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection<
94
102
} ) ;
95
103
96
104
let id = useId ( ) ;
97
- gridKeyboardDelegates . set ( state , delegate ) ;
105
+ gridMap . set ( state , { keyboardDelegate : delegate , actions : { onRowAction , onCellAction } } ) ;
98
106
99
- let domProps = filterDOMProps ( props , { labelable : true } ) ;
100
- let gridProps : HTMLAttributes < HTMLElement > = mergeProps ( domProps , {
101
- role : 'grid' ,
102
- id,
103
- 'aria-multiselectable' : state . selectionManager . selectionMode === 'multiple' ? 'true' : undefined ,
104
- ...collectionProps
107
+ let descriptionProps = useHighlightSelectionDescription ( {
108
+ selectionManager : state . selectionManager ,
109
+ hasItemActions : ! ! ( onRowAction || onCellAction )
105
110
} ) ;
106
111
112
+ let domProps = filterDOMProps ( props , { labelable : true } ) ;
113
+ let gridProps : HTMLAttributes < HTMLElement > = mergeProps (
114
+ domProps ,
115
+ {
116
+ role : 'grid' ,
117
+ id,
118
+ 'aria-multiselectable' : state . selectionManager . selectionMode === 'multiple' ? 'true' : undefined
119
+ } ,
120
+ collectionProps ,
121
+ descriptionProps
122
+ ) ;
123
+
107
124
if ( isVirtualized ) {
108
125
gridProps [ 'aria-rowcount' ] = state . collection . size ;
109
126
gridProps [ 'aria-colcount' ] = state . collection . columnCount ;
@@ -114,9 +131,7 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection<
114
131
let selection = state . selectionManager . rawSelection ;
115
132
let lastSelection = useRef ( selection ) ;
116
133
useUpdateEffect ( ( ) => {
117
- // Do not do this when using selectionBehavior = 'replace' to avoid selection announcements
118
- // every time the user presses the arrow keys.
119
- if ( ! state . selectionManager . isFocused || state . selectionManager . selectionBehavior === 'replace' ) {
134
+ if ( ! state . selectionManager . isFocused ) {
120
135
lastSelection . current = selection ;
121
136
122
137
return ;
@@ -126,8 +141,17 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection<
126
141
let removedKeys = diffSelection ( lastSelection . current , selection ) ;
127
142
128
143
// If adding or removing a single row from the selection, announce the name of that item.
144
+ let isReplace = state . selectionManager . selectionBehavior === 'replace' ;
129
145
let messages = [ ] ;
130
- if ( addedKeys . size === 1 && removedKeys . size === 0 ) {
146
+
147
+ if ( ( state . selectionManager . selectedKeys . size === 1 && isReplace ) ) {
148
+ if ( state . collection . getItem ( state . selectionManager . selectedKeys . keys ( ) . next ( ) . value ) ) {
149
+ let currentSelectionText = getRowText ( state . selectionManager . selectedKeys . keys ( ) . next ( ) . value ) ;
150
+ if ( currentSelectionText ) {
151
+ messages . push ( formatMessage ( 'selectedItem' , { item : currentSelectionText } ) ) ;
152
+ }
153
+ }
154
+ } else if ( addedKeys . size === 1 && removedKeys . size === 0 ) {
131
155
let addedText = getRowText ( addedKeys . keys ( ) . next ( ) . value ) ;
132
156
if ( addedText ) {
133
157
messages . push ( formatMessage ( 'selectedItem' , { item : addedText } ) ) ;
@@ -143,7 +167,7 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection<
143
167
144
168
// Announce how many items are selected, except when selecting the first item.
145
169
if ( state . selectionManager . selectionMode === 'multiple' ) {
146
- if ( messages . length === 0 || selection === 'all' || selection . size > 1 || lastSelection . current === 'all' || lastSelection . current . size > 1 ) {
170
+ if ( messages . length === 0 || selection === 'all' || selection . size > 1 || lastSelection . current === 'all' || lastSelection . current ? .size > 1 ) {
147
171
messages . push ( selection === 'all'
148
172
? formatMessage ( 'selectedAll' )
149
173
: formatMessage ( 'selectedCount' , { count : selection . size } )
0 commit comments