@react-spectrum/table 3.17.11 → 3.18.0

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