@react-aria/grid 3.1.1-nightly.3022 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/useGrid.ts CHANGED
@@ -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})
@@ -12,7 +12,7 @@
12
12
 
13
13
  import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus';
14
14
  import {GridCollection} from '@react-types/grid';
15
- import {gridKeyboardDelegates} from './utils';
15
+ import {gridMap} from './utils';
16
16
  import {GridState} from '@react-stately/grid';
17
17
  import {HTMLAttributes, KeyboardEvent as ReactKeyboardEvent, RefObject} from 'react';
18
18
  import {isFocusVisible} from '@react-aria/interactions';
@@ -30,7 +30,11 @@ interface GridCellProps {
30
30
  focusMode?: 'child' | 'cell',
31
31
  /** Whether selection should occur on press up instead of press down. */
32
32
  shouldSelectOnPressUp?: boolean,
33
- /** Handler that is called when a user performs an action on the cell. */
33
+ /**
34
+ * Handler that is called when a user performs an action on the cell.
35
+ * Please use onCellAction at the collection level instead.
36
+ * @deprecated
37
+ **/
34
38
  onAction?: () => void
35
39
  }
36
40
 
@@ -56,7 +60,7 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
56
60
  } = props;
57
61
 
58
62
  let {direction} = useLocale();
59
- let keyboardDelegate = gridKeyboardDelegates.get(state);
63
+ let {keyboardDelegate, actions: {onCellAction}} = gridMap.get(state);
60
64
 
61
65
  // Handles focusing the cell. If there is a focusable child,
62
66
  // it is focused, otherwise the cell itself is focused.
@@ -84,7 +88,7 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
84
88
  isVirtualized,
85
89
  focus,
86
90
  shouldSelectOnPressUp,
87
- onAction
91
+ onAction: onCellAction ? () => onCellAction(node.key) : onAction
88
92
  });
89
93
 
90
94
  let onKeyDown = (e: ReactKeyboardEvent) => {
package/src/useGridRow.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import {GridCollection} from '@react-types/grid';
14
+ import {gridMap} from './utils';
14
15
  import {GridState} from '@react-stately/grid';
15
16
  import {HTMLAttributes, RefObject} from 'react';
16
17
  import {Node} from '@react-types/shared';
@@ -23,7 +24,11 @@ export interface GridRowProps<T> {
23
24
  isVirtualized?: boolean,
24
25
  /** Whether selection should occur on press up instead of press down. */
25
26
  shouldSelectOnPressUp?: boolean,
26
- /** Handler that is called when a user performs an action on the row. */
27
+ /**
28
+ * Handler that is called when a user performs an action on the row.
29
+ * Please use onCellAction at the collection level instead.
30
+ * @deprecated
31
+ **/
27
32
  onAction?: () => void
28
33
  }
29
34
 
@@ -47,13 +52,14 @@ export function useGridRow<T, C extends GridCollection<T>, S extends GridState<T
47
52
  onAction
48
53
  } = props;
49
54
 
55
+ let {actions: {onRowAction}} = gridMap.get(state);
50
56
  let {itemProps, isPressed} = useSelectableItem({
51
57
  selectionManager: state.selectionManager,
52
58
  key: node.key,
53
59
  ref,
54
60
  isVirtualized,
55
61
  shouldSelectOnPressUp,
56
- onAction
62
+ onAction: onRowAction ? () => onRowAction(node.key) : onAction
57
63
  });
58
64
 
59
65
  let isSelected = state.selectionManager.isSelected(node.key);
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright 2021 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {AriaLabelingProps} from '@react-types/shared';
14
+ // @ts-ignore
15
+ import intlMessages from '../intl/*.json';
16
+ import {MultipleSelectionManager} from '@react-stately/selection';
17
+ import {useDescription} from '@react-aria/utils';
18
+ import {useInteractionModality} from '@react-aria/interactions';
19
+ import {useMemo} from 'react';
20
+ import {useMessageFormatter} from '@react-aria/i18n';
21
+
22
+ interface UseHighlightSelectionDescriptionProps {
23
+ selectionManager: MultipleSelectionManager,
24
+ hasItemActions?: boolean
25
+ }
26
+
27
+ /**
28
+ * Computes the description for a grid selectable collection.
29
+ * @param props
30
+ */
31
+ export function useHighlightSelectionDescription(props: UseHighlightSelectionDescriptionProps): AriaLabelingProps {
32
+ let formatMessage = useMessageFormatter(intlMessages);
33
+ let modality = useInteractionModality();
34
+ // null is the default if the user hasn't interacted with the table at all yet or the rest of the page
35
+ let shouldLongPress = (modality === 'pointer' || modality === 'virtual' || modality == null) && 'ontouchstart' in window;
36
+
37
+ let interactionDescription = useMemo(() => {
38
+ let selectionMode = props.selectionManager.selectionMode;
39
+ let selectionBehavior = props.selectionManager.selectionBehavior;
40
+
41
+ let message = undefined;
42
+ if (shouldLongPress) {
43
+ message = formatMessage('longPressToSelect');
44
+ }
45
+
46
+ return selectionBehavior === 'replace' && selectionMode !== 'none' && props.hasItemActions ? message : undefined;
47
+ }, [props.selectionManager.selectionMode, props.selectionManager.selectionBehavior, props.hasItemActions, formatMessage, shouldLongPress]);
48
+
49
+ let descriptionProps = useDescription(interactionDescription);
50
+ return descriptionProps;
51
+ }
package/src/utils.ts CHANGED
@@ -12,7 +12,18 @@
12
12
 
13
13
  import type {GridCollection} from '@react-types/grid';
14
14
  import type {GridState} from '@react-stately/grid';
15
+ import {Key} from 'react';
15
16
  import type {KeyboardDelegate} from '@react-types/shared';
16
17
 
17
- // Used to share keyboard delegate between useGrid and useGridCell
18
- export const gridKeyboardDelegates = new WeakMap<GridState<unknown, GridCollection<unknown>>, KeyboardDelegate>();
18
+ interface GridMapShared {
19
+ keyboardDelegate: KeyboardDelegate,
20
+ actions: {
21
+ onRowAction: (key: Key) => void,
22
+ onCellAction: (key: Key) => void
23
+ }
24
+ }
25
+
26
+ // Used to share:
27
+ // keyboard delegate between useGrid and useGridCell
28
+ // onRowAction/onCellAction across hooks
29
+ export const gridMap = new WeakMap<GridState<unknown, GridCollection<unknown>>, GridMapShared>();