react-aria-components 1.4.0 → 1.5.0

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 (183) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1 -1
  3. package/dist/Breadcrumbs.main.js +2 -2
  4. package/dist/Breadcrumbs.main.js.map +1 -1
  5. package/dist/Breadcrumbs.mjs +2 -2
  6. package/dist/Breadcrumbs.module.js +2 -2
  7. package/dist/Breadcrumbs.module.js.map +1 -1
  8. package/dist/Button.main.js.map +1 -1
  9. package/dist/Button.module.js.map +1 -1
  10. package/dist/Checkbox.main.js +2 -3
  11. package/dist/Checkbox.main.js.map +1 -1
  12. package/dist/Checkbox.mjs +3 -4
  13. package/dist/Checkbox.module.js +3 -4
  14. package/dist/Checkbox.module.js.map +1 -1
  15. package/dist/Collection.main.js +3 -2
  16. package/dist/Collection.main.js.map +1 -1
  17. package/dist/Collection.mjs +3 -2
  18. package/dist/Collection.module.js +3 -2
  19. package/dist/Collection.module.js.map +1 -1
  20. package/dist/ComboBox.main.js +2 -2
  21. package/dist/ComboBox.main.js.map +1 -1
  22. package/dist/ComboBox.mjs +2 -2
  23. package/dist/ComboBox.module.js +2 -2
  24. package/dist/ComboBox.module.js.map +1 -1
  25. package/dist/Dialog.main.js +34 -10
  26. package/dist/Dialog.main.js.map +1 -1
  27. package/dist/Dialog.mjs +36 -12
  28. package/dist/Dialog.module.js +36 -12
  29. package/dist/Dialog.module.js.map +1 -1
  30. package/dist/Disclosure.main.js +3 -1
  31. package/dist/Disclosure.main.js.map +1 -1
  32. package/dist/Disclosure.mjs +3 -1
  33. package/dist/Disclosure.module.js +3 -1
  34. package/dist/Disclosure.module.js.map +1 -1
  35. package/dist/DragAndDrop.main.js +1 -1
  36. package/dist/DragAndDrop.main.js.map +1 -1
  37. package/dist/DragAndDrop.mjs +1 -1
  38. package/dist/DragAndDrop.module.js +1 -1
  39. package/dist/DragAndDrop.module.js.map +1 -1
  40. package/dist/GridList.main.js +1 -0
  41. package/dist/GridList.main.js.map +1 -1
  42. package/dist/GridList.mjs +1 -0
  43. package/dist/GridList.module.js +1 -0
  44. package/dist/GridList.module.js.map +1 -1
  45. package/dist/Heading.main.js.map +1 -1
  46. package/dist/Heading.module.js.map +1 -1
  47. package/dist/ListBox.main.js +7 -2
  48. package/dist/ListBox.main.js.map +1 -1
  49. package/dist/ListBox.mjs +8 -4
  50. package/dist/ListBox.module.js +8 -4
  51. package/dist/ListBox.module.js.map +1 -1
  52. package/dist/Menu.main.js +67 -56
  53. package/dist/Menu.main.js.map +1 -1
  54. package/dist/Menu.mjs +69 -59
  55. package/dist/Menu.module.js +69 -59
  56. package/dist/Menu.module.js.map +1 -1
  57. package/dist/OverlayArrow.main.js +1 -1
  58. package/dist/OverlayArrow.main.js.map +1 -1
  59. package/dist/OverlayArrow.mjs +1 -1
  60. package/dist/OverlayArrow.module.js +1 -1
  61. package/dist/OverlayArrow.module.js.map +1 -1
  62. package/dist/Popover.main.js +1 -0
  63. package/dist/Popover.main.js.map +1 -1
  64. package/dist/Popover.mjs +1 -0
  65. package/dist/Popover.module.js +1 -0
  66. package/dist/Popover.module.js.map +1 -1
  67. package/dist/Select.main.js.map +1 -1
  68. package/dist/Select.module.js.map +1 -1
  69. package/dist/Table.main.js +3 -10
  70. package/dist/Table.main.js.map +1 -1
  71. package/dist/Table.mjs +3 -10
  72. package/dist/Table.module.js +3 -10
  73. package/dist/Table.module.js.map +1 -1
  74. package/dist/ToggleButton.main.js +20 -5
  75. package/dist/ToggleButton.main.js.map +1 -1
  76. package/dist/ToggleButton.mjs +22 -7
  77. package/dist/ToggleButton.module.js +22 -7
  78. package/dist/ToggleButton.module.js.map +1 -1
  79. package/dist/ToggleButtonGroup.main.js +62 -0
  80. package/dist/ToggleButtonGroup.main.js.map +1 -0
  81. package/dist/ToggleButtonGroup.mjs +51 -0
  82. package/dist/ToggleButtonGroup.module.js +51 -0
  83. package/dist/ToggleButtonGroup.module.js.map +1 -0
  84. package/dist/Tooltip.main.js +3 -2
  85. package/dist/Tooltip.main.js.map +1 -1
  86. package/dist/Tooltip.mjs +3 -2
  87. package/dist/Tooltip.module.js +3 -2
  88. package/dist/Tooltip.module.js.map +1 -1
  89. package/dist/Virtualizer.main.js.map +1 -1
  90. package/dist/Virtualizer.module.js.map +1 -1
  91. package/dist/import.mjs +5 -3
  92. package/dist/main.js +10 -3
  93. package/dist/main.js.map +1 -1
  94. package/dist/module.js +5 -3
  95. package/dist/module.js.map +1 -1
  96. package/dist/types.d.ts +149 -112
  97. package/dist/types.d.ts.map +1 -1
  98. package/i18n/ar-AE.js +1 -1
  99. package/i18n/ar-AE.mjs +1 -1
  100. package/i18n/bg-BG.js +1 -1
  101. package/i18n/bg-BG.mjs +1 -1
  102. package/i18n/cs-CZ.js +1 -1
  103. package/i18n/cs-CZ.mjs +1 -1
  104. package/i18n/da-DK.js +1 -1
  105. package/i18n/da-DK.mjs +1 -1
  106. package/i18n/de-DE.js +1 -1
  107. package/i18n/de-DE.mjs +1 -1
  108. package/i18n/el-GR.js +1 -1
  109. package/i18n/el-GR.mjs +1 -1
  110. package/i18n/en-US.js +1 -1
  111. package/i18n/en-US.mjs +1 -1
  112. package/i18n/es-ES.js +1 -1
  113. package/i18n/es-ES.mjs +1 -1
  114. package/i18n/et-EE.js +1 -1
  115. package/i18n/et-EE.mjs +1 -1
  116. package/i18n/fi-FI.js +1 -1
  117. package/i18n/fi-FI.mjs +1 -1
  118. package/i18n/fr-FR.js +1 -1
  119. package/i18n/fr-FR.mjs +1 -1
  120. package/i18n/he-IL.js +1 -1
  121. package/i18n/he-IL.mjs +1 -1
  122. package/i18n/hr-HR.js +1 -1
  123. package/i18n/hr-HR.mjs +1 -1
  124. package/i18n/hu-HU.js +1 -1
  125. package/i18n/hu-HU.mjs +1 -1
  126. package/i18n/it-IT.js +1 -1
  127. package/i18n/it-IT.mjs +1 -1
  128. package/i18n/ja-JP.js +1 -1
  129. package/i18n/ja-JP.mjs +1 -1
  130. package/i18n/ko-KR.js +1 -1
  131. package/i18n/ko-KR.mjs +1 -1
  132. package/i18n/lt-LT.js +1 -1
  133. package/i18n/lt-LT.mjs +1 -1
  134. package/i18n/lv-LV.js +1 -1
  135. package/i18n/lv-LV.mjs +1 -1
  136. package/i18n/nb-NO.js +1 -1
  137. package/i18n/nb-NO.mjs +1 -1
  138. package/i18n/nl-NL.js +1 -1
  139. package/i18n/nl-NL.mjs +1 -1
  140. package/i18n/pl-PL.js +1 -1
  141. package/i18n/pl-PL.mjs +1 -1
  142. package/i18n/pt-BR.js +1 -1
  143. package/i18n/pt-BR.mjs +1 -1
  144. package/i18n/pt-PT.js +1 -1
  145. package/i18n/pt-PT.mjs +1 -1
  146. package/i18n/ro-RO.js +1 -1
  147. package/i18n/ro-RO.mjs +1 -1
  148. package/i18n/ru-RU.js +1 -1
  149. package/i18n/ru-RU.mjs +1 -1
  150. package/i18n/sk-SK.js +1 -1
  151. package/i18n/sk-SK.mjs +1 -1
  152. package/i18n/sl-SI.js +1 -1
  153. package/i18n/sl-SI.mjs +1 -1
  154. package/i18n/sr-SP.js +1 -1
  155. package/i18n/sr-SP.mjs +1 -1
  156. package/i18n/sv-SE.js +1 -1
  157. package/i18n/sv-SE.mjs +1 -1
  158. package/i18n/tr-TR.js +1 -1
  159. package/i18n/tr-TR.mjs +1 -1
  160. package/i18n/uk-UA.js +1 -1
  161. package/i18n/uk-UA.mjs +1 -1
  162. package/i18n/zh-CN.js +1 -1
  163. package/i18n/zh-CN.mjs +1 -1
  164. package/i18n/zh-TW.js +1 -1
  165. package/i18n/zh-TW.mjs +1 -1
  166. package/package.json +35 -34
  167. package/src/Checkbox.tsx +3 -4
  168. package/src/Collection.tsx +7 -4
  169. package/src/Dialog.tsx +29 -14
  170. package/src/Disclosure.tsx +14 -6
  171. package/src/DragAndDrop.tsx +2 -2
  172. package/src/GridList.tsx +1 -0
  173. package/src/ListBox.tsx +12 -4
  174. package/src/Menu.tsx +74 -45
  175. package/src/OverlayArrow.tsx +5 -3
  176. package/src/Popover.tsx +2 -2
  177. package/src/Select.tsx +2 -2
  178. package/src/Table.tsx +2 -2
  179. package/src/ToggleButton.tsx +26 -9
  180. package/src/ToggleButtonGroup.tsx +68 -0
  181. package/src/Tooltip.tsx +4 -3
  182. package/src/Virtualizer.tsx +2 -2
  183. package/src/index.ts +8 -6
package/src/Dialog.tsx CHANGED
@@ -10,14 +10,16 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
  import {AriaDialogProps, useDialog, useId, useOverlayTrigger} from 'react-aria';
13
- import {ContextValue, DEFAULT_SLOT, Provider, SlotProps, StyleProps, useContextProps} from './utils';
13
+ import {ButtonContext} from './Button';
14
+ import {ContextValue, DEFAULT_SLOT, Provider, SlotProps, StyleProps, useContextProps, useRenderProps} from './utils';
14
15
  import {filterDOMProps} from '@react-aria/utils';
15
16
  import {forwardRefType} from '@react-types/shared';
16
17
  import {HeadingContext} from './RSPContexts';
17
- import {OverlayTriggerProps, OverlayTriggerState, useOverlayTriggerState} from 'react-stately';
18
+ import {OverlayTriggerProps, OverlayTriggerState, useMenuTriggerState} from 'react-stately';
18
19
  import {PopoverContext} from './Popover';
19
20
  import {PressResponder} from '@react-aria/interactions';
20
21
  import React, {createContext, ForwardedRef, forwardRef, ReactNode, useContext, useRef} from 'react';
22
+ import {RootMenuTriggerStateContext} from './Menu';
21
23
 
22
24
  export interface DialogTriggerProps extends OverlayTriggerProps {
23
25
  children: ReactNode
@@ -39,7 +41,9 @@ export const OverlayTriggerStateContext = createContext<OverlayTriggerState | nu
39
41
  * A DialogTrigger opens a dialog when a trigger element is pressed.
40
42
  */
41
43
  export function DialogTrigger(props: DialogTriggerProps) {
42
- let state = useOverlayTriggerState(props);
44
+ // Use useMenuTriggerState instead of useOverlayTriggerState in case a menu is embedded in the dialog.
45
+ // This is needed to handle submenus.
46
+ let state = useMenuTriggerState(props);
43
47
 
44
48
  let buttonRef = useRef<HTMLButtonElement>(null);
45
49
  let {triggerProps, overlayProps} = useOverlayTrigger({type: 'dialog'}, state, buttonRef);
@@ -55,6 +59,7 @@ export function DialogTrigger(props: DialogTriggerProps) {
55
59
  <Provider
56
60
  values={[
57
61
  [OverlayTriggerStateContext, state],
62
+ [RootMenuTriggerStateContext, state],
58
63
  [DialogContext, overlayProps],
59
64
  [PopoverContext, {trigger: 'DialogTrigger', triggerRef: buttonRef}]
60
65
  ]}>
@@ -76,13 +81,6 @@ function Dialog(props: DialogProps, ref: ForwardedRef<HTMLElement>) {
76
81
  }, ref);
77
82
  let state = useContext(OverlayTriggerStateContext);
78
83
 
79
- let children = props.children;
80
- if (typeof children === 'function') {
81
- children = children({
82
- close: state?.close || (() => {})
83
- });
84
- }
85
-
86
84
  if (!dialogProps['aria-label'] && !dialogProps['aria-labelledby']) {
87
85
  // If aria-labelledby exists on props, we know it came from context.
88
86
  // Use that as a fallback in case there is no title slot.
@@ -93,14 +91,23 @@ function Dialog(props: DialogProps, ref: ForwardedRef<HTMLElement>) {
93
91
  }
94
92
  }
95
93
 
94
+ let renderProps = useRenderProps({
95
+ defaultClassName: 'react-aria-Dialog',
96
+ className: props.className,
97
+ style: props.style,
98
+ children: props.children,
99
+ values: {
100
+ close: state?.close || (() => {})
101
+ }
102
+ });
103
+
96
104
  return (
97
105
  <section
98
106
  {...filterDOMProps(props)}
99
107
  {...dialogProps}
108
+ {...renderProps}
100
109
  ref={ref}
101
- slot={props.slot || undefined}
102
- style={props.style}
103
- className={props.className ?? 'react-aria-Dialog'}>
110
+ slot={props.slot || undefined}>
104
111
  <Provider
105
112
  values={[
106
113
  [HeadingContext, {
@@ -108,9 +115,17 @@ function Dialog(props: DialogProps, ref: ForwardedRef<HTMLElement>) {
108
115
  [DEFAULT_SLOT]: {},
109
116
  title: {...titleProps, level: 2}
110
117
  }
118
+ }],
119
+ [ButtonContext, {
120
+ slots: {
121
+ [DEFAULT_SLOT]: {},
122
+ close: {
123
+ onPress: () => state?.close()
124
+ }
125
+ }
111
126
  }]
112
127
  ]}>
113
- {children}
128
+ {renderProps.children}
114
129
  </Provider>
115
130
  </section>
116
131
  );
@@ -63,7 +63,7 @@ function DisclosureGroup(props: DisclosureGroupProps, ref: ForwardedRef<HTMLDivE
63
63
  }
64
64
 
65
65
  /**
66
- * A DisclosureGroup is a grouping of related disclosures, sometimes called an Accordion.
66
+ * A DisclosureGroup is a grouping of related disclosures, sometimes called an accordion.
67
67
  * It supports both single and multiple expanded items.
68
68
  */
69
69
  const _DisclosureGroup = forwardRef(DisclosureGroup);
@@ -71,9 +71,7 @@ export {_DisclosureGroup as DisclosureGroup};
71
71
 
72
72
  export interface DisclosureProps extends Omit<AriaDisclosureProps, 'children'>, RenderProps<DisclosureRenderProps>, SlotProps {
73
73
  /** An id for the disclosure when used within a DisclosureGroup, matching the id used in `expandedKeys`. */
74
- id?: Key,
75
- /** The children of the component. A function may be provided to alter the children based on component state. */
76
- children: ReactNode | ((values: DisclosureRenderProps & {defaultChildren: ReactNode | undefined}) => ReactNode)
74
+ id?: Key
77
75
  }
78
76
 
79
77
  export interface DisclosureRenderProps {
@@ -110,7 +108,7 @@ const InternalDisclosureContext = createContext<InternalDisclosureContextValue |
110
108
 
111
109
  function Disclosure(props: DisclosureProps, ref: ForwardedRef<HTMLDivElement>) {
112
110
  [props, ref] = useContextProps(props, ref, DisclosureContext);
113
- let groupState = useContext(DisclosureGroupStateContext);
111
+ let groupState = useContext(DisclosureGroupStateContext)!;
114
112
  let {id, ...otherProps} = props;
115
113
 
116
114
  // Generate an id if one wasn't provided.
@@ -183,7 +181,15 @@ function Disclosure(props: DisclosureProps, ref: ForwardedRef<HTMLDivElement>) {
183
181
  );
184
182
  }
185
183
 
186
- export interface DisclosurePanelProps extends RenderProps<{}> {
184
+ export interface DisclosurePanelRenderProps {
185
+ /**
186
+ * Whether keyboard focus is within the disclosure panel.
187
+ * @selector [data-focus-visible-within]
188
+ */
189
+ isFocusVisibleWithin: boolean
190
+ }
191
+
192
+ export interface DisclosurePanelProps extends RenderProps<DisclosurePanelRenderProps>, DOMProps {
187
193
  /**
188
194
  * The accessibility role for the disclosure's panel.
189
195
  * @default 'group'
@@ -209,8 +215,10 @@ function DisclosurePanel(props: DisclosurePanelProps, ref: ForwardedRef<HTMLDivE
209
215
  isFocusVisibleWithin
210
216
  }
211
217
  });
218
+ let DOMProps = filterDOMProps(props);
212
219
  return (
213
220
  <div
221
+ {...DOMProps}
214
222
  ref={mergeRefs(ref, panelRef)}
215
223
  {...mergeProps(panelProps, focusWithinProps)}
216
224
  {...renderProps}
@@ -65,7 +65,7 @@ export function useRenderDropIndicator(dragAndDropHooks?: DragAndDropHooks, drop
65
65
  export function useDndPersistedKeys(selectionManager: MultipleSelectionManager, dragAndDropHooks?: DragAndDropHooks, dropState?: DroppableCollectionState) {
66
66
  // Persist the focused key and the drop target key.
67
67
  let focusedKey = selectionManager.focusedKey;
68
- let dropTargetKey: Key | null = null;
68
+ let dropTargetKey: Key | null | undefined = null;
69
69
  if (dragAndDropHooks?.isVirtualDragging?.() && dropState?.target?.type === 'item') {
70
70
  dropTargetKey = dropState.target.key;
71
71
  if (dropState.target.dropPosition === 'after') {
@@ -75,6 +75,6 @@ export function useDndPersistedKeys(selectionManager: MultipleSelectionManager,
75
75
  }
76
76
 
77
77
  return useMemo(() => {
78
- return new Set([focusedKey, dropTargetKey].filter(k => k !== null));
78
+ return new Set([focusedKey, dropTargetKey].filter(k => k != null));
79
79
  }, [focusedKey, dropTargetKey]);
80
80
  }
package/src/GridList.tsx CHANGED
@@ -375,6 +375,7 @@ export const GridListItem = /*#__PURE__*/ createLeafComponent('item', function G
375
375
  values={[
376
376
  [CheckboxContext, {
377
377
  slots: {
378
+ [DEFAULT_SLOT]: {},
378
379
  selection: checkboxProps
379
380
  }
380
381
  }],
package/src/ListBox.tsx CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import {AriaListBoxOptions, AriaListBoxProps, DraggableItemResult, DragPreviewRenderer, DroppableCollectionResult, DroppableItemResult, FocusScope, ListKeyboardDelegate, mergeProps, useCollator, useFocusRing, useHover, useListBox, useListBoxSection, useLocale, useOption} from 'react-aria';
14
- import {Collection, CollectionBuilder, createLeafComponent} from '@react-aria/collections';
14
+ import {Collection, CollectionBuilder, createBranchComponent, createLeafComponent} from '@react-aria/collections';
15
15
  import {CollectionProps, CollectionRendererContext, ItemRenderProps, SectionContext, SectionProps} from './Collection';
16
16
  import {ContextValue, Provider, RenderProps, ScrollableProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlot} from './utils';
17
17
  import {DragAndDropContext, DropIndicatorContext, DropIndicatorProps, useDndPersistedKeys, useRenderDropIndicator} from './DragAndDrop';
@@ -246,7 +246,7 @@ function ListBoxInner<T extends object>({state, props, listBoxRef}: ListBoxInner
246
246
  [DragAndDropContext, {dragAndDropHooks, dragState, dropState}],
247
247
  [SeparatorContext, {elementType: 'div'}],
248
248
  [DropIndicatorContext, {render: ListBoxDropIndicatorWrapper}],
249
- [SectionContext, {render: ListBoxSection}]
249
+ [SectionContext, {name: 'ListBoxSection', render: ListBoxSection}]
250
250
  ]}>
251
251
  <CollectionRoot
252
252
  collection={collection}
@@ -261,7 +261,9 @@ function ListBoxInner<T extends object>({state, props, listBoxRef}: ListBoxInner
261
261
  );
262
262
  }
263
263
 
264
- function ListBoxSection<T extends object>(props: SectionProps<T>, ref: ForwardedRef<HTMLElement>, section: Node<T>) {
264
+ export interface ListBoxSectionProps<T> extends SectionProps<T> {}
265
+
266
+ function ListBoxSection<T extends object>(props: ListBoxSectionProps<T>, ref: ForwardedRef<HTMLElement>, section: Node<T>, className = 'react-aria-ListBoxSection') {
265
267
  let state = useContext(ListStateContext)!;
266
268
  let {dragAndDropHooks, dropState} = useContext(DragAndDropContext)!;
267
269
  let {CollectionBranch} = useContext(CollectionRendererContext);
@@ -271,7 +273,7 @@ function ListBoxSection<T extends object>(props: SectionProps<T>, ref: Forwarded
271
273
  'aria-label': props['aria-label'] ?? undefined
272
274
  });
273
275
  let renderProps = useRenderProps({
274
- defaultClassName: 'react-aria-Section',
276
+ defaultClassName: className,
275
277
  className: props.className,
276
278
  style: props.style,
277
279
  values: {}
@@ -293,6 +295,12 @@ function ListBoxSection<T extends object>(props: SectionProps<T>, ref: Forwarded
293
295
  );
294
296
  }
295
297
 
298
+ /**
299
+ * A ListBoxSection represents a section within a ListBox.
300
+ */
301
+ const _ListBoxSection = /*#__PURE__*/ createBranchComponent('section', ListBoxSection);
302
+ export {_ListBoxSection as ListBoxSection};
303
+
296
304
  export interface ListBoxItemRenderProps extends ItemRenderProps {}
297
305
 
298
306
  export interface ListBoxItemProps<T = object> extends RenderProps<ListBoxItemRenderProps>, LinkDOMProps, HoverEvents {
package/src/Menu.tsx CHANGED
@@ -17,12 +17,13 @@ import {Collection, CollectionBuilder, createBranchComponent, createLeafComponen
17
17
  import {CollectionProps, CollectionRendererContext, ItemRenderProps, SectionContext, SectionProps, usePersistedKeys} from './Collection';
18
18
  import {ContextValue, Provider, RenderProps, ScrollableProps, SlotProps, StyleProps, useContextProps, useRenderProps, useSlot, useSlottedContext} from './utils';
19
19
  import {filterDOMProps, useObjectRef, useResizeObserver} from '@react-aria/utils';
20
- import {forwardRefType, HoverEvents, Key, LinkDOMProps} from '@react-types/shared';
20
+ import {FocusStrategy, forwardRefType, HoverEvents, Key, LinkDOMProps, MultipleSelection} from '@react-types/shared';
21
21
  import {HeaderContext} from './Header';
22
22
  import {KeyboardContext} from './Keyboard';
23
+ import {MultipleSelectionState, SelectionManager, useMultipleSelectionState} from '@react-stately/selection';
23
24
  import {OverlayTriggerStateContext} from './Dialog';
24
- import {PopoverContext, PopoverProps} from './Popover';
25
- import {PressResponder, useHover, useInteractOutside} from '@react-aria/interactions';
25
+ import {PopoverContext} from './Popover';
26
+ import {PressResponder, useHover} from '@react-aria/interactions';
26
27
  import React, {
27
28
  createContext,
28
29
  ForwardedRef,
@@ -32,7 +33,6 @@ import React, {
32
33
  RefObject,
33
34
  useCallback,
34
35
  useContext,
35
- useEffect,
36
36
  useRef,
37
37
  useState
38
38
  } from 'react';
@@ -44,6 +44,7 @@ import {useSubmenuTrigger} from '@react-aria/menu';
44
44
  export const MenuContext = createContext<ContextValue<MenuProps<any>, HTMLDivElement>>(null);
45
45
  export const MenuStateContext = createContext<TreeState<any> | null>(null);
46
46
  export const RootMenuTriggerStateContext = createContext<RootMenuTriggerState | null>(null);
47
+ const SelectionManagerContext = createContext<SelectionManager | null>(null);
47
48
 
48
49
  export interface MenuTriggerProps extends BaseMenuTriggerProps {
49
50
  children: ReactNode
@@ -51,7 +52,6 @@ export interface MenuTriggerProps extends BaseMenuTriggerProps {
51
52
 
52
53
  export function MenuTrigger(props: MenuTriggerProps) {
53
54
  let state = useMenuTriggerState(props);
54
-
55
55
  let ref = useRef<HTMLButtonElement>(null);
56
56
  let {menuTriggerProps, menuProps} = useMenuTrigger({
57
57
  ...props,
@@ -119,7 +119,6 @@ export const SubmenuTrigger = /*#__PURE__*/ createBranchComponent('submenutrigg
119
119
  let submenuTriggerState = useSubmenuTriggerState({triggerKey: item.key}, rootMenuTriggerState);
120
120
  let submenuRef = useRef<HTMLDivElement>(null);
121
121
  let itemRef = useObjectRef(ref);
122
- let popoverContext = useSlottedContext(PopoverContext)!;
123
122
  let {parentMenuRef} = useContext(SubmenuTriggerContext)!;
124
123
  let {submenuTriggerProps, submenuProps, popoverProps} = useSubmenuTrigger({
125
124
  parentMenuRef,
@@ -138,7 +137,9 @@ export const SubmenuTrigger = /*#__PURE__*/ createBranchComponent('submenutrigg
138
137
  trigger: 'SubmenuTrigger',
139
138
  triggerRef: itemRef,
140
139
  placement: 'end top',
141
- UNSTABLE_portalContainer: popoverContext.UNSTABLE_portalContainer || undefined,
140
+ // Prevent parent popover from hiding submenu.
141
+ // @ts-ignore
142
+ 'data-react-aria-top-layer': true,
142
143
  ...popoverProps
143
144
  }]
144
145
  ]}>
@@ -173,33 +174,9 @@ function MenuInner<T extends object>({props, collection, menuRef: ref}: MenuInne
173
174
  collection,
174
175
  children: undefined
175
176
  });
176
- let [popoverContainer, setPopoverContainer] = useState<HTMLDivElement | null>(null);
177
+ let triggerState = useContext(RootMenuTriggerStateContext);
177
178
  let {isVirtualized, CollectionRoot} = useContext(CollectionRendererContext);
178
- let {menuProps} = useMenu({...props, isVirtualized}, state, ref);
179
- let rootMenuTriggerState = useContext(RootMenuTriggerStateContext)!;
180
- let popoverContext = useContext(PopoverContext)!;
181
-
182
- let isSubmenu = (popoverContext as PopoverProps)?.trigger === 'SubmenuTrigger';
183
- useInteractOutside({
184
- ref,
185
- onInteractOutside: (e) => {
186
- if (rootMenuTriggerState && !popoverContainer?.contains(e.target as HTMLElement)) {
187
- rootMenuTriggerState.close();
188
- }
189
- },
190
- isDisabled: isSubmenu || rootMenuTriggerState?.expandedKeysStack.length === 0
191
- });
192
-
193
- let prevPopoverContainer = useRef<HTMLDivElement | null>(null) ;
194
- let [leftOffset, setLeftOffset] = useState({left: 0});
195
- useEffect(() => {
196
- if (popoverContainer && prevPopoverContainer.current !== popoverContainer && leftOffset.left === 0) {
197
- prevPopoverContainer.current = popoverContainer;
198
- let {left} = popoverContainer.getBoundingClientRect();
199
- setLeftOffset({left: -1 * left});
200
- }
201
- }, [leftOffset, popoverContainer]);
202
-
179
+ let {menuProps} = useMenu({...props, isVirtualized, onClose: props.onClose || triggerState?.close}, state, ref);
203
180
  let renderProps = useRenderProps({
204
181
  defaultClassName: 'react-aria-Menu',
205
182
  className: props.className,
@@ -220,10 +197,10 @@ function MenuInner<T extends object>({props, collection, menuRef: ref}: MenuInne
220
197
  values={[
221
198
  [MenuStateContext, state],
222
199
  [SeparatorContext, {elementType: 'div'}],
223
- [PopoverContext, {UNSTABLE_portalContainer: popoverContainer || undefined}],
224
- [SectionContext, {render: MenuSection}],
200
+ [SectionContext, {name: 'MenuSection', render: MenuSection}],
225
201
  [SubmenuTriggerContext, {parentMenuRef: ref}],
226
- [MenuItemContext, null]
202
+ [MenuItemContext, null],
203
+ [SelectionManagerContext, state.selectionManager]
227
204
  ]}>
228
205
  <CollectionRoot
229
206
  collection={collection}
@@ -231,7 +208,6 @@ function MenuInner<T extends object>({props, collection, menuRef: ref}: MenuInne
231
208
  scrollRef={ref} />
232
209
  </Provider>
233
210
  </div>
234
- <div ref={setPopoverContainer} style={{width: '100vw', position: 'absolute', top: 0, ...leftOffset}} />
235
211
  </FocusScope>
236
212
  );
237
213
  }
@@ -242,7 +218,40 @@ function MenuInner<T extends object>({props, collection, menuRef: ref}: MenuInne
242
218
  const _Menu = /*#__PURE__*/ (forwardRef as forwardRefType)(Menu);
243
219
  export {_Menu as Menu};
244
220
 
245
- function MenuSection<T extends object>(props: SectionProps<T>, ref: ForwardedRef<HTMLElement>, section: Node<T>) {
221
+ export interface MenuSectionProps<T> extends SectionProps<T>, MultipleSelection {}
222
+
223
+ // A subclass of SelectionManager that forwards focus-related properties to the parent,
224
+ // but has its own local selection state.
225
+ class GroupSelectionManager extends SelectionManager {
226
+ private parent: SelectionManager;
227
+
228
+ constructor(parent: SelectionManager, state: MultipleSelectionState) {
229
+ super(parent.collection, state);
230
+ this.parent = parent;
231
+ }
232
+
233
+ get focusedKey() {
234
+ return this.parent.focusedKey;
235
+ }
236
+
237
+ get isFocused() {
238
+ return this.parent.isFocused;
239
+ }
240
+
241
+ setFocusedKey(key: Key | null, childFocusStrategy?: FocusStrategy): void {
242
+ return this.parent.setFocusedKey(key, childFocusStrategy);
243
+ }
244
+
245
+ setFocused(isFocused: boolean): void {
246
+ this.parent.setFocused(isFocused);
247
+ }
248
+
249
+ get childFocusStrategy() {
250
+ return this.parent.childFocusStrategy;
251
+ }
252
+ }
253
+
254
+ function MenuSection<T extends object>(props: MenuSectionProps<T>, ref: ForwardedRef<HTMLElement>, section: Node<T>, className = 'react-aria-MenuSection') {
246
255
  let state = useContext(MenuStateContext)!;
247
256
  let {CollectionBranch} = useContext(CollectionRendererContext);
248
257
  let [headingRef, heading] = useSlot();
@@ -251,25 +260,39 @@ function MenuSection<T extends object>(props: SectionProps<T>, ref: ForwardedRef
251
260
  'aria-label': section.props['aria-label'] ?? undefined
252
261
  });
253
262
  let renderProps = useRenderProps({
254
- defaultClassName: 'react-aria-Section',
263
+ defaultClassName: className,
255
264
  className: section.props?.className,
256
265
  style: section.props?.style,
257
266
  values: {}
258
267
  });
259
268
 
269
+ let parent = useContext(SelectionManagerContext)!;
270
+ let selectionState = useMultipleSelectionState(props);
271
+ let manager = props.selectionMode != null ? new GroupSelectionManager(parent, selectionState) : parent;
272
+
260
273
  return (
261
274
  <section
262
275
  {...filterDOMProps(props as any)}
263
276
  {...groupProps}
264
277
  {...renderProps}
265
278
  ref={ref}>
266
- <HeaderContext.Provider value={{...headingProps, ref: headingRef}}>
279
+ <Provider
280
+ values={[
281
+ [HeaderContext, {...headingProps, ref: headingRef}],
282
+ [SelectionManagerContext, manager]
283
+ ]}>
267
284
  <CollectionBranch collection={state.collection} parent={section} />
268
- </HeaderContext.Provider>
285
+ </Provider>
269
286
  </section>
270
287
  );
271
288
  }
272
289
 
290
+ /**
291
+ * A MenuSection represents a section within a Menu.
292
+ */
293
+ const _MenuSection = /*#__PURE__*/ createBranchComponent('section', MenuSection);
294
+ export {_MenuSection as MenuSection};
295
+
273
296
  export interface MenuItemRenderProps extends ItemRenderProps {
274
297
  /**
275
298
  * Whether the item has a submenu.
@@ -310,8 +333,14 @@ export const MenuItem = /*#__PURE__*/ createLeafComponent('item', function MenuI
310
333
  let id = useSlottedContext(MenuItemContext)?.id as string;
311
334
  let state = useContext(MenuStateContext)!;
312
335
  let ref = useObjectRef<any>(forwardedRef);
336
+ let selectionManager = useContext(SelectionManagerContext)!;
313
337
 
314
- let {menuItemProps, labelProps, descriptionProps, keyboardShortcutProps, ...states} = useMenuItem({...props, id, key: item.key}, state, ref);
338
+ let {menuItemProps, labelProps, descriptionProps, keyboardShortcutProps, ...states} = useMenuItem({
339
+ ...props,
340
+ id,
341
+ key: item.key,
342
+ selectionManager
343
+ }, state, ref);
315
344
 
316
345
  let {isFocusVisible, focusProps} = useFocusRing();
317
346
  let {hoverProps, isHovered} = useHover({
@@ -326,8 +355,8 @@ export const MenuItem = /*#__PURE__*/ createLeafComponent('item', function MenuI
326
355
  ...states,
327
356
  isHovered,
328
357
  isFocusVisible,
329
- selectionMode: state.selectionManager.selectionMode,
330
- selectionBehavior: state.selectionManager.selectionBehavior,
358
+ selectionMode: selectionManager.selectionMode,
359
+ selectionBehavior: selectionManager.selectionBehavior,
331
360
  hasSubmenu: !!props['aria-haspopup'],
332
361
  isOpen: props['aria-expanded'] === 'true'
333
362
  }
@@ -346,7 +375,7 @@ export const MenuItem = /*#__PURE__*/ createLeafComponent('item', function MenuI
346
375
  data-focus-visible={isFocusVisible || undefined}
347
376
  data-pressed={states.isPressed || undefined}
348
377
  data-selected={states.isSelected || undefined}
349
- data-selection-mode={state.selectionManager.selectionMode === 'none' ? undefined : state.selectionManager.selectionMode}
378
+ data-selection-mode={selectionManager.selectionMode === 'none' ? undefined : selectionManager.selectionMode}
350
379
  data-has-submenu={!!props['aria-haspopup'] || undefined}
351
380
  data-open={props['aria-expanded'] === 'true' || undefined}>
352
381
  <Provider
@@ -16,7 +16,7 @@ import {PlacementAxis} from 'react-aria';
16
16
  import React, {createContext, CSSProperties, ForwardedRef, forwardRef, HTMLAttributes} from 'react';
17
17
 
18
18
  interface OverlayArrowContextValue extends OverlayArrowProps {
19
- placement: PlacementAxis
19
+ placement: PlacementAxis | null
20
20
  }
21
21
 
22
22
  export const OverlayArrowContext = createContext<ContextValue<OverlayArrowContextValue, HTMLDivElement>>({
@@ -30,7 +30,7 @@ export interface OverlayArrowRenderProps {
30
30
  * The placement of the overlay relative to the trigger.
31
31
  * @selector [data-placement="left | right | top | bottom"]
32
32
  */
33
- placement: PlacementAxis
33
+ placement: PlacementAxis | null
34
34
  }
35
35
 
36
36
  function OverlayArrow(props: OverlayArrowProps, ref: ForwardedRef<HTMLDivElement>) {
@@ -38,9 +38,11 @@ function OverlayArrow(props: OverlayArrowProps, ref: ForwardedRef<HTMLDivElement
38
38
  let placement = (props as OverlayArrowContextValue).placement;
39
39
  let style: CSSProperties = {
40
40
  position: 'absolute',
41
- [placement]: '100%',
42
41
  transform: placement === 'top' || placement === 'bottom' ? 'translateX(-50%)' : 'translateY(-50%)'
43
42
  };
43
+ if (placement != null) {
44
+ style[placement] = '100%';
45
+ }
44
46
 
45
47
  let renderProps = useRenderProps({
46
48
  ...props,
package/src/Popover.tsx CHANGED
@@ -65,7 +65,7 @@ export interface PopoverRenderProps {
65
65
  * The placement of the popover relative to the trigger.
66
66
  * @selector [data-placement="left | right | top | bottom"]
67
67
  */
68
- placement: PlacementAxis,
68
+ placement: PlacementAxis | null,
69
69
  /**
70
70
  * Whether the popover is currently entering. Use this to apply animations.
71
71
  * @selector [data-entering]
@@ -165,7 +165,7 @@ function PopoverInner({state, isExiting, UNSTABLE_portalContainer, ...props}: Po
165
165
  let style = {...popoverProps.style, ...renderProps.style};
166
166
 
167
167
  return (
168
- <Overlay isExiting={isExiting} portalContainer={UNSTABLE_portalContainer}>
168
+ <Overlay {...props} isExiting={isExiting} portalContainer={UNSTABLE_portalContainer}>
169
169
  {!props.isNonModal && state.isOpen && <div data-testid="underlay" {...underlayProps} style={{position: 'fixed', inset: 0}} />}
170
170
  <div
171
171
  {...mergeProps(filterDOMProps(props as any), popoverProps)}
package/src/Select.tsx CHANGED
@@ -62,12 +62,12 @@ export interface SelectRenderProps {
62
62
  isRequired: boolean
63
63
  }
64
64
 
65
- export interface SelectProps<T extends object> extends Omit<AriaSelectProps<T>, 'children' | 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior' | 'items'>, RACValidation, RenderProps<SelectRenderProps>, SlotProps {}
65
+ export interface SelectProps<T extends object = {}> extends Omit<AriaSelectProps<T>, 'children' | 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior' | 'items'>, RACValidation, RenderProps<SelectRenderProps>, SlotProps {}
66
66
 
67
67
  export const SelectContext = createContext<ContextValue<SelectProps<any>, HTMLDivElement>>(null);
68
68
  export const SelectStateContext = createContext<SelectState<unknown> | null>(null);
69
69
 
70
- function Select<T extends object>(props: SelectProps<T>, ref: ForwardedRef<HTMLDivElement>) {
70
+ function Select<T extends object = {}>(props: SelectProps<T>, ref: ForwardedRef<HTMLDivElement>) {
71
71
  [props, ref] = useContextProps(props, ref, SelectContext);
72
72
  let {children, isDisabled = false, isInvalid = false, isRequired = false} = props;
73
73
  let content = useMemo(() => (
package/src/Table.tsx CHANGED
@@ -1118,6 +1118,7 @@ export const Row = /*#__PURE__*/ createBranchComponent(
1118
1118
  values={[
1119
1119
  [CheckboxContext, {
1120
1120
  slots: {
1121
+ [DEFAULT_SLOT]: {},
1121
1122
  selection: checkboxProps
1122
1123
  }
1123
1124
  }],
@@ -1142,7 +1143,7 @@ export const Row = /*#__PURE__*/ createBranchComponent(
1142
1143
  },
1143
1144
  props => {
1144
1145
  if (props.id == null && typeof props.children === 'function') {
1145
- console.warn('No id detected for the Row element. The Row element requires a id to be provided to it when the cells are rendered dynamically.');
1146
+ throw new Error('No id detected for the Row element. The Row element requires a id to be provided to it when the cells are rendered dynamically.');
1146
1147
  }
1147
1148
 
1148
1149
  let dependencies = [props.value].concat(props.dependencies);
@@ -1193,7 +1194,6 @@ export const Cell = /*#__PURE__*/ createLeafComponent('cell', (props: CellProps,
1193
1194
  let {dragState} = useContext(DragAndDropContext);
1194
1195
  let {isVirtualized} = useContext(CollectionRendererContext);
1195
1196
 
1196
- // @ts-ignore
1197
1197
  cell.column = state.collection.columns[cell.index];
1198
1198
 
1199
1199
  let {gridCellProps, isPressed} = useTableCell({
@@ -10,11 +10,12 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {AriaToggleButtonProps, HoverEvents, mergeProps, useFocusRing, useHover, useToggleButton} from 'react-aria';
13
+ import {AriaToggleButtonProps, HoverEvents, mergeProps, useFocusRing, useHover, useToggleButton, useToggleButtonGroupItem} from 'react-aria';
14
14
  import {ButtonRenderProps} from './Button';
15
15
  import {ContextValue, RenderProps, SlotProps, useContextProps, useRenderProps} from './utils';
16
- import {forwardRefType} from '@react-types/shared';
17
- import React, {createContext, ForwardedRef, forwardRef} from 'react';
16
+ import {forwardRefType, Key} from '@react-types/shared';
17
+ import React, {createContext, ForwardedRef, forwardRef, useContext} from 'react';
18
+ import {ToggleGroupStateContext} from './ToggleButtonGroup';
18
19
  import {ToggleState, useToggleState} from 'react-stately';
19
20
 
20
21
  export interface ToggleButtonRenderProps extends Omit<ButtonRenderProps, 'isPending'> {
@@ -29,19 +30,35 @@ export interface ToggleButtonRenderProps extends Omit<ButtonRenderProps, 'isPend
29
30
  state: ToggleState
30
31
  }
31
32
 
32
- export interface ToggleButtonProps extends Omit<AriaToggleButtonProps, 'children' | 'elementType'>, HoverEvents, SlotProps, RenderProps<ToggleButtonRenderProps> {}
33
+ export interface ToggleButtonProps extends Omit<AriaToggleButtonProps, 'children' | 'elementType' | 'id'>, HoverEvents, SlotProps, RenderProps<ToggleButtonRenderProps> {
34
+ /** When used in a ToggleButtonGroup, an identifier for the item in `selectedKeys`. When used standalone, a DOM id. */
35
+ id?: Key
36
+ }
33
37
 
34
38
  export const ToggleButtonContext = createContext<ContextValue<ToggleButtonProps, HTMLButtonElement>>({});
35
39
 
36
40
  function ToggleButton(props: ToggleButtonProps, ref: ForwardedRef<HTMLButtonElement>) {
37
41
  [props, ref] = useContextProps(props, ref, ToggleButtonContext);
38
- let state = useToggleState(props);
39
- let {buttonProps, isPressed} = useToggleButton(props, state, ref);
42
+ let groupState = useContext(ToggleGroupStateContext);
43
+ let state = useToggleState(groupState && props.id != null ? {
44
+ isSelected: groupState.selectedKeys.has(props.id),
45
+ onChange(isSelected) {
46
+ groupState.setSelected(props.id!, isSelected);
47
+ }
48
+ } : props);
49
+
50
+ let {buttonProps, isPressed, isSelected, isDisabled} = groupState && props.id != null
51
+ // eslint-disable-next-line react-hooks/rules-of-hooks
52
+ ? useToggleButtonGroupItem({...props, id: props.id}, groupState, ref)
53
+ // eslint-disable-next-line react-hooks/rules-of-hooks
54
+ : useToggleButton({...props, id: props.id != null ? String(props.id) : undefined}, state, ref);
55
+
40
56
  let {focusProps, isFocused, isFocusVisible} = useFocusRing(props);
41
57
  let {hoverProps, isHovered} = useHover(props);
42
58
  let renderProps = useRenderProps({
43
59
  ...props,
44
- values: {isHovered, isPressed, isFocused, isSelected: state.isSelected, isFocusVisible, isDisabled: props.isDisabled || false, state},
60
+ id: undefined,
61
+ values: {isHovered, isPressed, isFocused, isSelected: state.isSelected, isFocusVisible, isDisabled, state},
45
62
  defaultClassName: 'react-aria-ToggleButton'
46
63
  });
47
64
 
@@ -52,9 +69,9 @@ function ToggleButton(props: ToggleButtonProps, ref: ForwardedRef<HTMLButtonElem
52
69
  ref={ref}
53
70
  slot={props.slot || undefined}
54
71
  data-focused={isFocused || undefined}
55
- data-disabled={props.isDisabled || undefined}
72
+ data-disabled={isDisabled || undefined}
56
73
  data-pressed={isPressed || undefined}
57
- data-selected={state.isSelected || undefined}
74
+ data-selected={isSelected || undefined}
58
75
  data-hovered={isHovered || undefined}
59
76
  data-focus-visible={isFocusVisible || undefined} />
60
77
  );