@react-spectrum/list 3.0.0-beta.0 → 3.0.0-nightly-4980928d3-240906

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 (230) hide show
  1. package/dist/DragPreview.main.js +79 -0
  2. package/dist/DragPreview.main.js.map +1 -0
  3. package/dist/DragPreview.mjs +74 -0
  4. package/dist/DragPreview.module.js +74 -0
  5. package/dist/DragPreview.module.js.map +1 -0
  6. package/dist/InsertionIndicator.main.js +49 -0
  7. package/dist/InsertionIndicator.main.js.map +1 -0
  8. package/dist/InsertionIndicator.mjs +44 -0
  9. package/dist/InsertionIndicator.module.js +44 -0
  10. package/dist/InsertionIndicator.module.js.map +1 -0
  11. package/dist/ListView.main.js +299 -0
  12. package/dist/ListView.main.js.map +1 -0
  13. package/dist/ListView.mjs +293 -0
  14. package/dist/ListView.module.js +293 -0
  15. package/dist/ListView.module.js.map +1 -0
  16. package/dist/ListViewItem.main.js +252 -0
  17. package/dist/ListViewItem.main.js.map +1 -0
  18. package/dist/ListViewItem.mjs +247 -0
  19. package/dist/ListViewItem.module.js +247 -0
  20. package/dist/ListViewItem.module.js.map +1 -0
  21. package/dist/ListViewLayout.main.js +69 -0
  22. package/dist/ListViewLayout.main.js.map +1 -0
  23. package/dist/ListViewLayout.mjs +64 -0
  24. package/dist/ListViewLayout.module.js +64 -0
  25. package/dist/ListViewLayout.module.js.map +1 -0
  26. package/dist/RootDropIndicator.main.js +46 -0
  27. package/dist/RootDropIndicator.main.js.map +1 -0
  28. package/dist/RootDropIndicator.mjs +37 -0
  29. package/dist/RootDropIndicator.module.js +37 -0
  30. package/dist/RootDropIndicator.module.js.map +1 -0
  31. package/dist/ar-AE.main.js +7 -0
  32. package/dist/ar-AE.main.js.map +1 -0
  33. package/dist/ar-AE.mjs +9 -0
  34. package/dist/ar-AE.module.js +9 -0
  35. package/dist/ar-AE.module.js.map +1 -0
  36. package/dist/bg-BG.main.js +7 -0
  37. package/dist/bg-BG.main.js.map +1 -0
  38. package/dist/bg-BG.mjs +9 -0
  39. package/dist/bg-BG.module.js +9 -0
  40. package/dist/bg-BG.module.js.map +1 -0
  41. package/dist/cs-CZ.main.js +7 -0
  42. package/dist/cs-CZ.main.js.map +1 -0
  43. package/dist/cs-CZ.mjs +9 -0
  44. package/dist/cs-CZ.module.js +9 -0
  45. package/dist/cs-CZ.module.js.map +1 -0
  46. package/dist/da-DK.main.js +7 -0
  47. package/dist/da-DK.main.js.map +1 -0
  48. package/dist/da-DK.mjs +9 -0
  49. package/dist/da-DK.module.js +9 -0
  50. package/dist/da-DK.module.js.map +1 -0
  51. package/dist/de-DE.main.js +7 -0
  52. package/dist/de-DE.main.js.map +1 -0
  53. package/dist/de-DE.mjs +9 -0
  54. package/dist/de-DE.module.js +9 -0
  55. package/dist/de-DE.module.js.map +1 -0
  56. package/dist/el-GR.main.js +7 -0
  57. package/dist/el-GR.main.js.map +1 -0
  58. package/dist/el-GR.mjs +9 -0
  59. package/dist/el-GR.module.js +9 -0
  60. package/dist/el-GR.module.js.map +1 -0
  61. package/dist/en-US.main.js +7 -0
  62. package/dist/en-US.main.js.map +1 -0
  63. package/dist/en-US.mjs +9 -0
  64. package/dist/en-US.module.js +9 -0
  65. package/dist/en-US.module.js.map +1 -0
  66. package/dist/es-ES.main.js +7 -0
  67. package/dist/es-ES.main.js.map +1 -0
  68. package/dist/es-ES.mjs +9 -0
  69. package/dist/es-ES.module.js +9 -0
  70. package/dist/es-ES.module.js.map +1 -0
  71. package/dist/et-EE.main.js +7 -0
  72. package/dist/et-EE.main.js.map +1 -0
  73. package/dist/et-EE.mjs +9 -0
  74. package/dist/et-EE.module.js +9 -0
  75. package/dist/et-EE.module.js.map +1 -0
  76. package/dist/fi-FI.main.js +7 -0
  77. package/dist/fi-FI.main.js.map +1 -0
  78. package/dist/fi-FI.mjs +9 -0
  79. package/dist/fi-FI.module.js +9 -0
  80. package/dist/fi-FI.module.js.map +1 -0
  81. package/dist/fr-FR.main.js +7 -0
  82. package/dist/fr-FR.main.js.map +1 -0
  83. package/dist/fr-FR.mjs +9 -0
  84. package/dist/fr-FR.module.js +9 -0
  85. package/dist/fr-FR.module.js.map +1 -0
  86. package/dist/he-IL.main.js +7 -0
  87. package/dist/he-IL.main.js.map +1 -0
  88. package/dist/he-IL.mjs +9 -0
  89. package/dist/he-IL.module.js +9 -0
  90. package/dist/he-IL.module.js.map +1 -0
  91. package/dist/hr-HR.main.js +7 -0
  92. package/dist/hr-HR.main.js.map +1 -0
  93. package/dist/hr-HR.mjs +9 -0
  94. package/dist/hr-HR.module.js +9 -0
  95. package/dist/hr-HR.module.js.map +1 -0
  96. package/dist/hu-HU.main.js +7 -0
  97. package/dist/hu-HU.main.js.map +1 -0
  98. package/dist/hu-HU.mjs +9 -0
  99. package/dist/hu-HU.module.js +9 -0
  100. package/dist/hu-HU.module.js.map +1 -0
  101. package/dist/import.mjs +19 -0
  102. package/dist/intlStrings.main.js +108 -0
  103. package/dist/intlStrings.main.js.map +1 -0
  104. package/dist/intlStrings.mjs +110 -0
  105. package/dist/intlStrings.module.js +110 -0
  106. package/dist/intlStrings.module.js.map +1 -0
  107. package/dist/it-IT.main.js +7 -0
  108. package/dist/it-IT.main.js.map +1 -0
  109. package/dist/it-IT.mjs +9 -0
  110. package/dist/it-IT.module.js +9 -0
  111. package/dist/it-IT.module.js.map +1 -0
  112. package/dist/ja-JP.main.js +7 -0
  113. package/dist/ja-JP.main.js.map +1 -0
  114. package/dist/ja-JP.mjs +9 -0
  115. package/dist/ja-JP.module.js +9 -0
  116. package/dist/ja-JP.module.js.map +1 -0
  117. package/dist/ko-KR.main.js +7 -0
  118. package/dist/ko-KR.main.js.map +1 -0
  119. package/dist/ko-KR.mjs +9 -0
  120. package/dist/ko-KR.module.js +9 -0
  121. package/dist/ko-KR.module.js.map +1 -0
  122. package/dist/lt-LT.main.js +7 -0
  123. package/dist/lt-LT.main.js.map +1 -0
  124. package/dist/lt-LT.mjs +9 -0
  125. package/dist/lt-LT.module.js +9 -0
  126. package/dist/lt-LT.module.js.map +1 -0
  127. package/dist/lv-LV.main.js +7 -0
  128. package/dist/lv-LV.main.js.map +1 -0
  129. package/dist/lv-LV.mjs +9 -0
  130. package/dist/lv-LV.module.js +9 -0
  131. package/dist/lv-LV.module.js.map +1 -0
  132. package/dist/main.js +14 -962
  133. package/dist/main.js.map +1 -1
  134. package/dist/module.js +12 -964
  135. package/dist/module.js.map +1 -1
  136. package/dist/nb-NO.main.js +7 -0
  137. package/dist/nb-NO.main.js.map +1 -0
  138. package/dist/nb-NO.mjs +9 -0
  139. package/dist/nb-NO.module.js +9 -0
  140. package/dist/nb-NO.module.js.map +1 -0
  141. package/dist/nl-NL.main.js +7 -0
  142. package/dist/nl-NL.main.js.map +1 -0
  143. package/dist/nl-NL.mjs +9 -0
  144. package/dist/nl-NL.module.js +9 -0
  145. package/dist/nl-NL.module.js.map +1 -0
  146. package/dist/pl-PL.main.js +7 -0
  147. package/dist/pl-PL.main.js.map +1 -0
  148. package/dist/pl-PL.mjs +9 -0
  149. package/dist/pl-PL.module.js +9 -0
  150. package/dist/pl-PL.module.js.map +1 -0
  151. package/dist/pt-BR.main.js +7 -0
  152. package/dist/pt-BR.main.js.map +1 -0
  153. package/dist/pt-BR.mjs +9 -0
  154. package/dist/pt-BR.module.js +9 -0
  155. package/dist/pt-BR.module.js.map +1 -0
  156. package/dist/pt-PT.main.js +7 -0
  157. package/dist/pt-PT.main.js.map +1 -0
  158. package/dist/pt-PT.mjs +9 -0
  159. package/dist/pt-PT.module.js +9 -0
  160. package/dist/pt-PT.module.js.map +1 -0
  161. package/dist/ro-RO.main.js +7 -0
  162. package/dist/ro-RO.main.js.map +1 -0
  163. package/dist/ro-RO.mjs +9 -0
  164. package/dist/ro-RO.module.js +9 -0
  165. package/dist/ro-RO.module.js.map +1 -0
  166. package/dist/ru-RU.main.js +7 -0
  167. package/dist/ru-RU.main.js.map +1 -0
  168. package/dist/ru-RU.mjs +9 -0
  169. package/dist/ru-RU.module.js +9 -0
  170. package/dist/ru-RU.module.js.map +1 -0
  171. package/dist/sk-SK.main.js +7 -0
  172. package/dist/sk-SK.main.js.map +1 -0
  173. package/dist/sk-SK.mjs +9 -0
  174. package/dist/sk-SK.module.js +9 -0
  175. package/dist/sk-SK.module.js.map +1 -0
  176. package/dist/sl-SI.main.js +7 -0
  177. package/dist/sl-SI.main.js.map +1 -0
  178. package/dist/sl-SI.mjs +9 -0
  179. package/dist/sl-SI.module.js +9 -0
  180. package/dist/sl-SI.module.js.map +1 -0
  181. package/dist/sr-SP.main.js +7 -0
  182. package/dist/sr-SP.main.js.map +1 -0
  183. package/dist/sr-SP.mjs +9 -0
  184. package/dist/sr-SP.module.js +9 -0
  185. package/dist/sr-SP.module.js.map +1 -0
  186. package/dist/styles.d09a6cc6.css +839 -0
  187. package/dist/styles.d09a6cc6.css.map +1 -0
  188. package/dist/styles_css.main.js +197 -0
  189. package/dist/styles_css.main.js.map +1 -0
  190. package/dist/styles_css.mjs +199 -0
  191. package/dist/styles_css.module.js +199 -0
  192. package/dist/styles_css.module.js.map +1 -0
  193. package/dist/sv-SE.main.js +7 -0
  194. package/dist/sv-SE.main.js.map +1 -0
  195. package/dist/sv-SE.mjs +9 -0
  196. package/dist/sv-SE.module.js +9 -0
  197. package/dist/sv-SE.module.js.map +1 -0
  198. package/dist/tr-TR.main.js +7 -0
  199. package/dist/tr-TR.main.js.map +1 -0
  200. package/dist/tr-TR.mjs +9 -0
  201. package/dist/tr-TR.module.js +9 -0
  202. package/dist/tr-TR.module.js.map +1 -0
  203. package/dist/types.d.ts +33 -5
  204. package/dist/types.d.ts.map +1 -1
  205. package/dist/uk-UA.main.js +7 -0
  206. package/dist/uk-UA.main.js.map +1 -0
  207. package/dist/uk-UA.mjs +9 -0
  208. package/dist/uk-UA.module.js +9 -0
  209. package/dist/uk-UA.module.js.map +1 -0
  210. package/dist/zh-CN.main.js +7 -0
  211. package/dist/zh-CN.main.js.map +1 -0
  212. package/dist/zh-CN.mjs +9 -0
  213. package/dist/zh-CN.module.js +9 -0
  214. package/dist/zh-CN.module.js.map +1 -0
  215. package/dist/zh-TW.main.js +7 -0
  216. package/dist/zh-TW.main.js.map +1 -0
  217. package/dist/zh-TW.mjs +9 -0
  218. package/dist/zh-TW.module.js +9 -0
  219. package/dist/zh-TW.module.js.map +1 -0
  220. package/package.json +39 -43
  221. package/src/DragPreview.tsx +20 -8
  222. package/src/InsertionIndicator.tsx +3 -3
  223. package/src/ListView.tsx +187 -178
  224. package/src/ListViewItem.tsx +30 -17
  225. package/src/ListViewLayout.ts +66 -0
  226. package/src/RootDropIndicator.tsx +6 -7
  227. package/src/index.ts +13 -0
  228. package/src/styles.css +195 -68
  229. package/LICENSE +0 -201
  230. package/dist/main.css +0 -1
package/src/ListView.tsx CHANGED
@@ -9,42 +9,70 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
+
13
+ import {AriaGridListProps, useGridList} from '@react-aria/gridlist';
14
+ import {AsyncLoadable, DOMRef, Key, LoadingState, Node, SpectrumSelectionProps, StyleProps} from '@react-types/shared';
12
15
  import {classNames, useDOMRef, useStyleProps} from '@react-spectrum/utils';
13
- import {DOMRef, LoadingState} from '@react-types/shared';
16
+ import type {DragAndDropHooks} from '@react-spectrum/dnd';
14
17
  import type {DraggableCollectionState, DroppableCollectionState} from '@react-stately/dnd';
15
- import {DragHooks, DropHooks} from '@react-spectrum/dnd';
16
18
  import type {DroppableCollectionResult} from '@react-aria/dnd';
17
- import {filterDOMProps, useLayoutEffect} from '@react-aria/utils';
19
+ import {filterDOMProps, mergeProps, useLayoutEffect} from '@react-aria/utils';
20
+ import {FocusRing, FocusScope} from '@react-aria/focus';
18
21
  import InsertionIndicator from './InsertionIndicator';
19
22
  // @ts-ignore
20
23
  import intlMessages from '../intl/*.json';
21
- import {ListLayout} from '@react-stately/layout';
24
+ import {ListKeyboardDelegate} from '@react-aria/selection';
22
25
  import {ListState, useListState} from '@react-stately/list';
23
26
  import listStyles from './styles.css';
24
27
  import {ListViewItem} from './ListViewItem';
25
- import {mergeProps} from '@react-aria/utils';
28
+ import {ListViewLayout} from './ListViewLayout';
26
29
  import {ProgressCircle} from '@react-spectrum/progress';
27
- import React, {Key, ReactElement, useContext, useMemo, useRef, useState} from 'react';
28
- import {Rect} from '@react-stately/virtualizer';
30
+ import React, {JSX, ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
29
31
  import RootDropIndicator from './RootDropIndicator';
30
32
  import {DragPreview as SpectrumDragPreview} from './DragPreview';
31
- import {SpectrumListProps} from '@react-types/list';
32
- import {useCollator, useMessageFormatter} from '@react-aria/i18n';
33
- import {useList} from '@react-aria/list';
33
+ import {useLocalizedStringFormatter} from '@react-aria/i18n';
34
34
  import {useProvider} from '@react-spectrum/provider';
35
35
  import {Virtualizer} from '@react-aria/virtualizer';
36
36
 
37
+ export interface SpectrumListViewProps<T> extends Omit<AriaGridListProps<T>, 'keyboardNavigationBehavior'>, StyleProps, SpectrumSelectionProps, Omit<AsyncLoadable, 'isLoading'> {
38
+ /**
39
+ * Sets the amount of vertical padding within each cell.
40
+ * @default 'regular'
41
+ */
42
+ density?: 'compact' | 'regular' | 'spacious',
43
+ /** Whether the ListView should be displayed with a quiet style. */
44
+ isQuiet?: boolean,
45
+ /** The current loading state of the ListView. Determines whether or not the progress circle should be shown. */
46
+ loadingState?: LoadingState,
47
+ /**
48
+ * Sets the text behavior for the row contents.
49
+ * @default 'truncate'
50
+ */
51
+ overflowMode?: 'truncate' | 'wrap',
52
+ /** Sets what the ListView should render when there is no content to display. */
53
+ renderEmptyState?: () => JSX.Element,
54
+ /**
55
+ * Handler that is called when a user performs an action on an item. The exact user event depends on
56
+ * the collection's `selectionStyle` prop and the interaction modality.
57
+ */
58
+ onAction?: (key: Key) => void,
59
+ /**
60
+ * The drag and drop hooks returned by `useDragAndDrop` used to enable drag and drop behavior for the ListView.
61
+ */
62
+ dragAndDropHooks?: DragAndDropHooks['dragAndDropHooks']
63
+ }
64
+
37
65
  interface ListViewContextValue<T> {
38
66
  state: ListState<T>,
39
67
  dragState: DraggableCollectionState,
40
68
  dropState: DroppableCollectionState,
41
- dragHooks: DragHooks,
42
- dropHooks: DropHooks,
69
+ dragAndDropHooks: DragAndDropHooks['dragAndDropHooks'],
43
70
  onAction:(key: Key) => void,
44
71
  isListDraggable: boolean,
45
72
  isListDroppable: boolean,
46
- layout: ListLayout<T>,
47
- loadingState: LoadingState
73
+ layout: ListViewLayout<T>,
74
+ loadingState: LoadingState,
75
+ renderEmptyState?: () => JSX.Element
48
76
  }
49
77
 
50
78
  export const ListViewContext = React.createContext<ListViewContextValue<unknown>>(null);
@@ -64,156 +92,115 @@ const ROW_HEIGHTS = {
64
92
  }
65
93
  };
66
94
 
67
- function useListLayout<T>(state: ListState<T>, density: SpectrumListProps<T>['density'], overflowMode: SpectrumListProps<T>['overflowMode']) {
95
+ function useListLayout<T>(state: ListState<T>, density: SpectrumListViewProps<T>['density'], overflowMode: SpectrumListViewProps<T>['overflowMode']) {
68
96
  let {scale} = useProvider();
69
- let collator = useCollator({usage: 'search', sensitivity: 'base'});
70
- let isEmpty = state.collection.size === 0;
71
97
  let layout = useMemo(() =>
72
- new ListLayout<T>({
73
- estimatedRowHeight: ROW_HEIGHTS[density][scale],
74
- padding: 0,
75
- collator,
76
- loaderHeight: isEmpty ? null : ROW_HEIGHTS[density][scale]
98
+ new ListViewLayout<T>({
99
+ estimatedRowHeight: ROW_HEIGHTS[density][scale]
77
100
  })
78
101
  // eslint-disable-next-line react-hooks/exhaustive-deps
79
- , [collator, scale, density, isEmpty, overflowMode]);
102
+ , [scale, density, overflowMode]);
80
103
 
81
- layout.collection = state.collection;
82
- layout.disabledKeys = state.disabledKeys;
83
104
  return layout;
84
105
  }
85
106
 
86
- function ListView<T extends object>(props: SpectrumListProps<T>, ref: DOMRef<HTMLDivElement>) {
107
+ function ListView<T extends object>(props: SpectrumListViewProps<T>, ref: DOMRef<HTMLDivElement>) {
87
108
  let {
88
109
  density = 'regular',
89
- onLoadMore,
90
110
  loadingState,
111
+ onLoadMore,
91
112
  isQuiet,
92
113
  overflowMode = 'truncate',
93
114
  onAction,
94
- dragHooks,
95
- dropHooks,
115
+ dragAndDropHooks,
116
+ renderEmptyState,
96
117
  ...otherProps
97
118
  } = props;
98
- let isListDraggable = !!dragHooks;
99
- let isListDroppable = !!dropHooks;
119
+ let isListDraggable = !!dragAndDropHooks?.useDraggableCollectionState;
120
+ let isListDroppable = !!dragAndDropHooks?.useDroppableCollectionState;
100
121
  let dragHooksProvided = useRef(isListDraggable);
101
122
  let dropHooksProvided = useRef(isListDroppable);
102
- if (dragHooksProvided.current !== isListDraggable) {
103
- console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
104
- }
105
- if (dropHooksProvided.current !== isListDroppable) {
106
- console.warn('Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
107
- }
123
+ useEffect(() => {
124
+ if (dragHooksProvided.current !== isListDraggable) {
125
+ console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
126
+ }
127
+ if (dropHooksProvided.current !== isListDroppable) {
128
+ console.warn('Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
129
+ }
130
+ }, [isListDraggable, isListDroppable]);
131
+
108
132
  let domRef = useDOMRef(ref);
109
133
  let state = useListState({
110
134
  ...props,
111
135
  selectionBehavior: props.selectionStyle === 'highlight' ? 'replace' : 'toggle'
112
136
  });
113
137
  let {collection, selectionManager} = state;
114
- let formatMessage = useMessageFormatter(intlMessages);
115
138
  let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
116
139
 
117
140
  let {styleProps} = useStyleProps(props);
118
141
  let dragState: DraggableCollectionState;
119
142
  let preview = useRef(null);
120
143
  if (isListDraggable) {
121
- dragState = dragHooks.useDraggableCollectionState({
144
+ dragState = dragAndDropHooks.useDraggableCollectionState({
122
145
  collection,
123
146
  selectionManager,
124
147
  preview
125
148
  });
149
+ dragAndDropHooks.useDraggableCollection({}, dragState, domRef);
126
150
  }
127
151
  let layout = useListLayout(
128
152
  state,
129
153
  props.density || 'regular',
130
154
  overflowMode
131
155
  );
132
- // !!0 is false, so we can cast size or undefined and they'll be falsy
133
- layout.allowDisabledKeyFocus = state.selectionManager.disabledBehavior === 'selection' || !!dragState?.draggingKeys.size;
134
-
135
156
 
136
- let DragPreview = dragHooks?.DragPreview;
157
+ let DragPreview = dragAndDropHooks?.DragPreview;
137
158
  let dropState: DroppableCollectionState;
138
159
  let droppableCollection: DroppableCollectionResult;
139
160
  let isRootDropTarget: boolean;
140
161
  if (isListDroppable) {
141
- dropState = dropHooks.useDroppableCollectionState({
162
+ dropState = dragAndDropHooks.useDroppableCollectionState({
142
163
  collection,
143
164
  selectionManager
144
165
  });
145
- droppableCollection = dropHooks.useDroppableCollection({
146
- keyboardDelegate: layout,
147
- getDropTargetFromPoint(x, y) {
148
- let closest = null;
149
- let closestDistance = Infinity;
150
- let closestDir = null;
151
-
152
- x += domRef.current.scrollLeft;
153
- y += domRef.current.scrollTop;
154
-
155
- let visible = layout.getVisibleLayoutInfos(new Rect(x - 50, y - 50, x + 50, y + 50));
156
-
157
- for (let layoutInfo of visible) {
158
- let r = layoutInfo.rect;
159
- let points: [number, number, string][] = [
160
- [r.x, r.y + 4, 'before'],
161
- [r.maxX, r.y + 4, 'before'],
162
- [r.x, r.maxY - 8, 'after'],
163
- [r.maxX, r.maxY - 8, 'after']
164
- ];
165
-
166
- for (let [px, py, dir] of points) {
167
- let dx = px - x;
168
- let dy = py - y;
169
- let d = dx * dx + dy * dy;
170
- if (d < closestDistance) {
171
- closestDistance = d;
172
- closest = layoutInfo;
173
- closestDir = dir;
174
- }
175
- }
176
-
177
- // TODO: Best way to implement only for when closest can be dropped on
178
- // TODO: Figure out the typescript for this
179
- // @ts-ignore
180
- if (y >= r.y + 10 && y <= r.maxY - 10 && collection.getItem(closest.key).value.type === 'folder') {
181
- closestDir = 'on';
182
- }
183
- }
184
-
185
- let key = closest?.key;
186
- if (key) {
187
- return {
188
- type: 'item',
189
- key,
190
- dropPosition: closestDir
191
- };
192
- }
193
- }
166
+ droppableCollection = dragAndDropHooks.useDroppableCollection({
167
+ keyboardDelegate: new ListKeyboardDelegate({
168
+ collection,
169
+ disabledKeys: dragState?.draggingKeys.size ? null : selectionManager.disabledKeys,
170
+ ref: domRef,
171
+ layoutDelegate: layout
172
+ }),
173
+ dropTargetDelegate: layout
194
174
  }, dropState, domRef);
195
175
 
196
176
  isRootDropTarget = dropState.isDropTarget({type: 'root'});
197
177
  }
198
178
 
199
- let {gridProps} = useList({
179
+ let {gridProps} = useGridList({
200
180
  ...props,
201
181
  isVirtualized: true,
202
- keyboardDelegate: layout,
182
+ layoutDelegate: layout,
203
183
  onAction
204
184
  }, state, domRef);
205
185
 
206
- // Sync loading state into the layout.
207
- layout.isLoading = isLoading;
208
-
209
186
  let focusedKey = selectionManager.focusedKey;
187
+ let dropTargetKey: Key | null = null;
210
188
  if (dropState?.target?.type === 'item') {
211
- focusedKey = dropState.target.key;
189
+ dropTargetKey = dropState.target.key;
190
+ if (dropState.target.dropPosition === 'after') {
191
+ // Normalize to the "before" drop position since we only render those in the DOM.
192
+ dropTargetKey = state.collection.getKeyAfter(dropTargetKey) ?? dropTargetKey;
193
+ }
212
194
  }
213
195
 
196
+ let persistedKeys = useMemo(() => {
197
+ return new Set([focusedKey, dropTargetKey].filter(k => k !== null));
198
+ }, [focusedKey, dropTargetKey]);
199
+
214
200
  // wait for layout to get accurate measurements
215
201
  let [isVerticalScrollbarVisible, setVerticalScollbarVisible] = useState(false);
216
202
  let [isHorizontalScrollbarVisible, setHorizontalScollbarVisible] = useState(false);
203
+ // eslint-disable-next-line react-hooks/exhaustive-deps
217
204
  useLayoutEffect(() => {
218
205
  if (domRef.current) {
219
206
  // 2 is the width of the border which is not part of the box size
@@ -225,90 +212,63 @@ function ListView<T extends object>(props: SpectrumListProps<T>, ref: DOMRef<HTM
225
212
  let hasAnyChildren = useMemo(() => [...collection].some(item => item.hasChildNodes), [collection]);
226
213
 
227
214
  return (
228
- <ListViewContext.Provider value={{state, dragState, dropState, dragHooks, dropHooks, onAction, isListDraggable, isListDroppable, layout, loadingState}}>
229
- <Virtualizer
230
- {...mergeProps(isListDroppable && droppableCollection?.collectionProps, gridProps)}
231
- {...filterDOMProps(otherProps)}
232
- {...gridProps}
233
- {...styleProps}
234
- isLoading={isLoading}
235
- onLoadMore={onLoadMore}
236
- ref={domRef}
237
- focusedKey={focusedKey}
238
- scrollDirection="vertical"
239
- className={
240
- classNames(
241
- listStyles,
242
- 'react-spectrum-ListView',
243
- `react-spectrum-ListView--${density}`,
244
- 'react-spectrum-ListView--emphasized',
245
- {
246
- 'react-spectrum-ListView--quiet': isQuiet,
247
- 'react-spectrum-ListView--loadingMore': loadingState === 'loadingMore',
248
- 'react-spectrum-ListView--draggable': !!isListDraggable,
249
- 'react-spectrum-ListView--dropTarget': !!isRootDropTarget,
250
- 'react-spectrum-ListView--isVerticalScrollbarVisible': isVerticalScrollbarVisible,
251
- 'react-spectrum-ListView--isHorizontalScrollbarVisible': isHorizontalScrollbarVisible,
252
- 'react-spectrum-ListView--hasAnyChildren': hasAnyChildren,
253
- 'react-spectrum-ListView--wrap': overflowMode === 'wrap'
254
- },
255
- styleProps.className
256
- )
257
- }
258
- layout={layout}
259
- collection={collection}
260
- transitionDuration={isLoading ? 160 : 220}>
261
- {(type, item) => {
262
- if (type === 'item') {
263
- return (
264
- <>
265
- {isListDroppable && collection.getKeyBefore(item.key) == null &&
266
- <RootDropIndicator key="root" />
267
- }
268
- {isListDroppable &&
269
- <InsertionIndicator
270
- key={`${item.key}-before`}
271
- target={{key: item.key, type: 'item', dropPosition: 'before'}} />
272
- }
273
- <ListViewItem item={item} isEmphasized hasActions={!!onAction} />
274
- {isListDroppable &&
275
- <InsertionIndicator
276
- key={`${item.key}-after`}
277
- target={{key: item.key, type: 'item', dropPosition: 'after'}}
278
- isPresentationOnly={collection.getKeyAfter(item.key) != null} />
279
- }
280
- </>
281
- );
282
- } else if (type === 'loader') {
283
- return (
284
- <CenteredWrapper>
285
- <ProgressCircle
286
- isIndeterminate
287
- aria-label={collection.size > 0 ? formatMessage('loadingMore') : formatMessage('loading')} />
288
- </CenteredWrapper>
289
- );
290
- } else if (type === 'placeholder') {
291
- let emptyState = props.renderEmptyState ? props.renderEmptyState() : null;
292
- if (emptyState == null) {
293
- return null;
215
+ <ListViewContext.Provider value={{state, dragState, dropState, dragAndDropHooks, onAction, isListDraggable, isListDroppable, layout, loadingState, renderEmptyState}}>
216
+ <FocusScope>
217
+ <FocusRing focusRingClass={classNames(listStyles, 'focus-ring')}>
218
+ <Virtualizer
219
+ {...mergeProps(isListDroppable && droppableCollection?.collectionProps, gridProps)}
220
+ {...filterDOMProps(otherProps)}
221
+ {...gridProps}
222
+ {...styleProps}
223
+ isLoading={isLoading}
224
+ onLoadMore={onLoadMore}
225
+ ref={domRef}
226
+ persistedKeys={persistedKeys}
227
+ scrollDirection="vertical"
228
+ className={
229
+ classNames(
230
+ listStyles,
231
+ 'react-spectrum-ListView',
232
+ `react-spectrum-ListView--${density}`,
233
+ 'react-spectrum-ListView--emphasized',
234
+ {
235
+ 'react-spectrum-ListView--quiet': isQuiet,
236
+ 'react-spectrum-ListView--loadingMore': loadingState === 'loadingMore',
237
+ 'react-spectrum-ListView--draggable': !!isListDraggable,
238
+ 'react-spectrum-ListView--dropTarget': !!isRootDropTarget,
239
+ 'react-spectrum-ListView--isVerticalScrollbarVisible': isVerticalScrollbarVisible,
240
+ 'react-spectrum-ListView--isHorizontalScrollbarVisible': isHorizontalScrollbarVisible,
241
+ 'react-spectrum-ListView--hasAnyChildren': hasAnyChildren,
242
+ 'react-spectrum-ListView--wrap': overflowMode === 'wrap'
243
+ },
244
+ styleProps.className
245
+ )
294
246
  }
295
-
296
- return (
297
- <CenteredWrapper>
298
- {emptyState}
299
- </CenteredWrapper>
300
- );
301
- }
302
-
303
- }}
304
- </Virtualizer>
247
+ layout={layout}
248
+ layoutOptions={useMemo(() => ({isLoading}), [isLoading])}
249
+ collection={collection}>
250
+ {useCallback((type, item) => {
251
+ if (type === 'item') {
252
+ return <Item item={item} />;
253
+ } else if (type === 'loader') {
254
+ return <LoadingView />;
255
+ } else if (type === 'placeholder') {
256
+ return <EmptyState />;
257
+ }
258
+ }, [])}
259
+ </Virtualizer>
260
+ </FocusRing>
261
+ </FocusScope>
305
262
  {DragPreview && isListDraggable &&
306
263
  <DragPreview ref={preview}>
307
264
  {() => {
265
+ if (dragAndDropHooks.renderPreview) {
266
+ return dragAndDropHooks.renderPreview(dragState.draggingKeys, dragState.draggedKey);
267
+ }
308
268
  let item = state.collection.getItem(dragState.draggedKey);
309
269
  let itemCount = dragState.draggingKeys.size;
310
270
  let itemHeight = layout.getLayoutInfo(dragState.draggedKey).rect.height;
311
- return <SpectrumDragPreview item={item} itemCount={itemCount} itemHeight={itemHeight} />;
271
+ return <SpectrumDragPreview item={item} itemCount={itemCount} itemHeight={itemHeight} density={density} />;
312
272
  }}
313
273
  </DragPreview>
314
274
  }
@@ -316,6 +276,55 @@ function ListView<T extends object>(props: SpectrumListProps<T>, ref: DOMRef<HTM
316
276
  );
317
277
  }
318
278
 
279
+ function Item({item}: {item: Node<unknown>}) {
280
+ let {isListDroppable, state, onAction} = useContext(ListViewContext);
281
+ return (
282
+ <>
283
+ {isListDroppable && state.collection.getKeyBefore(item.key) == null &&
284
+ <RootDropIndicator key="root" />
285
+ }
286
+ {isListDroppable &&
287
+ <InsertionIndicator
288
+ key={`${item.key}-before`}
289
+ target={{key: item.key, type: 'item', dropPosition: 'before'}} />
290
+ }
291
+ <ListViewItem item={item} isEmphasized hasActions={!!onAction} />
292
+ {isListDroppable &&
293
+ <InsertionIndicator
294
+ key={`${item.key}-after`}
295
+ target={{key: item.key, type: 'item', dropPosition: 'after'}}
296
+ isPresentationOnly={state.collection.getKeyAfter(item.key) != null} />
297
+ }
298
+ </>
299
+ );
300
+ }
301
+
302
+ function LoadingView() {
303
+ let {state} = useContext(ListViewContext);
304
+ let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/list');
305
+ return (
306
+ <CenteredWrapper>
307
+ <ProgressCircle
308
+ isIndeterminate
309
+ aria-label={state.collection.size > 0 ? stringFormatter.format('loadingMore') : stringFormatter.format('loading')} />
310
+ </CenteredWrapper>
311
+ );
312
+ }
313
+
314
+ function EmptyState() {
315
+ let {renderEmptyState} = useContext(ListViewContext);
316
+ let emptyState = renderEmptyState ? renderEmptyState() : null;
317
+ if (emptyState == null) {
318
+ return null;
319
+ }
320
+
321
+ return (
322
+ <CenteredWrapper>
323
+ {emptyState}
324
+ </CenteredWrapper>
325
+ );
326
+ }
327
+
319
328
  function CenteredWrapper({children}) {
320
329
  let {state} = useContext(ListViewContext);
321
330
  return (
@@ -338,7 +347,7 @@ function CenteredWrapper({children}) {
338
347
  }
339
348
 
340
349
  /**
341
- * Lists display a linear collection of data. They allow users to quickly scan, sort, compare, and take action on large amounts of data.
350
+ * A ListView displays a list of interactive items, and allows a user to navigate, select, or perform an action.
342
351
  */
343
- const _ListView = React.forwardRef(ListView) as <T>(props: SpectrumListProps<T> & {ref?: DOMRef<HTMLDivElement>}) => ReactElement;
352
+ const _ListView = React.forwardRef(ListView) as <T>(props: SpectrumListViewProps<T> & {ref?: DOMRef<HTMLDivElement>}) => ReactElement;
344
353
  export {_ListView as ListView};
@@ -27,7 +27,7 @@ import {Provider} from '@react-spectrum/provider';
27
27
  import React, {useContext, useRef} from 'react';
28
28
  import {Text} from '@react-spectrum/text';
29
29
  import {useButton} from '@react-aria/button';
30
- import {useListItem, useListSelectionCheckbox} from '@react-aria/list';
30
+ import {useGridListItem, useGridListSelectionCheckbox} from '@react-aria/gridlist';
31
31
  import {useLocale} from '@react-aria/i18n';
32
32
  import {useVisuallyHidden} from '@react-aria/visually-hidden';
33
33
 
@@ -42,9 +42,19 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
42
42
  item,
43
43
  isEmphasized
44
44
  } = props;
45
- let {state, dragState, dropState, isListDraggable, isListDroppable, layout, dragHooks, dropHooks, loadingState} = useContext(ListViewContext);
45
+ let {
46
+ state,
47
+ dragState,
48
+ dropState,
49
+ isListDraggable,
50
+ isListDroppable,
51
+ layout,
52
+ dragAndDropHooks,
53
+ loadingState
54
+ } = useContext(ListViewContext);
46
55
  let {direction} = useLocale();
47
- let rowRef = useRef<HTMLDivElement>();
56
+ let rowRef = useRef<HTMLDivElement>(undefined);
57
+ let checkboxWrapperRef = useRef<HTMLDivElement>(undefined);
48
58
  let {
49
59
  isFocusVisible: isFocusVisibleWithin,
50
60
  focusProps: focusWithinProps
@@ -59,7 +69,7 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
59
69
  isDisabled,
60
70
  allowsSelection,
61
71
  hasAction
62
- } = useListItem({
72
+ } = useGridListItem({
63
73
  node: item,
64
74
  isVirtualized: true,
65
75
  shouldSelectOnPressUp: isListDraggable
@@ -67,13 +77,13 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
67
77
  let isDroppable = isListDroppable && !isDisabled;
68
78
  let {hoverProps, isHovered} = useHover({isDisabled: !allowsSelection && !hasAction});
69
79
 
70
- let {checkboxProps} = useListSelectionCheckbox({key: item.key}, state);
80
+ let {checkboxProps} = useGridListSelectionCheckbox({key: item.key}, state);
71
81
  let hasDescription = useHasChild(`.${listStyles['react-spectrum-ListViewItem-description']}`, rowRef);
72
82
 
73
83
  let draggableItem: DraggableItemResult;
74
84
  if (isListDraggable) {
75
85
  // eslint-disable-next-line react-hooks/rules-of-hooks
76
- draggableItem = dragHooks.useDraggableItem({key: item.key}, dragState);
86
+ draggableItem = dragAndDropHooks.useDraggableItem({key: item.key, hasDragButton: true}, dragState);
77
87
  if (isDisabled) {
78
88
  draggableItem = null;
79
89
  }
@@ -81,16 +91,15 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
81
91
  let droppableItem: DroppableItemResult;
82
92
  let isDropTarget: boolean;
83
93
  let dropIndicator: DropIndicatorAria;
84
- let dropIndicatorRef = useRef();
94
+ let dropIndicatorRef = useRef(undefined);
85
95
  if (isListDroppable) {
86
96
  let target = {type: 'item', key: item.key, dropPosition: 'on'} as DropTarget;
87
97
  isDropTarget = dropState.isDropTarget(target);
88
98
  // eslint-disable-next-line react-hooks/rules-of-hooks
89
- droppableItem = dropHooks.useDroppableItem({target}, dropState, rowRef);
90
- dropIndicator = dropHooks.useDropIndicator({target}, dropState, dropIndicatorRef);
99
+ dropIndicator = dragAndDropHooks.useDropIndicator({target}, dropState, dropIndicatorRef);
91
100
  }
92
101
 
93
- let dragButtonRef = React.useRef();
102
+ let dragButtonRef = React.useRef(undefined);
94
103
  let {buttonProps} = useButton({
95
104
  ...draggableItem?.dragButtonProps,
96
105
  elementType: 'div'
@@ -136,7 +145,10 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
136
145
  dropProps,
137
146
  hoverProps,
138
147
  focusWithinProps,
139
- focusProps
148
+ focusProps,
149
+ // Remove tab index from list row if performing a screenreader drag. This prevents TalkBack from focusing the row,
150
+ // allowing for single swipe navigation between row drop indicator
151
+ dragAndDropHooks?.isVirtualDragging() && {tabIndex: null}
140
152
  );
141
153
 
142
154
  let isFirstRow = item.prevKey == null;
@@ -146,7 +158,7 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
146
158
  // with bottom border
147
159
  let isFlushWithContainerBottom = false;
148
160
  if (isLastRow && loadingState !== 'loadingMore') {
149
- if (layout.getContentSize()?.height >= layout.virtualizer?.getVisibleRect().height) {
161
+ if (layout.getContentSize()?.height >= layout.virtualizer?.visibleRect.height) {
150
162
  isFlushWithContainerBottom = true;
151
163
  }
152
164
  }
@@ -226,7 +238,7 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
226
238
  }
227
239
  </div>
228
240
  }
229
- {isDropTarget && !dropIndicator?.dropIndicatorProps['aria-hidden'] &&
241
+ {isListDroppable && !dropIndicator?.isHidden &&
230
242
  <div role="button" {...visuallyHiddenProps} {...dropIndicator?.dropIndicatorProps} ref={dropIndicatorRef} />
231
243
  }
232
244
  <CSSTransition
@@ -238,8 +250,9 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
238
250
  exit: listStyles['react-spectrum-ListViewItem-checkbox--exit'],
239
251
  exitActive: listStyles['react-spectrum-ListViewItem-checkbox--exitActive']
240
252
  }}
241
- timeout={160} >
242
- <div className={listStyles['react-spectrum-ListViewItem-checkboxWrapper']}>
253
+ timeout={160}
254
+ nodeRef={checkboxWrapperRef} >
255
+ <div ref={checkboxWrapperRef} className={listStyles['react-spectrum-ListViewItem-checkboxWrapper']}>
243
256
  <Checkbox
244
257
  {...checkboxProps}
245
258
  UNSAFE_className={listStyles['react-spectrum-ListViewItem-checkbox']}
@@ -250,8 +263,8 @@ export function ListViewItem<T>(props: ListViewItemProps<T>) {
250
263
  slots={{
251
264
  text: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-content']},
252
265
  description: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-description'], ...descriptionProps},
253
- icon: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-icon'], size: 'M'},
254
- image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-image']},
266
+ illustration: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-thumbnail']},
267
+ image: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-thumbnail']},
255
268
  actionButton: {UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'], isQuiet: true},
256
269
  actionGroup: {
257
270
  UNSAFE_className: listStyles['react-spectrum-ListViewItem-actions'],