@octanejs/aria 0.0.46 → 0.0.48

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.
Files changed (201) hide show
  1. package/README.md +7 -6
  2. package/UPSTREAM.md +58 -69
  3. package/package.json +11 -11
  4. package/src/calendar/useCalendarGrid.tsx +64 -62
  5. package/src/collections/Document.ts +3 -0
  6. package/src/color/useColorArea.tsx +95 -69
  7. package/src/color/useColorWheel.tsx +38 -37
  8. package/src/combobox/{useComboBox.ts → useComboBox.tsx} +243 -258
  9. package/src/components/ComboBox.ts +35 -2
  10. package/src/components/Dialog.ts +12 -1
  11. package/src/components/DropZone.tsx +3 -2
  12. package/src/components/FileTrigger.tsx +1 -0
  13. package/src/components/GridList.ts +11 -2
  14. package/src/components/ListBox.ts +1 -1
  15. package/src/components/Menu.ts +1 -0
  16. package/src/components/Meter.ts +3 -1
  17. package/src/components/Popover.ts +20 -8
  18. package/src/components/PreviewTrigger.tsx +101 -0
  19. package/src/components/ProgressBar.ts +10 -2
  20. package/src/components/Select.ts +1 -1
  21. package/src/components/Table.ts +26 -15
  22. package/src/components/TokenField.tsx +395 -0
  23. package/src/components/Tooltip.ts +4 -6
  24. package/src/components/Tree.ts +14 -7
  25. package/src/components/Virtualizer.tsx +30 -14
  26. package/src/components/index.ts +22 -0
  27. package/src/components/utils.ts +1 -1
  28. package/src/datepicker/useDateField.tsx +2 -11
  29. package/src/datepicker/useDatePickerGroup.tsx +44 -33
  30. package/src/datepicker/useDateSegment.tsx +31 -30
  31. package/src/dialog/useDialog.ts +8 -1
  32. package/src/dnd/DragManager.tsx +5 -1
  33. package/src/dnd/useDrag.tsx +2 -1
  34. package/src/focus/FocusScope.ts +2 -2
  35. package/src/grid/useGrid.ts +3 -0
  36. package/src/grid/{useGridCell.ts → useGridCell.tsx} +128 -66
  37. package/src/grid/utils.ts +1 -0
  38. package/src/gridlist/{useGridListItem.ts → useGridListItem.tsx} +154 -112
  39. package/src/index.ts +22 -0
  40. package/src/interactions/createEventHandler.ts +11 -8
  41. package/src/interactions/createKeyboardShortcutHandler.tsx +218 -0
  42. package/src/interactions/textSelection.ts +3 -3
  43. package/src/interactions/useContextMenu.tsx +125 -0
  44. package/src/interactions/useFocusVisible.ts +4 -4
  45. package/src/interactions/useKeyboard.ts +50 -2
  46. package/src/interactions/useLongPress.ts +33 -22
  47. package/src/interactions/useMove.ts +6 -6
  48. package/src/interactions/usePress.ts +8 -0
  49. package/src/interactions/utils.ts +10 -10
  50. package/src/intl/combobox/fi-FI.json +2 -2
  51. package/src/intl/previewtrigger/ar-AE.json +3 -0
  52. package/src/intl/previewtrigger/bg-BG.json +3 -0
  53. package/src/intl/previewtrigger/cs-CZ.json +3 -0
  54. package/src/intl/previewtrigger/da-DK.json +3 -0
  55. package/src/intl/previewtrigger/de-DE.json +3 -0
  56. package/src/intl/previewtrigger/el-GR.json +3 -0
  57. package/src/intl/previewtrigger/en-US.json +3 -0
  58. package/src/intl/previewtrigger/es-ES.json +3 -0
  59. package/src/intl/previewtrigger/et-EE.json +3 -0
  60. package/src/intl/previewtrigger/fi-FI.json +3 -0
  61. package/src/intl/previewtrigger/fr-FR.json +3 -0
  62. package/src/intl/previewtrigger/he-IL.json +3 -0
  63. package/src/intl/previewtrigger/hr-HR.json +3 -0
  64. package/src/intl/previewtrigger/hu-HU.json +3 -0
  65. package/src/intl/previewtrigger/index.ts +73 -0
  66. package/src/intl/previewtrigger/it-IT.json +3 -0
  67. package/src/intl/previewtrigger/ja-JP.json +3 -0
  68. package/src/intl/previewtrigger/ko-KR.json +3 -0
  69. package/src/intl/previewtrigger/lt-LT.json +3 -0
  70. package/src/intl/previewtrigger/lv-LV.json +3 -0
  71. package/src/intl/previewtrigger/nb-NO.json +3 -0
  72. package/src/intl/previewtrigger/nl-NL.json +3 -0
  73. package/src/intl/previewtrigger/pl-PL.json +3 -0
  74. package/src/intl/previewtrigger/pt-BR.json +3 -0
  75. package/src/intl/previewtrigger/pt-PT.json +3 -0
  76. package/src/intl/previewtrigger/ro-RO.json +3 -0
  77. package/src/intl/previewtrigger/ru-RU.json +3 -0
  78. package/src/intl/previewtrigger/sk-SK.json +3 -0
  79. package/src/intl/previewtrigger/sl-SI.json +3 -0
  80. package/src/intl/previewtrigger/sr-SP.json +3 -0
  81. package/src/intl/previewtrigger/sv-SE.json +3 -0
  82. package/src/intl/previewtrigger/tr-TR.json +3 -0
  83. package/src/intl/previewtrigger/uk-UA.json +3 -0
  84. package/src/intl/previewtrigger/zh-CN.json +3 -0
  85. package/src/intl/previewtrigger/zh-TW.json +3 -0
  86. package/src/intl/table/ar-AE.json +3 -3
  87. package/src/intl/table/bg-BG.json +3 -3
  88. package/src/intl/table/cs-CZ.json +3 -3
  89. package/src/intl/table/da-DK.json +3 -3
  90. package/src/intl/table/de-DE.json +3 -3
  91. package/src/intl/table/el-GR.json +3 -3
  92. package/src/intl/table/es-ES.json +3 -3
  93. package/src/intl/table/et-EE.json +3 -3
  94. package/src/intl/table/fi-FI.json +3 -3
  95. package/src/intl/table/fr-FR.json +3 -3
  96. package/src/intl/table/he-IL.json +3 -3
  97. package/src/intl/table/hr-HR.json +3 -3
  98. package/src/intl/table/hu-HU.json +3 -3
  99. package/src/intl/table/it-IT.json +3 -3
  100. package/src/intl/table/ja-JP.json +3 -3
  101. package/src/intl/table/ko-KR.json +3 -3
  102. package/src/intl/table/lt-LT.json +3 -3
  103. package/src/intl/table/lv-LV.json +3 -3
  104. package/src/intl/table/nb-NO.json +3 -3
  105. package/src/intl/table/nl-NL.json +3 -3
  106. package/src/intl/table/pl-PL.json +3 -3
  107. package/src/intl/table/pt-BR.json +3 -3
  108. package/src/intl/table/pt-PT.json +3 -3
  109. package/src/intl/table/ro-RO.json +3 -3
  110. package/src/intl/table/ru-RU.json +3 -3
  111. package/src/intl/table/sk-SK.json +3 -3
  112. package/src/intl/table/sl-SI.json +3 -3
  113. package/src/intl/table/sr-SP.json +3 -3
  114. package/src/intl/table/sv-SE.json +3 -3
  115. package/src/intl/table/tr-TR.json +3 -3
  116. package/src/intl/table/uk-UA.json +3 -3
  117. package/src/intl/table/zh-CN.json +4 -4
  118. package/src/intl/table/zh-TW.json +3 -3
  119. package/src/intl/tree/es-ES.json +1 -1
  120. package/src/link/useLink.ts +2 -0
  121. package/src/menu/useMenuItem.ts +33 -36
  122. package/src/menu/useMenuTrigger.tsx +222 -0
  123. package/src/menu/{useSubmenuTrigger.ts → useSubmenuTrigger.tsx} +116 -151
  124. package/src/numberfield/{useNumberField.ts → useNumberField.tsx} +188 -238
  125. package/src/overlays/Overlay.ts +5 -1
  126. package/src/overlays/ariaHideOutside.ts +6 -8
  127. package/src/overlays/useCloseOnScroll.ts +7 -5
  128. package/src/overlays/{useOverlay.ts → useOverlay.tsx} +76 -83
  129. package/src/overlays/usePopover.ts +10 -5
  130. package/src/overlays/usePreventScroll.ts +4 -4
  131. package/src/progress/useProgressBar.ts +2 -1
  132. package/src/radio/{useRadioGroup.ts → useRadioGroup.tsx} +87 -79
  133. package/src/searchfield/useSearchField.tsx +152 -0
  134. package/src/select/useSelect.ts +20 -20
  135. package/src/selection/useSelectableCollection.tsx +755 -0
  136. package/src/selection/useSelectableItem.ts +48 -11
  137. package/src/slider/{useSliderThumb.ts → useSliderThumb.tsx} +120 -158
  138. package/src/spinbutton/useSpinButton.tsx +335 -0
  139. package/src/stately/combobox/useComboBoxState.ts +1 -1
  140. package/src/stately/grid/useGridState.ts +9 -10
  141. package/src/stately/index.ts +11 -0
  142. package/src/stately/menu/useMenuTriggerState.ts +1 -1
  143. package/src/stately/menu/useSubmenuTriggerState.ts +2 -0
  144. package/src/stately/overlays/useOverlayTriggerState.ts +12 -1
  145. package/src/stately/radio/useRadioGroupState.ts +5 -1
  146. package/src/stately/table/TableUtils.ts +15 -1
  147. package/src/stately/table/useTableState.ts +7 -0
  148. package/src/stately/tokenfield/TokenFieldValue.tsx +440 -0
  149. package/src/stately/tokenfield/useTokenFieldState.tsx +57 -0
  150. package/src/stately/tooltip/useTooltipTriggerState.ts +16 -7
  151. package/src/stately/virtualizer/Layout.tsx +2 -0
  152. package/src/stately/virtualizer/ScrollAnchor.tsx +338 -0
  153. package/src/stately/virtualizer/Virtualizer.tsx +52 -1
  154. package/src/stately/virtualizer/types.tsx +2 -0
  155. package/src/table/useTableCell.ts +12 -0
  156. package/src/table/useTableColumnHeader.ts +8 -1
  157. package/src/table/useTableColumnResize.tsx +359 -0
  158. package/src/tag/useTag.ts +22 -15
  159. package/src/textfield/useTextField.ts +2 -0
  160. package/src/tokenfield/useToken.tsx +64 -0
  161. package/src/tokenfield/useTokenField.tsx +849 -0
  162. package/src/tooltip/usePreviewTrigger.tsx +266 -0
  163. package/src/tooltip/useSafeArea.tsx +171 -0
  164. package/src/upstream-exports/react-aria/mergeRefs.tsx +3 -0
  165. package/src/upstream-exports/react-aria/usePreviewTrigger.tsx +18 -0
  166. package/src/upstream-exports/react-aria/useTokenField.tsx +22 -0
  167. package/src/upstream-exports/react-stately/private/collections/getChildNodes.tsx +8 -0
  168. package/src/upstream-exports/react-stately/private/collections/getItemCount.tsx +2 -0
  169. package/src/upstream-exports/react-stately/private/grid/GridCollection.tsx +7 -0
  170. package/src/upstream-exports/react-stately/private/grid/useGridState.tsx +6 -0
  171. package/src/upstream-exports/react-stately/private/utils/number.tsx +2 -0
  172. package/src/upstream-exports/react-stately/useComboBoxState.tsx +23 -0
  173. package/src/upstream-exports/react-stately/useListState.tsx +15 -0
  174. package/src/upstream-exports/react-stately/useMenuTriggerState.tsx +26 -0
  175. package/src/upstream-exports/react-stately/useMultipleSelectionState.tsx +21 -0
  176. package/src/upstream-exports/react-stately/useNumberFieldState.tsx +20 -0
  177. package/src/upstream-exports/react-stately/useOverlayTriggerState.tsx +20 -0
  178. package/src/upstream-exports/react-stately/useRadioGroupState.tsx +16 -0
  179. package/src/upstream-exports/react-stately/useSearchFieldState.tsx +19 -0
  180. package/src/upstream-exports/react-stately/useSliderState.tsx +20 -0
  181. package/src/upstream-exports/react-stately/useTableState.tsx +45 -0
  182. package/src/upstream-exports/react-stately/useTokenFieldState.tsx +25 -0
  183. package/src/upstream-exports/react-stately/useTooltipTriggerState.tsx +20 -0
  184. package/src/upstream-exports/react-stately/useTreeState.tsx +14 -0
  185. package/src/utils/domHelpers.ts +74 -17
  186. package/src/utils/isVirtualEvent.ts +2 -1
  187. package/src/utils/openLink.ts +8 -3
  188. package/src/utils/platform.ts +7 -3
  189. package/src/utils/scrollIntoView.ts +4 -4
  190. package/src/utils/shadowdom/DOMFunctions.ts +42 -2
  191. package/src/utils/useId.ts +11 -2
  192. package/src/utils/useLoadMoreSentinel.ts +8 -3
  193. package/src/utils/useViewportSize.ts +3 -3
  194. package/src/virtualizer/ScrollView.tsx +13 -4
  195. package/src/virtualizer/VirtualizerItem.tsx +4 -1
  196. package/src/virtualizer/useVirtualizerItem.tsx +43 -3
  197. package/src/menu/useMenuTrigger.ts +0 -189
  198. package/src/searchfield/useSearchField.ts +0 -156
  199. package/src/selection/useSelectableCollection.ts +0 -750
  200. package/src/spinbutton/useSpinButton.ts +0 -380
  201. package/src/table/useTableColumnResize.ts +0 -374
@@ -1,14 +1,20 @@
1
- // Ported from react-aria (source: .react-spectrum/packages/react-aria/src/grid/useGridCell.ts).
2
- // octane adaptations:
3
- // - Handlers receive NATIVE events (there is no synthetic layer): keydown handlers take
4
- // the native KeyboardEvent, and the ArrowUp/ArrowDown/edge re-dispatch builds
5
- // `new KeyboardEvent(e.type, e)` — upstream's `e.nativeEvent` IS the event here.
6
- // `onKeyDownCapture` maps to the capture phase natively in octane, matching
7
- // upstream's React capture prop.
8
- // - `GridState`/`IGridCollection`/`GridNode` come from the ported stately grid hooks;
9
- // `DOMAttributes` is a local structural prop-bag alias.
10
- // - Public-hook slot threading (splitSlot/subSlot).
11
- import type { FocusableElement, Key, RefObject } from '@react-types/shared';
1
+ /** @jsxImportSource octane */
2
+ type DOMAttributes = Record<string, any>;
3
+ // Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-aria/src/grid/useGridCell.ts).
4
+ /*
5
+ * Copyright 2020 Adobe. All rights reserved.
6
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License. You may obtain a copy
8
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software distributed under
11
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
12
+ * OF ANY KIND, either express or implied. See the License for the specific language
13
+ * governing permissions and limitations under the License.
14
+ */
15
+
16
+ import { FocusableElement, Key, RefObject } from '@react-types/shared';
17
+ import { withSlot } from 'octane';
12
18
  import { focusSafely } from '../interactions/focusSafely';
13
19
  import {
14
20
  getActiveElement,
@@ -17,23 +23,21 @@ import {
17
23
  nodeContains,
18
24
  } from '../utils/shadowdom/DOMFunctions';
19
25
  import { getFocusableTreeWalker } from '../focus/FocusScope';
26
+ import { getOwnerDocument } from '../utils/domHelpers';
20
27
  import { getScrollParent } from '../utils/getScrollParent';
21
- import type { IGridCollection as GridCollection, GridNode } from '../stately/grid/GridCollection';
28
+ import {
29
+ IGridCollection as GridCollection,
30
+ GridNode,
31
+ } from '../upstream-exports/react-stately/private/grid/GridCollection';
22
32
  import { gridMap } from './utils';
23
- import type { GridState } from '../stately/grid/useGridState';
33
+ import { GridState } from '../upstream-exports/react-stately/private/grid/useGridState';
24
34
  import { isFocusVisible } from '../interactions/useFocusVisible';
25
35
  import { mergeProps } from '../utils/mergeProps';
26
- import { useRef } from 'octane';
36
+ import { KeyboardEvent as ReactKeyboardEvent, useRef } from '../compat/react';
27
37
  import { scrollIntoViewport } from '../utils/scrollIntoView';
28
38
  import { useLocale } from '../i18n/I18nProvider';
29
39
  import { useSelectableItem } from '../selection/useSelectableItem';
30
40
 
31
- import { S, splitSlot, subSlot } from '../internal';
32
-
33
- // octane adaptation: minimal structural DOMAttributes (upstream's drags React's synthetic
34
- // handler types along).
35
- type DOMAttributes = Record<string, any>;
36
-
37
41
  export interface GridCellProps {
38
42
  /**
39
43
  * An object representing the grid cell. Contains all the relevant information that makes up the
@@ -44,9 +48,16 @@ export interface GridCellProps {
44
48
  isVirtualized?: boolean;
45
49
  /**
46
50
  * Whether the cell or its first focusable child element should be focused when the grid cell is
47
- * focused.
51
+ * focused. Defaults to 'child' in arrow keyboard navigation mode and 'cell' in
52
+ * tab keyboard navigation mode.
48
53
  */
49
54
  focusMode?: 'child' | 'cell';
55
+ /**
56
+ * Whether the cell should support arrow key navigation even when the containing collection uses
57
+ * tab keyboard navigation. Allows users to navigate between rows and cells with arrow keys while
58
+ * focus is on an interactive child element within the cell.
59
+ */
60
+ allowsArrowNavigation?: boolean;
50
61
  /** Whether selection should occur on press up instead of press down. */
51
62
  shouldSelectOnPressUp?: boolean;
52
63
  /** Indicates how many columns the data cell spans. */
@@ -77,32 +88,32 @@ export function useGridCell<T, C extends GridCollection<T>>(
77
88
  props: GridCellProps,
78
89
  state: GridState<T, C>,
79
90
  ref: RefObject<FocusableElement | null>,
80
- ): GridCellAria;
81
- // Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
82
- export function useGridCell<T, C extends GridCollection<T>>(
83
- props: GridCellProps,
84
- state: GridState<T, C>,
85
- ref: RefObject<FocusableElement | null>,
86
- slot: symbol | undefined,
87
- ): GridCellAria;
88
- export function useGridCell(...args: any[]): GridCellAria {
89
- const [user, slotArg] = splitSlot(args);
90
- const slot = slotArg ?? S('useGridCell');
91
- const props = user[0] as GridCellProps;
92
- const state = user[1] as GridState<any, GridCollection<any>>;
93
- const ref = user[2] as RefObject<FocusableElement | null>;
94
-
95
- let { node, isVirtualized, focusMode = 'child', shouldSelectOnPressUp, onAction } = props;
96
-
97
- let { direction } = useLocale(subSlot(slot, 'locale'));
91
+ _slot?: symbol,
92
+ ): GridCellAria {
93
+ if (_slot !== undefined) {
94
+ return withSlot(_slot, useGridCell<T, C>, props, state, ref);
95
+ }
96
+
97
+ let {
98
+ node,
99
+ isVirtualized,
100
+ focusMode: focusModeProp,
101
+ allowsArrowNavigation,
102
+ shouldSelectOnPressUp,
103
+ onAction,
104
+ } = props;
105
+
106
+ let { direction } = useLocale();
98
107
  let {
99
108
  keyboardDelegate,
100
109
  actions: { onCellAction },
110
+ keyboardNavigationBehavior,
101
111
  } = gridMap.get(state)!;
112
+ let focusMode = focusModeProp ?? (keyboardNavigationBehavior === 'tab' ? 'cell' : 'child');
102
113
 
103
114
  // We need to track the key of the item at the time it was last focused so that we force
104
115
  // focus to go to the item when the DOM node is reused for a different item in a virtualizer.
105
- let keyWhenFocused = useRef<Key | null>(null, subSlot(slot, 'keyWhenFocused'));
116
+ let keyWhenFocused = useRef<Key | null>(null);
106
117
 
107
118
  // Handles focusing the cell. If there is a focusable child,
108
119
  // it is focused, otherwise the cell itself is focused.
@@ -111,7 +122,10 @@ export function useGridCell(...args: any[]): GridCellAria {
111
122
  let treeWalker = getFocusableTreeWalker(ref.current);
112
123
  if (focusMode === 'child') {
113
124
  // If focus is already on a focusable child within the cell, early return so we don't shift focus
114
- if (isFocusWithin(ref.current) && ref.current !== getActiveElement()) {
125
+ if (
126
+ isFocusWithin(ref.current) &&
127
+ ref.current !== getActiveElement(getOwnerDocument(ref.current))
128
+ ) {
115
129
  return;
116
130
  }
117
131
 
@@ -134,24 +148,21 @@ export function useGridCell(...args: any[]): GridCellAria {
134
148
  }
135
149
  };
136
150
 
137
- let { itemProps, isPressed } = useSelectableItem(
138
- {
139
- selectionManager: state.selectionManager,
140
- key: node.key,
141
- ref,
142
- isVirtualized,
143
- focus,
144
- shouldSelectOnPressUp,
145
- onAction: onCellAction ? () => onCellAction(node.key) : onAction,
146
- isDisabled: state.collection.size === 0,
147
- },
148
- subSlot(slot, 'selectableItem'),
149
- );
150
-
151
- let onKeyDownCapture = (e: KeyboardEvent) => {
152
- let activeElement = getActiveElement();
151
+ let { itemProps, isPressed } = useSelectableItem({
152
+ selectionManager: state.selectionManager,
153
+ key: node.key,
154
+ ref,
155
+ isVirtualized,
156
+ focus,
157
+ shouldSelectOnPressUp,
158
+ onAction: onCellAction ? () => onCellAction(node.key) : onAction,
159
+ isDisabled: state.collection.size === 0,
160
+ });
161
+
162
+ let onKeyDownCapture = (e: ReactKeyboardEvent) => {
163
+ let activeElement = getActiveElement(getOwnerDocument(ref.current));
153
164
  if (
154
- !nodeContains(e.currentTarget as Element, getEventTarget(e) as Element) ||
165
+ !nodeContains(e.currentTarget as Node, getEventTarget(e) as Element) ||
155
166
  state.isKeyboardNavigationDisabled ||
156
167
  !ref.current ||
157
168
  !activeElement
@@ -191,7 +202,6 @@ export function useGridCell(...args: any[]): GridCellAria {
191
202
  // We prevent the capturing event from reaching children of the cell, e.g. pickers.
192
203
  // We want arrow keys to navigate to the next cell instead. We need to re-dispatch
193
204
  // the event from a higher parent so it still bubbles and gets handled by useSelectableCollection.
194
- // octane adaptation: the native event IS `e.nativeEvent`.
195
205
  ref.current.parentElement?.dispatchEvent(new KeyboardEvent(e.type, e));
196
206
  break;
197
207
  }
@@ -232,7 +242,6 @@ export function useGridCell(...args: any[]): GridCellAria {
232
242
  // We prevent the capturing event from reaching children of the cell, e.g. pickers.
233
243
  // We want arrow keys to navigate to the next cell instead. We need to re-dispatch
234
244
  // the event from a higher parent so it still bubbles and gets handled by useSelectableCollection.
235
- // octane adaptation: the native event IS `e.nativeEvent`.
236
245
  ref.current.parentElement?.dispatchEvent(new KeyboardEvent(e.type, e));
237
246
  break;
238
247
  }
@@ -260,13 +269,47 @@ export function useGridCell(...args: any[]): GridCellAria {
260
269
  if (!e.altKey && nodeContains(ref.current, getEventTarget(e) as Element)) {
261
270
  e.stopPropagation();
262
271
  e.preventDefault();
263
- // octane adaptation: the native event IS `e.nativeEvent`.
264
272
  ref.current.parentElement?.dispatchEvent(new KeyboardEvent(e.type, e));
265
273
  }
266
274
  break;
267
275
  }
268
276
  };
269
277
 
278
+ let onKeyDown = (e: ReactKeyboardEvent) => {
279
+ let activeElement = getActiveElement(getOwnerDocument(ref.current));
280
+ if (
281
+ !nodeContains(e.currentTarget as Node, getEventTarget(e) as Element) ||
282
+ state.isKeyboardNavigationDisabled ||
283
+ !ref.current ||
284
+ !activeElement
285
+ ) {
286
+ return;
287
+ }
288
+
289
+ if (keyboardNavigationBehavior === 'tab') {
290
+ if (getEventTarget(e) !== ref.current && e.key !== 'Tab') {
291
+ e.stopPropagation();
292
+ return;
293
+ }
294
+ }
295
+
296
+ switch (e.key) {
297
+ case 'Tab': {
298
+ if (keyboardNavigationBehavior === 'tab') {
299
+ // If there is another focusable element within this item, stop propagation so the tab key
300
+ // is handled by the browser and not by useSelectableCollection (which would take us out of the list).
301
+ let walker = getFocusableTreeWalker(ref.current, { tabbable: true });
302
+ walker.currentNode = activeElement;
303
+ let next = e.shiftKey ? walker.previousNode() : walker.nextNode();
304
+
305
+ if (next) {
306
+ e.stopPropagation();
307
+ }
308
+ }
309
+ }
310
+ }
311
+ };
312
+
270
313
  // Grid cells can have focusable elements inside them. In this case, focus should
271
314
  // be marshalled to that element rather than focusing the cell itself.
272
315
  let onFocus = (e: FocusEvent) => {
@@ -284,22 +327,41 @@ export function useGridCell(...args: any[]): GridCellAria {
284
327
  return;
285
328
  }
286
329
 
287
- // If the cell itself is focused, wait a frame so that focus finishes propagatating
330
+ // if focus goes back to cell from child, make sure we don't refocus the cell if we are in focusMode=child
331
+ // since that would be a focus trap
332
+ if (
333
+ focusMode === 'child' &&
334
+ e.relatedTarget &&
335
+ nodeContains(ref.current, e.relatedTarget as Element)
336
+ ) {
337
+ return;
338
+ }
339
+
340
+ // If the cell itself is focused, wait a frame so that focus finishes propagating
288
341
  // up to the tree, and move focus to a focusable child if possible.
289
342
  requestAnimationFrame(() => {
290
- if (focusMode === 'child' && getActiveElement() === ref.current) {
343
+ if (
344
+ focusMode === 'child' &&
345
+ getActiveElement(getOwnerDocument(ref.current)) === ref.current
346
+ ) {
291
347
  focus();
292
348
  }
293
349
  });
294
350
  };
295
351
 
352
+ // oxlint-disable-next-line react/react-compiler
296
353
  let gridCellProps: DOMAttributes = mergeProps(itemProps, {
297
354
  role: 'gridcell',
298
- onKeyDownCapture,
355
+ onKeyDownCapture:
356
+ keyboardNavigationBehavior !== 'tab' || allowsArrowNavigation ? onKeyDownCapture : undefined,
357
+ onKeyDown: keyboardNavigationBehavior === 'tab' ? onKeyDown : undefined,
299
358
  'aria-colspan': node.colSpan,
300
359
  'aria-colindex': node.colIndex != null ? node.colIndex + 1 : undefined, // aria-colindex is 1-based
301
360
  colSpan: isVirtualized ? undefined : node.colSpan,
302
361
  onFocus,
362
+ // make sure shift tabbing from a child of a cell doesnt move focus back to cell if focusMode="child" and in tab nav
363
+ // consistent with arrow nav and focusMode="child" since you can't go back to the cell there either
364
+ ...(focusMode === 'child' && keyboardNavigationBehavior === 'tab' ? { tabIndex: -1 } : {}),
303
365
  });
304
366
 
305
367
  if (isVirtualized) {
@@ -315,8 +377,8 @@ export function useGridCell(...args: any[]): GridCellAria {
315
377
  gridCellProps.tabIndex != null &&
316
378
  gridCellProps.onPointerDown == null
317
379
  ) {
318
- gridCellProps.onPointerDown = (e: PointerEvent) => {
319
- let el = e.currentTarget as Element;
380
+ gridCellProps.onPointerDown = (e: import('octane').PointerEvent<HTMLElement>) => {
381
+ let el = e.currentTarget;
320
382
  let tabindex = el.getAttribute('tabindex');
321
383
  el.removeAttribute('tabindex');
322
384
  requestAnimationFrame(() => {
package/src/grid/utils.ts CHANGED
@@ -13,6 +13,7 @@ interface GridMapShared {
13
13
  onCellAction?: (key: Key) => void;
14
14
  };
15
15
  shouldSelectOnPressUp?: boolean;
16
+ keyboardNavigationBehavior?: 'arrow' | 'tab';
16
17
  }
17
18
 
18
19
  // Used to share: