@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,23 +1,20 @@
1
- // Ported from react-aria (source: .react-spectrum/packages/react-aria/src/gridlist/useGridListItem.ts).
2
- // octane adaptations:
3
- // - Handlers receive NATIVE events (there is no synthetic layer): keydown handlers take
4
- // the native KeyboardEvent, `e.nativeEvent` reads collapse to the event itself (the
5
- // ArrowUp/ArrowDown re-dispatch builds `new KeyboardEvent(e.type, e)` — WebIDL
6
- // dictionary conversion reads the inherited native-event accessors), and upstream's
7
- // synthetic `e.isPropagationStopped()` reads the native `cancelBubble` flag (same
8
- // source of truth the ported interactions/utils shim uses). `onKeyDownCapture` maps to
9
- // the capture phase natively in octane, matching upstream's React capture prop.
10
- // - `ListState`/`TreeState` come from the ported stately hooks; `HTMLAttributes` /
11
- // `DOMAttributes` collapse to a structural prop bag.
12
- // - Public-hook slot threading (splitSlot/subSlot).
1
+ /** @jsxImportSource octane */
2
+ type DOMAttributes = Record<string, any>;
3
+ // Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-aria/src/gridlist/useGridListItem.ts).
4
+ /*
5
+ * Copyright 2022 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
+
13
16
  import { chain } from '../utils/chain';
14
- import type {
15
- Collection,
16
- FocusableElement,
17
- Key,
18
- RefObject,
19
- Node as RSNode,
20
- } from '@react-types/shared';
17
+ import { Collection, FocusableElement, Key, RefObject, Node as RSNode } from '@react-types/shared';
21
18
  import { focusSafely } from '../interactions/focusSafely';
22
19
  import {
23
20
  getActiveElement,
@@ -26,26 +23,21 @@ import {
26
23
  nodeContains,
27
24
  } from '../utils/shadowdom/DOMFunctions';
28
25
  import { getFocusableTreeWalker } from '../focus/FocusScope';
26
+ import { getOwnerDocument } from '../utils/domHelpers';
29
27
  import { getRowId, listMap } from './utils';
30
28
  import { getScrollParent } from '../utils/getScrollParent';
31
- import { useRef } from 'octane';
29
+ import { HTMLAttributes, KeyboardEvent as ReactKeyboardEvent, useRef } from '../compat/react';
30
+ import { withSlot } from 'octane';
32
31
  import { isFocusVisible } from '../interactions/useFocusVisible';
33
- import { isTabbable } from '../utils/isFocusable';
34
- import type { ListState } from '../stately/list/useListState';
32
+ import type { ListState } from '../upstream-exports/react-stately/useListState';
35
33
  import { mergeProps } from '../utils/mergeProps';
36
34
  import { scrollIntoViewport } from '../utils/scrollIntoView';
37
35
  import { SelectableItemStates, useSelectableItem } from '../selection/useSelectableItem';
38
- import type { TreeState } from '../stately/tree/useTreeState';
36
+ import type { TreeState } from '../upstream-exports/react-stately/useTreeState';
39
37
  import { useLocale } from '../i18n/I18nProvider';
40
38
  import { useSlotId } from '../utils/useId';
41
39
  import { useSyntheticLinkProps } from '../utils/openLink';
42
40
 
43
- import { S, splitSlot, subSlot } from '../internal';
44
-
45
- // octane adaptation: minimal structural DOMAttributes (upstream's drags React's synthetic
46
- // handler types along).
47
- type DOMAttributes = Record<string, any>;
48
-
49
41
  export interface AriaGridListItemOptions {
50
42
  /**
51
43
  * An object representing the list item. Contains all the relevant information that makes up the
@@ -58,6 +50,19 @@ export interface AriaGridListItemOptions {
58
50
  shouldSelectOnPressUp?: boolean;
59
51
  /** Whether this item has children, even if not loaded yet. */
60
52
  hasChildItems?: boolean;
53
+ /**
54
+ * Whether the row or its first focusable child element should be focused when the row is
55
+ * focused.
56
+ *
57
+ * @default 'row'
58
+ */
59
+ focusMode?: 'child' | 'row';
60
+ /**
61
+ * Whether the row should support arrow key navigation even when the containing collection uses
62
+ * tab keyboard navigation. Allows users to navigate between rows with arrow keys while
63
+ * focus is on an interactive child element within the row.
64
+ */
65
+ allowsArrowNavigation?: boolean;
61
66
  }
62
67
 
63
68
  export interface GridListItemAria extends SelectableItemStates {
@@ -91,46 +96,58 @@ export function useGridListItem<T>(
91
96
  props: AriaGridListItemOptions,
92
97
  state: ListState<T> | TreeState<T>,
93
98
  ref: RefObject<FocusableElement | null>,
94
- ): GridListItemAria;
95
- // Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
96
- export function useGridListItem<T>(
97
- props: AriaGridListItemOptions,
98
- state: ListState<T> | TreeState<T>,
99
- ref: RefObject<FocusableElement | null>,
100
- slot: symbol | undefined,
101
- ): GridListItemAria;
102
- export function useGridListItem(...args: any[]): GridListItemAria {
103
- const [user, slotArg] = splitSlot(args);
104
- const slot = slotArg ?? S('useGridListItem');
105
- const props = user[0] as AriaGridListItemOptions;
106
- const state = user[1] as ListState<any> | TreeState<any>;
107
- const ref = user[2] as RefObject<FocusableElement | null>;
99
+ _slot?: symbol,
100
+ ): GridListItemAria {
101
+ if (_slot !== undefined) {
102
+ return withSlot(_slot, useGridListItem<T>, props, state, ref);
103
+ }
108
104
 
109
105
  // Copied from useGridCell + some modifications to make it not so grid specific
110
- let { node, isVirtualized } = props;
106
+ let { node, isVirtualized, focusMode = 'row', allowsArrowNavigation } = props;
111
107
 
112
108
  // let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/gridlist');
113
- let { direction } = useLocale(subSlot(slot, 'locale'));
109
+ let { direction } = useLocale();
114
110
  let { onAction, linkBehavior, keyboardNavigationBehavior, shouldSelectOnPressUp } =
115
111
  listMap.get(state)!;
116
- let descriptionId = useSlotId(undefined, subSlot(slot, 'descriptionId'));
112
+ let descriptionId = useSlotId();
117
113
 
118
114
  // We need to track the key of the item at the time it was last focused so that we force
119
115
  // focus to go to the item when the DOM node is reused for a different item in a virtualizer.
120
- let keyWhenFocused = useRef<Key | null>(null, subSlot(slot, 'keyWhenFocused'));
116
+ let keyWhenFocused = useRef<Key | null>(null);
121
117
  let focus = () => {
118
+ if (ref.current === null) {
119
+ return;
120
+ }
121
+
122
+ if (focusMode === 'child') {
123
+ // If focus is already on a focusable child within the row, early return so we don't shift focus
124
+ if (
125
+ isFocusWithin(ref.current) &&
126
+ ref.current !== getActiveElement(getOwnerDocument(ref.current))
127
+ ) {
128
+ return;
129
+ }
130
+
131
+ let treeWalker = getFocusableTreeWalker(ref.current, { tabbable: true });
132
+ let focusable = treeWalker.firstChild() as FocusableElement;
133
+ if (focusable) {
134
+ focusSafely(focusable);
135
+ scrollIntoViewport(focusable, { containingElement: getScrollParent(focusable) });
136
+ return;
137
+ }
138
+ }
139
+
122
140
  // Don't shift focus to the row if the active element is a element within the row already
123
141
  // (e.g. clicking on a row button)
124
142
  if (
125
- ref.current !== null &&
126
- ((keyWhenFocused.current != null && node.key !== keyWhenFocused.current) ||
127
- !isFocusWithin(ref.current))
143
+ (keyWhenFocused.current != null && node.key !== keyWhenFocused.current) ||
144
+ !isFocusWithin(ref.current)
128
145
  ) {
129
146
  focusSafely(ref.current);
130
147
  }
131
148
  };
132
149
 
133
- let treeGridRowProps: DOMAttributes = {};
150
+ let treeGridRowProps: HTMLAttributes<HTMLElement> = {};
134
151
  let hasChildRows = props.hasChildItems;
135
152
  let hasLink = state.selectionManager.isLink(node.key);
136
153
  if (node != null && 'expandedKeys' in state) {
@@ -175,27 +192,24 @@ export function useGridListItem(...args: any[]): GridListItemAria {
175
192
  };
176
193
  }
177
194
 
178
- let { itemProps, ...itemStates } = useSelectableItem(
179
- {
180
- selectionManager: state.selectionManager,
181
- key: node.key,
182
- ref,
183
- isVirtualized,
184
- shouldSelectOnPressUp: props.shouldSelectOnPressUp || shouldSelectOnPressUp,
185
- onAction:
186
- onAction || node.props?.onAction
187
- ? chain(node.props?.onAction, onAction ? () => onAction(node.key) : undefined)
188
- : undefined,
189
- focus,
190
- linkBehavior,
191
- },
192
- subSlot(slot, 'selectableItem'),
193
- );
195
+ let { itemProps, ...itemStates } = useSelectableItem({
196
+ selectionManager: state.selectionManager,
197
+ key: node.key,
198
+ ref,
199
+ isVirtualized,
200
+ shouldSelectOnPressUp: props.shouldSelectOnPressUp || shouldSelectOnPressUp,
201
+ onAction:
202
+ onAction || node.props?.onAction
203
+ ? chain(node.props?.onAction, onAction ? () => onAction(node.key) : undefined)
204
+ : undefined,
205
+ focus,
206
+ linkBehavior,
207
+ });
194
208
 
195
- let onKeyDownCapture = (e: KeyboardEvent) => {
196
- let activeElement = getActiveElement();
209
+ let onKeyDownCapture = (e: ReactKeyboardEvent) => {
210
+ let activeElement = getActiveElement(getOwnerDocument(ref.current));
197
211
  if (
198
- !nodeContains(e.currentTarget as Element, getEventTarget(e) as Element) ||
212
+ !nodeContains(e.currentTarget as Node, getEventTarget(e) as Element) ||
199
213
  !ref.current ||
200
214
  !activeElement
201
215
  ) {
@@ -206,7 +220,16 @@ export function useGridListItem(...args: any[]): GridListItemAria {
206
220
  walker.currentNode = activeElement;
207
221
 
208
222
  if (
209
- handleTreeExpansionKeys(e, state, node, hasChildRows, direction, activeElement, ref.current)
223
+ handleTreeExpansionKeys(
224
+ e,
225
+ state,
226
+ node,
227
+ hasChildRows,
228
+ direction,
229
+ activeElement,
230
+ ref.current,
231
+ allowsArrowNavigation,
232
+ )
210
233
  ) {
211
234
  return;
212
235
  }
@@ -235,6 +258,7 @@ export function useGridListItem(...args: any[]): GridListItemAria {
235
258
  } else {
236
259
  walker.currentNode = ref.current;
237
260
  let lastElement = last(walker);
261
+ // oxlint-disable-next-line max-depth
238
262
  if (lastElement) {
239
263
  focusSafely(lastElement);
240
264
  scrollIntoViewport(lastElement, {
@@ -267,6 +291,7 @@ export function useGridListItem(...args: any[]): GridListItemAria {
267
291
  } else {
268
292
  walker.currentNode = ref.current;
269
293
  let lastElement = last(walker);
294
+ // oxlint-disable-next-line max-depth
270
295
  if (lastElement) {
271
296
  focusSafely(lastElement);
272
297
  scrollIntoViewport(lastElement, {
@@ -286,7 +311,6 @@ export function useGridListItem(...args: any[]): GridListItemAria {
286
311
  if (!e.altKey && nodeContains(ref.current, getEventTarget(e) as Element)) {
287
312
  e.stopPropagation();
288
313
  e.preventDefault();
289
- // octane adaptation: the native event IS `e.nativeEvent`.
290
314
  ref.current.parentElement?.dispatchEvent(new KeyboardEvent(e.type, e));
291
315
  }
292
316
  break;
@@ -307,12 +331,33 @@ export function useGridListItem(...args: any[]): GridListItemAria {
307
331
  }
308
332
  return;
309
333
  }
334
+
335
+ // if focus goes back to cell from child, make sure we don't refocus the cell if we are in focusMode=child
336
+ // since that would be a focus trap
337
+ if (
338
+ focusMode === 'child' &&
339
+ e.relatedTarget &&
340
+ nodeContains(ref.current, e.relatedTarget as Element)
341
+ ) {
342
+ return;
343
+ }
344
+
345
+ // If the cell itself is focused, wait a frame so that focus finishes propagating
346
+ // up to the tree, and move focus to a focusable child if possible.
347
+ requestAnimationFrame(() => {
348
+ if (
349
+ focusMode === 'child' &&
350
+ getActiveElement(getOwnerDocument(ref.current)) === ref.current
351
+ ) {
352
+ focus();
353
+ }
354
+ });
310
355
  };
311
356
 
312
- let onKeyDown = (e: KeyboardEvent) => {
313
- let activeElement = getActiveElement();
357
+ let onKeyDown = (e: ReactKeyboardEvent) => {
358
+ let activeElement = getActiveElement(getOwnerDocument(ref.current));
314
359
  if (
315
- !nodeContains(e.currentTarget as Element, getEventTarget(e) as Element) ||
360
+ !nodeContains(e.currentTarget as Node, getEventTarget(e) as Element) ||
316
361
  !ref.current ||
317
362
  !activeElement
318
363
  ) {
@@ -329,7 +374,16 @@ export function useGridListItem(...args: any[]): GridListItemAria {
329
374
  }
330
375
 
331
376
  if (
332
- handleTreeExpansionKeys(e, state, node, hasChildRows, direction, activeElement, ref.current)
377
+ handleTreeExpansionKeys(
378
+ e,
379
+ state,
380
+ node,
381
+ hasChildRows,
382
+ direction,
383
+ activeElement,
384
+ ref.current,
385
+ allowsArrowNavigation,
386
+ )
333
387
  ) {
334
388
  return;
335
389
  }
@@ -364,56 +418,41 @@ export function useGridListItem(...args: any[]): GridListItemAria {
364
418
  // });
365
419
  // }
366
420
 
421
+ // oxlint-disable-next-line react/react-compiler
367
422
  let rowProps: DOMAttributes = mergeProps(itemProps, linkProps, {
368
423
  role: 'row',
369
- onKeyDownCapture: keyboardNavigationBehavior === 'arrow' ? onKeyDownCapture : undefined,
424
+ onKeyDownCapture:
425
+ keyboardNavigationBehavior === 'arrow' || allowsArrowNavigation
426
+ ? onKeyDownCapture
427
+ : undefined,
370
428
  onFocus,
371
429
  // 'aria-label': [(node.textValue || undefined), rowAnnouncement].filter(Boolean).join(', '),
372
- 'aria-label': (node as any)['aria-label'] || node.textValue || undefined,
430
+ 'aria-label': node['aria-label'] || node.textValue || undefined,
373
431
  'aria-selected': state.selectionManager.canSelectItem(node.key)
374
432
  ? state.selectionManager.isSelected(node.key)
375
433
  : undefined,
376
434
  'aria-disabled': state.selectionManager.isDisabled(node.key) || undefined,
377
435
  'aria-labelledby':
378
- descriptionId && ((node as any)['aria-label'] || node.textValue)
436
+ descriptionId && (node['aria-label'] || node.textValue)
379
437
  ? `${getRowId(state, node.key)} ${descriptionId}`
380
438
  : undefined,
381
439
  id: getRowId(state, node.key),
382
440
  });
383
441
 
384
- // we need to guard against space/enter triggering selection/row link via usePress (from itemProps) so check if propagation
385
- // is stopped. this also fixes space not working in a textfield in a tree parent row
442
+ if (focusMode === 'child' && allowsArrowNavigation && keyboardNavigationBehavior === 'tab') {
443
+ rowProps.tabIndex = -1;
444
+ }
445
+
446
+ // Keep Space/Enter on a child control from reaching the row's usePress handler. With native
447
+ // events, stopPropagation() marks cancelBubble rather than exposing isPropagationStopped().
386
448
  let baseOnKeyDown = rowProps.onKeyDown;
387
- rowProps.onKeyDown = (e: KeyboardEvent) => {
388
- onKeyDown(e);
389
- // octane adaptation: synthetic isPropagationStopped() → the native cancelBubble flag.
390
- if (!(e as any).cancelBubble) {
449
+ rowProps.onKeyDown = (e: ReactKeyboardEvent<FocusableElement>) => {
450
+ onKeyDown(e as ReactKeyboardEvent);
451
+ if (!e.cancelBubble) {
391
452
  baseOnKeyDown?.(e);
392
453
  }
393
454
  };
394
455
 
395
- // guard against presses triggering row selecition when they happen on elements within the row
396
- // am currently assuming if it is tabbable it is interactive, but maybe can use a different kind of check
397
- let baseOnPointerDown = rowProps.onPointerDown;
398
- rowProps.onPointerDown = (e: PointerEvent) => {
399
- let target = getEventTarget(e) as Element | null;
400
- if (target && target !== ref.current && isTabbable(target)) {
401
- e.stopPropagation();
402
- return;
403
- }
404
- baseOnPointerDown?.(e);
405
- };
406
-
407
- let baseOnMouseDown = rowProps.onMouseDown;
408
- rowProps.onMouseDown = (e: MouseEvent) => {
409
- let target = getEventTarget(e) as Element | null;
410
- if (target && target !== ref.current && isTabbable(target)) {
411
- e.stopPropagation();
412
- return;
413
- }
414
- baseOnMouseDown?.(e);
415
- };
416
-
417
456
  if (isVirtualized) {
418
457
  let { collection } = state;
419
458
  let nodes = [...collection];
@@ -431,6 +470,7 @@ export function useGridListItem(...args: any[]): GridListItemAria {
431
470
  };
432
471
 
433
472
  // TODO: should isExpanded and hasChildRows be a item state that gets returned by the hook?
473
+ // oxlint-disable react/react-compiler
434
474
  return {
435
475
  rowProps: { ...mergeProps(rowProps, treeGridRowProps) },
436
476
  gridCellProps,
@@ -439,22 +479,24 @@ export function useGridListItem(...args: any[]): GridListItemAria {
439
479
  },
440
480
  ...itemStates,
441
481
  };
482
+ // oxlint-enable react/react-compiler
442
483
  }
443
484
 
444
485
  function handleTreeExpansionKeys<T>(
445
- e: KeyboardEvent,
486
+ e: ReactKeyboardEvent,
446
487
  state: ListState<T> | TreeState<T>,
447
488
  node: RSNode<unknown>,
448
489
  hasChildRows: boolean | undefined,
449
- direction: string,
490
+ direction: 'ltr' | 'rtl',
450
491
  activeElement: Element | null,
451
492
  rowRef: FocusableElement | null,
493
+ allowsArrowNavigation: boolean | undefined,
452
494
  ): boolean {
453
- if (!('expandedKeys' in state) || activeElement !== rowRef) {
495
+ if (!('expandedKeys' in state) || (!allowsArrowNavigation && activeElement !== rowRef)) {
454
496
  return false;
455
497
  }
456
498
  if (
457
- e.key === (EXPANSION_KEYS['expand'] as Record<string, string>)[direction] &&
499
+ e.key === EXPANSION_KEYS['expand'][direction] &&
458
500
  state.selectionManager.focusedKey === node.key &&
459
501
  hasChildRows &&
460
502
  !state.expandedKeys.has(node.key)
@@ -463,7 +505,7 @@ function handleTreeExpansionKeys<T>(
463
505
  e.stopPropagation();
464
506
  return true;
465
507
  } else if (
466
- e.key === (EXPANSION_KEYS['collapse'] as Record<string, string>)[direction] &&
508
+ e.key === EXPANSION_KEYS['collapse'][direction] &&
467
509
  state.selectionManager.focusedKey === node.key
468
510
  ) {
469
511
  // If item is collapsible, collapse it; else move to parent
package/src/index.ts CHANGED
@@ -359,3 +359,25 @@ export type {
359
359
  } from './table/useTableColumnResize';
360
360
  export type { GridAria, GridProps } from './grid/useGrid';
361
361
  export type { GridRowAria, GridRowProps } from './grid/useGridRow';
362
+
363
+ export { useContextMenu } from './interactions/useContextMenu';
364
+ export type {
365
+ ContextMenuProps,
366
+ ContextMenuAria,
367
+ ContextMenuEvent,
368
+ } from './interactions/useContextMenu';
369
+ export type { KeyboardShortcutBindings } from './interactions/createKeyboardShortcutHandler';
370
+ export { usePreviewTrigger } from './tooltip/usePreviewTrigger';
371
+ export type {
372
+ AriaPreviewTriggerProps,
373
+ PreviewTriggerTriggerAria,
374
+ } from './tooltip/usePreviewTrigger';
375
+ export {
376
+ useTokenField,
377
+ tokenFieldPositionToDOMRange,
378
+ setTokenFieldSelection,
379
+ getSelection,
380
+ } from './tokenfield/useTokenField';
381
+ export type { AriaTokenFieldProps, TokenFieldAria } from './tokenfield/useTokenField';
382
+ export { useToken } from './tokenfield/useToken';
383
+ export type { TokenProps, TokenAria } from './tokenfield/useToken';
@@ -20,6 +20,8 @@ export type BaseEvent<T extends Event> = T & {
20
20
  */
21
21
  stopPropagation(): void;
22
22
  continuePropagation(): void;
23
+ isDefaultPrevented(): boolean;
24
+ isPropagationStopped(): boolean;
23
25
  };
24
26
 
25
27
  const hasOwn = Object.prototype.hasOwnProperty;
@@ -35,14 +37,8 @@ export function createEventHandler<T extends Event>(
35
37
  return undefined;
36
38
  }
37
39
 
38
- // NB: the flag deliberately lives on the WRAPPER closure, not per dispatch — after a
39
- // handler calls continuePropagation(), a later event on the same wrapper instance does
40
- // not re-arm stop-by-default. That is upstream's exact (and admittedly surprising)
41
- // contract at the pinned version; the differential KeyLatch fixture pins octane to
42
- // React's observable behavior across consecutive dispatches, so an upstream semantics
43
- // change will surface at the next pin bump rather than silently diverging here.
44
- let shouldStopPropagation = true;
45
40
  return (e: T) => {
41
+ let shouldStopPropagation = true;
46
42
  const overrides: Record<PropertyKey, any> = {
47
43
  preventDefault() {
48
44
  e.preventDefault();
@@ -87,7 +83,14 @@ export function createEventHandler<T extends Event>(
87
83
 
88
84
  handler(event);
89
85
 
90
- if (shouldStopPropagation) {
86
+ if (
87
+ shouldStopPropagation &&
88
+ !(
89
+ 'isPropagationStopped' in e &&
90
+ typeof e.isPropagationStopped === 'function' &&
91
+ e.isPropagationStopped()
92
+ )
93
+ ) {
91
94
  e.stopPropagation();
92
95
  }
93
96
  };