@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
@@ -1,3 +1,4 @@
1
+ import type { FocusableElement } from '@react-types/shared';
1
2
  // Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/ComboBox.tsx).
2
3
  // octane adaptations: `.tsx` → `.ts`, JSX → `createElement`; NO forwardRef —
3
4
  // `createHideableComponent` forwards `props.ref` positionally exactly like TextField; the
@@ -34,6 +35,7 @@ import { useResizeObserver } from '../utils/useResizeObserver';
34
35
  import { ButtonContext } from './Button';
35
36
  import { OverlayTriggerStateContext } from './Dialog';
36
37
  import { FieldErrorContext } from './FieldError';
38
+ import { FieldInputContext } from './Autocomplete';
37
39
  import { FormContext } from './Form';
38
40
  import { GroupContext } from './Group';
39
41
  import { InputContext } from './Input';
@@ -186,7 +188,14 @@ export const ComboBox: <T, M extends SelectionMode = 'single'>(
186
188
  }) as any;
187
189
 
188
190
  // Contexts to clear inside the popover.
189
- const CLEAR_CONTEXTS = [LabelContext, ButtonContext, InputContext, GroupContext, TextContext];
191
+ const CLEAR_CONTEXTS = [
192
+ LabelContext,
193
+ ButtonContext,
194
+ InputContext,
195
+ FieldInputContext,
196
+ GroupContext,
197
+ TextContext,
198
+ ];
190
199
 
191
200
  interface ComboBoxInnerProps<T> {
192
201
  props: ComboBoxProps<T, SelectionMode>;
@@ -226,6 +235,11 @@ function ComboBoxInner<T>({ props, collection, comboBoxRef: ref }: ComboBoxInner
226
235
  !props['aria-label'] && !props['aria-labelledby'],
227
236
  subSlot(slot, 'labelSlot'),
228
237
  );
238
+ let [labelElementType, setLabelElementType] = useState<'span' | 'label'>(
239
+ 'label',
240
+ subSlot(slot, 'labelElementType'),
241
+ );
242
+
229
243
  let {
230
244
  buttonProps,
231
245
  inputProps,
@@ -242,6 +256,7 @@ function ComboBoxInner<T>({ props, collection, comboBoxRef: ref }: ComboBoxInner
242
256
  inputRef,
243
257
  buttonRef,
244
258
  listBoxRef,
259
+ labelElementType,
245
260
  popoverRef,
246
261
  name: formValue === 'text' ? name : undefined,
247
262
  validationBehavior,
@@ -334,9 +349,27 @@ function ComboBoxInner<T>({ props, collection, comboBoxRef: ref }: ComboBoxInner
334
349
  return createElement(Provider, {
335
350
  values: [
336
351
  [ComboBoxStateContext, state],
337
- [LabelContext, { ...labelProps, ref: labelRef }],
352
+ [LabelContext, { ...labelProps, elementType: labelElementType, ref: labelRef }],
338
353
  [ButtonContext, { ...buttonProps, ref: buttonRef, isPressed: state.isOpen }],
339
354
  [InputContext, { ...inputProps, ref: inputRef }],
355
+ [
356
+ FieldInputContext,
357
+ {
358
+ ...inputProps,
359
+ ref: useCallback(
360
+ (el: FocusableElement) => {
361
+ inputRef.current = el as HTMLInputElement; // TODO: figure out how to fix non-input element types in useComboBox/useTextField
362
+ if (el) {
363
+ setLabelElementType(el.tagName.toLowerCase() === 'input' ? 'label' : 'span');
364
+ }
365
+ },
366
+ [],
367
+ subSlot(slot, 'fieldInputRef'),
368
+ ),
369
+ value: state.inputValue,
370
+ onChange: (v: string) => state.setInputValue(v as string),
371
+ } as any,
372
+ ],
340
373
  [OverlayTriggerStateContext, state],
341
374
  [
342
375
  PopoverContext,
@@ -19,6 +19,7 @@ import type {
19
19
  import { PopoverContext } from './Popover';
20
20
  import { PressResponder } from '../interactions/PressResponder';
21
21
  import { RootMenuTriggerStateContext } from './Menu';
22
+ import { TextContext } from './Text';
22
23
  import { useId } from '../utils/useId';
23
24
  import { useMenuTriggerState } from '../stately/menu/useMenuTriggerState';
24
25
  import { useOverlayTrigger } from '../overlays/useOverlayTrigger';
@@ -101,6 +102,7 @@ export function DialogTrigger(props: DialogTriggerProps): any {
101
102
  {
102
103
  trigger: 'DialogTrigger',
103
104
  triggerRef: buttonRef,
105
+ id: overlayProps.id,
104
106
  'aria-labelledby': (overlayProps as any)['aria-labelledby'],
105
107
  },
106
108
  ],
@@ -122,7 +124,7 @@ export function Dialog(props: DialogProps): any {
122
124
  let originalAriaLabelledby = props['aria-labelledby'];
123
125
  let ref: any;
124
126
  [props, ref] = useContextProps(props, (props as any).ref, DialogContext, subSlot(slot, 'ctx'));
125
- let { dialogProps, titleProps } = useDialog(
127
+ let { dialogProps, titleProps, contentProps } = useDialog(
126
128
  {
127
129
  ...props,
128
130
  // Only pass aria-labelledby from props, not context.
@@ -177,6 +179,15 @@ export function Dialog(props: DialogProps): any {
177
179
  },
178
180
  },
179
181
  ],
182
+ [
183
+ TextContext,
184
+ {
185
+ slots: {
186
+ [DEFAULT_SLOT]: {},
187
+ description: contentProps,
188
+ },
189
+ },
190
+ ],
180
191
  [
181
192
  ButtonContext,
182
193
  {
@@ -1,4 +1,5 @@
1
1
  /** @jsxImportSource octane */
2
+ import { focusWithoutScrolling } from '../utils/focusWithoutScrolling';
2
3
  // Ported from adobe/react-spectrum@1c84a49a1faf50b571c84e00bcf9c60b22ddd03e (packages/react-aria-components/src/DropZone.tsx).
3
4
  /*
4
5
  * Copyright 2023 Adobe. All rights reserved.
@@ -154,8 +155,8 @@ export const DropZone = forwardRef(function DropZone(
154
155
  while (target && nodeContains(dropzoneRef.current, target)) {
155
156
  if (isFocusable(target)) {
156
157
  break;
157
- } else if (target === dropzoneRef.current) {
158
- buttonRef.current?.focus();
158
+ } else if (target === dropzoneRef.current && buttonRef.current) {
159
+ focusWithoutScrolling(buttonRef.current);
159
160
  break;
160
161
  }
161
162
 
@@ -84,6 +84,7 @@ export const FileTrigger = forwardRef(function FileTrigger(
84
84
  className=""
85
85
  type="file"
86
86
  ref={inputRef}
87
+ onClick={(e: MouseEvent) => e.stopPropagation()}
87
88
  style={{ display: 'none' }}
88
89
  accept={acceptedFileTypes?.toString()}
89
90
  onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSelect?.(e.currentTarget.files)}
@@ -554,6 +554,13 @@ export interface GridListItemProps<T = object>
554
554
  * on the collection's `selectionBehavior` prop and the interaction modality.
555
555
  */
556
556
  onAction?: () => void;
557
+ focusMode?: 'child' | 'row';
558
+ /**
559
+ * Whether the row should support arrow key navigation even when the containing collection uses
560
+ * tab keyboard navigation. Allows users to navigate between rows with arrow keys while
561
+ * focus is on an interactive child element within the row.
562
+ */
563
+ allowsArrowNavigation?: boolean;
557
564
  }
558
565
 
559
566
  /**
@@ -577,6 +584,8 @@ export const GridListItem: <T extends object = object>(
577
584
  let { rowProps, gridCellProps, descriptionProps, ...states } = useGridListItem(
578
585
  {
579
586
  node: item!,
587
+ focusMode: props.focusMode,
588
+ allowsArrowNavigation: props.allowsArrowNavigation,
580
589
  shouldSelectOnPressUp: !!dragState,
581
590
  isVirtualized,
582
591
  },
@@ -902,7 +911,7 @@ function RootDropIndicator(): any {
902
911
 
903
912
  export interface GridListLoadMoreItemProps
904
913
  extends
905
- Omit<LoadMoreSentinelProps, 'collection'>,
914
+ Omit<LoadMoreSentinelProps, 'collection' | 'direction'>,
906
915
  StyleProps,
907
916
  DOMRenderProps<'div', undefined>,
908
917
  GlobalDOMAttributes {
@@ -945,7 +954,7 @@ export const GridListLoadMoreItem: (props: GridListLoadMoreItemProps & { ref?: a
945
954
  sentinelRef,
946
955
  scrollOffset,
947
956
  }),
948
- [onLoadMore, scrollOffset, state?.collection],
957
+ [onLoadMore, scrollOffset, sentinelRef, state?.collection],
949
958
  subSlot(slot, 'loadMoreProps'),
950
959
  );
951
960
  useLoadMoreSentinel(
@@ -786,7 +786,7 @@ function ListBoxDropIndicator(props: ListBoxDropIndicatorProps): any {
786
786
 
787
787
  export interface ListBoxLoadMoreItemProps
788
788
  extends
789
- Omit<LoadMoreSentinelProps, 'collection'>,
789
+ Omit<LoadMoreSentinelProps, 'collection' | 'direction'>,
790
790
  StyleProps,
791
791
  DOMRenderProps<'div', undefined>,
792
792
  GlobalDOMAttributes {
@@ -145,6 +145,7 @@ export function MenuTrigger(props: MenuTriggerProps): any {
145
145
  triggerRef: ref,
146
146
  scrollRef,
147
147
  placement: 'bottom start',
148
+ offset: props.trigger === 'contextMenu' ? 0 : undefined,
148
149
  'aria-labelledby': (menuProps as any)['aria-labelledby'],
149
150
  },
150
151
  ],
@@ -65,6 +65,8 @@ export function Meter(props: MeterProps): any {
65
65
  let { value = 0, minValue = 0, maxValue = 100 } = props;
66
66
  value = clamp(value, minValue, maxValue);
67
67
 
68
+ let range = maxValue - minValue;
69
+
68
70
  let [labelRef, label] = useSlot(
69
71
  !props['aria-label'] && !props['aria-labelledby'],
70
72
  subSlot(slot, 'labelSlot'),
@@ -72,7 +74,7 @@ export function Meter(props: MeterProps): any {
72
74
  let { meterProps, labelProps } = useMeter({ ...props, label }, subSlot(slot, 'meter'));
73
75
 
74
76
  // Calculate the width of the progress bar as a percentage
75
- let percentage = ((value - minValue) / (maxValue - minValue)) * 100;
77
+ let percentage = range === 0 ? 0 : ((value - minValue) / range) * 100;
76
78
 
77
79
  let renderProps = useRenderProps(
78
80
  {
@@ -100,6 +100,8 @@ export interface PopoverProps
100
100
  * @default document.body
101
101
  */
102
102
  UNSTABLE_portalContainer?: Element;
103
+ /** Whether enter and exit animations should be skipped. */
104
+ shouldSkipAnimation?: boolean;
103
105
  /**
104
106
  * The additional offset applied along the main axis between the element and its
105
107
  * anchor element.
@@ -138,6 +140,7 @@ export interface PopoverRenderProps {
138
140
 
139
141
  interface PopoverContextValue extends PopoverProps {
140
142
  /** Contexts to clear. */
143
+ id?: string;
141
144
  clearContexts?: Context<any>[];
142
145
  }
143
146
 
@@ -157,8 +160,8 @@ export function Popover(props: PopoverProps): any {
157
160
  let localState = useOverlayTriggerState(props, subSlot(slot, 'state'));
158
161
  let state =
159
162
  props.isOpen != null || props.defaultOpen != null || !contextState ? localState : contextState;
160
- let isExiting =
161
- useExitAnimation(ref, state.isOpen, subSlot(slot, 'exit')) || props.isExiting || false;
163
+ let exitAnimation = useExitAnimation(ref, state.isOpen, subSlot(slot, 'exit'));
164
+ let isExiting = props.isExiting || (!props.shouldSkipAnimation && exitAnimation) || false;
162
165
  let isHidden = useIsHidden();
163
166
  let { direction } = useLocale(subSlot(slot, 'locale'));
164
167
 
@@ -198,10 +201,12 @@ interface PopoverInnerProps extends AriaPopoverProps, RenderProps<PopoverRenderP
198
201
  state: OverlayTriggerState;
199
202
  isEntering?: boolean;
200
203
  isExiting: boolean;
204
+ shouldSkipAnimation?: boolean;
201
205
  UNSTABLE_portalContainer?: Element;
202
206
  trigger?: string;
203
207
  dir?: 'ltr' | 'rtl';
204
208
  clearContexts?: Context<any>[];
209
+ id?: string;
205
210
  }
206
211
 
207
212
  function PopoverInner(allProps: PopoverInnerProps): any {
@@ -229,8 +234,9 @@ function PopoverInner(allProps: PopoverInnerProps): any {
229
234
  );
230
235
 
231
236
  let ref = props.popoverRef as RefObject<HTMLDivElement | null>;
232
- let isEntering =
233
- useEnterAnimation(ref, !!placement, subSlot(slot, 'enter')) || props.isEntering || false;
237
+ let enterAnimation = useEnterAnimation(ref, !!placement, subSlot(slot, 'enter'));
238
+ // oxlint-disable-next-line react/react-compiler
239
+ let isEntering = props.isEntering || (!props.shouldSkipAnimation && enterAnimation) || false;
234
240
  let renderProps = useRenderProps(
235
241
  {
236
242
  ...props,
@@ -247,8 +253,12 @@ function PopoverInner(allProps: PopoverInnerProps): any {
247
253
 
248
254
  // Automatically render Popover with role=dialog except when isNonModal is true,
249
255
  // or a dialog is already nested inside the popover.
250
- let shouldBeDialog = !props.isNonModal || props.trigger === 'SubmenuTrigger';
251
- let [isDialog, setDialog] = useState(false, subSlot(slot, 'isDialog'));
256
+ let shouldBeDialog =
257
+ !props.isNonModal || props.trigger === 'SubmenuTrigger' || props.trigger === 'PreviewTrigger';
258
+ let [isDialog, setDialog] = useState(
259
+ props.trigger === 'PreviewTrigger',
260
+ subSlot(slot, 'isDialog'),
261
+ );
252
262
  useLayoutEffect(
253
263
  () => {
254
264
  if (ref.current) {
@@ -265,6 +275,7 @@ function PopoverInner(allProps: PopoverInnerProps): any {
265
275
  () => {
266
276
  if (
267
277
  isDialog &&
278
+ props.trigger !== 'PreviewTrigger' &&
268
279
  (props.trigger !== 'SubmenuTrigger' || getInteractionModality() !== 'pointer') &&
269
280
  ref.current &&
270
281
  !isFocusWithin(ref.current)
@@ -327,6 +338,7 @@ function PopoverInner(allProps: PopoverInnerProps): any {
327
338
  {
328
339
  ...mergeProps(filterDOMProps(props, { global: true }), popoverProps),
329
340
  ...renderProps,
341
+ id: isDialog ? props.id : undefined,
330
342
  role: isDialog ? 'dialog' : undefined,
331
343
  tabIndex: isDialog ? -1 : undefined,
332
344
  'aria-label': props['aria-label'],
@@ -354,7 +366,7 @@ function PopoverInner(allProps: PopoverInnerProps): any {
354
366
  Overlay,
355
367
  {
356
368
  ...props,
357
- shouldContainFocus: isDialog,
369
+ shouldContainFocus: isDialog && props.trigger !== 'PreviewTrigger',
358
370
  isExiting,
359
371
  portalContainer: UNSTABLE_portalContainer,
360
372
  // octane adaptation: children arrive positionally below.
@@ -380,7 +392,7 @@ function PopoverInner(allProps: PopoverInnerProps): any {
380
392
  // Submenus/subdialogs are mounted into the root popover's container.
381
393
  return createElement(Overlay, {
382
394
  ...props,
383
- shouldContainFocus: isDialog,
395
+ shouldContainFocus: isDialog && props.trigger !== 'PreviewTrigger',
384
396
  isExiting,
385
397
  portalContainer: UNSTABLE_portalContainer ?? groupCtx?.current ?? undefined,
386
398
  children: overlay,
@@ -0,0 +1,101 @@
1
+ /** @jsxImportSource octane */
2
+ import type { OctaneNode } from 'octane';
3
+ // Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-aria-components/src/PreviewTrigger.tsx).
4
+ /*
5
+ * Copyright 2026 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 {
17
+ AriaPreviewTriggerProps,
18
+ usePreviewTrigger,
19
+ } from '../upstream-exports/react-aria/usePreviewTrigger';
20
+ import { FocusableElement } from '@react-types/shared';
21
+ import { FocusableProvider } from '../interactions/useFocusable';
22
+ import { OverlayTriggerState } from '../upstream-exports/react-stately/useOverlayTriggerState';
23
+ import { OverlayTriggerStateContext } from './Dialog';
24
+ import { PopoverContext } from './Popover';
25
+ import { Provider } from './utils';
26
+ import React, { JSX, useMemo, useRef } from '../compat/react';
27
+ import { useTooltipTriggerState } from '../upstream-exports/react-stately/useTooltipTriggerState';
28
+
29
+ export interface PreviewTriggerProps extends AriaPreviewTriggerProps {
30
+ /** The trigger and Popover that make up the preview trigger. */
31
+ children: OctaneNode;
32
+ /**
33
+ * The delay time in milliseconds before the preview opens.
34
+ *
35
+ * @default 600
36
+ */
37
+ delay?: number;
38
+ /**
39
+ * The delay time in milliseconds before the preview closes.
40
+ *
41
+ * @default 200
42
+ */
43
+ closeDelay?: number;
44
+ }
45
+
46
+ /**
47
+ * A PreviewTrigger displays a non-modal popover on hover, focus, or long press. Unlike a tooltip,
48
+ * the popover may contain interactive content.
49
+ */
50
+ export function PreviewTrigger(props: PreviewTriggerProps): JSX.Element {
51
+ let state = useTooltipTriggerState({
52
+ ...props,
53
+ delay: props.delay ?? 600,
54
+ closeDelay: props.closeDelay ?? 200,
55
+ });
56
+ let triggerRef = useRef<FocusableElement>(null);
57
+ let popoverRef = useRef<HTMLElement>(null);
58
+ let { triggerProps, popoverProps } = usePreviewTrigger(
59
+ { ...props, triggerRef, popoverRef },
60
+ state,
61
+ );
62
+
63
+ // The Popover and usePopover expect an OverlayTriggerState. Adapt the TooltipTriggerState (which
64
+ // provides the warmup/cooldown delay behavior) to that interface.
65
+ let overlayState = useMemo<OverlayTriggerState>(
66
+ () => ({
67
+ isOpen: state.isOpen,
68
+ open: () => state.open(),
69
+ close: () => state.close(),
70
+ setOpen: (isOpen) => (isOpen ? state.open() : state.close()),
71
+ toggle: () => (state.isOpen ? state.close() : state.open()),
72
+ point: null,
73
+ setPoint: () => {},
74
+ }),
75
+ [state],
76
+ );
77
+
78
+ return (
79
+ <Provider
80
+ values={[
81
+ [OverlayTriggerStateContext, overlayState],
82
+ [
83
+ PopoverContext,
84
+ {
85
+ trigger: 'PreviewTrigger',
86
+ triggerRef,
87
+ ref: popoverRef,
88
+ isNonModal: true,
89
+ // Skip enter/exit animations when swapping between previews during the warmup period.
90
+ shouldSkipAnimation: state.shouldSkipAnimation,
91
+ ...popoverProps,
92
+ },
93
+ ],
94
+ ]}
95
+ >
96
+ <FocusableProvider {...triggerProps} ref={triggerRef}>
97
+ {props.children}
98
+ </FocusableProvider>
99
+ </Provider>
100
+ );
101
+ }
@@ -83,8 +83,16 @@ export function ProgressBar(props: ProgressBarProps): any {
83
83
  );
84
84
 
85
85
  // Calculate the width of the progress bar as a percentage
86
- let percentage = isIndeterminate ? undefined : ((value - minValue) / (maxValue - minValue)) * 100;
87
-
86
+ let range = maxValue - minValue;
87
+ // Calculate the width of the progress bar as a percentage
88
+ let percentage: number | undefined = undefined;
89
+ if (!isIndeterminate) {
90
+ if (range === 0) {
91
+ percentage = 0;
92
+ } else {
93
+ percentage = ((value - minValue) / range) * 100;
94
+ }
95
+ }
88
96
  let renderProps = useRenderProps(
89
97
  {
90
98
  ...props,
@@ -146,7 +146,7 @@ export interface SelectRenderProps {
146
146
  isRequired: boolean;
147
147
  }
148
148
 
149
- export interface SelectProps<T, M extends SelectionMode = 'single'>
149
+ export interface SelectProps<T = {}, M extends SelectionMode = 'single'>
150
150
  extends
151
151
  Omit<
152
152
  AriaSelectProps<T, M>,
@@ -1308,6 +1308,13 @@ export interface ColumnProps
1308
1308
  * `<ResizableTableContainer>`.
1309
1309
  */
1310
1310
  maxWidth?: ColumnStaticSize | null;
1311
+ focusMode?: 'child' | 'cell';
1312
+ /**
1313
+ * Whether the column should support arrow key navigation even when the containing table uses tab
1314
+ * keyboard navigation. Allows users to navigate between columns and rows with arrow keys while
1315
+ * focus is on an interactive child element within the column header.
1316
+ */
1317
+ allowsArrowNavigation?: boolean;
1311
1318
  }
1312
1319
 
1313
1320
  class TableColumnNode extends CollectionNode<unknown> {
@@ -1337,7 +1344,12 @@ export const Column: (props: ColumnProps & { ref?: any }) => any =
1337
1344
  let state = useContext(TableStateContext)!;
1338
1345
  let { isVirtualized } = useContext(CollectionRendererContext);
1339
1346
  let { columnHeaderProps, isPressed } = useTableColumnHeader(
1340
- { node: column as GridNode<unknown>, isVirtualized },
1347
+ {
1348
+ node: column as GridNode<unknown>,
1349
+ isVirtualized,
1350
+ focusMode: props.focusMode,
1351
+ allowsArrowNavigation: props.allowsArrowNavigation,
1352
+ },
1341
1353
  state,
1342
1354
  ref,
1343
1355
  subSlot(slot, 'columnHeader'),
@@ -1500,7 +1512,7 @@ export function ColumnResizer(props: ColumnResizerProps & { ref?: any }): any {
1500
1512
  let { onResizeStart, onResize, onResizeEnd } = useContext(ResizableTableContainerContext)!;
1501
1513
  let { column, triggerRef } = useContext(ColumnResizerContext)!;
1502
1514
  let inputRef = useRef<HTMLInputElement | null>(null, subSlot(slot, 'input'));
1503
- let { resizerProps, inputProps, isResizing } = useTableColumnResize(
1515
+ let { resizerProps, inputProps, isResizing, isMouseResizing } = useTableColumnResize(
1504
1516
  {
1505
1517
  column,
1506
1518
  'aria-label': props['aria-label'] || stringFormatter.format('tableResizer'),
@@ -1562,17 +1574,6 @@ export function ColumnResizer(props: ColumnResizerProps & { ref?: any }): any {
1562
1574
  subSlot(slot, 'render'),
1563
1575
  );
1564
1576
 
1565
- let [isMouseDown, setMouseDown] = useState(false, subSlot(slot, 'mouseDown'));
1566
- let onPointerDown = (e: PointerEvent) => {
1567
- if (e.pointerType === 'mouse') {
1568
- setMouseDown(true);
1569
- }
1570
- };
1571
-
1572
- if (!isResizing && isMouseDown) {
1573
- setMouseDown(false);
1574
- }
1575
-
1576
1577
  let DOMProps = filterDOMProps(props, { global: true });
1577
1578
 
1578
1579
  // Positional (non-array) children: the resizer content, the visually hidden input, and
@@ -1582,7 +1583,7 @@ export function ColumnResizer(props: ColumnResizerProps & { ref?: any }): any {
1582
1583
  {
1583
1584
  ref: objectRef,
1584
1585
  role: 'presentation',
1585
- ...mergeProps(DOMProps, renderProps, resizerProps, { onPointerDown }, hoverProps),
1586
+ ...mergeProps(DOMProps, renderProps, resizerProps, hoverProps),
1586
1587
  'data-hovered': isHovered || undefined,
1587
1588
  'data-focused': isFocused || undefined,
1588
1589
  'data-focus-visible': isFocusVisible || undefined,
@@ -1591,9 +1592,10 @@ export function ColumnResizer(props: ColumnResizerProps & { ref?: any }): any {
1591
1592
  },
1592
1593
  renderProps.children,
1593
1594
  createElement('input', { ref: inputRef, ...mergeProps(inputProps, focusProps) }),
1594
- isResizing && isMouseDown
1595
+ isResizing && isMouseResizing
1595
1596
  ? createPortal(
1596
1597
  createElement('div', {
1598
+ 'data-testid': 'cursor-overlay',
1597
1599
  style: { position: 'fixed', top: 0, left: 0, bottom: 0, right: 0, cursor },
1598
1600
  }),
1599
1601
  document.body,
@@ -2250,6 +2252,13 @@ export interface CellProps extends RenderProps<CellRenderProps, 'td' | 'div'>, G
2250
2252
  textValue?: string;
2251
2253
  /** Indicates how many columns the data cell spans. */
2252
2254
  colSpan?: number;
2255
+ focusMode?: 'child' | 'cell';
2256
+ /**
2257
+ * Whether the cell should support arrow key navigation even when the containing table uses
2258
+ * tab keyboard navigation. Allows users to navigate between cells and rows with arrow keys while
2259
+ * focus is on an interactive child element within the cell.
2260
+ */
2261
+ allowsArrowNavigation?: boolean;
2253
2262
  }
2254
2263
 
2255
2264
  class TableCellNode extends CollectionNode<unknown> {
@@ -2285,6 +2294,8 @@ export const Cell: (props: CellProps & { ref?: any }) => any = /*#__PURE__*/ cre
2285
2294
  let { gridCellProps, isPressed } = useTableCell(
2286
2295
  {
2287
2296
  node: cell,
2297
+ focusMode: props.focusMode,
2298
+ allowsArrowNavigation: props.allowsArrowNavigation,
2288
2299
  shouldSelectOnPressUp: !!dragState,
2289
2300
  isVirtualized,
2290
2301
  },