@react-spectrum/table 3.0.0-beta.0 → 3.0.0-nightly-641446f65-240905

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 (266) hide show
  1. package/dist/DragPreview.main.js +54 -0
  2. package/dist/DragPreview.main.js.map +1 -0
  3. package/dist/DragPreview.mjs +49 -0
  4. package/dist/DragPreview.module.js +49 -0
  5. package/dist/DragPreview.module.js.map +1 -0
  6. package/dist/InsertionIndicator.main.js +63 -0
  7. package/dist/InsertionIndicator.main.js.map +1 -0
  8. package/dist/InsertionIndicator.mjs +58 -0
  9. package/dist/InsertionIndicator.module.js +58 -0
  10. package/dist/InsertionIndicator.module.js.map +1 -0
  11. package/dist/Nubbin.main.js +62 -0
  12. package/dist/Nubbin.main.js.map +1 -0
  13. package/dist/Nubbin.mjs +53 -0
  14. package/dist/Nubbin.module.js +53 -0
  15. package/dist/Nubbin.module.js.map +1 -0
  16. package/dist/Resizer.main.js +150 -0
  17. package/dist/Resizer.main.js.map +1 -0
  18. package/dist/Resizer.mjs +144 -0
  19. package/dist/Resizer.module.js +144 -0
  20. package/dist/Resizer.module.js.map +1 -0
  21. package/dist/RootDropIndicator.main.js +57 -0
  22. package/dist/RootDropIndicator.main.js.map +1 -0
  23. package/dist/RootDropIndicator.mjs +48 -0
  24. package/dist/RootDropIndicator.module.js +48 -0
  25. package/dist/RootDropIndicator.module.js.map +1 -0
  26. package/dist/TableView.main.js +50 -0
  27. package/dist/TableView.main.js.map +1 -0
  28. package/dist/TableView.mjs +41 -0
  29. package/dist/TableView.module.js +41 -0
  30. package/dist/TableView.module.js.map +1 -0
  31. package/dist/TableViewBase.main.js +1238 -0
  32. package/dist/TableViewBase.main.js.map +1 -0
  33. package/dist/TableViewBase.mjs +1231 -0
  34. package/dist/TableViewBase.module.js +1231 -0
  35. package/dist/TableViewBase.module.js.map +1 -0
  36. package/dist/TableViewLayout.main.js +97 -0
  37. package/dist/TableViewLayout.main.js.map +1 -0
  38. package/dist/TableViewLayout.mjs +92 -0
  39. package/dist/TableViewLayout.module.js +92 -0
  40. package/dist/TableViewLayout.module.js.map +1 -0
  41. package/dist/TableViewWrapper.main.js +46 -0
  42. package/dist/TableViewWrapper.main.js.map +1 -0
  43. package/dist/TableViewWrapper.mjs +37 -0
  44. package/dist/TableViewWrapper.module.js +37 -0
  45. package/dist/TableViewWrapper.module.js.map +1 -0
  46. package/dist/TreeGridTableView.main.js +50 -0
  47. package/dist/TreeGridTableView.main.js.map +1 -0
  48. package/dist/TreeGridTableView.mjs +41 -0
  49. package/dist/TreeGridTableView.module.js +41 -0
  50. package/dist/TreeGridTableView.module.js.map +1 -0
  51. package/dist/ar-AE.main.js +14 -0
  52. package/dist/ar-AE.main.js.map +1 -0
  53. package/dist/ar-AE.mjs +16 -0
  54. package/dist/ar-AE.module.js +16 -0
  55. package/dist/ar-AE.module.js.map +1 -0
  56. package/dist/bg-BG.main.js +14 -0
  57. package/dist/bg-BG.main.js.map +1 -0
  58. package/dist/bg-BG.mjs +16 -0
  59. package/dist/bg-BG.module.js +16 -0
  60. package/dist/bg-BG.module.js.map +1 -0
  61. package/dist/cs-CZ.main.js +14 -0
  62. package/dist/cs-CZ.main.js.map +1 -0
  63. package/dist/cs-CZ.mjs +16 -0
  64. package/dist/cs-CZ.module.js +16 -0
  65. package/dist/cs-CZ.module.js.map +1 -0
  66. package/dist/da-DK.main.js +14 -0
  67. package/dist/da-DK.main.js.map +1 -0
  68. package/dist/da-DK.mjs +16 -0
  69. package/dist/da-DK.module.js +16 -0
  70. package/dist/da-DK.module.js.map +1 -0
  71. package/dist/de-DE.main.js +14 -0
  72. package/dist/de-DE.main.js.map +1 -0
  73. package/dist/de-DE.mjs +16 -0
  74. package/dist/de-DE.module.js +16 -0
  75. package/dist/de-DE.module.js.map +1 -0
  76. package/dist/el-GR.main.js +14 -0
  77. package/dist/el-GR.main.js.map +1 -0
  78. package/dist/el-GR.mjs +16 -0
  79. package/dist/el-GR.module.js +16 -0
  80. package/dist/el-GR.module.js.map +1 -0
  81. package/dist/en-US.main.js +14 -0
  82. package/dist/en-US.main.js.map +1 -0
  83. package/dist/en-US.mjs +16 -0
  84. package/dist/en-US.module.js +16 -0
  85. package/dist/en-US.module.js.map +1 -0
  86. package/dist/es-ES.main.js +14 -0
  87. package/dist/es-ES.main.js.map +1 -0
  88. package/dist/es-ES.mjs +16 -0
  89. package/dist/es-ES.module.js +16 -0
  90. package/dist/es-ES.module.js.map +1 -0
  91. package/dist/et-EE.main.js +14 -0
  92. package/dist/et-EE.main.js.map +1 -0
  93. package/dist/et-EE.mjs +16 -0
  94. package/dist/et-EE.module.js +16 -0
  95. package/dist/et-EE.module.js.map +1 -0
  96. package/dist/fi-FI.main.js +14 -0
  97. package/dist/fi-FI.main.js.map +1 -0
  98. package/dist/fi-FI.mjs +16 -0
  99. package/dist/fi-FI.module.js +16 -0
  100. package/dist/fi-FI.module.js.map +1 -0
  101. package/dist/fr-FR.main.js +14 -0
  102. package/dist/fr-FR.main.js.map +1 -0
  103. package/dist/fr-FR.mjs +16 -0
  104. package/dist/fr-FR.module.js +16 -0
  105. package/dist/fr-FR.module.js.map +1 -0
  106. package/dist/he-IL.main.js +14 -0
  107. package/dist/he-IL.main.js.map +1 -0
  108. package/dist/he-IL.mjs +16 -0
  109. package/dist/he-IL.module.js +16 -0
  110. package/dist/he-IL.module.js.map +1 -0
  111. package/dist/hr-HR.main.js +14 -0
  112. package/dist/hr-HR.main.js.map +1 -0
  113. package/dist/hr-HR.mjs +16 -0
  114. package/dist/hr-HR.module.js +16 -0
  115. package/dist/hr-HR.module.js.map +1 -0
  116. package/dist/hu-HU.main.js +14 -0
  117. package/dist/hu-HU.main.js.map +1 -0
  118. package/dist/hu-HU.mjs +16 -0
  119. package/dist/hu-HU.module.js +16 -0
  120. package/dist/hu-HU.module.js.map +1 -0
  121. package/dist/import.mjs +22 -0
  122. package/dist/intlStrings.main.js +108 -0
  123. package/dist/intlStrings.main.js.map +1 -0
  124. package/dist/intlStrings.mjs +110 -0
  125. package/dist/intlStrings.module.js +110 -0
  126. package/dist/intlStrings.module.js.map +1 -0
  127. package/dist/it-IT.main.js +14 -0
  128. package/dist/it-IT.main.js.map +1 -0
  129. package/dist/it-IT.mjs +16 -0
  130. package/dist/it-IT.module.js +16 -0
  131. package/dist/it-IT.module.js.map +1 -0
  132. package/dist/ja-JP.main.js +14 -0
  133. package/dist/ja-JP.main.js.map +1 -0
  134. package/dist/ja-JP.mjs +16 -0
  135. package/dist/ja-JP.module.js +16 -0
  136. package/dist/ja-JP.module.js.map +1 -0
  137. package/dist/ko-KR.main.js +14 -0
  138. package/dist/ko-KR.main.js.map +1 -0
  139. package/dist/ko-KR.mjs +16 -0
  140. package/dist/ko-KR.module.js +16 -0
  141. package/dist/ko-KR.module.js.map +1 -0
  142. package/dist/lt-LT.main.js +14 -0
  143. package/dist/lt-LT.main.js.map +1 -0
  144. package/dist/lt-LT.mjs +16 -0
  145. package/dist/lt-LT.module.js +16 -0
  146. package/dist/lt-LT.module.js.map +1 -0
  147. package/dist/lv-LV.main.js +14 -0
  148. package/dist/lv-LV.main.js.map +1 -0
  149. package/dist/lv-LV.mjs +16 -0
  150. package/dist/lv-LV.module.js +16 -0
  151. package/dist/lv-LV.module.js.map +1 -0
  152. package/dist/main.js +28 -936
  153. package/dist/main.js.map +1 -1
  154. package/dist/module.js +17 -844
  155. package/dist/module.js.map +1 -1
  156. package/dist/nb-NO.main.js +14 -0
  157. package/dist/nb-NO.main.js.map +1 -0
  158. package/dist/nb-NO.mjs +16 -0
  159. package/dist/nb-NO.module.js +16 -0
  160. package/dist/nb-NO.module.js.map +1 -0
  161. package/dist/nl-NL.main.js +14 -0
  162. package/dist/nl-NL.main.js.map +1 -0
  163. package/dist/nl-NL.mjs +16 -0
  164. package/dist/nl-NL.module.js +16 -0
  165. package/dist/nl-NL.module.js.map +1 -0
  166. package/dist/pl-PL.main.js +14 -0
  167. package/dist/pl-PL.main.js.map +1 -0
  168. package/dist/pl-PL.mjs +16 -0
  169. package/dist/pl-PL.module.js +16 -0
  170. package/dist/pl-PL.module.js.map +1 -0
  171. package/dist/pt-BR.main.js +14 -0
  172. package/dist/pt-BR.main.js.map +1 -0
  173. package/dist/pt-BR.mjs +16 -0
  174. package/dist/pt-BR.module.js +16 -0
  175. package/dist/pt-BR.module.js.map +1 -0
  176. package/dist/pt-PT.main.js +14 -0
  177. package/dist/pt-PT.main.js.map +1 -0
  178. package/dist/pt-PT.mjs +16 -0
  179. package/dist/pt-PT.module.js +16 -0
  180. package/dist/pt-PT.module.js.map +1 -0
  181. package/dist/ro-RO.main.js +14 -0
  182. package/dist/ro-RO.main.js.map +1 -0
  183. package/dist/ro-RO.mjs +16 -0
  184. package/dist/ro-RO.module.js +16 -0
  185. package/dist/ro-RO.module.js.map +1 -0
  186. package/dist/ru-RU.main.js +14 -0
  187. package/dist/ru-RU.main.js.map +1 -0
  188. package/dist/ru-RU.mjs +16 -0
  189. package/dist/ru-RU.module.js +16 -0
  190. package/dist/ru-RU.module.js.map +1 -0
  191. package/dist/sk-SK.main.js +14 -0
  192. package/dist/sk-SK.main.js.map +1 -0
  193. package/dist/sk-SK.mjs +16 -0
  194. package/dist/sk-SK.module.js +16 -0
  195. package/dist/sk-SK.module.js.map +1 -0
  196. package/dist/sl-SI.main.js +14 -0
  197. package/dist/sl-SI.main.js.map +1 -0
  198. package/dist/sl-SI.mjs +16 -0
  199. package/dist/sl-SI.module.js +16 -0
  200. package/dist/sl-SI.module.js.map +1 -0
  201. package/dist/sr-SP.main.js +14 -0
  202. package/dist/sr-SP.main.js.map +1 -0
  203. package/dist/sr-SP.mjs +16 -0
  204. package/dist/sr-SP.module.js +16 -0
  205. package/dist/sr-SP.module.js.map +1 -0
  206. package/dist/sv-SE.main.js +14 -0
  207. package/dist/sv-SE.main.js.map +1 -0
  208. package/dist/sv-SE.mjs +16 -0
  209. package/dist/sv-SE.module.js +16 -0
  210. package/dist/sv-SE.module.js.map +1 -0
  211. package/dist/table.b13eee90.css +220 -0
  212. package/dist/table.b13eee90.css.map +1 -0
  213. package/dist/table_css.main.js +74 -0
  214. package/dist/table_css.main.js.map +1 -0
  215. package/dist/table_css.mjs +76 -0
  216. package/dist/table_css.module.js +76 -0
  217. package/dist/table_css.module.js.map +1 -0
  218. package/dist/table_vars_css.main.js +197 -0
  219. package/dist/table_vars_css.main.js.map +1 -0
  220. package/dist/table_vars_css.mjs +199 -0
  221. package/dist/table_vars_css.module.js +199 -0
  222. package/dist/table_vars_css.module.js.map +1 -0
  223. package/dist/tr-TR.main.js +14 -0
  224. package/dist/tr-TR.main.js.map +1 -0
  225. package/dist/tr-TR.mjs +16 -0
  226. package/dist/tr-TR.module.js +16 -0
  227. package/dist/tr-TR.module.js.map +1 -0
  228. package/dist/types.d.ts +77 -4
  229. package/dist/types.d.ts.map +1 -1
  230. package/dist/uk-UA.main.js +14 -0
  231. package/dist/uk-UA.main.js.map +1 -0
  232. package/dist/uk-UA.mjs +16 -0
  233. package/dist/uk-UA.module.js +16 -0
  234. package/dist/uk-UA.module.js.map +1 -0
  235. package/dist/vars.ce47d5ba.css +992 -0
  236. package/dist/vars.ce47d5ba.css.map +1 -0
  237. package/dist/zh-CN.main.js +14 -0
  238. package/dist/zh-CN.main.js.map +1 -0
  239. package/dist/zh-CN.mjs +16 -0
  240. package/dist/zh-CN.module.js +16 -0
  241. package/dist/zh-CN.module.js.map +1 -0
  242. package/dist/zh-TW.main.js +14 -0
  243. package/dist/zh-TW.main.js.map +1 -0
  244. package/dist/zh-TW.mjs +16 -0
  245. package/dist/zh-TW.module.js +16 -0
  246. package/dist/zh-TW.module.js.map +1 -0
  247. package/package.json +40 -28
  248. package/src/DragPreview.tsx +77 -0
  249. package/src/InsertionIndicator.tsx +62 -0
  250. package/src/Nubbin.tsx +28 -0
  251. package/src/Resizer.tsx +139 -0
  252. package/src/RootDropIndicator.tsx +40 -0
  253. package/src/TableView.tsx +24 -682
  254. package/src/TableViewBase.tsx +1552 -0
  255. package/src/TableViewLayout.ts +95 -0
  256. package/src/TableViewWrapper.tsx +106 -0
  257. package/src/TreeGridTableView.tsx +47 -0
  258. package/src/cursors/Cur_MoveHorizontal_9_9.svg +10 -0
  259. package/src/cursors/Cur_MoveToLeft_9_9.svg +10 -0
  260. package/src/cursors/Cur_MoveToRight_9_9.svg +10 -0
  261. package/src/index.ts +5 -1
  262. package/src/table.css +193 -0
  263. package/LICENSE +0 -201
  264. package/dist/main.css +0 -2
  265. package/dist/main.css.map +0 -1
  266. package/src/.DS_Store +0 -0
@@ -0,0 +1,1552 @@
1
+ /*
2
+ * Copyright 2023 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 ArrowDownSmall from '@spectrum-icons/ui/ArrowDownSmall';
14
+ import {Checkbox} from '@react-spectrum/checkbox';
15
+ import ChevronDownMedium from '@spectrum-icons/ui/ChevronDownMedium';
16
+ import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium';
17
+ import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium';
18
+ import {
19
+ classNames,
20
+ useDOMRef,
21
+ useFocusableRef,
22
+ useStyleProps,
23
+ useUnwrapDOMRef
24
+ } from '@react-spectrum/utils';
25
+ import {ColumnSize, SpectrumColumnProps, TableCollection} from '@react-types/table';
26
+ import {DOMRef, DropTarget, FocusableElement, FocusableRef, Key, RefObject} from '@react-types/shared';
27
+ import type {DragAndDropHooks} from '@react-spectrum/dnd';
28
+ import type {DraggableCollectionState, DroppableCollectionState} from '@react-stately/dnd';
29
+ import type {DraggableItemResult, DropIndicatorAria, DroppableCollectionResult, DroppableItemResult} from '@react-aria/dnd';
30
+ import {FocusRing, FocusScope, useFocusRing} from '@react-aria/focus';
31
+ import {getInteractionModality, isFocusVisible, useHover, usePress} from '@react-aria/interactions';
32
+ import {GridNode} from '@react-types/grid';
33
+ import {InsertionIndicator} from './InsertionIndicator';
34
+ // @ts-ignore
35
+ import intlMessages from '../intl/*.json';
36
+ import {isAndroid, mergeProps, scrollIntoView, scrollIntoViewport, useLoadMore} from '@react-aria/utils';
37
+ import {Item, Menu, MenuTrigger} from '@react-spectrum/menu';
38
+ import {LayoutInfo, Rect, ReusableView, useVirtualizerState} from '@react-stately/virtualizer';
39
+ import {layoutInfoToStyle, ScrollView, setScrollLeft, VirtualizerItem} from '@react-aria/virtualizer';
40
+ import ListGripper from '@spectrum-icons/ui/ListGripper';
41
+ import {ListKeyboardDelegate} from '@react-aria/selection';
42
+ import {Nubbin} from './Nubbin';
43
+ import {ProgressCircle} from '@react-spectrum/progress';
44
+ import React, {DOMAttributes, HTMLAttributes, ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
45
+ import {Resizer, ResizeStateContext} from './Resizer';
46
+ import {RootDropIndicator} from './RootDropIndicator';
47
+ import {DragPreview as SpectrumDragPreview} from './DragPreview';
48
+ import {SpectrumTableProps} from './TableViewWrapper';
49
+ import styles from '@adobe/spectrum-css-temp/components/table/vars.css';
50
+ import stylesOverrides from './table.css';
51
+ import {TableState, TreeGridState, useTableColumnResizeState} from '@react-stately/table';
52
+ import {TableViewLayout} from './TableViewLayout';
53
+ import {Tooltip, TooltipTrigger} from '@react-spectrum/tooltip';
54
+ import {useButton} from '@react-aria/button';
55
+ import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';
56
+ import {useProvider, useProviderProps} from '@react-spectrum/provider';
57
+ import {
58
+ useTable,
59
+ useTableCell,
60
+ useTableColumnHeader,
61
+ useTableHeaderRow,
62
+ useTableRow,
63
+ useTableRowGroup,
64
+ useTableSelectAllCheckbox,
65
+ useTableSelectionCheckbox
66
+ } from '@react-aria/table';
67
+ import {useVisuallyHidden, VisuallyHidden} from '@react-aria/visually-hidden';
68
+
69
+ const DEFAULT_HEADER_HEIGHT = {
70
+ medium: 34,
71
+ large: 40
72
+ };
73
+
74
+ const DEFAULT_HIDE_HEADER_CELL_WIDTH = {
75
+ medium: 38,
76
+ large: 46
77
+ };
78
+
79
+ const ROW_HEIGHTS = {
80
+ compact: {
81
+ medium: 32,
82
+ large: 40
83
+ },
84
+ regular: {
85
+ medium: 40,
86
+ large: 50
87
+ },
88
+ spacious: {
89
+ medium: 48,
90
+ large: 60
91
+ }
92
+ };
93
+
94
+ const SELECTION_CELL_DEFAULT_WIDTH = {
95
+ medium: 38,
96
+ large: 48
97
+ };
98
+
99
+ const DRAG_BUTTON_CELL_DEFAULT_WIDTH = {
100
+ medium: 16,
101
+ large: 20
102
+ };
103
+
104
+ const LEVEL_OFFSET_WIDTH = {
105
+ medium: 16,
106
+ large: 20
107
+ };
108
+
109
+ export interface TableContextValue<T> {
110
+ state: TableState<T> | TreeGridState<T>,
111
+ dragState: DraggableCollectionState,
112
+ dropState: DroppableCollectionState,
113
+ dragAndDropHooks: DragAndDropHooks['dragAndDropHooks'],
114
+ isTableDraggable: boolean,
115
+ isTableDroppable: boolean,
116
+ layout: TableViewLayout<T>,
117
+ headerRowHovered: boolean,
118
+ isInResizeMode: boolean,
119
+ setIsInResizeMode: (val: boolean) => void,
120
+ isEmpty: boolean,
121
+ onFocusedResizer: () => void,
122
+ onResizeStart: (widths: Map<Key, ColumnSize>) => void,
123
+ onResize: (widths: Map<Key, ColumnSize>) => void,
124
+ onResizeEnd: (widths: Map<Key, ColumnSize>) => void,
125
+ headerMenuOpen: boolean,
126
+ setHeaderMenuOpen: (val: boolean) => void,
127
+ renderEmptyState?: () => ReactElement
128
+ }
129
+
130
+ export const TableContext = React.createContext<TableContextValue<unknown>>(null);
131
+ export function useTableContext() {
132
+ return useContext(TableContext);
133
+ }
134
+
135
+ export const VirtualizerContext = React.createContext(null);
136
+ export function useVirtualizerContext() {
137
+ return useContext(VirtualizerContext);
138
+ }
139
+
140
+ interface TableBaseProps<T> extends SpectrumTableProps<T> {
141
+ state: TableState<T> | TreeGridState<T>
142
+ }
143
+
144
+ type View = ReusableView<GridNode<unknown>, ReactNode>;
145
+
146
+ function TableViewBase<T extends object>(props: TableBaseProps<T>, ref: DOMRef<HTMLDivElement>) {
147
+ props = useProviderProps(props);
148
+ let {
149
+ isQuiet,
150
+ onAction,
151
+ onResizeStart: propsOnResizeStart,
152
+ onResizeEnd: propsOnResizeEnd,
153
+ dragAndDropHooks,
154
+ state
155
+ } = props;
156
+ let isTableDraggable = !!dragAndDropHooks?.useDraggableCollectionState;
157
+ let isTableDroppable = !!dragAndDropHooks?.useDroppableCollectionState;
158
+ let dragHooksProvided = useRef(isTableDraggable);
159
+ let dropHooksProvided = useRef(isTableDroppable);
160
+ useEffect(() => {
161
+ if (dragHooksProvided.current !== isTableDraggable) {
162
+ console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
163
+ }
164
+ if (dropHooksProvided.current !== isTableDroppable) {
165
+ console.warn('Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
166
+ }
167
+ if ('expandedKeys' in state && (isTableDraggable || isTableDroppable)) {
168
+ console.warn('Drag and drop is not yet fully supported with expandable rows and may produce unexpected results.');
169
+ }
170
+ }, [isTableDraggable, isTableDroppable, state]);
171
+
172
+ let {styleProps} = useStyleProps(props);
173
+ let {scale} = useProvider();
174
+
175
+ // Starts when the user selects resize from the menu, ends when resizing ends
176
+ // used to control the visibility of the resizer Nubbin
177
+ let [isInResizeMode, setIsInResizeMode] = useState(false);
178
+ // Starts when the resizer is actually moved
179
+ // entering resizing/exiting resizing doesn't trigger a render
180
+ // with table layout, so we need to track it here
181
+ let [, setIsResizing] = useState(false);
182
+
183
+ let domRef = useDOMRef(ref);
184
+ let headerRef = useRef<HTMLDivElement>(undefined);
185
+ let bodyRef = useRef<HTMLDivElement>(undefined);
186
+
187
+ let density = props.density || 'regular';
188
+ let layout = useMemo(() => new TableViewLayout({
189
+ // If props.rowHeight is auto, then use estimated heights based on scale, otherwise use fixed heights.
190
+ rowHeight: props.overflowMode === 'wrap'
191
+ ? null
192
+ : ROW_HEIGHTS[density][scale],
193
+ estimatedRowHeight: props.overflowMode === 'wrap'
194
+ ? ROW_HEIGHTS[density][scale]
195
+ : null,
196
+ headingHeight: props.overflowMode === 'wrap'
197
+ ? null
198
+ : DEFAULT_HEADER_HEIGHT[scale],
199
+ estimatedHeadingHeight: props.overflowMode === 'wrap'
200
+ ? DEFAULT_HEADER_HEIGHT[scale]
201
+ : null
202
+ }),
203
+ // don't recompute when state.collection changes, only used for initial value
204
+ // eslint-disable-next-line react-hooks/exhaustive-deps
205
+ [props.overflowMode, scale, density]
206
+ );
207
+
208
+ let dragState: DraggableCollectionState;
209
+ let preview = useRef(null);
210
+ if (isTableDraggable) {
211
+ dragState = dragAndDropHooks.useDraggableCollectionState({
212
+ collection: state.collection,
213
+ selectionManager: state.selectionManager,
214
+ preview
215
+ });
216
+ dragAndDropHooks.useDraggableCollection({}, dragState, domRef);
217
+ }
218
+
219
+ let DragPreview = dragAndDropHooks?.DragPreview;
220
+ let dropState: DroppableCollectionState;
221
+ let droppableCollection: DroppableCollectionResult;
222
+ let isRootDropTarget: boolean;
223
+ if (isTableDroppable) {
224
+ dropState = dragAndDropHooks.useDroppableCollectionState({
225
+ collection: state.collection,
226
+ selectionManager: state.selectionManager
227
+ });
228
+ droppableCollection = dragAndDropHooks.useDroppableCollection({
229
+ keyboardDelegate: new ListKeyboardDelegate({
230
+ collection: state.collection,
231
+ disabledKeys: state.selectionManager.disabledKeys,
232
+ ref: domRef,
233
+ layoutDelegate: layout
234
+ }),
235
+ dropTargetDelegate: layout
236
+ }, dropState, domRef);
237
+
238
+ isRootDropTarget = dropState.isDropTarget({type: 'root'});
239
+ }
240
+
241
+ let {gridProps} = useTable({
242
+ ...props,
243
+ isVirtualized: true,
244
+ layoutDelegate: layout,
245
+ onRowAction: onAction,
246
+ scrollRef: bodyRef
247
+ }, state, domRef);
248
+ let [headerMenuOpen, setHeaderMenuOpen] = useState(false);
249
+ let [headerRowHovered, setHeaderRowHovered] = useState(false);
250
+
251
+ // This overrides collection view's renderWrapper to support DOM hierarchy.
252
+ let renderWrapper = useCallback((parent: View, reusableView: View, children: View[], renderChildren: (views: View[]) => ReactElement[]) => {
253
+ if (reusableView.viewType === 'rowgroup') {
254
+ return (
255
+ <TableRowGroup
256
+ key={reusableView.key}
257
+ layoutInfo={reusableView.layoutInfo}
258
+ parent={parent?.layoutInfo}
259
+ // Override the default role="rowgroup" with role="presentation",
260
+ // in favor or adding role="rowgroup" to the ScrollView with
261
+ // ref={bodyRef} in the TableVirtualizer below.
262
+ role="presentation">
263
+ {renderChildren(children)}
264
+ </TableRowGroup>
265
+ );
266
+ }
267
+
268
+ if (reusableView.viewType === 'header') {
269
+ return (
270
+ <TableHeader
271
+ key={reusableView.key}
272
+ layoutInfo={reusableView.layoutInfo}
273
+ parent={parent?.layoutInfo}>
274
+ {renderChildren(children)}
275
+ </TableHeader>
276
+ );
277
+ }
278
+
279
+ if (reusableView.viewType === 'row') {
280
+ return (
281
+ <TableRow
282
+ key={reusableView.key}
283
+ item={reusableView.content}
284
+ layoutInfo={reusableView.layoutInfo}
285
+ parent={parent?.layoutInfo}>
286
+ {renderChildren(children)}
287
+ </TableRow>
288
+ );
289
+ }
290
+
291
+ if (reusableView.viewType === 'headerrow') {
292
+ return (
293
+ <TableHeaderRow
294
+ onHoverChange={setHeaderRowHovered}
295
+ key={reusableView.key}
296
+ layoutInfo={reusableView.layoutInfo}
297
+ parent={parent?.layoutInfo}
298
+ item={reusableView.content}>
299
+ {renderChildren(children)}
300
+ </TableHeaderRow>
301
+ );
302
+ }
303
+
304
+ return (
305
+ <TableCellWrapper
306
+ key={reusableView.key}
307
+ layoutInfo={reusableView.layoutInfo}
308
+ virtualizer={reusableView.virtualizer}
309
+ parent={parent}>
310
+ {reusableView.rendered}
311
+ </TableCellWrapper>
312
+ );
313
+ }, []);
314
+
315
+ let renderView = useCallback((type: string, item: GridNode<T>) => {
316
+ switch (type) {
317
+ case 'header':
318
+ case 'rowgroup':
319
+ case 'section':
320
+ case 'row':
321
+ case 'headerrow':
322
+ return null;
323
+ case 'cell': {
324
+ if (item.props.isSelectionCell) {
325
+ return <TableCheckboxCell cell={item} />;
326
+ }
327
+
328
+ if (item.props.isDragButtonCell) {
329
+ return <TableDragCell cell={item} />;
330
+ }
331
+
332
+ return <TableCell cell={item} />;
333
+ }
334
+ case 'placeholder':
335
+ // TODO: move to react-aria?
336
+ return (
337
+ <div
338
+ role="gridcell"
339
+ aria-colindex={item.index + 1}
340
+ aria-colspan={item.colspan > 1 ? item.colspan : null} />
341
+ );
342
+ case 'column':
343
+ if (item.props.isSelectionCell) {
344
+ return <TableSelectAllCell column={item} />;
345
+ }
346
+
347
+ if (item.props.isDragButtonCell) {
348
+ return <TableDragHeaderCell column={item} />;
349
+ }
350
+
351
+ // TODO: consider this case, what if we have hidden headers and a empty table
352
+ if (item.props.hideHeader) {
353
+ return (
354
+ <TooltipTrigger placement="top" trigger="focus">
355
+ <TableColumnHeader column={item} />
356
+ <Tooltip placement="top">{item.rendered}</Tooltip>
357
+ </TooltipTrigger>
358
+ );
359
+ }
360
+
361
+ if (item.props.allowsResizing && !item.hasChildNodes) {
362
+ return <ResizableTableColumnHeader column={item} />;
363
+ }
364
+
365
+ return (
366
+ <TableColumnHeader column={item} />
367
+ );
368
+ case 'loader':
369
+ return <LoadingState />;
370
+ case 'empty': {
371
+ return <EmptyState />;
372
+ }
373
+ }
374
+ }, []);
375
+
376
+ let [isVerticalScrollbarVisible, setVerticalScollbarVisible] = useState(false);
377
+ let [isHorizontalScrollbarVisible, setHorizontalScollbarVisible] = useState(false);
378
+ let viewport = useRef({x: 0, y: 0, width: 0, height: 0});
379
+ let onVisibleRectChange = useCallback((e) => {
380
+ if (viewport.current.width === e.width && viewport.current.height === e.height) {
381
+ return;
382
+ }
383
+ viewport.current = e;
384
+ if (bodyRef.current) {
385
+ setVerticalScollbarVisible(bodyRef.current.clientWidth + 2 < bodyRef.current.offsetWidth);
386
+ setHorizontalScollbarVisible(bodyRef.current.clientHeight + 2 < bodyRef.current.offsetHeight);
387
+ }
388
+ }, []);
389
+ let {isFocusVisible, focusProps} = useFocusRing();
390
+ let isEmpty = state.collection.size === 0;
391
+
392
+ let onFocusedResizer = () => {
393
+ bodyRef.current.scrollLeft = headerRef.current.scrollLeft;
394
+ };
395
+
396
+ let onResizeStart = useCallback((widths) => {
397
+ setIsResizing(true);
398
+ propsOnResizeStart?.(widths);
399
+ }, [setIsResizing, propsOnResizeStart]);
400
+ let onResizeEnd = useCallback((widths) => {
401
+ setIsInResizeMode(false);
402
+ setIsResizing(false);
403
+ propsOnResizeEnd?.(widths);
404
+ }, [propsOnResizeEnd, setIsInResizeMode, setIsResizing]);
405
+
406
+ let focusedKey = state.selectionManager.focusedKey;
407
+ let dropTargetKey: Key | null = null;
408
+ if (dropState?.target?.type === 'item') {
409
+ dropTargetKey = dropState.target.key;
410
+ if (dropState.target.dropPosition === 'before' && dropTargetKey !== state.collection.getFirstKey()) {
411
+ // Normalize to the "after" drop position since we only render those in the DOM.
412
+ // The exception to this is for the first row in the table, where we also render the "before" position.
413
+ dropTargetKey = state.collection.getKeyBefore(dropTargetKey);
414
+ }
415
+ }
416
+
417
+ let persistedKeys = useMemo(() => {
418
+ return new Set([focusedKey, dropTargetKey].filter(k => k !== null));
419
+ }, [focusedKey, dropTargetKey]);
420
+
421
+ let mergedProps = mergeProps(
422
+ isTableDroppable && droppableCollection?.collectionProps,
423
+ gridProps,
424
+ focusProps,
425
+ dragAndDropHooks?.isVirtualDragging() && {tabIndex: null}
426
+ );
427
+
428
+ return (
429
+ <TableContext.Provider
430
+ value={{
431
+ state,
432
+ dragState,
433
+ dropState,
434
+ dragAndDropHooks,
435
+ isTableDraggable,
436
+ isTableDroppable,
437
+ layout,
438
+ onResizeStart,
439
+ onResize: props.onResize,
440
+ onResizeEnd,
441
+ headerRowHovered,
442
+ isInResizeMode,
443
+ setIsInResizeMode,
444
+ isEmpty,
445
+ onFocusedResizer,
446
+ headerMenuOpen,
447
+ setHeaderMenuOpen,
448
+ renderEmptyState: props.renderEmptyState
449
+ }}>
450
+ <TableVirtualizer
451
+ {...mergedProps}
452
+ {...styleProps}
453
+ className={
454
+ classNames(
455
+ styles,
456
+ 'spectrum-Table',
457
+ `spectrum-Table--${density}`,
458
+ {
459
+ 'spectrum-Table--quiet': isQuiet,
460
+ 'spectrum-Table--wrap': props.overflowMode === 'wrap',
461
+ 'spectrum-Table--loadingMore': state.collection.body.props.loadingState === 'loadingMore',
462
+ 'spectrum-Table--isVerticalScrollbarVisible': isVerticalScrollbarVisible,
463
+ 'spectrum-Table--isHorizontalScrollbarVisible': isHorizontalScrollbarVisible
464
+ },
465
+ classNames(
466
+ stylesOverrides,
467
+ 'react-spectrum-Table'
468
+ ),
469
+ styleProps.className
470
+ )
471
+ }
472
+ tableState={state}
473
+ layout={layout}
474
+ collection={state.collection}
475
+ persistedKeys={persistedKeys}
476
+ renderView={renderView}
477
+ renderWrapper={renderWrapper}
478
+ onVisibleRectChange={onVisibleRectChange}
479
+ domRef={domRef}
480
+ headerRef={headerRef}
481
+ bodyRef={bodyRef}
482
+ isFocusVisible={isFocusVisible}
483
+ isVirtualDragging={dragAndDropHooks?.isVirtualDragging()}
484
+ isRootDropTarget={isRootDropTarget} />
485
+ {DragPreview && isTableDraggable &&
486
+ <DragPreview ref={preview}>
487
+ {() => {
488
+ if (dragAndDropHooks.renderPreview) {
489
+ return dragAndDropHooks.renderPreview(dragState.draggingKeys, dragState.draggedKey);
490
+ }
491
+ let itemCount = dragState.draggingKeys.size;
492
+ let maxWidth = bodyRef.current.getBoundingClientRect().width;
493
+ let height = ROW_HEIGHTS[density][scale];
494
+ let itemText = state.collection.getTextValue(dragState.draggedKey);
495
+ return <SpectrumDragPreview itemText={itemText} itemCount={itemCount} height={height} maxWidth={maxWidth} />;
496
+ }}
497
+ </DragPreview>
498
+ }
499
+ </TableContext.Provider>
500
+ );
501
+ }
502
+
503
+ interface TableVirtualizerProps<T> extends HTMLAttributes<HTMLElement> {
504
+ tableState: TableState<T>,
505
+ layout: TableViewLayout<T>,
506
+ collection: TableCollection<T>,
507
+ persistedKeys: Set<Key> | null,
508
+ renderView: (type: string, content: GridNode<T>) => ReactElement,
509
+ renderWrapper?: (
510
+ parent: View | null,
511
+ reusableView: View,
512
+ children: View[],
513
+ renderChildren: (views: View[]) => ReactElement[]
514
+ ) => ReactElement,
515
+ domRef: RefObject<HTMLDivElement>,
516
+ bodyRef: RefObject<HTMLDivElement>,
517
+ headerRef: RefObject<HTMLDivElement>,
518
+ onVisibleRectChange: (rect: Rect) => void,
519
+ isFocusVisible: boolean,
520
+ isVirtualDragging: boolean,
521
+ isRootDropTarget: boolean
522
+ }
523
+
524
+ // This is a custom Virtualizer that also has a header that syncs its scroll position with the body.
525
+ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
526
+ let {tableState, layout, collection, persistedKeys, renderView, renderWrapper, domRef, bodyRef, headerRef, onVisibleRectChange: onVisibleRectChangeProp, isFocusVisible, isVirtualDragging, isRootDropTarget, ...otherProps} = props;
527
+ let {direction} = useLocale();
528
+ let loadingState = collection.body.props.loadingState;
529
+ let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';
530
+ let onLoadMore = collection.body.props.onLoadMore;
531
+ let [tableWidth, setTableWidth] = useState(0);
532
+ let {scale} = useProvider();
533
+
534
+ const getDefaultWidth = useCallback(({props: {hideHeader, isSelectionCell, showDivider, isDragButtonCell}}: GridNode<T>): ColumnSize | null | undefined => {
535
+ if (hideHeader) {
536
+ let width = DEFAULT_HIDE_HEADER_CELL_WIDTH[scale];
537
+ return showDivider ? width + 1 : width;
538
+ } else if (isSelectionCell) {
539
+ return SELECTION_CELL_DEFAULT_WIDTH[scale];
540
+ } else if (isDragButtonCell) {
541
+ return DRAG_BUTTON_CELL_DEFAULT_WIDTH[scale];
542
+ }
543
+ }, [scale]);
544
+
545
+ const getDefaultMinWidth = useCallback(({props: {hideHeader, isSelectionCell, showDivider, isDragButtonCell}}: GridNode<T>): ColumnSize | null | undefined => {
546
+ if (hideHeader) {
547
+ let width = DEFAULT_HIDE_HEADER_CELL_WIDTH[scale];
548
+ return showDivider ? width + 1 : width;
549
+ } else if (isSelectionCell) {
550
+ return SELECTION_CELL_DEFAULT_WIDTH[scale];
551
+ } else if (isDragButtonCell) {
552
+ return DRAG_BUTTON_CELL_DEFAULT_WIDTH[scale];
553
+ }
554
+ return 75;
555
+ }, [scale]);
556
+
557
+ let columnResizeState = useTableColumnResizeState({
558
+ tableWidth,
559
+ getDefaultWidth,
560
+ getDefaultMinWidth
561
+ }, tableState);
562
+
563
+ let state = useVirtualizerState<GridNode<unknown>, ReactNode>({
564
+ layout,
565
+ collection,
566
+ renderView,
567
+ onVisibleRectChange(rect) {
568
+ bodyRef.current.scrollTop = rect.y;
569
+ setScrollLeft(bodyRef.current, direction, rect.x);
570
+ },
571
+ persistedKeys,
572
+ layoutOptions: useMemo(() => ({
573
+ columnWidths: columnResizeState.columnWidths
574
+ }), [columnResizeState.columnWidths])
575
+ });
576
+
577
+ useLoadMore({isLoading, onLoadMore, scrollOffset: 1}, bodyRef);
578
+ let onVisibleRectChange = useCallback((rect: Rect) => {
579
+ state.setVisibleRect(rect);
580
+ }, [state]);
581
+
582
+ let onVisibleRectChangeMemo = useCallback(rect => {
583
+ setTableWidth(rect.width);
584
+ onVisibleRectChange(rect);
585
+ onVisibleRectChangeProp(rect);
586
+ }, [onVisibleRectChange, onVisibleRectChangeProp]);
587
+
588
+ // this effect runs whenever the contentSize changes, it doesn't matter what the content size is
589
+ // only that it changes in a resize, and when that happens, we want to sync the body to the
590
+ // header scroll position
591
+ useEffect(() => {
592
+ if (getInteractionModality() === 'keyboard' && headerRef.current.contains(document.activeElement)) {
593
+ scrollIntoView(headerRef.current, document.activeElement as HTMLElement);
594
+ scrollIntoViewport(document.activeElement, {containingElement: domRef.current});
595
+ bodyRef.current.scrollLeft = headerRef.current.scrollLeft;
596
+ }
597
+ }, [state.contentSize, headerRef, bodyRef, domRef]);
598
+
599
+ let headerHeight = layout.getLayoutInfo('header')?.rect.height || 0;
600
+
601
+ // Sync the scroll position from the table body to the header container.
602
+ let onScroll = useCallback(() => {
603
+ headerRef.current.scrollLeft = bodyRef.current.scrollLeft;
604
+ }, [bodyRef, headerRef]);
605
+
606
+ let resizerPosition = columnResizeState.resizingColumn != null ? layout.getLayoutInfo(columnResizeState.resizingColumn).rect.maxX - 2 : 0;
607
+
608
+ let resizerAtEdge = resizerPosition > Math.max(state.virtualizer.contentSize.width, state.virtualizer.visibleRect.width) - 3;
609
+ // this should be fine, every movement of the resizer causes a rerender
610
+ // scrolling can cause it to lag for a moment, but it's always updated
611
+ let resizerInVisibleRegion = resizerPosition < state.virtualizer.visibleRect.maxX;
612
+ let shouldHardCornerResizeCorner = resizerAtEdge && resizerInVisibleRegion;
613
+
614
+ // minimize re-render caused on Resizers by memoing this
615
+ let resizingColumnWidth = columnResizeState.resizingColumn != null ? columnResizeState.getColumnWidth(columnResizeState.resizingColumn) : 0;
616
+ let resizingColumn = useMemo(() => ({
617
+ width: resizingColumnWidth,
618
+ key: columnResizeState.resizingColumn
619
+ }), [resizingColumnWidth, columnResizeState.resizingColumn]);
620
+ let mergedProps = mergeProps(
621
+ otherProps,
622
+ isVirtualDragging && {tabIndex: null}
623
+ );
624
+
625
+ let firstColumn = collection.columns[0];
626
+ let scrollPadding = 0;
627
+ if (firstColumn.props.isSelectionCell || firstColumn.props.isDragButtonCell) {
628
+ scrollPadding = columnResizeState.getColumnWidth(firstColumn.key);
629
+ }
630
+
631
+ let visibleViews = renderChildren(null, state.visibleViews, renderWrapper);
632
+
633
+ return (
634
+ <VirtualizerContext.Provider value={resizingColumn}>
635
+ <FocusScope>
636
+ <div
637
+ {...mergedProps}
638
+ ref={domRef}>
639
+ <div
640
+ role="presentation"
641
+ className={classNames(styles, 'spectrum-Table-headWrapper')}
642
+ style={{
643
+ height: headerHeight,
644
+ overflow: 'hidden',
645
+ position: 'relative',
646
+ willChange: state.isScrolling ? 'scroll-position' : undefined,
647
+ scrollPaddingInlineStart: scrollPadding
648
+ }}
649
+ ref={headerRef}>
650
+ <ResizeStateContext.Provider value={columnResizeState}>
651
+ {visibleViews[0]}
652
+ </ResizeStateContext.Provider>
653
+ </div>
654
+ <ScrollView
655
+ className={
656
+ classNames(
657
+ styles,
658
+ 'spectrum-Table-body',
659
+ {
660
+ 'focus-ring': isFocusVisible,
661
+ 'spectrum-Table-body--resizerAtTableEdge': shouldHardCornerResizeCorner
662
+ },
663
+ classNames(
664
+ stylesOverrides,
665
+ 'react-spectrum-Table-body',
666
+ {
667
+ 'react-spectrum-Table-body--dropTarget': !!isRootDropTarget
668
+ }
669
+ )
670
+ )
671
+ }
672
+ // Firefox and Chrome make generic elements using CSS overflow 'scroll' or 'auto' tabbable,
673
+ // including them within the accessibility tree, which breaks the table structure in Firefox.
674
+ // Using tabIndex={-1} prevents the ScrollView from being tabbable, and using role="rowgroup"
675
+ // here and role="presentation" on the table body content fixes the table structure.
676
+ role="rowgroup"
677
+ tabIndex={isVirtualDragging ? null : -1}
678
+ style={{
679
+ flex: 1,
680
+ scrollPaddingInlineStart: scrollPadding
681
+ }}
682
+ innerStyle={{overflow: 'visible'}}
683
+ ref={bodyRef}
684
+ contentSize={state.contentSize}
685
+ onVisibleRectChange={onVisibleRectChangeMemo}
686
+ onScrollStart={state.startScrolling}
687
+ onScrollEnd={state.endScrolling}
688
+ onScroll={onScroll}>
689
+ {visibleViews[1]}
690
+ <div
691
+ className={classNames(styles, 'spectrum-Table-bodyResizeIndicator')}
692
+ style={{[direction === 'ltr' ? 'left' : 'right']: `${resizerPosition}px`, height: `${Math.max(state.virtualizer.contentSize.height, state.virtualizer.visibleRect.height)}px`, display: columnResizeState.resizingColumn ? 'block' : 'none'}} />
693
+ </ScrollView>
694
+ </div>
695
+ </FocusScope>
696
+ </VirtualizerContext.Provider>
697
+ );
698
+ }
699
+
700
+ function renderChildren<T extends object>(parent: View | null, views: View[], renderWrapper: TableVirtualizerProps<T>['renderWrapper']) {
701
+ return views.map(view => {
702
+ return renderWrapper(
703
+ parent,
704
+ view,
705
+ view.children ? Array.from(view.children) : [],
706
+ childViews => renderChildren(view, childViews, renderWrapper)
707
+ );
708
+ });
709
+ }
710
+
711
+ function useStyle(layoutInfo: LayoutInfo, parent: LayoutInfo | null) {
712
+ let {direction} = useLocale();
713
+ let style = layoutInfoToStyle(layoutInfo, direction, parent);
714
+ if (style.overflow === 'hidden') {
715
+ style.overflow = 'visible'; // needed to support position: sticky
716
+ }
717
+ return style;
718
+ }
719
+
720
+ function TableHeader({children, layoutInfo, parent, ...otherProps}) {
721
+ let {rowGroupProps} = useTableRowGroup();
722
+ let style = useStyle(layoutInfo, parent);
723
+
724
+ return (
725
+ <div {...rowGroupProps} {...otherProps} className={classNames(styles, 'spectrum-Table-head')} style={style}>
726
+ {children}
727
+ </div>
728
+ );
729
+ }
730
+
731
+ function TableColumnHeader(props) {
732
+ let {column} = props;
733
+ let ref = useRef<HTMLDivElement>(null);
734
+ let {state, isEmpty} = useTableContext();
735
+ let {pressProps, isPressed} = usePress({isDisabled: isEmpty});
736
+ let columnProps = column.props as SpectrumColumnProps<unknown>;
737
+ useEffect(() => {
738
+ if (column.hasChildNodes && columnProps.allowsResizing) {
739
+ console.warn(`Column key: ${column.key}. Columns with child columns don't allow resizing.`);
740
+ }
741
+ }, [column.hasChildNodes, column.key, columnProps.allowsResizing]);
742
+
743
+ let {columnHeaderProps} = useTableColumnHeader({
744
+ node: column,
745
+ isVirtualized: true
746
+ }, state, ref);
747
+
748
+ let {hoverProps, isHovered} = useHover({...props, isDisabled: isEmpty});
749
+
750
+ const allProps = [columnHeaderProps, hoverProps, pressProps];
751
+
752
+ return (
753
+ <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
754
+ <div
755
+ {...mergeProps(...allProps)}
756
+ ref={ref}
757
+ className={
758
+ classNames(
759
+ styles,
760
+ 'spectrum-Table-headCell',
761
+ {
762
+ 'is-active': isPressed,
763
+ 'is-sortable': columnProps.allowsSorting,
764
+ 'is-sorted-desc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'descending',
765
+ 'is-sorted-asc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'ascending',
766
+ 'is-hovered': isHovered,
767
+ 'spectrum-Table-cell--hideHeader': columnProps.hideHeader
768
+ },
769
+ classNames(
770
+ stylesOverrides,
771
+ 'react-spectrum-Table-cell',
772
+ {
773
+ 'react-spectrum-Table-cell--alignCenter': columnProps.align === 'center' || column.colspan > 1,
774
+ 'react-spectrum-Table-cell--alignEnd': columnProps.align === 'end'
775
+ }
776
+ )
777
+ )
778
+ }>
779
+ {columnProps.allowsSorting &&
780
+ <ArrowDownSmall UNSAFE_className={classNames(styles, 'spectrum-Table-sortedIcon')} />
781
+ }
782
+ {columnProps.hideHeader ?
783
+ <VisuallyHidden>{column.rendered}</VisuallyHidden> :
784
+ <div className={classNames(styles, 'spectrum-Table-headCellContents')}>{column.rendered}</div>
785
+ }
786
+ </div>
787
+ </FocusRing>
788
+ );
789
+ }
790
+
791
+ let _TableColumnHeaderButton = (props, ref: FocusableRef<HTMLDivElement>) => {
792
+ let {focusProps, alignment, ...otherProps} = props;
793
+ let {isEmpty} = useTableContext();
794
+ let domRef = useFocusableRef(ref);
795
+ let {buttonProps} = useButton({...otherProps, elementType: 'div', isDisabled: isEmpty}, domRef);
796
+ let {hoverProps, isHovered} = useHover({...otherProps, isDisabled: isEmpty});
797
+
798
+ return (
799
+ <div
800
+ className={
801
+ classNames(
802
+ styles,
803
+ 'spectrum-Table-headCellContents',
804
+ {
805
+ 'is-hovered': isHovered
806
+ }
807
+ )
808
+ }
809
+ {...hoverProps}>
810
+ <div
811
+ className={
812
+ classNames(
813
+ styles,
814
+ 'spectrum-Table-headCellButton',
815
+ {
816
+ 'spectrum-Table-headCellButton--alignStart': alignment === 'start',
817
+ 'spectrum-Table-headCellButton--alignCenter': alignment === 'center',
818
+ 'spectrum-Table-headCellButton--alignEnd': alignment === 'end'
819
+ }
820
+ )
821
+ }
822
+ {...mergeProps(buttonProps, focusProps)}
823
+ ref={domRef}>
824
+ {props.children}
825
+ </div>
826
+ </div>
827
+ );
828
+ };
829
+ let TableColumnHeaderButton = React.forwardRef(_TableColumnHeaderButton);
830
+
831
+ function ResizableTableColumnHeader(props) {
832
+ let {column} = props;
833
+ let ref = useRef(null);
834
+ let triggerRef = useRef(null);
835
+ let resizingRef = useRef(null);
836
+ let {
837
+ state,
838
+ onResizeStart,
839
+ onResize,
840
+ onResizeEnd,
841
+ headerRowHovered,
842
+ setIsInResizeMode,
843
+ isEmpty,
844
+ isInResizeMode,
845
+ headerMenuOpen,
846
+ setHeaderMenuOpen
847
+ } = useTableContext();
848
+ let columnResizeState = useContext(ResizeStateContext);
849
+ let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/table');
850
+ let {pressProps, isPressed} = usePress({isDisabled: isEmpty});
851
+ let {columnHeaderProps} = useTableColumnHeader({
852
+ node: column,
853
+ isVirtualized: true
854
+ }, state, ref);
855
+
856
+ let {hoverProps, isHovered} = useHover({...props, isDisabled: isEmpty || headerMenuOpen});
857
+
858
+ const allProps = [columnHeaderProps, pressProps, hoverProps];
859
+
860
+ let columnProps = column.props as SpectrumColumnProps<unknown>;
861
+
862
+ let {isFocusVisible, focusProps} = useFocusRing();
863
+
864
+ const onMenuSelect = (key) => {
865
+ switch (key) {
866
+ case 'sort-asc':
867
+ state.sort(column.key, 'ascending');
868
+ break;
869
+ case 'sort-desc':
870
+ state.sort(column.key, 'descending');
871
+ break;
872
+ case 'resize':
873
+ columnResizeState.startResize(column.key);
874
+ setIsInResizeMode(true);
875
+ state.setKeyboardNavigationDisabled(true);
876
+ break;
877
+ }
878
+ };
879
+ let allowsSorting = column.props?.allowsSorting;
880
+ let items = useMemo(() => {
881
+ let options = [
882
+ allowsSorting ? {
883
+ label: stringFormatter.format('sortAscending'),
884
+ id: 'sort-asc'
885
+ } : undefined,
886
+ allowsSorting ? {
887
+ label: stringFormatter.format('sortDescending'),
888
+ id: 'sort-desc'
889
+ } : undefined,
890
+ {
891
+ label: stringFormatter.format('resizeColumn'),
892
+ id: 'resize'
893
+ }
894
+ ];
895
+ return options;
896
+ // eslint-disable-next-line react-hooks/exhaustive-deps
897
+ }, [allowsSorting]);
898
+
899
+ let resizingColumn = columnResizeState.resizingColumn;
900
+ let showResizer = !isEmpty && ((headerRowHovered && getInteractionModality() !== 'keyboard') || resizingColumn != null);
901
+ let alignment = 'start';
902
+ let menuAlign = 'start' as 'start' | 'end';
903
+ if (columnProps.align === 'center' || column.colspan > 1) {
904
+ alignment = 'center';
905
+ } else if (columnProps.align === 'end') {
906
+ alignment = 'end';
907
+ menuAlign = 'end';
908
+ }
909
+
910
+ return (
911
+ <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
912
+ <div
913
+ {...mergeProps(...allProps)}
914
+ ref={ref}
915
+ className={
916
+ classNames(
917
+ styles,
918
+ 'spectrum-Table-headCell',
919
+ {
920
+ 'is-active': isPressed,
921
+ 'is-resizable': columnProps.allowsResizing,
922
+ 'is-sortable': columnProps.allowsSorting,
923
+ 'is-sorted-desc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'descending',
924
+ 'is-sorted-asc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'ascending',
925
+ 'is-hovered': isHovered,
926
+ 'focus-ring': isFocusVisible,
927
+ 'spectrum-Table-cell--hideHeader': columnProps.hideHeader
928
+ },
929
+ classNames(
930
+ stylesOverrides,
931
+ 'react-spectrum-Table-cell',
932
+ {
933
+ 'react-spectrum-Table-cell--alignCenter': alignment === 'center',
934
+ 'react-spectrum-Table-cell--alignEnd': alignment === 'end'
935
+ }
936
+ )
937
+ )
938
+ }>
939
+ <MenuTrigger onOpenChange={setHeaderMenuOpen} align={menuAlign}>
940
+ <TableColumnHeaderButton alignment={alignment} ref={triggerRef} focusProps={focusProps}>
941
+ {columnProps.allowsSorting &&
942
+ <ArrowDownSmall UNSAFE_className={classNames(styles, 'spectrum-Table-sortedIcon')} />
943
+ }
944
+ {columnProps.hideHeader ?
945
+ <VisuallyHidden>{column.rendered}</VisuallyHidden> :
946
+ <div className={classNames(styles, 'spectrum-Table-headerCellText')}>{column.rendered}</div>
947
+ }
948
+ {
949
+ columnProps.allowsResizing && <ChevronDownMedium UNSAFE_className={classNames(styles, 'spectrum-Table-menuChevron')} />
950
+ }
951
+ </TableColumnHeaderButton>
952
+ <Menu onAction={onMenuSelect} minWidth="size-2000" items={items}>
953
+ {(item) => (
954
+ <Item>
955
+ {item.label}
956
+ </Item>
957
+ )}
958
+ </Menu>
959
+ </MenuTrigger>
960
+ <Resizer
961
+ ref={resizingRef}
962
+ column={column}
963
+ showResizer={showResizer}
964
+ onResizeStart={onResizeStart}
965
+ onResize={onResize}
966
+ onResizeEnd={onResizeEnd}
967
+ triggerRef={useUnwrapDOMRef(triggerRef)} />
968
+ <div
969
+ aria-hidden
970
+ className={classNames(
971
+ styles,
972
+ 'spectrum-Table-colResizeIndicator',
973
+ {
974
+ 'spectrum-Table-colResizeIndicator--visible': resizingColumn != null,
975
+ 'spectrum-Table-colResizeIndicator--resizing': resizingColumn === column.key
976
+ }
977
+ )}>
978
+ <div
979
+ className={classNames(
980
+ styles,
981
+ 'spectrum-Table-colResizeNubbin',
982
+ {
983
+ 'spectrum-Table-colResizeNubbin--visible': isInResizeMode && resizingColumn === column.key
984
+ }
985
+ )}>
986
+ <Nubbin />
987
+ </div>
988
+ </div>
989
+ </div>
990
+ </FocusRing>
991
+ );
992
+ }
993
+
994
+ function TableSelectAllCell({column}) {
995
+ let ref = useRef(undefined);
996
+ let {state} = useTableContext();
997
+ let isSingleSelectionMode = state.selectionManager.selectionMode === 'single';
998
+ let {columnHeaderProps} = useTableColumnHeader({
999
+ node: column,
1000
+ isVirtualized: true
1001
+ }, state, ref);
1002
+
1003
+ let {checkboxProps} = useTableSelectAllCheckbox(state);
1004
+ let {hoverProps, isHovered} = useHover({});
1005
+
1006
+ return (
1007
+ <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1008
+ <div
1009
+ {...mergeProps(columnHeaderProps, hoverProps)}
1010
+ ref={ref}
1011
+ className={
1012
+ classNames(
1013
+ styles,
1014
+ 'spectrum-Table-headCell',
1015
+ 'spectrum-Table-checkboxCell',
1016
+ {
1017
+ 'is-hovered': isHovered
1018
+ }
1019
+ )
1020
+ }>
1021
+ {
1022
+ /*
1023
+ In single selection mode, the checkbox will be hidden.
1024
+ So to avoid leaving a column header with no accessible content,
1025
+ we use a VisuallyHidden component to include the aria-label from the checkbox,
1026
+ which for single selection will be "Select."
1027
+ */
1028
+ isSingleSelectionMode &&
1029
+ <VisuallyHidden>{checkboxProps['aria-label']}</VisuallyHidden>
1030
+ }
1031
+ <Checkbox
1032
+ {...checkboxProps}
1033
+ isEmphasized
1034
+ UNSAFE_style={isSingleSelectionMode ? {visibility: 'hidden'} : undefined}
1035
+ UNSAFE_className={classNames(styles, 'spectrum-Table-checkbox')} />
1036
+ </div>
1037
+ </FocusRing>
1038
+ );
1039
+ }
1040
+
1041
+ function TableDragHeaderCell({column}) {
1042
+ let ref = useRef(undefined);
1043
+ let {state} = useTableContext();
1044
+ let {columnHeaderProps} = useTableColumnHeader({
1045
+ node: column,
1046
+ isVirtualized: true
1047
+ }, state, ref);
1048
+ let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/table');
1049
+
1050
+ return (
1051
+ <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1052
+ <div
1053
+ {...columnHeaderProps}
1054
+ ref={ref}
1055
+ className={
1056
+ classNames(
1057
+ styles,
1058
+ 'spectrum-Table-headCell',
1059
+ classNames(
1060
+ stylesOverrides,
1061
+ 'react-spectrum-Table-headCell',
1062
+ 'react-spectrum-Table-dragButtonHeadCell'
1063
+ )
1064
+ )
1065
+ }>
1066
+ <VisuallyHidden>{stringFormatter.format('drag')}</VisuallyHidden>
1067
+ </div>
1068
+ </FocusRing>
1069
+ );
1070
+ }
1071
+
1072
+ function TableRowGroup({children, layoutInfo, parent, ...otherProps}) {
1073
+ let {rowGroupProps} = useTableRowGroup();
1074
+ let {isTableDroppable} = useContext(TableContext);
1075
+ let style = useStyle(layoutInfo, parent);
1076
+
1077
+ return (
1078
+ <div {...rowGroupProps} style={style} {...otherProps}>
1079
+ {isTableDroppable &&
1080
+ <RootDropIndicator key="root" />
1081
+ }
1082
+ {children}
1083
+ </div>
1084
+ );
1085
+ }
1086
+
1087
+ function DragButton() {
1088
+ let {dragButtonProps, dragButtonRef, isFocusVisibleWithin} = useTableRowContext();
1089
+ let {visuallyHiddenProps} = useVisuallyHidden();
1090
+ return (
1091
+ <FocusRing focusRingClass={classNames(stylesOverrides, 'focus-ring')}>
1092
+ <div
1093
+ {...dragButtonProps as React.HTMLAttributes<HTMLElement>}
1094
+ className={
1095
+ classNames(
1096
+ stylesOverrides,
1097
+ 'react-spectrum-Table-dragButton'
1098
+ )
1099
+ }
1100
+ style={!isFocusVisibleWithin ? {...visuallyHiddenProps.style} : {}}
1101
+ ref={dragButtonRef}
1102
+ draggable="true">
1103
+ <ListGripper UNSAFE_className={classNames(stylesOverrides)} />
1104
+ </div>
1105
+ </FocusRing>
1106
+ );
1107
+ }
1108
+
1109
+ interface TableRowContextValue {
1110
+ dragButtonProps: React.HTMLAttributes<HTMLDivElement>,
1111
+ dragButtonRef: React.MutableRefObject<undefined>,
1112
+ isFocusVisibleWithin: boolean
1113
+ }
1114
+
1115
+
1116
+ const TableRowContext = React.createContext<TableRowContextValue>(null);
1117
+ export function useTableRowContext() {
1118
+ return useContext(TableRowContext);
1119
+ }
1120
+
1121
+ function TableRow({item, children, layoutInfo, parent, ...otherProps}) {
1122
+ let ref = useRef(undefined);
1123
+ let {state, layout, dragAndDropHooks, isTableDraggable, isTableDroppable, dragState, dropState} = useTableContext();
1124
+ let isSelected = state.selectionManager.isSelected(item.key);
1125
+ let {rowProps, hasAction, allowsSelection} = useTableRow({
1126
+ node: item,
1127
+ isVirtualized: true,
1128
+ shouldSelectOnPressUp: isTableDraggable
1129
+ }, state, ref);
1130
+
1131
+ let isDisabled = state.selectionManager.isDisabled(item.key);
1132
+ let isInteractive = !isDisabled && (hasAction || allowsSelection || isTableDraggable);
1133
+ let isDroppable = isTableDroppable && !isDisabled;
1134
+ let {pressProps, isPressed} = usePress({isDisabled: !isInteractive});
1135
+
1136
+ // The row should show the focus background style when any cell inside it is focused.
1137
+ // If the row itself is focused, then it should have a blue focus indicator on the left.
1138
+ let {
1139
+ isFocusVisible: isFocusVisibleWithin,
1140
+ focusProps: focusWithinProps
1141
+ } = useFocusRing({within: true});
1142
+ let {isFocusVisible, focusProps} = useFocusRing();
1143
+ let {hoverProps, isHovered} = useHover({isDisabled: !isInteractive});
1144
+ let isFirstRow = state.collection.rows.find(row => row.level === 1)?.key === item.key;
1145
+ let isLastRow = item.nextKey == null;
1146
+ // Figure out if the TableView content is equal or greater in height to the container. If so, we'll need to round the bottom
1147
+ // border corners of the last row when selected.
1148
+ let isFlushWithContainerBottom = false;
1149
+ if (isLastRow) {
1150
+ if (layout.getContentSize()?.height >= layout.virtualizer?.visibleRect.height) {
1151
+ isFlushWithContainerBottom = true;
1152
+ }
1153
+ }
1154
+
1155
+ let draggableItem: DraggableItemResult;
1156
+ if (isTableDraggable) {
1157
+ // eslint-disable-next-line react-hooks/rules-of-hooks
1158
+ draggableItem = dragAndDropHooks.useDraggableItem({key: item.key, hasDragButton: true}, dragState);
1159
+ if (isDisabled) {
1160
+ draggableItem = null;
1161
+ }
1162
+ }
1163
+ let droppableItem: DroppableItemResult;
1164
+ let isDropTarget: boolean;
1165
+ let dropIndicator: DropIndicatorAria;
1166
+ let dropIndicatorRef = useRef(undefined);
1167
+ if (isTableDroppable) {
1168
+ let target = {type: 'item', key: item.key, dropPosition: 'on'} as DropTarget;
1169
+ isDropTarget = dropState.isDropTarget(target);
1170
+ // eslint-disable-next-line react-hooks/rules-of-hooks
1171
+ dropIndicator = dragAndDropHooks.useDropIndicator({target}, dropState, dropIndicatorRef);
1172
+ }
1173
+
1174
+ let dragButtonRef = React.useRef(undefined);
1175
+ let {buttonProps: dragButtonProps} = useButton({
1176
+ ...draggableItem?.dragButtonProps,
1177
+ elementType: 'div'
1178
+ }, dragButtonRef);
1179
+
1180
+ let style = useStyle(layoutInfo, parent);
1181
+
1182
+ let props = mergeProps(
1183
+ rowProps,
1184
+ otherProps,
1185
+ {style},
1186
+ focusWithinProps,
1187
+ focusProps,
1188
+ hoverProps,
1189
+ pressProps,
1190
+ draggableItem?.dragProps,
1191
+ // Remove tab index from list row if performing a screenreader drag. This prevents TalkBack from focusing the row,
1192
+ // allowing for single swipe navigation between row drop indicator
1193
+ dragAndDropHooks?.isVirtualDragging() && {tabIndex: null}
1194
+ ) as HTMLAttributes<HTMLElement> & DOMAttributes<FocusableElement>;
1195
+
1196
+ let dropProps = isDroppable ? droppableItem?.dropProps : {'aria-hidden': droppableItem?.dropProps['aria-hidden']};
1197
+ let {visuallyHiddenProps} = useVisuallyHidden();
1198
+
1199
+ return (
1200
+ <TableRowContext.Provider value={{dragButtonProps, dragButtonRef, isFocusVisibleWithin}}>
1201
+ {isTableDroppable && isFirstRow &&
1202
+ <InsertionIndicator
1203
+ rowProps={props}
1204
+ key={`${item.key}-before`}
1205
+ target={{key: item.key, type: 'item', dropPosition: 'before'}} />
1206
+ }
1207
+ {isTableDroppable && !dropIndicator?.isHidden &&
1208
+ <div role="row" {...visuallyHiddenProps}>
1209
+ <div role="gridcell">
1210
+ <div role="button" {...dropIndicator?.dropIndicatorProps} ref={dropIndicatorRef} />
1211
+ </div>
1212
+ </div>
1213
+ }
1214
+ <div
1215
+ {...mergeProps(props, dropProps)}
1216
+ ref={ref}
1217
+ className={
1218
+ classNames(
1219
+ styles,
1220
+ 'spectrum-Table-row',
1221
+ {
1222
+ 'is-active': isPressed,
1223
+ 'is-selected': isSelected,
1224
+ 'spectrum-Table-row--highlightSelection': state.selectionManager.selectionBehavior === 'replace',
1225
+ 'is-next-selected': state.selectionManager.isSelected(item.nextKey),
1226
+ 'is-focused': isFocusVisibleWithin,
1227
+ 'focus-ring': isFocusVisible,
1228
+ 'is-hovered': isHovered,
1229
+ 'is-disabled': isDisabled,
1230
+ 'spectrum-Table-row--firstRow': isFirstRow,
1231
+ 'spectrum-Table-row--lastRow': isLastRow,
1232
+ 'spectrum-Table-row--isFlushBottom': isFlushWithContainerBottom
1233
+ },
1234
+ classNames(
1235
+ stylesOverrides,
1236
+ 'react-spectrum-Table-row',
1237
+ {'react-spectrum-Table-row--dropTarget': isDropTarget}
1238
+ )
1239
+ )
1240
+ }>
1241
+ {children}
1242
+ </div>
1243
+ {isTableDroppable &&
1244
+ <InsertionIndicator
1245
+ rowProps={props}
1246
+ key={`${item.key}-after`}
1247
+ target={{key: item.key, type: 'item', dropPosition: 'after'}} />
1248
+ }
1249
+ </TableRowContext.Provider>
1250
+ );
1251
+ }
1252
+
1253
+ function TableHeaderRow({item, children, layoutInfo, parent, ...props}) {
1254
+ let {state, headerMenuOpen} = useTableContext();
1255
+ let ref = useRef(undefined);
1256
+ let {rowProps} = useTableHeaderRow({node: item, isVirtualized: true}, state, ref);
1257
+ let {hoverProps} = useHover({...props, isDisabled: headerMenuOpen});
1258
+ let style = useStyle(layoutInfo, parent);
1259
+
1260
+ return (
1261
+ <div {...mergeProps(rowProps, hoverProps)} ref={ref} style={style}>
1262
+ {children}
1263
+ </div>
1264
+ );
1265
+ }
1266
+
1267
+ function TableDragCell({cell}) {
1268
+ let ref = useRef(undefined);
1269
+ let {state, isTableDraggable} = useTableContext();
1270
+ let isDisabled = state.selectionManager.isDisabled(cell.parentKey);
1271
+ let {gridCellProps} = useTableCell({
1272
+ node: cell,
1273
+ isVirtualized: true
1274
+ }, state, ref);
1275
+
1276
+
1277
+ return (
1278
+ <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1279
+ <div
1280
+ {...gridCellProps}
1281
+ ref={ref}
1282
+ className={
1283
+ classNames(
1284
+ styles,
1285
+ 'spectrum-Table-cell',
1286
+ {
1287
+ 'is-disabled': isDisabled
1288
+ },
1289
+ classNames(
1290
+ stylesOverrides,
1291
+ 'react-spectrum-Table-cell',
1292
+ 'react-spectrum-Table-dragButtonCell'
1293
+ )
1294
+ )
1295
+ }>
1296
+ {isTableDraggable && !isDisabled && <DragButton />}
1297
+ </div>
1298
+ </FocusRing>
1299
+ );
1300
+ }
1301
+
1302
+ function TableCheckboxCell({cell}) {
1303
+ let ref = useRef(undefined);
1304
+ let {state} = useTableContext();
1305
+ // The TableCheckbox should always render its disabled status if the row is disabled, regardless of disabledBehavior,
1306
+ // but the cell itself should not render its disabled styles if disabledBehavior="selection" because the row might have actions on it.
1307
+ let isSelectionDisabled = state.disabledKeys.has(cell.parentKey);
1308
+ let isDisabled = state.selectionManager.isDisabled(cell.parentKey);
1309
+ let {gridCellProps} = useTableCell({
1310
+ node: cell,
1311
+ isVirtualized: true
1312
+ }, state, ref);
1313
+
1314
+ let {checkboxProps} = useTableSelectionCheckbox({key: cell.parentKey}, state);
1315
+
1316
+ return (
1317
+ <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1318
+ <div
1319
+ {...gridCellProps}
1320
+ ref={ref}
1321
+ className={
1322
+ classNames(
1323
+ styles,
1324
+ 'spectrum-Table-cell',
1325
+ 'spectrum-Table-checkboxCell',
1326
+ {
1327
+ 'is-disabled': isDisabled
1328
+ },
1329
+ classNames(
1330
+ stylesOverrides,
1331
+ 'react-spectrum-Table-cell'
1332
+ )
1333
+ )
1334
+ }>
1335
+ {state.selectionManager.selectionMode !== 'none' &&
1336
+ <Checkbox
1337
+ {...checkboxProps}
1338
+ isEmphasized
1339
+ isDisabled={isSelectionDisabled}
1340
+ UNSAFE_className={classNames(styles, 'spectrum-Table-checkbox')} />
1341
+ }
1342
+ </div>
1343
+ </FocusRing>
1344
+ );
1345
+ }
1346
+
1347
+ function TableCell({cell}) {
1348
+ let {scale} = useProvider();
1349
+ let {state} = useTableContext();
1350
+ let isExpandableTable = 'expandedKeys' in state;
1351
+ let ref = useRef(undefined);
1352
+ let columnProps = cell.column.props as SpectrumColumnProps<unknown>;
1353
+ let isDisabled = state.selectionManager.isDisabled(cell.parentKey);
1354
+ let {gridCellProps} = useTableCell({
1355
+ node: cell,
1356
+ isVirtualized: true
1357
+ }, state, ref);
1358
+ let {id, ...otherGridCellProps} = gridCellProps;
1359
+ let isFirstRowHeaderCell = state.collection.rowHeaderColumnKeys.keys().next().value === cell.column.key;
1360
+ let isRowExpandable = false;
1361
+ let showExpandCollapseButton = false;
1362
+ let levelOffset = 0;
1363
+
1364
+ if ('expandedKeys' in state) {
1365
+ isRowExpandable = state.keyMap.get(cell.parentKey)?.props.UNSTABLE_childItems?.length > 0 || state.keyMap.get(cell.parentKey)?.props?.children?.length > state.userColumnCount;
1366
+ showExpandCollapseButton = isFirstRowHeaderCell && isRowExpandable;
1367
+ // Offset based on level, and add additional offset if there is no expand/collapse button on a row
1368
+ levelOffset = (cell.level - 2) * LEVEL_OFFSET_WIDTH[scale] + (!showExpandCollapseButton ? LEVEL_OFFSET_WIDTH[scale] * 2 : 0);
1369
+ }
1370
+
1371
+ return (
1372
+ <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1373
+ <div
1374
+ {...otherGridCellProps}
1375
+ aria-labelledby={id}
1376
+ ref={ref}
1377
+ style={isExpandableTable && isFirstRowHeaderCell ? {paddingInlineStart: levelOffset} : {}}
1378
+ className={
1379
+ classNames(
1380
+ styles,
1381
+ 'spectrum-Table-cell',
1382
+ {
1383
+ 'spectrum-Table-cell--divider': columnProps.showDivider && cell.column.nextKey !== null,
1384
+ 'spectrum-Table-cell--hideHeader': columnProps.hideHeader,
1385
+ 'spectrum-Table-cell--hasExpandCollapseButton': showExpandCollapseButton,
1386
+ 'is-disabled': isDisabled
1387
+ },
1388
+ classNames(
1389
+ stylesOverrides,
1390
+ 'react-spectrum-Table-cell',
1391
+ {
1392
+ 'react-spectrum-Table-cell--alignStart': columnProps.align === 'start',
1393
+ 'react-spectrum-Table-cell--alignCenter': columnProps.align === 'center',
1394
+ 'react-spectrum-Table-cell--alignEnd': columnProps.align === 'end'
1395
+ }
1396
+ )
1397
+ )
1398
+ }>
1399
+ {showExpandCollapseButton && <ExpandableRowChevron cell={cell} />}
1400
+ <span
1401
+ id={id}
1402
+ className={
1403
+ classNames(
1404
+ styles,
1405
+ 'spectrum-Table-cellContents'
1406
+ )
1407
+ }>
1408
+ {cell.rendered}
1409
+ </span>
1410
+ </div>
1411
+ </FocusRing>
1412
+ );
1413
+ }
1414
+
1415
+ function TableCellWrapper({layoutInfo, virtualizer, parent, children}) {
1416
+ let {isTableDroppable, dropState} = useContext(TableContext);
1417
+ let isDropTarget: boolean;
1418
+ let isRootDroptarget: boolean;
1419
+ if (isTableDroppable) {
1420
+ if (parent.content) {
1421
+ isDropTarget = dropState.isDropTarget({type: 'item', dropPosition: 'on', key: parent.content.key});
1422
+ }
1423
+ isRootDroptarget = dropState.isDropTarget({type: 'root'});
1424
+ }
1425
+
1426
+ return (
1427
+ <VirtualizerItem
1428
+ layoutInfo={layoutInfo}
1429
+ virtualizer={virtualizer}
1430
+ parent={parent?.layoutInfo}
1431
+ className={
1432
+ useMemo(() => classNames(
1433
+ styles,
1434
+ 'spectrum-Table-cellWrapper',
1435
+ classNames(
1436
+ stylesOverrides,
1437
+ {
1438
+ 'react-spectrum-Table-cellWrapper': !layoutInfo.estimatedSize,
1439
+ 'react-spectrum-Table-cellWrapper--dropTarget': isDropTarget || isRootDroptarget
1440
+ }
1441
+ )
1442
+ ), [layoutInfo.estimatedSize, isDropTarget, isRootDroptarget])
1443
+ }>
1444
+ {children}
1445
+ </VirtualizerItem>
1446
+ );
1447
+ }
1448
+
1449
+ function ExpandableRowChevron({cell}) {
1450
+ // TODO: move some/all of the chevron button setup into a separate hook?
1451
+ let {direction} = useLocale();
1452
+ let {state} = useTableContext();
1453
+ let expandButtonRef = useRef(undefined);
1454
+ let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/table');
1455
+ let isExpanded;
1456
+
1457
+ if ('expandedKeys' in state) {
1458
+ isExpanded = state.expandedKeys === 'all' || state.expandedKeys.has(cell.parentKey);
1459
+ }
1460
+
1461
+ // Will need to keep the chevron as a button for iOS VO at all times since VO doesn't focus the cell. Also keep as button if cellAction is defined by the user in the future
1462
+ let {buttonProps} = useButton({
1463
+ // Desktop and mobile both toggle expansion of a native expandable row on mouse/touch up
1464
+ onPress: () => {
1465
+ (state as TreeGridState<unknown>).toggleKey(cell.parentKey);
1466
+ if (!isFocusVisible()) {
1467
+ state.selectionManager.setFocused(true);
1468
+ state.selectionManager.setFocusedKey(cell.parentKey);
1469
+ }
1470
+ },
1471
+ elementType: 'span',
1472
+ 'aria-label': isExpanded ? stringFormatter.format('collapse') : stringFormatter.format('expand')
1473
+ }, expandButtonRef);
1474
+
1475
+ return (
1476
+ <span
1477
+ {...buttonProps}
1478
+ ref={expandButtonRef}
1479
+ // Override tabindex so that grid keyboard nav skips over it. Needs -1 so android talkback can actually "focus" it
1480
+ tabIndex={isAndroid() ? -1 : undefined}
1481
+ className={
1482
+ classNames(
1483
+ styles,
1484
+ 'spectrum-Table-expandButton',
1485
+ {
1486
+ 'is-open': isExpanded
1487
+ }
1488
+ )
1489
+ }>
1490
+ {direction === 'ltr' ? <ChevronRightMedium /> : <ChevronLeftMedium />}
1491
+ </span>
1492
+ );
1493
+ }
1494
+
1495
+ function LoadingState() {
1496
+ let {state} = useContext(TableContext);
1497
+ let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/table');
1498
+ return (
1499
+ <CenteredWrapper>
1500
+ <ProgressCircle
1501
+ isIndeterminate
1502
+ aria-label={state.collection.size > 0 ? stringFormatter.format('loadingMore') : stringFormatter.format('loading')} />
1503
+ </CenteredWrapper>
1504
+ );
1505
+ }
1506
+
1507
+ function EmptyState() {
1508
+ let {renderEmptyState} = useContext(TableContext);
1509
+ let emptyState = renderEmptyState ? renderEmptyState() : null;
1510
+ if (emptyState == null) {
1511
+ return null;
1512
+ }
1513
+
1514
+ return (
1515
+ <CenteredWrapper>
1516
+ {emptyState}
1517
+ </CenteredWrapper>
1518
+ );
1519
+ }
1520
+
1521
+ function CenteredWrapper({children}) {
1522
+ let {state} = useTableContext();
1523
+ let rowProps;
1524
+
1525
+ if ('expandedKeys' in state) {
1526
+ let topLevelRowCount = [...state.keyMap.get(state.collection.body.key).childNodes].length;
1527
+ rowProps = {
1528
+ 'aria-level': 1,
1529
+ 'aria-posinset': topLevelRowCount + 1,
1530
+ 'aria-setsize': topLevelRowCount + 1
1531
+ };
1532
+ } else {
1533
+ rowProps = {
1534
+ 'aria-rowindex': state.collection.headerRows.length + state.collection.size + 1
1535
+ };
1536
+ }
1537
+
1538
+ return (
1539
+ <div
1540
+ role="row"
1541
+ {...rowProps}
1542
+ className={classNames(stylesOverrides, 'react-spectrum-Table-centeredWrapper')}>
1543
+ <div role="rowheader" aria-colspan={state.collection.columns.length}>
1544
+ {children}
1545
+ </div>
1546
+ </div>
1547
+ );
1548
+ }
1549
+
1550
+ const _TableViewBase = React.forwardRef(TableViewBase) as <T>(props: TableBaseProps<T> & {ref?: DOMRef<HTMLDivElement>}) => ReactElement;
1551
+
1552
+ export {_TableViewBase as TableViewBase};