@react-aria/grid 3.11.1 → 3.12.1

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.
@@ -13,7 +13,7 @@
13
13
  import {Direction, DisabledBehavior, Key, KeyboardDelegate, LayoutDelegate, Node, Rect, RefObject, Size} from '@react-types/shared';
14
14
  import {DOMLayoutDelegate} from '@react-aria/selection';
15
15
  import {getChildNodes, getFirstItem, getLastItem, getNthItem} from '@react-stately/collections';
16
- import {GridCollection} from '@react-types/grid';
16
+ import {GridCollection, GridNode} from '@react-types/grid';
17
17
 
18
18
  export interface GridKeyboardDelegateOptions<C> {
19
19
  collection: C,
@@ -103,6 +103,35 @@ export class GridKeyboardDelegate<T, C extends GridCollection<T>> implements Key
103
103
  return null;
104
104
  }
105
105
 
106
+ protected getKeyForItemInRowByIndex(key: Key, index: number = 0): Key | null {
107
+ if (index < 0) {
108
+ return null;
109
+ }
110
+
111
+ let item = this.collection.getItem(key);
112
+ if (!item) {
113
+ return null;
114
+ }
115
+
116
+ let i = 0;
117
+ for (let child of getChildNodes(item, this.collection) as Iterable<GridNode<T>>) {
118
+ if (child.colSpan && child.colSpan + i > index) {
119
+ return child.key ?? null;
120
+ }
121
+
122
+ if (child.colSpan) {
123
+ i = i + child.colSpan - 1;
124
+ }
125
+
126
+ if (i === index) {
127
+ return child.key ?? null;
128
+ }
129
+
130
+ i++;
131
+ }
132
+ return null;
133
+ }
134
+
106
135
  getKeyBelow(fromKey: Key) {
107
136
  let key: Key | null = fromKey;
108
137
  let startItem = this.collection.getItem(key);
@@ -123,11 +152,8 @@ export class GridKeyboardDelegate<T, C extends GridCollection<T>> implements Key
123
152
  if (key != null) {
124
153
  // If focus was on a cell, focus the cell with the same index in the next row.
125
154
  if (this.isCell(startItem)) {
126
- let item = this.collection.getItem(key);
127
- if (!item) {
128
- return null;
129
- }
130
- return getNthItem(getChildNodes(item, this.collection), startItem.index ?? 0)?.key ?? null;
155
+ let startIndex = startItem.colIndex ? startItem.colIndex : startItem.index;
156
+ return this.getKeyForItemInRowByIndex(key, startIndex);
131
157
  }
132
158
 
133
159
  // Otherwise, focus the next row
@@ -158,11 +184,8 @@ export class GridKeyboardDelegate<T, C extends GridCollection<T>> implements Key
158
184
  if (key != null) {
159
185
  // If focus was on a cell, focus the cell with the same index in the previous row.
160
186
  if (this.isCell(startItem)) {
161
- let item = this.collection.getItem(key);
162
- if (!item) {
163
- return null;
164
- }
165
- return getNthItem(getChildNodes(item, this.collection), startItem.index ?? 0)?.key || null;
187
+ let startIndex = startItem.colIndex ? startItem.colIndex : startItem.index;
188
+ return this.getKeyForItemInRowByIndex(key, startIndex);
166
189
  }
167
190
 
168
191
  // Otherwise, focus the previous row
package/src/useGrid.ts CHANGED
@@ -26,6 +26,11 @@ import {useSelectableCollection} from '@react-aria/selection';
26
26
  export interface GridProps extends DOMProps, AriaLabelingProps {
27
27
  /** Whether the grid uses virtual scrolling. */
28
28
  isVirtualized?: boolean,
29
+ /**
30
+ * Whether typeahead navigation is disabled.
31
+ * @default false
32
+ */
33
+ disallowTypeAhead?: boolean,
29
34
  /**
30
35
  * An optional keyboard delegate implementation for type to select,
31
36
  * to override the default.
@@ -66,6 +71,7 @@ export interface GridAria {
66
71
  export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection<T>>, ref: RefObject<HTMLElement | null>): GridAria {
67
72
  let {
68
73
  isVirtualized,
74
+ disallowTypeAhead,
69
75
  keyboardDelegate,
70
76
  focusMode,
71
77
  scrollRef,
@@ -99,7 +105,8 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection<
99
105
  selectionManager: manager,
100
106
  keyboardDelegate: delegate,
101
107
  isVirtualized,
102
- scrollRef
108
+ scrollRef,
109
+ disallowTypeAhead
103
110
  });
104
111
 
105
112
  let id = useId(props.id);
@@ -11,12 +11,12 @@
11
11
  */
12
12
 
13
13
  import {DOMAttributes, FocusableElement, Key, RefObject} from '@react-types/shared';
14
- import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus';
14
+ import {focusSafely, isFocusVisible} from '@react-aria/interactions';
15
+ import {getFocusableTreeWalker} from '@react-aria/focus';
15
16
  import {getScrollParent, mergeProps, scrollIntoViewport} from '@react-aria/utils';
16
17
  import {GridCollection, GridNode} from '@react-types/grid';
17
18
  import {gridMap} from './utils';
18
19
  import {GridState} from '@react-stately/grid';
19
- import {isFocusVisible} from '@react-aria/interactions';
20
20
  import {KeyboardEvent as ReactKeyboardEvent, useRef} from 'react';
21
21
  import {useLocale} from '@react-aria/i18n';
22
22
  import {useSelectableItem} from '@react-aria/selection';
@@ -30,6 +30,8 @@ export 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
+ /** Indicates how many columns the data cell spans. */
34
+ colSpan?: number,
33
35
  /**
34
36
  * Handler that is called when a user performs an action on the cell.
35
37
  * Please use onCellAction at the collection level instead.
@@ -251,6 +253,9 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
251
253
  let gridCellProps: DOMAttributes = mergeProps(itemProps, {
252
254
  role: 'gridcell',
253
255
  onKeyDownCapture,
256
+ 'aria-colspan': node.colSpan,
257
+ 'aria-colindex': node.colIndex != null ? node.colIndex + 1 : undefined, // aria-colindex is 1-based
258
+ colSpan: isVirtualized ? undefined : node.colSpan,
254
259
  onFocus
255
260
  });
256
261
 
@@ -15,9 +15,9 @@ import {Collection, Key, Node, Selection} from '@react-types/shared';
15
15
  // @ts-ignore
16
16
  import intlMessages from '../intl/*.json';
17
17
  import {SelectionManager} from '@react-stately/selection';
18
+ import {useEffectEvent, useUpdateEffect} from '@react-aria/utils';
18
19
  import {useLocalizedStringFormatter} from '@react-aria/i18n';
19
20
  import {useRef} from 'react';
20
- import {useUpdateEffect} from '@react-aria/utils';
21
21
 
22
22
  export interface GridSelectionAnnouncementProps {
23
23
  /**
@@ -46,8 +46,8 @@ export function useGridSelectionAnnouncement<T>(props: GridSelectionAnnouncement
46
46
  // We do this using an ARIA live region.
47
47
  let selection = state.selectionManager.rawSelection;
48
48
  let lastSelection = useRef(selection);
49
- useUpdateEffect(() => {
50
- if (!state.selectionManager.isFocused) {
49
+ let announceSelectionChange = useEffectEvent(() => {
50
+ if (!state.selectionManager.isFocused || selection === lastSelection.current) {
51
51
  lastSelection.current = selection;
52
52
 
53
53
  return;
@@ -96,7 +96,17 @@ export function useGridSelectionAnnouncement<T>(props: GridSelectionAnnouncement
96
96
  }
97
97
 
98
98
  lastSelection.current = selection;
99
- }, [selection]);
99
+ });
100
+
101
+ useUpdateEffect(() => {
102
+ if (state.selectionManager.isFocused) {
103
+ announceSelectionChange();
104
+ } else {
105
+ // Wait a frame in case the collection is about to become focused (e.g. on mouse down).
106
+ let raf = requestAnimationFrame(announceSelectionChange);
107
+ return () => cancelAnimationFrame(raf);
108
+ }
109
+ }, [selection, state.selectionManager.isFocused]);
100
110
  }
101
111
 
102
112
  function diffSelection(a: Selection, b: Selection): Set<Key> {