react-aria-components 1.12.2 → 1.13.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 (162) hide show
  1. package/dist/Autocomplete.main.js +3 -3
  2. package/dist/Autocomplete.main.js.map +1 -1
  3. package/dist/Autocomplete.mjs +3 -3
  4. package/dist/Autocomplete.module.js +3 -3
  5. package/dist/Autocomplete.module.js.map +1 -1
  6. package/dist/Button.main.js +5 -11
  7. package/dist/Button.main.js.map +1 -1
  8. package/dist/Button.mjs +5 -11
  9. package/dist/Button.module.js +5 -11
  10. package/dist/Button.module.js.map +1 -1
  11. package/dist/DateField.main.js +12 -8
  12. package/dist/DateField.main.js.map +1 -1
  13. package/dist/DateField.mjs +12 -8
  14. package/dist/DateField.module.js +12 -8
  15. package/dist/DateField.module.js.map +1 -1
  16. package/dist/DatePicker.main.js +6 -2
  17. package/dist/DatePicker.main.js.map +1 -1
  18. package/dist/DatePicker.mjs +6 -2
  19. package/dist/DatePicker.module.js +6 -2
  20. package/dist/DatePicker.module.js.map +1 -1
  21. package/dist/GridList.main.js +24 -21
  22. package/dist/GridList.main.js.map +1 -1
  23. package/dist/GridList.mjs +25 -23
  24. package/dist/GridList.module.js +25 -23
  25. package/dist/GridList.module.js.map +1 -1
  26. package/dist/Group.main.js +3 -2
  27. package/dist/Group.main.js.map +1 -1
  28. package/dist/Group.mjs +3 -2
  29. package/dist/Group.module.js +3 -2
  30. package/dist/Group.module.js.map +1 -1
  31. package/dist/Input.main.js.map +1 -1
  32. package/dist/Input.module.js.map +1 -1
  33. package/dist/ListBox.main.js +14 -4
  34. package/dist/ListBox.main.js.map +1 -1
  35. package/dist/ListBox.mjs +14 -4
  36. package/dist/ListBox.module.js +14 -4
  37. package/dist/ListBox.module.js.map +1 -1
  38. package/dist/Menu.main.js +16 -6
  39. package/dist/Menu.main.js.map +1 -1
  40. package/dist/Menu.mjs +16 -6
  41. package/dist/Menu.module.js +16 -6
  42. package/dist/Menu.module.js.map +1 -1
  43. package/dist/Modal.main.js +9 -1
  44. package/dist/Modal.main.js.map +1 -1
  45. package/dist/Modal.mjs +10 -2
  46. package/dist/Modal.module.js +10 -2
  47. package/dist/Modal.module.js.map +1 -1
  48. package/dist/NumberField.main.js +2 -1
  49. package/dist/NumberField.main.js.map +1 -1
  50. package/dist/NumberField.mjs +2 -1
  51. package/dist/NumberField.module.js +2 -1
  52. package/dist/NumberField.module.js.map +1 -1
  53. package/dist/ProgressBar.main.js.map +1 -1
  54. package/dist/ProgressBar.module.js.map +1 -1
  55. package/dist/RSPContexts.main.js +4 -0
  56. package/dist/RSPContexts.main.js.map +1 -1
  57. package/dist/RSPContexts.mjs +3 -1
  58. package/dist/RSPContexts.module.js +3 -1
  59. package/dist/RSPContexts.module.js.map +1 -1
  60. package/dist/RadioGroup.main.js +10 -2
  61. package/dist/RadioGroup.main.js.map +1 -1
  62. package/dist/RadioGroup.mjs +10 -2
  63. package/dist/RadioGroup.module.js +10 -2
  64. package/dist/RadioGroup.module.js.map +1 -1
  65. package/dist/SearchField.main.js +2 -2
  66. package/dist/SearchField.main.js.map +1 -1
  67. package/dist/SearchField.mjs +2 -2
  68. package/dist/SearchField.module.js +2 -2
  69. package/dist/SearchField.module.js.map +1 -1
  70. package/dist/Select.main.js +62 -22
  71. package/dist/Select.main.js.map +1 -1
  72. package/dist/Select.mjs +65 -25
  73. package/dist/Select.module.js +65 -25
  74. package/dist/Select.module.js.map +1 -1
  75. package/dist/SelectionIndicator.main.js +45 -0
  76. package/dist/SelectionIndicator.main.js.map +1 -0
  77. package/dist/SelectionIndicator.mjs +35 -0
  78. package/dist/SelectionIndicator.module.js +35 -0
  79. package/dist/SelectionIndicator.module.js.map +1 -0
  80. package/dist/SharedElementTransition.main.js +139 -0
  81. package/dist/SharedElementTransition.main.js.map +1 -0
  82. package/dist/SharedElementTransition.mjs +129 -0
  83. package/dist/SharedElementTransition.module.js +129 -0
  84. package/dist/SharedElementTransition.module.js.map +1 -0
  85. package/dist/Table.main.js +16 -11
  86. package/dist/Table.main.js.map +1 -1
  87. package/dist/Table.mjs +17 -12
  88. package/dist/Table.module.js +17 -12
  89. package/dist/Table.module.js.map +1 -1
  90. package/dist/Tabs.main.js +11 -3
  91. package/dist/Tabs.main.js.map +1 -1
  92. package/dist/Tabs.mjs +11 -3
  93. package/dist/Tabs.module.js +11 -3
  94. package/dist/Tabs.module.js.map +1 -1
  95. package/dist/TagGroup.main.js +28 -16
  96. package/dist/TagGroup.main.js.map +1 -1
  97. package/dist/TagGroup.mjs +28 -16
  98. package/dist/TagGroup.module.js +28 -16
  99. package/dist/TagGroup.module.js.map +1 -1
  100. package/dist/TextField.main.js +2 -2
  101. package/dist/TextField.main.js.map +1 -1
  102. package/dist/TextField.mjs +2 -2
  103. package/dist/TextField.module.js +2 -2
  104. package/dist/TextField.module.js.map +1 -1
  105. package/dist/ToggleButton.main.js +7 -1
  106. package/dist/ToggleButton.main.js.map +1 -1
  107. package/dist/ToggleButton.mjs +7 -1
  108. package/dist/ToggleButton.module.js +7 -1
  109. package/dist/ToggleButton.module.js.map +1 -1
  110. package/dist/ToggleButtonGroup.main.js +3 -1
  111. package/dist/ToggleButtonGroup.main.js.map +1 -1
  112. package/dist/ToggleButtonGroup.mjs +3 -1
  113. package/dist/ToggleButtonGroup.module.js +3 -1
  114. package/dist/ToggleButtonGroup.module.js.map +1 -1
  115. package/dist/Tree.main.js +16 -4
  116. package/dist/Tree.main.js.map +1 -1
  117. package/dist/Tree.mjs +16 -4
  118. package/dist/Tree.module.js +16 -4
  119. package/dist/Tree.module.js.map +1 -1
  120. package/dist/import.mjs +10 -4
  121. package/dist/main.js +18 -3
  122. package/dist/main.js.map +1 -1
  123. package/dist/module.js +10 -4
  124. package/dist/module.js.map +1 -1
  125. package/dist/types.d.ts +99 -24
  126. package/dist/types.d.ts.map +1 -1
  127. package/dist/utils.main.js.map +1 -1
  128. package/dist/utils.module.js.map +1 -1
  129. package/package.json +23 -23
  130. package/src/Autocomplete.tsx +1 -1
  131. package/src/Button.tsx +9 -11
  132. package/src/DateField.tsx +21 -12
  133. package/src/DatePicker.tsx +11 -2
  134. package/src/GridList.tsx +32 -33
  135. package/src/Group.tsx +5 -2
  136. package/src/Input.tsx +7 -1
  137. package/src/ListBox.tsx +12 -7
  138. package/src/Menu.tsx +11 -6
  139. package/src/Modal.tsx +11 -2
  140. package/src/NumberField.tsx +1 -1
  141. package/src/ProgressBar.tsx +1 -1
  142. package/src/RSPContexts.ts +19 -0
  143. package/src/RadioGroup.tsx +8 -2
  144. package/src/SearchField.tsx +1 -1
  145. package/src/Select.tsx +75 -34
  146. package/src/SelectionIndicator.tsx +40 -0
  147. package/src/SharedElementTransition.tsx +185 -0
  148. package/src/Table.tsx +17 -16
  149. package/src/Tabs.tsx +8 -2
  150. package/src/TagGroup.tsx +31 -24
  151. package/src/TextField.tsx +1 -1
  152. package/src/ToggleButton.tsx +6 -1
  153. package/src/ToggleButtonGroup.tsx +4 -1
  154. package/src/Tree.tsx +16 -9
  155. package/src/index.ts +10 -3
  156. package/src/utils.tsx +1 -1
  157. package/dist/context.main.js +0 -25
  158. package/dist/context.main.js.map +0 -1
  159. package/dist/context.mjs +0 -19
  160. package/dist/context.module.js +0 -19
  161. package/dist/context.module.js.map +0 -1
  162. package/src/context.tsx +0 -34
package/src/Select.tsx CHANGED
@@ -10,10 +10,10 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {AriaSelectProps, HiddenSelect, useFocusRing, useLocalizedStringFormatter, useSelect} from 'react-aria';
13
+ import {AriaSelectProps, HiddenSelect, useFocusRing, useListFormatter, useLocalizedStringFormatter, useSelect} from 'react-aria';
14
14
  import {ButtonContext} from './Button';
15
15
  import {Collection, Node, SelectState, useSelectState} from 'react-stately';
16
- import {CollectionBuilder} from '@react-aria/collections';
16
+ import {CollectionBuilder, createHideableComponent} from '@react-aria/collections';
17
17
  import {ContextValue, Provider, RACValidation, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot, useSlottedContext} from './utils';
18
18
  import {FieldErrorContext} from './FieldError';
19
19
  import {filterDOMProps, mergeProps, useResizeObserver} from '@react-aria/utils';
@@ -26,9 +26,11 @@ import {LabelContext} from './Label';
26
26
  import {ListBoxContext, ListStateContext} from './ListBox';
27
27
  import {OverlayTriggerStateContext} from './Dialog';
28
28
  import {PopoverContext} from './Popover';
29
- import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes, ReactNode, useCallback, useContext, useMemo, useRef, useState} from 'react';
29
+ import React, {createContext, ForwardedRef, forwardRef, Fragment, HTMLAttributes, ReactNode, useCallback, useContext, useMemo, useRef, useState} from 'react';
30
30
  import {TextContext} from './Text';
31
31
 
32
+ type SelectionMode = 'single' | 'multiple';
33
+
32
34
  export interface SelectRenderProps {
33
35
  /**
34
36
  * Whether the select is focused, either via a mouse or keyboard.
@@ -62,7 +64,7 @@ export interface SelectRenderProps {
62
64
  isRequired: boolean
63
65
  }
64
66
 
65
- export interface SelectProps<T extends object = {}> extends Omit<AriaSelectProps<T>, 'children' | 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior' | 'items'>, RACValidation, RenderProps<SelectRenderProps>, SlotProps, GlobalDOMAttributes<HTMLDivElement> {
67
+ export interface SelectProps<T extends object = {}, M extends SelectionMode = 'single'> extends Omit<AriaSelectProps<T, M>, 'children' | 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior' | 'items'>, RACValidation, RenderProps<SelectRenderProps>, SlotProps, GlobalDOMAttributes<HTMLDivElement> {
66
68
  /**
67
69
  * Temporary text that occupies the select when it is empty.
68
70
  * @default 'Select an item' (localized)
@@ -70,13 +72,13 @@ export interface SelectProps<T extends object = {}> extends Omit<AriaSelectProps
70
72
  placeholder?: string
71
73
  }
72
74
 
73
- export const SelectContext = createContext<ContextValue<SelectProps<any>, HTMLDivElement>>(null);
74
- export const SelectStateContext = createContext<SelectState<unknown> | null>(null);
75
+ export const SelectContext = createContext<ContextValue<SelectProps<any, SelectionMode>, HTMLDivElement>>(null);
76
+ export const SelectStateContext = createContext<SelectState<unknown, SelectionMode> | null>(null);
75
77
 
76
78
  /**
77
79
  * A select displays a collapsible list of options and allows a user to select one of them.
78
80
  */
79
- export const Select = /*#__PURE__*/ (forwardRef as forwardRefType)(function Select<T extends object = {}>(props: SelectProps<T>, ref: ForwardedRef<HTMLDivElement>) {
81
+ export const Select = /*#__PURE__*/ (forwardRef as forwardRefType)(function Select<T extends object = {}, M extends SelectionMode = 'single'>(props: SelectProps<T, M>, ref: ForwardedRef<HTMLDivElement>) {
80
82
  [props, ref] = useContextProps(props, ref, SelectContext);
81
83
  let {children, isDisabled = false, isInvalid = false, isRequired = false} = props;
82
84
  let content = useMemo(() => (
@@ -104,7 +106,7 @@ export const Select = /*#__PURE__*/ (forwardRef as forwardRefType)(function Sele
104
106
  const CLEAR_CONTEXTS = [LabelContext, ButtonContext, TextContext];
105
107
 
106
108
  interface SelectInnerProps<T extends object> {
107
- props: SelectProps<T>,
109
+ props: SelectProps<T, SelectionMode>,
108
110
  selectRef: ForwardedRef<HTMLDivElement>,
109
111
  collection: Collection<Node<T>>
110
112
  }
@@ -228,10 +230,17 @@ export interface SelectValueRenderProps<T> {
228
230
  * @selector [data-placeholder]
229
231
  */
230
232
  isPlaceholder: boolean,
231
- /** The object value of the currently selected item. */
233
+ /**
234
+ * The object value of the first selected item.
235
+ * @deprecated
236
+ */
232
237
  selectedItem: T | null,
233
- /** The textValue of the currently selected item. */
234
- selectedText: string | null
238
+ /** The object values of the currently selected items. */
239
+ selectedItems: (T | null)[],
240
+ /** The textValue of the currently selected items. */
241
+ selectedText: string,
242
+ /** The state of the select. */
243
+ state: SelectState<T, 'single' | 'multiple'>
235
244
  }
236
245
 
237
246
  export interface SelectValueProps<T extends object> extends Omit<HTMLAttributes<HTMLElement>, keyof RenderProps<unknown>>, RenderProps<SelectValueRenderProps<T>> {}
@@ -242,46 +251,78 @@ export const SelectValueContext = createContext<ContextValue<SelectValueProps<an
242
251
  * SelectValue renders the current value of a Select, or a placeholder if no value is selected.
243
252
  * It is usually placed within the button element.
244
253
  */
245
- export const SelectValue = /*#__PURE__*/ (forwardRef as forwardRefType)(function SelectValue<T extends object>(props: SelectValueProps<T>, ref: ForwardedRef<HTMLSpanElement>) {
254
+ export const SelectValue = /*#__PURE__*/ createHideableComponent(function SelectValue<T extends object>(props: SelectValueProps<T>, ref: ForwardedRef<HTMLSpanElement>) {
246
255
  [props, ref] = useContextProps(props, ref, SelectValueContext);
247
- let state = useContext(SelectStateContext)!;
256
+ let state = useContext(SelectStateContext)! as SelectState<T, 'single' | 'multiple'>;
248
257
  let {placeholder} = useSlottedContext(SelectContext)!;
249
- let selectedItem = state.selectedKey != null
250
- ? state.collection.getItem(state.selectedKey)
251
- : null;
252
- let rendered = selectedItem?.props.children;
253
- if (typeof rendered === 'function') {
258
+ let rendered = state.selectedItems.map((item) => {
259
+ let rendered = item.props?.children;
254
260
  // If the selected item has a function as a child, we need to call it to render to React.JSX.
255
- let fn = rendered as (s: ItemRenderProps) => ReactNode;
256
- rendered = fn({
257
- isHovered: false,
258
- isPressed: false,
259
- isSelected: false,
260
- isFocused: false,
261
- isFocusVisible: false,
262
- isDisabled: false,
263
- selectionMode: 'single',
264
- selectionBehavior: 'toggle'
261
+ if (typeof rendered === 'function') {
262
+ let fn = rendered as (s: ItemRenderProps) => ReactNode;
263
+ rendered = fn({
264
+ isHovered: false,
265
+ isPressed: false,
266
+ isSelected: false,
267
+ isFocused: false,
268
+ isFocusVisible: false,
269
+ isDisabled: false,
270
+ selectionMode: 'single',
271
+ selectionBehavior: 'toggle'
272
+ });
273
+ }
274
+
275
+ return rendered;
276
+ });
277
+
278
+ let formatter = useListFormatter();
279
+ let textValue = useMemo(() => state.selectedItems.map(item => item?.textValue), [state.selectedItems]);
280
+ let selectionMode = state.selectionManager.selectionMode;
281
+ let selectedText = useMemo(() => (
282
+ selectionMode === 'single'
283
+ ? textValue[0] ?? ''
284
+ : formatter.format(textValue)
285
+ ), [selectionMode, formatter, textValue]);
286
+
287
+ let defaultChildren = useMemo(() => {
288
+ if (selectionMode === 'single') {
289
+ return rendered[0];
290
+ }
291
+
292
+ let parts = formatter.formatToParts(textValue);
293
+ if (parts.length === 0) {
294
+ return null;
295
+ }
296
+
297
+ let index = 0;
298
+ return parts.map(part => {
299
+ if (part.type === 'element') {
300
+ return <Fragment key={index}>{rendered[index++]}</Fragment>;
301
+ } else {
302
+ return part.value;
303
+ }
265
304
  });
266
- }
305
+ }, [selectionMode, formatter, textValue, rendered]);
267
306
 
268
307
  let stringFormatter = useLocalizedStringFormatter(intlMessages, 'react-aria-components');
269
308
 
270
309
  let renderProps = useRenderProps({
271
310
  ...props,
272
- defaultChildren: rendered ?? placeholder ?? stringFormatter.format('selectPlaceholder'),
311
+ defaultChildren: defaultChildren ?? placeholder ?? stringFormatter.format('selectPlaceholder'),
273
312
  defaultClassName: 'react-aria-SelectValue',
274
313
  values: {
275
- selectedItem: state.selectedItem?.value as T ?? null,
276
- selectedText: state.selectedItem?.textValue ?? null,
277
- isPlaceholder: !selectedItem
314
+ selectedItem: state.selectedItems[0]?.value as T ?? null,
315
+ selectedItems: useMemo(() => state.selectedItems.map(item => item.value as T ?? null), [state.selectedItems]),
316
+ selectedText,
317
+ isPlaceholder: state.selectedItems.length === 0,
318
+ state
278
319
  }
279
320
  });
280
321
 
281
322
  let DOMProps = filterDOMProps(props, {global: true});
282
323
 
283
324
  return (
284
- <span ref={ref} {...DOMProps} {...renderProps} data-placeholder={!selectedItem || undefined}>
325
+ <span ref={ref} {...DOMProps} {...renderProps} data-placeholder={state.selectedItems.length === 0 || undefined}>
285
326
  {/* clear description and error message slots */}
286
327
  <TextContext.Provider value={undefined}>
287
328
  {renderProps.children}
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Copyright 2025 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {ContextValue, useContextProps} from './utils';
14
+ import React, {createContext, ForwardedRef, forwardRef} from 'react';
15
+ import {SharedElement, SharedElementPropsBase} from './SharedElementTransition';
16
+
17
+ export interface SelectionIndicatorProps extends SharedElementPropsBase {
18
+ isSelected?: boolean
19
+ }
20
+
21
+ export const SelectionIndicatorContext = createContext<ContextValue<SelectionIndicatorProps, HTMLDivElement>>({
22
+ isSelected: false
23
+ });
24
+
25
+
26
+ /**
27
+ * An animated indicator of selection state within a group of items.
28
+ */
29
+ export const SelectionIndicator = forwardRef(function SelectionIndicator(props: SelectionIndicatorProps, ref: ForwardedRef<HTMLDivElement>) {
30
+ [props, ref] = useContextProps(props, ref, SelectionIndicatorContext);
31
+ let {isSelected, ...otherProps} = props;
32
+ return (
33
+ <SharedElement
34
+ {...otherProps}
35
+ ref={ref}
36
+ className={props.className || 'react-aria-SelectionIndicator'}
37
+ name="SelectionIndicator"
38
+ isVisible={isSelected} />
39
+ );
40
+ });
@@ -0,0 +1,185 @@
1
+ /*
2
+ * Copyright 2025 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {flushSync} from 'react-dom';
14
+ import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes, ReactNode, RefObject, useContext, useRef, useState} from 'react';
15
+ import {RenderProps, useRenderProps} from './utils';
16
+ import {useLayoutEffect} from '@react-aria/utils';
17
+ import {useObjectRef} from 'react-aria';
18
+
19
+ interface Snapshot {
20
+ rect: DOMRect,
21
+ style: [string, string][]
22
+ }
23
+
24
+ const SharedElementContext = createContext<RefObject<{[name: string]: Snapshot}> | null>(null);
25
+
26
+ export interface SharedElementTransitionProps {
27
+ children: ReactNode
28
+ }
29
+
30
+ /**
31
+ * A scope for SharedElements, which animate between parents.
32
+ */
33
+ export function SharedElementTransition(props: SharedElementTransitionProps) {
34
+ let ref = useRef({});
35
+ return (
36
+ <SharedElementContext.Provider value={ref}>
37
+ {props.children}
38
+ </SharedElementContext.Provider>
39
+ );
40
+ }
41
+
42
+ export interface SharedElementRenderProps {
43
+ /**
44
+ * Whether the element is currently entering.
45
+ * @selector [data-entering]
46
+ */
47
+ isEntering: boolean,
48
+ /**
49
+ * Whether the element is currently exiting.
50
+ * @selector [data-exiting]
51
+ */
52
+ isExiting: boolean
53
+ }
54
+
55
+ export interface SharedElementPropsBase extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'className' | 'style'>, RenderProps<SharedElementRenderProps> {}
56
+
57
+ export interface SharedElementProps extends SharedElementPropsBase {
58
+ name: string,
59
+ isVisible?: boolean
60
+ }
61
+
62
+ /**
63
+ * An element that animates between its old and new position when moving between parents.
64
+ */
65
+ export const SharedElement = forwardRef(function SharedElement(props: SharedElementProps, ref: ForwardedRef<HTMLDivElement>) {
66
+ let {name, isVisible = true, children, className, style, ...divProps} = props;
67
+ let [state, setState] = useState(isVisible ? 'visible' : 'hidden');
68
+ let scopeRef = useContext(SharedElementContext);
69
+ if (!scopeRef) {
70
+ throw new Error('<SharedElement> must be rendered inside a <SharedElementTransition>');
71
+ }
72
+
73
+ if (isVisible && state === 'hidden') {
74
+ setState('visible');
75
+ }
76
+
77
+ ref = useObjectRef(ref);
78
+ useLayoutEffect(() => {
79
+ let element = ref.current;
80
+ let scope = scopeRef.current;
81
+ let prevSnapshot = scope[name];
82
+ let frame: number | null = null;
83
+
84
+ if (element && isVisible && prevSnapshot) {
85
+ // Element is transitioning from a previous instance.
86
+ setState('visible');
87
+ let animations = element.getAnimations();
88
+
89
+ // Set properties to animate from.
90
+ let values = prevSnapshot.style.map(([property, prevValue]) => {
91
+ let value = element.style[property];
92
+ if (property === 'translate') {
93
+ let prevRect = prevSnapshot.rect;
94
+ let currentItem = element.getBoundingClientRect();
95
+ let deltaX = prevRect.left - currentItem?.left;
96
+ let deltaY = prevRect.top - currentItem?.top;
97
+ element.style.translate = `${deltaX}px ${deltaY}px`;
98
+ } else {
99
+ element.style[property] = prevValue;
100
+ }
101
+ return [property, value];
102
+ });
103
+
104
+ // Cancel any new animations triggered by these properties.
105
+ for (let a of element.getAnimations()) {
106
+ if (!animations.includes(a)) {
107
+ a.cancel();
108
+ }
109
+ }
110
+
111
+ // Remove overrides after one frame to animate to the current values.
112
+ frame = requestAnimationFrame(() => {
113
+ frame = null;
114
+ for (let [property, value] of values) {
115
+ element.style[property] = value;
116
+ }
117
+ });
118
+
119
+ delete scope[name];
120
+ } else if (element && isVisible && !prevSnapshot) {
121
+ // No previous instance exists, apply the entering state.
122
+ queueMicrotask(() => flushSync(() => setState('entering')));
123
+ frame = requestAnimationFrame(() => {
124
+ frame = null;
125
+ setState('visible');
126
+ });
127
+ } else if (element && !isVisible) {
128
+ // Wait until layout effects finish, and check if a snapshot still exists.
129
+ // If so, no new SharedElement consumed it, so enter the exiting state.
130
+ queueMicrotask(() => {
131
+ if (scope[name]) {
132
+ delete scope[name];
133
+ flushSync(() => setState('exiting'));
134
+ Promise.all(element.getAnimations().map(a => a.finished))
135
+ .then(() => setState('hidden'))
136
+ .catch(() => {});
137
+ } else {
138
+ // Snapshot was consumed by another instance, unmount.
139
+ setState('hidden');
140
+ }
141
+ });
142
+ }
143
+
144
+ return () => {
145
+ if (frame != null) {
146
+ cancelAnimationFrame(frame);
147
+ }
148
+
149
+ if (element && element.isConnected && !element.hasAttribute('data-exiting')) {
150
+ // On unmount, store a snapshot of the rectangle and computed style for transitioning properties.
151
+ let style = window.getComputedStyle(element);
152
+ if (style.transitionProperty !== 'none') {
153
+ let transitionProperty = style.transitionProperty.split(/\s*,\s*/);
154
+ scope[name] = {
155
+ rect: element.getBoundingClientRect(),
156
+ style: transitionProperty.map(p => [p, style[p]])
157
+ };
158
+ }
159
+ }
160
+ };
161
+ }, [ref, scopeRef, name, isVisible]);
162
+
163
+ let renderProps = useRenderProps({
164
+ children,
165
+ className,
166
+ style,
167
+ values: {
168
+ isEntering: state === 'entering',
169
+ isExiting: state === 'exiting'
170
+ }
171
+ });
172
+
173
+ if (state === 'hidden') {
174
+ return null;
175
+ }
176
+
177
+ return (
178
+ <div
179
+ {...divProps}
180
+ {...renderProps}
181
+ ref={ref}
182
+ data-entering={state === 'entering' || undefined}
183
+ data-exiting={state === 'exiting' || undefined} />
184
+ );
185
+ });
package/src/Table.tsx CHANGED
@@ -2,7 +2,7 @@ import {AriaLabelingProps, GlobalDOMAttributes, HoverEvents, Key, LinkDOMProps,
2
2
  import {BaseCollection, Collection, CollectionBuilder, CollectionNode, createBranchComponent, createLeafComponent, FilterableNode, LoaderNode, useCachedChildren} from '@react-aria/collections';
3
3
  import {buildHeaderRows, TableColumnResizeState} from '@react-stately/table';
4
4
  import {ButtonContext} from './Button';
5
- import {CheckboxContext} from './RSPContexts';
5
+ import {CheckboxContext, FieldInputContext, SelectableCollectionContext, SelectableCollectionContextValue} from './RSPContexts';
6
6
  import {CollectionProps, CollectionRendererContext, DefaultCollectionRenderer, ItemRenderProps} from './Collection';
7
7
  import {ColumnSize, ColumnStaticSize, TableCollection as ITableCollection, TableProps as SharedTableProps} from '@react-types/table';
8
8
  import {ContextValue, DEFAULT_SLOT, DOMProps, Provider, RenderProps, SlotProps, StyleProps, StyleRenderProps, useContextProps, useRenderProps} from './utils';
@@ -10,13 +10,14 @@ import {DisabledBehavior, DraggableCollectionState, DroppableCollectionState, Mu
10
10
  import {DragAndDropContext, DropIndicatorContext, DropIndicatorProps, useDndPersistedKeys, useRenderDropIndicator} from './DragAndDrop';
11
11
  import {DragAndDropHooks} from './useDragAndDrop';
12
12
  import {DraggableItemResult, DragPreviewRenderer, DropIndicatorAria, DroppableCollectionResult, FocusScope, ListKeyboardDelegate, mergeProps, useFocusRing, useHover, useLocale, useLocalizedStringFormatter, useTable, useTableCell, useTableColumnHeader, useTableColumnResize, useTableHeaderRow, useTableRow, useTableRowGroup, useTableSelectAllCheckbox, useTableSelectionCheckbox, useVisuallyHidden} from 'react-aria';
13
- import {FieldInputContext, SelectableCollectionContext} from './context';
14
13
  import {filterDOMProps, inertValue, isScrollable, LoadMoreSentinelProps, mergeRefs, useLayoutEffect, useLoadMoreSentinel, useObjectRef, useResizeObserver} from '@react-aria/utils';
15
14
  import {GridNode} from '@react-types/grid';
16
15
  // @ts-ignore
17
16
  import intlMessages from '../intl/*.json';
18
17
  import React, {createContext, ForwardedRef, forwardRef, JSX, ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
19
18
  import ReactDOM from 'react-dom';
19
+ import {SelectionIndicatorContext} from './SelectionIndicator';
20
+ import {SharedElementTransition} from './SharedElementTransition';
20
21
 
21
22
  class TableCollection<T> extends BaseCollection<T> implements ITableCollection<T> {
22
23
  headerRows: GridNode<T>[] = [];
@@ -355,23 +356,21 @@ export const Table = forwardRef(function Table(props: TableProps, ref: Forwarded
355
356
  });
356
357
 
357
358
  interface TableInnerProps {
358
- props: TableProps,
359
- forwardedRef: ForwardedRef<HTMLTableElement>,
359
+ props: TableProps & SelectableCollectionContextValue<unknown>,
360
+ forwardedRef: ForwardedRef<HTMLElement>,
360
361
  selectionState: MultipleSelectionState,
361
362
  collection: ITableCollection<Node<object>>
362
363
  }
363
364
 
364
365
 
365
366
  function TableInner({props, forwardedRef: ref, selectionState, collection}: TableInnerProps) {
366
- let contextProps;
367
- [contextProps] = useContextProps({}, null, SelectableCollectionContext);
368
- let {filter, ...collectionProps} = contextProps;
367
+ [props, ref] = useContextProps(props, ref, SelectableCollectionContext);
369
368
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
370
- let {shouldUseVirtualFocus, disallowTypeAhead, ...DOMCollectionProps} = collectionProps || {};
369
+ let {shouldUseVirtualFocus, disallowTypeAhead, filter, ...DOMCollectionProps} = props;
371
370
  let tableContainerContext = useContext(ResizableTableContainerContext);
372
371
  ref = useObjectRef(useMemo(() => mergeRefs(ref, tableContainerContext?.tableRef), [ref, tableContainerContext?.tableRef]));
373
372
  let tableState = useTableState({
374
- ...props,
373
+ ...DOMCollectionProps,
375
374
  collection,
376
375
  children: undefined,
377
376
  UNSAFE_selectionState: selectionState
@@ -381,7 +380,6 @@ function TableInner({props, forwardedRef: ref, selectionState, collection}: Tabl
381
380
  let {isVirtualized, layoutDelegate, dropTargetDelegate: ctxDropTargetDelegate, CollectionRoot} = useContext(CollectionRendererContext);
382
381
  let {dragAndDropHooks} = props;
383
382
  let {gridProps} = useTable({
384
- ...props,
385
383
  ...DOMCollectionProps,
386
384
  layoutDelegate,
387
385
  isVirtualized
@@ -493,17 +491,19 @@ function TableInner({props, forwardedRef: ref, selectionState, collection}: Tabl
493
491
  <ElementType
494
492
  {...mergeProps(DOMProps, renderProps, gridProps, focusProps, droppableCollection?.collectionProps)}
495
493
  style={style}
496
- ref={ref}
494
+ ref={ref as RefObject<HTMLTableElement>}
497
495
  slot={props.slot || undefined}
498
496
  onScroll={props.onScroll}
499
497
  data-allows-dragging={isListDraggable || undefined}
500
498
  data-drop-target={isRootDropTarget || undefined}
501
499
  data-focused={isFocused || undefined}
502
500
  data-focus-visible={isFocusVisible || undefined}>
503
- <CollectionRoot
504
- collection={filteredState.collection}
505
- scrollRef={tableContainerContext?.scrollRef ?? ref}
506
- persistedKeys={useDndPersistedKeys(selectionManager, dragAndDropHooks, dropState)} />
501
+ <SharedElementTransition>
502
+ <CollectionRoot
503
+ collection={filteredState.collection}
504
+ scrollRef={tableContainerContext?.scrollRef ?? ref}
505
+ persistedKeys={useDndPersistedKeys(selectionManager, dragAndDropHooks, dropState)} />
506
+ </SharedElementTransition>
507
507
  </ElementType>
508
508
  </FocusScope>
509
509
  {dragPreview}
@@ -1182,7 +1182,8 @@ export const Row = /*#__PURE__*/ createBranchComponent(
1182
1182
  }
1183
1183
  }
1184
1184
  }
1185
- }]
1185
+ }],
1186
+ [SelectionIndicatorContext, {isSelected: states.isSelected}]
1186
1187
  ]}>
1187
1188
  <CollectionBranch collection={state.collection} parent={item} />
1188
1189
  </Provider>
package/src/Tabs.tsx CHANGED
@@ -18,6 +18,8 @@ import {ContextValue, Provider, RenderProps, SlotProps, StyleRenderProps, useCon
18
18
  import {filterDOMProps, inertValue, useObjectRef} from '@react-aria/utils';
19
19
  import {Collection as ICollection, Node, TabListState, useTabListState} from 'react-stately';
20
20
  import React, {createContext, ForwardedRef, forwardRef, JSX, useContext, useMemo} from 'react';
21
+ import {SelectionIndicatorContext} from './SelectionIndicator';
22
+ import {SharedElementTransition} from './SharedElementTransition';
21
23
 
22
24
  export interface TabsProps extends Omit<AriaTabListProps<any>, 'items' | 'children'>, RenderProps<TabsRenderProps>, SlotProps, GlobalDOMAttributes<HTMLDivElement> {}
23
25
 
@@ -230,7 +232,9 @@ function TabListInner<T extends object>({props, forwardedRef: ref}: TabListInner
230
232
  {...mergeProps(DOMProps, renderProps, tabListProps)}
231
233
  ref={objectRef}
232
234
  data-orientation={orientation || undefined}>
233
- <CollectionRoot collection={state.collection} persistedKeys={usePersistedKeys(state.selectionManager.focusedKey)} />
235
+ <SharedElementTransition>
236
+ <CollectionRoot collection={state.collection} persistedKeys={usePersistedKeys(state.selectionManager.focusedKey)} />
237
+ </SharedElementTransition>
234
238
  </div>
235
239
  );
236
240
  }
@@ -284,7 +288,9 @@ export const Tab = /*#__PURE__*/ createLeafComponent(TabItemNode, (props: TabPro
284
288
  data-focus-visible={isFocusVisible || undefined}
285
289
  data-pressed={isPressed || undefined}
286
290
  data-hovered={isHovered || undefined}>
287
- {renderProps.children}
291
+ <SelectionIndicatorContext.Provider value={{isSelected}}>
292
+ {renderProps.children}
293
+ </SelectionIndicatorContext.Provider>
288
294
  </ElementType>
289
295
  );
290
296
  });
package/src/TagGroup.tsx CHANGED
@@ -16,12 +16,14 @@ import {Collection, CollectionBuilder, createLeafComponent, ItemNode} from '@rea
16
16
  import {CollectionProps, CollectionRendererContext, DefaultCollectionRenderer, ItemRenderProps, usePersistedKeys} from './Collection';
17
17
  import {ContextValue, DOMProps, Provider, RenderProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlot} from './utils';
18
18
  import {filterDOMProps, mergeProps, useObjectRef} from '@react-aria/utils';
19
- import {forwardRefType, GlobalDOMAttributes, HoverEvents, Key, LinkDOMProps, PressEvents} from '@react-types/shared';
19
+ import {forwardRefType, GlobalDOMAttributes, HoverEvents, Key, LinkDOMProps, PressEvents, RefObject} from '@react-types/shared';
20
20
  import {LabelContext} from './Label';
21
21
  import {ListState, Node, UNSTABLE_useFilteredListState, useListState} from 'react-stately';
22
22
  import {ListStateContext} from './ListBox';
23
23
  import React, {createContext, ForwardedRef, forwardRef, JSX, ReactNode, useContext, useEffect, useRef} from 'react';
24
- import {SelectableCollectionContext} from './context';
24
+ import {SelectableCollectionContext, SelectableCollectionContextValue} from './RSPContexts';
25
+ import {SelectionIndicatorContext} from './SelectionIndicator';
26
+ import {SharedElementTransition} from './SharedElementTransition';
25
27
  import {TextContext} from './Text';
26
28
 
27
29
  export interface TagGroupProps extends Omit<AriaTagGroupProps<unknown>, 'children' | 'items' | 'label' | 'description' | 'errorMessage' | 'keyboardDelegate'>, DOMProps, SlotProps, GlobalDOMAttributes<HTMLDivElement> {}
@@ -62,54 +64,56 @@ export const TagListContext = createContext<ContextValue<TagListProps<any>, HTML
62
64
  export const TagGroup = /*#__PURE__*/ (forwardRef as forwardRefType)(function TagGroup(props: TagGroupProps, ref: ForwardedRef<HTMLDivElement>) {
63
65
  [props, ref] = useContextProps(props, ref, TagGroupContext);
64
66
  return (
65
- <CollectionBuilder content={props.children}>
66
- {collection => <TagGroupInner props={props} forwardedRef={ref} collection={collection} />}
67
- </CollectionBuilder>
67
+ <ListStateContext.Provider value={null}>
68
+ <CollectionBuilder content={props.children}>
69
+ {collection => <TagGroupInner props={props} forwardedRef={ref} collection={collection} />}
70
+ </CollectionBuilder>
71
+ </ListStateContext.Provider>
68
72
  );
69
73
  });
70
74
 
71
- interface TagGroupInnerProps {
72
- props: TagGroupProps,
75
+ interface TagGroupInnerProps<T> {
76
+ props: TagGroupProps & SelectableCollectionContextValue<T>,
73
77
  forwardedRef: ForwardedRef<HTMLDivElement>,
74
78
  collection
75
79
  }
76
80
 
77
- function TagGroupInner({props, forwardedRef: ref, collection}: TagGroupInnerProps) {
78
- let contextProps;
79
- [contextProps] = useContextProps({}, null, SelectableCollectionContext);
80
- let {filter, ...collectionProps} = contextProps;
81
+ function TagGroupInner<T extends object>({props, forwardedRef: ref, collection}: TagGroupInnerProps<T>) {
82
+ let tagListRef = useRef<HTMLElement>(null);
83
+ // Extract the user provided id so it doesn't clash with the collection id provided by Autocomplete
84
+ let {id, ...otherProps} = props;
85
+ [otherProps, tagListRef] = useContextProps(otherProps, tagListRef, SelectableCollectionContext);
81
86
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
82
- let {shouldUseVirtualFocus, disallowTypeAhead, ...DOMCollectionProps} = collectionProps || {};
83
- let tagListRef = useRef<HTMLDivElement>(null);
87
+ let {filter, shouldUseVirtualFocus, ...DOMCollectionProps} = otherProps;
84
88
  let [labelRef, label] = useSlot(
85
89
  !props['aria-label'] && !props['aria-labelledby']
86
90
  );
87
91
  let tagGroupState = useListState({
88
- ...props,
92
+ ...DOMCollectionProps,
89
93
  children: undefined,
90
94
  collection
91
95
  });
92
96
 
93
- let filteredState = UNSTABLE_useFilteredListState(tagGroupState, filter);
97
+ let filteredState = UNSTABLE_useFilteredListState(tagGroupState as ListState<T>, filter);
94
98
 
95
99
  // Prevent DOM props from going to two places.
96
- let domProps = filterDOMProps(props, {global: true});
97
- let domPropOverrides = Object.fromEntries(Object.entries(domProps).map(([k]) => [k, undefined]));
100
+ let domProps = filterDOMProps(otherProps, {global: true});
101
+ let domPropOverrides = Object.fromEntries(Object.entries(domProps).map(([k, val]) => [k, k === 'id' ? val : undefined]));
98
102
  let {
99
103
  gridProps,
100
104
  labelProps,
101
105
  descriptionProps,
102
106
  errorMessageProps
103
107
  } = useTagGroup({
104
- ...props,
105
- ...domPropOverrides,
106
108
  ...DOMCollectionProps,
109
+ ...domPropOverrides,
107
110
  label
108
111
  }, filteredState, tagListRef);
109
112
 
110
113
  return (
111
114
  <div
112
115
  {...domProps}
116
+ id={id}
113
117
  ref={ref}
114
118
  slot={props.slot || undefined}
115
119
  className={props.className ?? 'react-aria-TagGroup'}
@@ -117,7 +121,7 @@ function TagGroupInner({props, forwardedRef: ref, collection}: TagGroupInnerProp
117
121
  <Provider
118
122
  values={[
119
123
  [LabelContext, {...labelProps, elementType: 'span', ref: labelRef}],
120
- [TagListContext, {...gridProps, ref: tagListRef}],
124
+ [TagListContext, {...gridProps, ref: tagListRef as RefObject<HTMLDivElement>}],
121
125
  [ListStateContext, filteredState],
122
126
  [TextContext, {
123
127
  slots: {
@@ -176,9 +180,11 @@ function TagListInner<T extends object>({props, forwardedRef}: TagListInnerProps
176
180
  data-empty={state.collection.size === 0 || undefined}
177
181
  data-focused={isFocused || undefined}
178
182
  data-focus-visible={isFocusVisible || undefined}>
179
- {state.collection.size === 0 && props.renderEmptyState
180
- ? props.renderEmptyState(renderValues)
181
- : <CollectionRoot collection={state.collection} persistedKeys={persistedKeys} />}
183
+ <SharedElementTransition>
184
+ {state.collection.size === 0 && props.renderEmptyState
185
+ ? props.renderEmptyState(renderValues)
186
+ : <CollectionRoot collection={state.collection} persistedKeys={persistedKeys} />}
187
+ </SharedElementTransition>
182
188
  </div>
183
189
  );
184
190
  }
@@ -263,7 +269,8 @@ export const Tag = /*#__PURE__*/ createLeafComponent(ItemNode, (props: TagProps,
263
269
  remove: removeButtonProps
264
270
  }
265
271
  }],
266
- [CollectionRendererContext, DefaultCollectionRenderer]
272
+ [CollectionRendererContext, DefaultCollectionRenderer],
273
+ [SelectionIndicatorContext, {isSelected: states.isSelected}]
267
274
  ]}>
268
275
  {renderProps.children}
269
276
  </Provider>