react-aria-components 1.0.0-beta.0 → 1.0.0-beta.2

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.
package/src/GridList.tsx CHANGED
@@ -15,8 +15,9 @@ import {CheckboxContext} from './Checkbox';
15
15
  import {Collection, DraggableCollectionState, DroppableCollectionState, ListState, Node, SelectionBehavior, useListState} from 'react-stately';
16
16
  import {CollectionProps, ItemProps, useCachedChildren, useCollection} from './Collection';
17
17
  import {ContextValue, defaultSlot, forwardRefType, Provider, SlotProps, StyleRenderProps, useContextProps, useRenderProps} from './utils';
18
- import {DragAndDropHooks, DropIndicator, DropIndicatorContext, DropIndicatorProps} from './useDragAndDrop';
18
+ import {DragAndDropContext, DragAndDropHooks, DropIndicator, DropIndicatorContext, DropIndicatorProps} from './useDragAndDrop';
19
19
  import {filterDOMProps, isIOS, isWebKit, useObjectRef} from '@react-aria/utils';
20
+ import {ListStateContext} from './ListBox';
20
21
  import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes, ReactNode, RefObject, useContext, useEffect, useRef} from 'react';
21
22
  import {TextContext} from './Text';
22
23
 
@@ -56,15 +57,8 @@ export interface GridListProps<T> extends Omit<AriaGridListProps<T>, 'children'>
56
57
  renderEmptyState?: () => ReactNode
57
58
  }
58
59
 
59
- interface InternalGridListContextValue {
60
- state: ListState<unknown>,
61
- dragAndDropHooks?: DragAndDropHooks,
62
- dragState?: DraggableCollectionState,
63
- dropState?: DroppableCollectionState
64
- }
65
60
 
66
61
  export const GridListContext = createContext<ContextValue<GridListProps<any>, HTMLDivElement>>(null);
67
- const InternalGridListContext = createContext<InternalGridListContextValue | null>(null);
68
62
 
69
63
  function GridList<T extends object>(props: GridListProps<T>, ref: ForwardedRef<HTMLDivElement>) {
70
64
  // Render the portal first so that we have the collection by the time we render the DOM in SSR.
@@ -220,14 +214,15 @@ function GridListInner<T extends object>({props, collection, gridListRef: ref}:
220
214
  {...renderProps}
221
215
  {...mergeProps(gridProps, focusProps, droppableCollection?.collectionProps, emptyStatePropOverrides)}
222
216
  ref={ref}
223
- slot={props.slot}
217
+ slot={props.slot || undefined}
224
218
  data-drop-target={isRootDropTarget || undefined}
225
219
  data-empty={state.collection.size === 0 || undefined}
226
220
  data-focused={isFocused || undefined}
227
221
  data-focus-visible={isFocusVisible || undefined}>
228
222
  <Provider
229
223
  values={[
230
- [InternalGridListContext, {state, dragAndDropHooks, dragState, dropState}],
224
+ [ListStateContext, state],
225
+ [DragAndDropContext, {dragAndDropHooks, dragState, dropState}],
231
226
  [DropIndicatorContext, {render: GridListDropIndicatorWrapper}]
232
227
  ]}>
233
228
  {isListDroppable && <RootDropIndicator />}
@@ -248,7 +243,8 @@ const _GridList = /*#__PURE__*/ (forwardRef as forwardRefType)(GridList);
248
243
  export {_GridList as GridList};
249
244
 
250
245
  function GridListItem({item}) {
251
- let {state, dragAndDropHooks, dragState, dropState} = useContext(InternalGridListContext)!;
246
+ let state = useContext(ListStateContext)!;
247
+ let {dragAndDropHooks, dragState, dropState} = useContext(DragAndDropContext);
252
248
  let ref = useObjectRef<HTMLDivElement>(item.props.ref);
253
249
  let {rowProps, gridCellProps, descriptionProps, ...states} = useGridListItem(
254
250
  {
@@ -382,7 +378,7 @@ function GridListItem({item}) {
382
378
 
383
379
  function GridListDropIndicatorWrapper(props: DropIndicatorProps, ref: ForwardedRef<HTMLElement>) {
384
380
  ref = useObjectRef(ref);
385
- let {dragAndDropHooks, dropState} = useContext(InternalGridListContext)!;
381
+ let {dragAndDropHooks, dropState} = useContext(DragAndDropContext);
386
382
  let buttonRef = useRef<HTMLDivElement>(null);
387
383
  let {dropIndicatorProps, isHidden, isDropTarget} = dragAndDropHooks!.useDropIndicator!(
388
384
  props,
@@ -438,7 +434,7 @@ function GridListDropIndicator(props: GridListDropIndicatorProps, ref: Forwarded
438
434
  const GridListDropIndicatorForwardRef = forwardRef(GridListDropIndicator);
439
435
 
440
436
  function RootDropIndicator() {
441
- let {dragAndDropHooks, dropState} = useContext(InternalGridListContext)!;
437
+ let {dragAndDropHooks, dropState} = useContext(DragAndDropContext);
442
438
  let ref = useRef<HTMLDivElement>(null);
443
439
  let {dropIndicatorProps} = dragAndDropHooks!.useDropIndicator!({
444
440
  target: {type: 'root'}
package/src/Group.tsx CHANGED
@@ -10,6 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ import {AriaLabelingProps} from '@react-types/shared';
13
14
  import {ContextValue, StyleRenderProps, useContextProps, useRenderProps} from './utils';
14
15
  import {mergeProps, useFocusRing, useHover} from 'react-aria';
15
16
  import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes} from 'react';
@@ -29,10 +30,33 @@ export interface GroupRenderProps {
29
30
  * Whether an element within the group is keyboard focused.
30
31
  * @selector [data-focus-visible]
31
32
  */
32
- isFocusVisible: boolean
33
+ isFocusVisible: boolean,
34
+ /**
35
+ * Whether the group is disabled.
36
+ * @selector [data-disabled]
37
+ */
38
+ isDisabled: boolean,
39
+ /**
40
+ * Whether the group is invalid.
41
+ * @selector [data-invalid]
42
+ */
43
+ isInvalid: boolean
33
44
  }
34
45
 
35
- export interface GroupProps extends Omit<HTMLAttributes<HTMLElement>, 'className' | 'style'>, StyleRenderProps<GroupRenderProps> {}
46
+ export interface GroupProps extends AriaLabelingProps, Omit<HTMLAttributes<HTMLElement>, 'className' | 'style' | 'role'>, StyleRenderProps<GroupRenderProps> {
47
+ /** Whether the group is disabled. */
48
+ isDisabled?: boolean,
49
+ /** Whether the group is invalid. */
50
+ isInvalid?: boolean,
51
+ /**
52
+ * An accessibility role for the group. By default, this is set to `'group'`.
53
+ * Use `'region'` when the contents of the group is important enough to be
54
+ * included in the page table of contents. Use `'presentation'` if the group
55
+ * is visual only and does not represent a semantic grouping of controls.
56
+ * @default 'group'
57
+ */
58
+ role?: 'group' | 'region' | 'presentation'
59
+ }
36
60
 
37
61
  export const GroupContext = createContext<ContextValue<GroupProps, HTMLDivElement>>({});
38
62
 
@@ -44,27 +68,33 @@ function Group(props: GroupProps, ref: ForwardedRef<HTMLDivElement>) {
44
68
  within: true
45
69
  });
46
70
 
71
+ let {isDisabled, isInvalid, ...otherProps} = props;
72
+ isDisabled ??= !!props['aria-disabled'] && props['aria-disabled'] !== 'false';
73
+ isInvalid ??= !!props['aria-invalid'] && props['aria-invalid'] !== 'false';
47
74
  let renderProps = useRenderProps({
48
75
  ...props,
49
- values: {isHovered, isFocusWithin: isFocused, isFocusVisible},
76
+ values: {isHovered, isFocusWithin: isFocused, isFocusVisible, isDisabled, isInvalid},
50
77
  defaultClassName: 'react-aria-Group'
51
78
  });
52
79
 
53
80
  return (
54
81
  <div
55
- {...mergeProps(props, focusProps, hoverProps)}
82
+ {...mergeProps(otherProps, focusProps, hoverProps)}
56
83
  {...renderProps}
57
84
  ref={ref}
85
+ role={props.role ?? 'group'}
58
86
  data-focus-within={isFocused || undefined}
59
87
  data-hovered={isHovered || undefined}
60
- data-focus-visible={isFocusVisible || undefined}>
88
+ data-focus-visible={isFocusVisible || undefined}
89
+ data-disabled={isDisabled || undefined}
90
+ data-invalid={isInvalid || undefined}>
61
91
  {props.children}
62
92
  </div>
63
93
  );
64
94
  }
65
95
 
66
96
  /**
67
- * An group represents a set of related UI controls.
97
+ * A group represents a set of related UI controls, and supports interactive states for styling.
68
98
  */
69
99
  const _Group = forwardRef(Group);
70
100
  export {_Group as Group};
package/src/Link.tsx CHANGED
@@ -12,8 +12,7 @@
12
12
 
13
13
  import {AriaLinkOptions, mergeProps, useFocusRing, useHover, useLink} from 'react-aria';
14
14
  import {ContextValue, forwardRefType, RenderProps, SlotProps, useContextProps, useRenderProps} from './utils';
15
- import {filterDOMProps, mergeRefs} from '@react-aria/utils';
16
- import React, {createContext, ForwardedRef, forwardRef, useMemo} from 'react';
15
+ import React, {createContext, ElementType, ForwardedRef, forwardRef} from 'react';
17
16
 
18
17
  export interface LinkProps extends Omit<AriaLinkOptions, 'elementType'>, RenderProps<LinkRenderProps>, SlotProps {}
19
18
 
@@ -55,8 +54,8 @@ export const LinkContext = createContext<ContextValue<LinkProps, HTMLAnchorEleme
55
54
  function Link(props: LinkProps, ref: ForwardedRef<HTMLAnchorElement>) {
56
55
  [props, ref] = useContextProps(props, ref, LinkContext);
57
56
 
58
- let elementType = typeof props.children === 'string' || typeof props.children === 'function' ? 'span' : 'a';
59
- let {linkProps, isPressed} = useLink({...props, elementType}, ref);
57
+ let ElementType: ElementType = props.href ? 'a' : 'span';
58
+ let {linkProps, isPressed} = useLink({...props, elementType: ElementType}, ref);
60
59
 
61
60
  let {hoverProps, isHovered} = useHover(props);
62
61
  let {focusProps, isFocused, isFocusVisible} = useFocusRing();
@@ -74,26 +73,20 @@ function Link(props: LinkProps, ref: ForwardedRef<HTMLAnchorElement>) {
74
73
  }
75
74
  });
76
75
 
77
- let DOMProps = filterDOMProps(props);
78
- delete DOMProps.id;
79
-
80
- let element: any = typeof renderProps.children === 'string'
81
- ? <span>{renderProps.children}</span>
82
- : React.Children.only(renderProps.children);
83
-
84
- return React.cloneElement(element, {
85
- ref: useMemo(() => element.ref ? mergeRefs(element.ref, ref) : ref, [element.ref, ref]),
86
- slot: props.slot,
87
- ...mergeProps(DOMProps, renderProps, linkProps, hoverProps, focusProps, {
88
- children: element.props.children,
89
- 'data-focused': isFocused || undefined,
90
- 'data-hovered': isHovered || undefined,
91
- 'data-pressed': isPressed || undefined,
92
- 'data-focus-visible': isFocusVisible || undefined,
93
- 'data-current': !!props['aria-current'] || undefined,
94
- 'data-disabled': props.isDisabled || undefined
95
- }, element.props)
96
- });
76
+ return (
77
+ <ElementType
78
+ ref={ref}
79
+ slot={props.slot || undefined}
80
+ {...mergeProps(renderProps, linkProps, hoverProps, focusProps)}
81
+ data-focused={isFocused || undefined}
82
+ data-hovered={isHovered || undefined}
83
+ data-pressed={isPressed || undefined}
84
+ data-focus-visible={isFocusVisible || undefined}
85
+ data-current={!!props['aria-current'] || undefined}
86
+ data-disabled={props.isDisabled || undefined}>
87
+ {renderProps.children}
88
+ </ElementType>
89
+ );
97
90
  }
98
91
 
99
92
  /**
package/src/ListBox.tsx CHANGED
@@ -10,10 +10,10 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {AriaListBoxOptions, AriaListBoxProps, DraggableItemResult, DragPreviewRenderer, DroppableCollectionResult, DroppableItemResult, FocusScope, ListKeyboardDelegate, mergeProps, useFocusRing, useHover, useListBox, useListBoxSection, useLocale, useOption} from 'react-aria';
14
- import {CollectionProps, Document, ItemProps, useCachedChildren, useCollection, useCollectionPortal} from './Collection';
15
- import {ContextValue, forwardRefType, HiddenContext, Provider, SlotProps, StyleProps, StyleRenderProps, useContextProps, useRenderProps, useSlot} from './utils';
16
- import {DragAndDropHooks, DropIndicator, DropIndicatorContext, DropIndicatorProps} from './useDragAndDrop';
13
+ import {AriaListBoxOptions, AriaListBoxProps, DraggableItemResult, DragPreviewRenderer, DroppableCollectionResult, DroppableItemResult, FocusScope, ListKeyboardDelegate, mergeProps, useCollator, useFocusRing, useHover, useListBox, useListBoxSection, useLocale, useOption} from 'react-aria';
14
+ import {CollectionDocumentContext, CollectionPortal, CollectionProps, ItemProps, useCachedChildren, useCollection} from './Collection';
15
+ import {ContextValue, forwardRefType, HiddenContext, Provider, SlotProps, StyleProps, StyleRenderProps, useContextProps, useRenderProps, useSlot, useSlottedContext} from './utils';
16
+ import {DragAndDropContext, DragAndDropHooks, DropIndicator, DropIndicatorContext, DropIndicatorProps} from './useDragAndDrop';
17
17
  import {DraggableCollectionState, DroppableCollectionState, ListState, Node, Orientation, SelectionBehavior, useListState} from 'react-stately';
18
18
  import {filterDOMProps, mergeRefs, useObjectRef} from '@react-aria/utils';
19
19
  import {Header} from './Header';
@@ -73,26 +73,14 @@ export interface ListBoxProps<T> extends Omit<AriaListBoxProps<T>, 'children' |
73
73
  orientation?: Orientation
74
74
  }
75
75
 
76
- interface ListBoxContextValue<T> extends ListBoxProps<T> {
77
- state?: ListState<T>,
78
- document?: Document<any, any>
79
- }
80
-
81
- interface InternalListBoxContextValue {
82
- state: ListState<unknown>,
83
- shouldFocusOnHover?: boolean,
84
- dragAndDropHooks?: DragAndDropHooks,
85
- dragState?: DraggableCollectionState,
86
- dropState?: DroppableCollectionState
87
- }
88
-
89
- export const ListBoxContext = createContext<ContextValue<ListBoxContextValue<any>, HTMLDivElement>>(null);
90
- const InternalListBoxContext = createContext<InternalListBoxContextValue | null>(null);
76
+ export const ListBoxContext = createContext<ContextValue<ListBoxProps<any>, HTMLDivElement>>(null);
77
+ export const ListStateContext = createContext<ListState<any> | null>(null);
91
78
 
92
- function ListBox<T>(props: ListBoxProps<T>, ref: ForwardedRef<HTMLDivElement>) {
79
+ function ListBox<T extends object>(props: ListBoxProps<T>, ref: ForwardedRef<HTMLDivElement>) {
93
80
  [props, ref] = useContextProps(props, ref, ListBoxContext);
94
- let ctx = props as ListBoxContextValue<T>;
95
81
  let isHidden = useContext(HiddenContext);
82
+ let state = useContext(ListStateContext);
83
+ let document = useContext(CollectionDocumentContext);
96
84
 
97
85
  // The structure of ListBox is a bit strange because it needs to work inside other components like ComboBox and Select.
98
86
  // Those components render two copies of their children so that the collection can be built even when the popover is closed.
@@ -100,12 +88,12 @@ function ListBox<T>(props: ListBoxProps<T>, ref: ForwardedRef<HTMLDivElement>) {
100
88
  // The second copy sends a ListState object via context which we use to render the ListBox without rebuilding the state.
101
89
  // Otherwise, we have a standalone ListBox, so we need to create a collection and state ourselves.
102
90
 
103
- if (ctx.document) {
104
- return <ListBoxPortal {...props} />;
91
+ if (document) {
92
+ return <CollectionPortal {...props} />;
105
93
  }
106
94
 
107
- if (ctx.state) {
108
- return isHidden ? null : <ListBoxInner state={ctx.state} props={props} listBoxRef={ref} />;
95
+ if (state) {
96
+ return isHidden ? null : <ListBoxInner state={state} props={props} listBoxRef={ref} />;
109
97
  }
110
98
 
111
99
  return <StandaloneListBox props={props} listBoxRef={ref} />;
@@ -123,10 +111,6 @@ function StandaloneListBox({props, listBoxRef}) {
123
111
  );
124
112
  }
125
113
 
126
- function ListBoxPortal(props) {
127
- return <>{useCollectionPortal(props, props.document)}</>;
128
- }
129
-
130
114
  /**
131
115
  * A listbox displays a list of options and allows a user to select one or more of them.
132
116
  */
@@ -146,16 +130,18 @@ function ListBoxInner<T>({state, props, listBoxRef}: ListBoxInnerProps<T>) {
146
130
  let isListDroppable = !!dragAndDropHooks?.useDroppableCollectionState;
147
131
  let {direction} = useLocale();
148
132
  let {disabledBehavior, disabledKeys} = selectionManager;
133
+ let collator = useCollator({usage: 'search', sensitivity: 'base'});
149
134
  let keyboardDelegate = useMemo(() => (
150
135
  props.keyboardDelegate || new ListKeyboardDelegate({
151
136
  collection,
137
+ collator,
152
138
  ref: listBoxRef,
153
139
  disabledKeys: disabledBehavior === 'selection' ? new Set<React.Key>() : disabledKeys,
154
140
  layout,
155
141
  orientation,
156
142
  direction
157
143
  })
158
- ), [collection, listBoxRef, disabledBehavior, disabledKeys, orientation, direction, props.keyboardDelegate, layout]);
144
+ ), [collection, collator, listBoxRef, disabledBehavior, disabledKeys, orientation, direction, props.keyboardDelegate, layout]);
159
145
 
160
146
  let {listBoxProps} = useListBox({
161
147
  ...props,
@@ -260,7 +246,7 @@ function ListBoxInner<T>({state, props, listBoxRef}: ListBoxInnerProps<T>) {
260
246
  {...mergeProps(listBoxProps, focusProps, droppableCollection?.collectionProps)}
261
247
  {...renderProps}
262
248
  ref={listBoxRef}
263
- slot={props.slot}
249
+ slot={props.slot || undefined}
264
250
  data-drop-target={isRootDropTarget || undefined}
265
251
  data-empty={state.collection.size === 0 || undefined}
266
252
  data-focused={isFocused || undefined}
@@ -269,7 +255,9 @@ function ListBoxInner<T>({state, props, listBoxRef}: ListBoxInnerProps<T>) {
269
255
  data-orientation={props.orientation || 'vertical'}>
270
256
  <Provider
271
257
  values={[
272
- [InternalListBoxContext, {state, shouldFocusOnHover: props.shouldFocusOnHover, dragAndDropHooks, dragState, dropState}],
258
+ [ListBoxContext, props],
259
+ [ListStateContext, state],
260
+ [DragAndDropContext, {dragAndDropHooks, dragState, dropState}],
273
261
  [SeparatorContext, {elementType: 'div'}],
274
262
  [DropIndicatorContext, {render: ListBoxDropIndicatorWrapper}]
275
263
  ]}>
@@ -287,7 +275,7 @@ interface ListBoxSectionProps<T> extends StyleProps {
287
275
  }
288
276
 
289
277
  function ListBoxSection<T>({section, className, style}: ListBoxSectionProps<T>) {
290
- let {state} = useContext(InternalListBoxContext)!;
278
+ let state = useContext(ListStateContext)!;
291
279
  let [headingRef, heading] = useSlot();
292
280
  let {headingProps, groupProps} = useListBoxSection({
293
281
  heading,
@@ -344,8 +332,10 @@ interface OptionProps<T> {
344
332
  }
345
333
 
346
334
  function Option<T>({item}: OptionProps<T>) {
347
- let ref = useObjectRef<HTMLDivElement>(item.props.ref);
348
- let {state, shouldFocusOnHover, dragAndDropHooks, dragState, dropState} = useContext(InternalListBoxContext)!;
335
+ let ref = useObjectRef<any>(item.props.ref);
336
+ let state = useContext(ListStateContext)!;
337
+ let {shouldFocusOnHover} = useSlottedContext(ListBoxContext)! as AriaListBoxOptions<T>;
338
+ let {dragAndDropHooks, dragState, dropState} = useContext(DragAndDropContext)!;
349
339
  let {optionProps, labelProps, descriptionProps, ...states} = useOption(
350
340
  {key: item.key},
351
341
  state,
@@ -399,13 +389,15 @@ function Option<T>({item}: OptionProps<T>) {
399
389
  }
400
390
  }, [item.textValue]);
401
391
 
392
+ let ElementType: React.ElementType = props.href ? 'a' : 'div';
393
+
402
394
  return (
403
395
  <>
404
396
  {dragAndDropHooks?.useDropIndicator &&
405
397
  renderDropIndicator({type: 'item', key: item.key, dropPosition: 'before'})
406
398
  }
407
- <div
408
- {...mergeProps(filterDOMProps(props as any), optionProps, hoverProps, draggableItem?.dragProps, droppableItem?.dropProps)}
399
+ <ElementType
400
+ {...mergeProps(optionProps, hoverProps, draggableItem?.dragProps, droppableItem?.dropProps)}
409
401
  {...renderProps}
410
402
  ref={ref}
411
403
  data-allows-dragging={!!dragState || undefined}
@@ -429,7 +421,7 @@ function Option<T>({item}: OptionProps<T>) {
429
421
  ]}>
430
422
  {renderProps.children}
431
423
  </Provider>
432
- </div>
424
+ </ElementType>
433
425
  {dragAndDropHooks?.useDropIndicator && state.collection.getKeyAfter(item.key) == null &&
434
426
  renderDropIndicator({type: 'item', key: item.key, dropPosition: 'after'})
435
427
  }
@@ -439,7 +431,7 @@ function Option<T>({item}: OptionProps<T>) {
439
431
 
440
432
  function ListBoxDropIndicatorWrapper(props: DropIndicatorProps, ref: ForwardedRef<HTMLElement>) {
441
433
  ref = useObjectRef(ref);
442
- let {dragAndDropHooks, dropState} = useContext(InternalListBoxContext)!;
434
+ let {dragAndDropHooks, dropState} = useContext(DragAndDropContext)!;
443
435
  let {dropIndicatorProps, isHidden, isDropTarget} = dragAndDropHooks!.useDropIndicator!(
444
436
  props,
445
437
  dropState!,
package/src/Menu.tsx CHANGED
@@ -14,18 +14,19 @@
14
14
  import {AriaMenuProps, mergeProps, useFocusRing, useMenu, useMenuItem, useMenuSection, useMenuTrigger} from 'react-aria';
15
15
  import {BaseCollection, CollectionProps, ItemProps, useCachedChildren, useCollection} from './Collection';
16
16
  import {MenuTriggerProps as BaseMenuTriggerProps, Node, TreeState, useMenuTriggerState, useTreeState} from 'react-stately';
17
- import {ButtonContext} from './Button';
18
17
  import {ContextValue, forwardRefType, Provider, SlotProps, StyleProps, useContextProps, useRenderProps, useSlot} from './utils';
19
18
  import {filterDOMProps, mergeRefs, useObjectRef} from '@react-aria/utils';
20
19
  import {Header} from './Header';
21
20
  import {KeyboardContext} from './Keyboard';
21
+ import {OverlayTriggerStateContext} from './Dialog';
22
22
  import {PopoverContext} from './Popover';
23
+ import {PressResponder} from '@react-aria/interactions';
23
24
  import React, {createContext, ForwardedRef, forwardRef, ReactNode, RefObject, useContext, useRef} from 'react';
24
25
  import {Separator, SeparatorContext} from './Separator';
25
26
  import {TextContext} from './Text';
26
27
 
27
28
  export const MenuContext = createContext<ContextValue<MenuProps<any>, HTMLDivElement>>(null);
28
- const InternalMenuContext = createContext<TreeState<unknown> | null>(null);
29
+ export const MenuStateContext = createContext<TreeState<unknown> | null>(null);
29
30
 
30
31
  export interface MenuTriggerProps extends BaseMenuTriggerProps {
31
32
  children?: ReactNode
@@ -44,10 +45,12 @@ export function MenuTrigger(props: MenuTriggerProps) {
44
45
  <Provider
45
46
  values={[
46
47
  [MenuContext, menuProps],
47
- [ButtonContext, {...menuTriggerProps, ref, isPressed: state.isOpen}],
48
- [PopoverContext, {state, triggerRef: ref, placement: 'bottom start'}]
48
+ [OverlayTriggerStateContext, state],
49
+ [PopoverContext, {triggerRef: ref, placement: 'bottom start'}]
49
50
  ]}>
50
- {props.children}
51
+ <PressResponder {...menuTriggerProps} ref={ref} isPressed={state.isOpen}>
52
+ {props.children}
53
+ </PressResponder>
51
54
  </Provider>
52
55
  );
53
56
  }
@@ -102,12 +105,12 @@ function MenuInner<T extends object>({props, collection, menuRef: ref}: MenuInne
102
105
  {...filterDOMProps(props)}
103
106
  {...menuProps}
104
107
  ref={ref}
105
- slot={props.slot}
108
+ slot={props.slot || undefined}
106
109
  style={props.style}
107
110
  className={props.className ?? 'react-aria-Menu'}>
108
111
  <Provider
109
112
  values={[
110
- [InternalMenuContext, state],
113
+ [MenuStateContext, state],
111
114
  [SeparatorContext, {elementType: 'div'}]
112
115
  ]}>
113
116
  {children}
@@ -127,7 +130,7 @@ interface MenuSectionProps<T> extends StyleProps {
127
130
  }
128
131
 
129
132
  function MenuSection<T>({section, className, style, ...otherProps}: MenuSectionProps<T>) {
130
- let state = useContext(InternalMenuContext)!;
133
+ let state = useContext(MenuStateContext)!;
131
134
  let [headingRef, heading] = useSlot();
132
135
  let {headingProps, groupProps} = useMenuSection({
133
136
  heading,
@@ -174,8 +177,8 @@ interface MenuItemProps<T> {
174
177
  }
175
178
 
176
179
  function MenuItem<T>({item}: MenuItemProps<T>) {
177
- let state = useContext(InternalMenuContext)!;
178
- let ref = useObjectRef<HTMLDivElement>(item.props.ref);
180
+ let state = useContext(MenuStateContext)!;
181
+ let ref = useObjectRef<any>(item.props.ref);
179
182
  let {menuItemProps, labelProps, descriptionProps, keyboardShortcutProps, ...states} = useMenuItem({key: item.key}, state, ref);
180
183
 
181
184
  let props: ItemProps<T> = item.props;
@@ -194,12 +197,11 @@ function MenuItem<T>({item}: MenuItemProps<T>) {
194
197
  }
195
198
  });
196
199
 
197
- let DOMProps = filterDOMProps(props as any);
198
- delete DOMProps.id;
200
+ let ElementType: React.ElementType = props.href ? 'a' : 'div';
199
201
 
200
202
  return (
201
- <div
202
- {...mergeProps(DOMProps, menuItemProps, focusProps)}
203
+ <ElementType
204
+ {...mergeProps(menuItemProps, focusProps)}
203
205
  {...renderProps}
204
206
  ref={ref}
205
207
  data-disabled={states.isDisabled || undefined}
@@ -221,6 +223,6 @@ function MenuItem<T>({item}: MenuItemProps<T>) {
221
223
  ]}>
222
224
  {renderProps.children}
223
225
  </Provider>
224
- </div>
226
+ </ElementType>
225
227
  );
226
228
  }
package/src/Meter.tsx CHANGED
@@ -58,7 +58,7 @@ function Meter(props: MeterProps, ref: ForwardedRef<HTMLDivElement>) {
58
58
  });
59
59
 
60
60
  return (
61
- <div {...meterProps} {...renderProps} ref={ref} slot={props.slot}>
61
+ <div {...meterProps} {...renderProps} ref={ref} slot={props.slot || undefined}>
62
62
  <LabelContext.Provider value={{...labelProps, ref: labelRef, elementType: 'span'}}>
63
63
  {renderProps.children}
64
64
  </LabelContext.Provider>
package/src/Modal.tsx CHANGED
@@ -11,27 +11,23 @@
11
11
  */
12
12
 
13
13
  import {AriaModalOverlayProps, DismissButton, Overlay, useIsSSR, useModalOverlay} from 'react-aria';
14
- import {ContextValue, forwardRefType, RenderProps, SlotProps, useContextProps, useEnterAnimation, useExitAnimation, useRenderProps} from './utils';
14
+ import {ContextValue, forwardRefType, Provider, RenderProps, SlotProps, useContextProps, useEnterAnimation, useExitAnimation, useRenderProps} from './utils';
15
15
  import {DOMAttributes} from '@react-types/shared';
16
16
  import {filterDOMProps, mergeProps, mergeRefs, useObjectRef, useViewportSize} from '@react-aria/utils';
17
17
  import {OverlayTriggerProps, OverlayTriggerState, useOverlayTriggerState} from 'react-stately';
18
+ import {OverlayTriggerStateContext} from './Dialog';
18
19
  import React, {createContext, ForwardedRef, forwardRef, RefObject, useContext, useMemo, useRef} from 'react';
19
20
 
20
21
  export interface ModalOverlayProps extends AriaModalOverlayProps, OverlayTriggerProps, RenderProps<ModalRenderProps>, SlotProps {}
21
22
 
22
- interface ModalContextValue extends ModalOverlayProps {
23
- state?: OverlayTriggerState
24
- }
25
-
26
23
  interface InternalModalContextValue {
27
24
  modalProps: DOMAttributes,
28
25
  modalRef: RefObject<HTMLDivElement>,
29
26
  isExiting: boolean,
30
- isDismissable?: boolean,
31
- state: OverlayTriggerState
27
+ isDismissable?: boolean
32
28
  }
33
29
 
34
- export const ModalContext = createContext<ContextValue<ModalContextValue, HTMLDivElement>>(null);
30
+ export const ModalContext = createContext<ContextValue<ModalOverlayProps, HTMLDivElement>>(null);
35
31
  const InternalModalContext = createContext<InternalModalContextValue | null>(null);
36
32
 
37
33
  export interface ModalRenderProps {
@@ -97,9 +93,9 @@ export {_Modal as Modal};
97
93
 
98
94
  function ModalOverlayWithForwardRef(props: ModalOverlayProps, ref: ForwardedRef<HTMLDivElement>) {
99
95
  [props, ref] = useContextProps(props, ref, ModalContext);
100
- let ctx = props as ModalContextValue;
96
+ let contextState = useContext(OverlayTriggerStateContext);
101
97
  let localState = useOverlayTriggerState(props);
102
- let state = props.isOpen != null || props.defaultOpen != null || !ctx?.state ? localState : ctx.state;
98
+ let state = props.isOpen != null || props.defaultOpen != null || !contextState ? localState : contextState;
103
99
 
104
100
  let objectRef = useObjectRef(ref);
105
101
  let modalRef = useRef<HTMLDivElement>(null);
@@ -158,9 +154,13 @@ function ModalOverlayInner(props: ModalOverlayInnerProps) {
158
154
  ref={props.overlayRef}
159
155
  data-entering={entering || undefined}
160
156
  data-exiting={props.isExiting || undefined}>
161
- <InternalModalContext.Provider value={{modalProps, modalRef, state, isExiting: props.isExiting, isDismissable: props.isDismissable}}>
157
+ <Provider
158
+ values={[
159
+ [InternalModalContext, {modalProps, modalRef, isExiting: props.isExiting, isDismissable: props.isDismissable}],
160
+ [OverlayTriggerStateContext, state]
161
+ ]}>
162
162
  {renderProps.children}
163
- </InternalModalContext.Provider>
163
+ </Provider>
164
164
  </div>
165
165
  </Overlay>
166
166
  );
@@ -171,7 +171,8 @@ interface ModalContentProps extends RenderProps<ModalRenderProps> {
171
171
  }
172
172
 
173
173
  function ModalContent(props: ModalContentProps) {
174
- let {modalProps, modalRef, isExiting, isDismissable, state} = useContext(InternalModalContext)!;
174
+ let {modalProps, modalRef, isExiting, isDismissable} = useContext(InternalModalContext)!;
175
+ let state = useContext(OverlayTriggerStateContext)!;
175
176
  let mergedRefs = useMemo(() => mergeRefs(props.modalRef, modalRef), [props.modalRef, modalRef]);
176
177
 
177
178
  let ref = useObjectRef(mergedRefs);
@@ -12,7 +12,7 @@
12
12
 
13
13
  import {AriaNumberFieldProps, useLocale, useNumberField} from 'react-aria';
14
14
  import {ButtonContext} from './Button';
15
- import {ContextValue, forwardRefType, Provider, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
15
+ import {ContextValue, forwardRefType, Provider, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
16
16
  import {filterDOMProps} from '@react-aria/utils';
17
17
  import {GroupContext} from './Group';
18
18
  import {InputContext} from './Input';
@@ -42,6 +42,7 @@ export interface NumberFieldRenderProps {
42
42
  export interface NumberFieldProps extends Omit<AriaNumberFieldProps, 'label' | 'placeholder' | 'description' | 'errorMessage' | 'validationState'>, InputDOMProps, RenderProps<NumberFieldRenderProps>, SlotProps {}
43
43
 
44
44
  export const NumberFieldContext = createContext<ContextValue<NumberFieldProps, HTMLDivElement>>(null);
45
+ export const NumberFieldStateContext = createContext<NumberFieldState | null>(null);
45
46
 
46
47
  function NumberField(props: NumberFieldProps, ref: ForwardedRef<HTMLDivElement>) {
47
48
  [props, ref] = useContextProps(props, ref, NumberFieldContext);
@@ -57,7 +58,7 @@ function NumberField(props: NumberFieldProps, ref: ForwardedRef<HTMLDivElement>)
57
58
  decrementButtonProps,
58
59
  descriptionProps,
59
60
  errorMessageProps
60
- } = useNumberField({...props, label}, state, inputRef);
61
+ } = useNumberField({...removeDataAttributes(props), label}, state, inputRef);
61
62
 
62
63
  let renderProps = useRenderProps({
63
64
  ...props,
@@ -75,6 +76,7 @@ function NumberField(props: NumberFieldProps, ref: ForwardedRef<HTMLDivElement>)
75
76
  return (
76
77
  <Provider
77
78
  values={[
79
+ [NumberFieldStateContext, state],
78
80
  [GroupContext, groupProps],
79
81
  [InputContext, {...inputProps, ref: inputRef}],
80
82
  [LabelContext, {...labelProps, ref: labelRef}],
@@ -95,7 +97,7 @@ function NumberField(props: NumberFieldProps, ref: ForwardedRef<HTMLDivElement>)
95
97
  {...DOMProps}
96
98
  {...renderProps}
97
99
  ref={ref}
98
- slot={props.slot}
100
+ slot={props.slot || undefined}
99
101
  data-disabled={props.isDisabled || undefined}
100
102
  data-invalid={props.isInvalid || undefined} />
101
103
  {props.name && <input type="hidden" name={props.name} value={isNaN(state.numberValue) ? '' : state.numberValue} />}
@@ -10,18 +10,15 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {forwardRefType, RenderProps, useRenderProps} from './utils';
14
- import {mergeProps} from '@react-aria/utils';
13
+ import {ContextValue, forwardRefType, RenderProps, useContextProps, useRenderProps} from './utils';
15
14
  import {PlacementAxis} from 'react-aria';
16
- import React, {createContext, CSSProperties, ForwardedRef, forwardRef, HTMLAttributes, useContext} from 'react';
15
+ import React, {createContext, CSSProperties, ForwardedRef, forwardRef, HTMLAttributes} from 'react';
17
16
 
18
- interface OverlayArrowContextValue {
19
- arrowProps: HTMLAttributes<HTMLElement>,
17
+ interface OverlayArrowContextValue extends OverlayArrowProps {
20
18
  placement: PlacementAxis
21
19
  }
22
20
 
23
- export const OverlayArrowContext = createContext<OverlayArrowContextValue>({
24
- arrowProps: {},
21
+ export const OverlayArrowContext = createContext<ContextValue<OverlayArrowContextValue, HTMLDivElement>>({
25
22
  placement: 'bottom'
26
23
  });
27
24
 
@@ -36,9 +33,9 @@ export interface OverlayArrowRenderProps {
36
33
  }
37
34
 
38
35
  function OverlayArrow(props: OverlayArrowProps, ref: ForwardedRef<HTMLDivElement>) {
39
- let {arrowProps, placement} = useContext(OverlayArrowContext)!;
36
+ [props, ref] = useContextProps(props, ref, OverlayArrowContext);
37
+ let placement = (props as OverlayArrowContextValue).placement;
40
38
  let style: CSSProperties = {
41
- ...arrowProps.style,
42
39
  position: 'absolute',
43
40
  [placement]: '100%',
44
41
  transform: placement === 'top' || placement === 'bottom' ? 'translateX(-50%)' : 'translateY(-50%)'
@@ -54,7 +51,7 @@ function OverlayArrow(props: OverlayArrowProps, ref: ForwardedRef<HTMLDivElement
54
51
 
55
52
  return (
56
53
  <div
57
- {...mergeProps(arrowProps, props)}
54
+ {...props}
58
55
  {...renderProps}
59
56
  style={{
60
57
  ...renderProps.style,