@octanejs/aria 0.0.46 → 0.0.47

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} +239 -259
  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} +121 -64
  37. package/src/grid/utils.ts +1 -0
  38. package/src/gridlist/{useGridListItem.ts → useGridListItem.tsx} +152 -111
  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 +217 -0
  123. package/src/menu/{useSubmenuTrigger.ts → useSubmenuTrigger.tsx} +112 -152
  124. package/src/numberfield/{useNumberField.ts → useNumberField.tsx} +185 -239
  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} +72 -84
  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} +83 -80
  133. package/src/searchfield/useSearchField.tsx +147 -0
  134. package/src/select/useSelect.ts +20 -20
  135. package/src/selection/useSelectableCollection.tsx +751 -0
  136. package/src/selection/useSelectableItem.ts +48 -11
  137. package/src/slider/{useSliderThumb.ts → useSliderThumb.tsx} +116 -159
  138. package/src/spinbutton/useSpinButton.tsx +330 -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 +354 -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 +3 -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
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:
@@ -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,20 @@ 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';
32
30
  import { isFocusVisible } from '../interactions/useFocusVisible';
33
- import { isTabbable } from '../utils/isFocusable';
34
- import type { ListState } from '../stately/list/useListState';
31
+ import type { ListState } from '../upstream-exports/react-stately/useListState';
35
32
  import { mergeProps } from '../utils/mergeProps';
36
33
  import { scrollIntoViewport } from '../utils/scrollIntoView';
37
34
  import { SelectableItemStates, useSelectableItem } from '../selection/useSelectableItem';
38
- import type { TreeState } from '../stately/tree/useTreeState';
35
+ import type { TreeState } from '../upstream-exports/react-stately/useTreeState';
39
36
  import { useLocale } from '../i18n/I18nProvider';
40
37
  import { useSlotId } from '../utils/useId';
41
38
  import { useSyntheticLinkProps } from '../utils/openLink';
42
39
 
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
40
  export interface AriaGridListItemOptions {
50
41
  /**
51
42
  * An object representing the list item. Contains all the relevant information that makes up the
@@ -58,6 +49,19 @@ export interface AriaGridListItemOptions {
58
49
  shouldSelectOnPressUp?: boolean;
59
50
  /** Whether this item has children, even if not loaded yet. */
60
51
  hasChildItems?: boolean;
52
+ /**
53
+ * Whether the row or its first focusable child element should be focused when the row is
54
+ * focused.
55
+ *
56
+ * @default 'row'
57
+ */
58
+ focusMode?: 'child' | 'row';
59
+ /**
60
+ * Whether the row should support arrow key navigation even when the containing collection uses
61
+ * tab keyboard navigation. Allows users to navigate between rows with arrow keys while
62
+ * focus is on an interactive child element within the row.
63
+ */
64
+ allowsArrowNavigation?: boolean;
61
65
  }
62
66
 
63
67
  export interface GridListItemAria extends SelectableItemStates {
@@ -91,46 +95,54 @@ export function useGridListItem<T>(
91
95
  props: AriaGridListItemOptions,
92
96
  state: ListState<T> | TreeState<T>,
93
97
  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>;
108
-
98
+ _slot?: symbol,
99
+ ): GridListItemAria {
109
100
  // Copied from useGridCell + some modifications to make it not so grid specific
110
- let { node, isVirtualized } = props;
101
+ let { node, isVirtualized, focusMode = 'row', allowsArrowNavigation } = props;
111
102
 
112
103
  // let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/gridlist');
113
- let { direction } = useLocale(subSlot(slot, 'locale'));
104
+ let { direction } = useLocale();
114
105
  let { onAction, linkBehavior, keyboardNavigationBehavior, shouldSelectOnPressUp } =
115
106
  listMap.get(state)!;
116
- let descriptionId = useSlotId(undefined, subSlot(slot, 'descriptionId'));
107
+ let descriptionId = useSlotId();
117
108
 
118
109
  // We need to track the key of the item at the time it was last focused so that we force
119
110
  // 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'));
111
+ let keyWhenFocused = useRef<Key | null>(null);
121
112
  let focus = () => {
113
+ if (ref.current === null) {
114
+ return;
115
+ }
116
+
117
+ if (focusMode === 'child') {
118
+ // If focus is already on a focusable child within the row, early return so we don't shift focus
119
+ if (
120
+ isFocusWithin(ref.current) &&
121
+ ref.current !== getActiveElement(getOwnerDocument(ref.current))
122
+ ) {
123
+ return;
124
+ }
125
+
126
+ let treeWalker = getFocusableTreeWalker(ref.current, { tabbable: true });
127
+ let focusable = treeWalker.firstChild() as FocusableElement;
128
+ if (focusable) {
129
+ focusSafely(focusable);
130
+ scrollIntoViewport(focusable, { containingElement: getScrollParent(focusable) });
131
+ return;
132
+ }
133
+ }
134
+
122
135
  // Don't shift focus to the row if the active element is a element within the row already
123
136
  // (e.g. clicking on a row button)
124
137
  if (
125
- ref.current !== null &&
126
- ((keyWhenFocused.current != null && node.key !== keyWhenFocused.current) ||
127
- !isFocusWithin(ref.current))
138
+ (keyWhenFocused.current != null && node.key !== keyWhenFocused.current) ||
139
+ !isFocusWithin(ref.current)
128
140
  ) {
129
141
  focusSafely(ref.current);
130
142
  }
131
143
  };
132
144
 
133
- let treeGridRowProps: DOMAttributes = {};
145
+ let treeGridRowProps: HTMLAttributes<HTMLElement> = {};
134
146
  let hasChildRows = props.hasChildItems;
135
147
  let hasLink = state.selectionManager.isLink(node.key);
136
148
  if (node != null && 'expandedKeys' in state) {
@@ -175,27 +187,24 @@ export function useGridListItem(...args: any[]): GridListItemAria {
175
187
  };
176
188
  }
177
189
 
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
- );
190
+ let { itemProps, ...itemStates } = useSelectableItem({
191
+ selectionManager: state.selectionManager,
192
+ key: node.key,
193
+ ref,
194
+ isVirtualized,
195
+ shouldSelectOnPressUp: props.shouldSelectOnPressUp || shouldSelectOnPressUp,
196
+ onAction:
197
+ onAction || node.props?.onAction
198
+ ? chain(node.props?.onAction, onAction ? () => onAction(node.key) : undefined)
199
+ : undefined,
200
+ focus,
201
+ linkBehavior,
202
+ });
194
203
 
195
- let onKeyDownCapture = (e: KeyboardEvent) => {
196
- let activeElement = getActiveElement();
204
+ let onKeyDownCapture = (e: ReactKeyboardEvent) => {
205
+ let activeElement = getActiveElement(getOwnerDocument(ref.current));
197
206
  if (
198
- !nodeContains(e.currentTarget as Element, getEventTarget(e) as Element) ||
207
+ !nodeContains(e.currentTarget as Node, getEventTarget(e) as Element) ||
199
208
  !ref.current ||
200
209
  !activeElement
201
210
  ) {
@@ -206,7 +215,16 @@ export function useGridListItem(...args: any[]): GridListItemAria {
206
215
  walker.currentNode = activeElement;
207
216
 
208
217
  if (
209
- handleTreeExpansionKeys(e, state, node, hasChildRows, direction, activeElement, ref.current)
218
+ handleTreeExpansionKeys(
219
+ e,
220
+ state,
221
+ node,
222
+ hasChildRows,
223
+ direction,
224
+ activeElement,
225
+ ref.current,
226
+ allowsArrowNavigation,
227
+ )
210
228
  ) {
211
229
  return;
212
230
  }
@@ -235,6 +253,7 @@ export function useGridListItem(...args: any[]): GridListItemAria {
235
253
  } else {
236
254
  walker.currentNode = ref.current;
237
255
  let lastElement = last(walker);
256
+ // oxlint-disable-next-line max-depth
238
257
  if (lastElement) {
239
258
  focusSafely(lastElement);
240
259
  scrollIntoViewport(lastElement, {
@@ -267,6 +286,7 @@ export function useGridListItem(...args: any[]): GridListItemAria {
267
286
  } else {
268
287
  walker.currentNode = ref.current;
269
288
  let lastElement = last(walker);
289
+ // oxlint-disable-next-line max-depth
270
290
  if (lastElement) {
271
291
  focusSafely(lastElement);
272
292
  scrollIntoViewport(lastElement, {
@@ -286,7 +306,6 @@ export function useGridListItem(...args: any[]): GridListItemAria {
286
306
  if (!e.altKey && nodeContains(ref.current, getEventTarget(e) as Element)) {
287
307
  e.stopPropagation();
288
308
  e.preventDefault();
289
- // octane adaptation: the native event IS `e.nativeEvent`.
290
309
  ref.current.parentElement?.dispatchEvent(new KeyboardEvent(e.type, e));
291
310
  }
292
311
  break;
@@ -307,12 +326,33 @@ export function useGridListItem(...args: any[]): GridListItemAria {
307
326
  }
308
327
  return;
309
328
  }
329
+
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
341
+ // up to the tree, and move focus to a focusable child if possible.
342
+ requestAnimationFrame(() => {
343
+ if (
344
+ focusMode === 'child' &&
345
+ getActiveElement(getOwnerDocument(ref.current)) === ref.current
346
+ ) {
347
+ focus();
348
+ }
349
+ });
310
350
  };
311
351
 
312
- let onKeyDown = (e: KeyboardEvent) => {
313
- let activeElement = getActiveElement();
352
+ let onKeyDown = (e: ReactKeyboardEvent) => {
353
+ let activeElement = getActiveElement(getOwnerDocument(ref.current));
314
354
  if (
315
- !nodeContains(e.currentTarget as Element, getEventTarget(e) as Element) ||
355
+ !nodeContains(e.currentTarget as Node, getEventTarget(e) as Element) ||
316
356
  !ref.current ||
317
357
  !activeElement
318
358
  ) {
@@ -329,7 +369,16 @@ export function useGridListItem(...args: any[]): GridListItemAria {
329
369
  }
330
370
 
331
371
  if (
332
- handleTreeExpansionKeys(e, state, node, hasChildRows, direction, activeElement, ref.current)
372
+ handleTreeExpansionKeys(
373
+ e,
374
+ state,
375
+ node,
376
+ hasChildRows,
377
+ direction,
378
+ activeElement,
379
+ ref.current,
380
+ allowsArrowNavigation,
381
+ )
333
382
  ) {
334
383
  return;
335
384
  }
@@ -364,56 +413,45 @@ export function useGridListItem(...args: any[]): GridListItemAria {
364
413
  // });
365
414
  // }
366
415
 
416
+ // oxlint-disable-next-line react/react-compiler
367
417
  let rowProps: DOMAttributes = mergeProps(itemProps, linkProps, {
368
418
  role: 'row',
369
- onKeyDownCapture: keyboardNavigationBehavior === 'arrow' ? onKeyDownCapture : undefined,
419
+ onKeyDownCapture:
420
+ keyboardNavigationBehavior === 'arrow' || allowsArrowNavigation
421
+ ? onKeyDownCapture
422
+ : undefined,
370
423
  onFocus,
371
424
  // 'aria-label': [(node.textValue || undefined), rowAnnouncement].filter(Boolean).join(', '),
372
- 'aria-label': (node as any)['aria-label'] || node.textValue || undefined,
425
+ 'aria-label': node['aria-label'] || node.textValue || undefined,
373
426
  'aria-selected': state.selectionManager.canSelectItem(node.key)
374
427
  ? state.selectionManager.isSelected(node.key)
375
428
  : undefined,
376
429
  'aria-disabled': state.selectionManager.isDisabled(node.key) || undefined,
377
430
  'aria-labelledby':
378
- descriptionId && ((node as any)['aria-label'] || node.textValue)
431
+ descriptionId && (node['aria-label'] || node.textValue)
379
432
  ? `${getRowId(state, node.key)} ${descriptionId}`
380
433
  : undefined,
381
434
  id: getRowId(state, node.key),
382
435
  });
383
436
 
437
+ if (focusMode === 'child' && allowsArrowNavigation && keyboardNavigationBehavior === 'tab') {
438
+ rowProps.tabIndex = -1;
439
+ }
440
+
384
441
  // we need to guard against space/enter triggering selection/row link via usePress (from itemProps) so check if propagation
385
442
  // is stopped. this also fixes space not working in a textfield in a tree parent row
386
443
  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) {
444
+ rowProps.onKeyDown = (e: ReactKeyboardEvent<FocusableElement>) => {
445
+ onKeyDown(e as ReactKeyboardEvent);
446
+ if (!(
447
+ 'isPropagationStopped' in e &&
448
+ typeof e.isPropagationStopped === 'function' &&
449
+ e.isPropagationStopped()
450
+ )) {
391
451
  baseOnKeyDown?.(e);
392
452
  }
393
453
  };
394
454
 
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
455
  if (isVirtualized) {
418
456
  let { collection } = state;
419
457
  let nodes = [...collection];
@@ -431,6 +469,7 @@ export function useGridListItem(...args: any[]): GridListItemAria {
431
469
  };
432
470
 
433
471
  // TODO: should isExpanded and hasChildRows be a item state that gets returned by the hook?
472
+ // oxlint-disable react/react-compiler
434
473
  return {
435
474
  rowProps: { ...mergeProps(rowProps, treeGridRowProps) },
436
475
  gridCellProps,
@@ -439,22 +478,24 @@ export function useGridListItem(...args: any[]): GridListItemAria {
439
478
  },
440
479
  ...itemStates,
441
480
  };
481
+ // oxlint-enable react/react-compiler
442
482
  }
443
483
 
444
484
  function handleTreeExpansionKeys<T>(
445
- e: KeyboardEvent,
485
+ e: ReactKeyboardEvent,
446
486
  state: ListState<T> | TreeState<T>,
447
487
  node: RSNode<unknown>,
448
488
  hasChildRows: boolean | undefined,
449
- direction: string,
489
+ direction: 'ltr' | 'rtl',
450
490
  activeElement: Element | null,
451
491
  rowRef: FocusableElement | null,
492
+ allowsArrowNavigation: boolean | undefined,
452
493
  ): boolean {
453
- if (!('expandedKeys' in state) || activeElement !== rowRef) {
494
+ if (!('expandedKeys' in state) || (!allowsArrowNavigation && activeElement !== rowRef)) {
454
495
  return false;
455
496
  }
456
497
  if (
457
- e.key === (EXPANSION_KEYS['expand'] as Record<string, string>)[direction] &&
498
+ e.key === EXPANSION_KEYS['expand'][direction] &&
458
499
  state.selectionManager.focusedKey === node.key &&
459
500
  hasChildRows &&
460
501
  !state.expandedKeys.has(node.key)
@@ -463,7 +504,7 @@ function handleTreeExpansionKeys<T>(
463
504
  e.stopPropagation();
464
505
  return true;
465
506
  } else if (
466
- e.key === (EXPANSION_KEYS['collapse'] as Record<string, string>)[direction] &&
507
+ e.key === EXPANSION_KEYS['collapse'][direction] &&
467
508
  state.selectionManager.focusedKey === node.key
468
509
  ) {
469
510
  // 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
  };