@react-spectrum/table 3.17.10 → 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 -1239
  40. package/dist/TableViewBase.main.js.map +0 -1
  41. package/dist/TableViewBase.mjs +0 -1232
  42. package/dist/TableViewBase.module.js +0 -1232
  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 -1569
  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,1569 +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 {getInteractionModality, HoverProps, 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, nodeContains, 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 | 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 && nodeContains(headerRef.current, document.activeElement) && bodyRef.current) {
610
- scrollIntoView(headerRef.current, document.activeElement as HTMLElement);
611
- scrollIntoViewport(document.activeElement, {containingElement: domRef.current});
612
- bodyRef.current.scrollLeft = headerRef.current.scrollLeft;
613
- }
614
- }, [state.contentSize, headerRef, bodyRef, domRef]);
615
-
616
- let headerHeight = layout.getLayoutInfo('header')?.rect.height || 0;
617
-
618
- // Sync the scroll position from the table body to the header container.
619
- let onScroll = useCallback(() => {
620
- if (headerRef.current && bodyRef.current) {
621
- headerRef.current.scrollLeft = bodyRef.current.scrollLeft;
622
- }
623
- }, [bodyRef, headerRef]);
624
-
625
- let resizerPosition = columnResizeState.resizingColumn != null ? layout.getLayoutInfo(columnResizeState.resizingColumn)!.rect.maxX - 2 : 0;
626
-
627
- let resizerAtEdge = resizerPosition > Math.max(state.virtualizer.contentSize.width, state.virtualizer.visibleRect.width) - 3;
628
- // this should be fine, every movement of the resizer causes a rerender
629
- // scrolling can cause it to lag for a moment, but it's always updated
630
- let resizerInVisibleRegion = resizerPosition < state.virtualizer.visibleRect.maxX;
631
- let shouldHardCornerResizeCorner = resizerAtEdge && resizerInVisibleRegion;
632
-
633
- // minimize re-render caused on Resizers by memoing this
634
- let resizingColumnWidth = columnResizeState.resizingColumn != null ? columnResizeState.getColumnWidth(columnResizeState.resizingColumn) : 0;
635
- let resizingColumn = useMemo(() => ({
636
- width: resizingColumnWidth,
637
- key: columnResizeState.resizingColumn
638
- }), [resizingColumnWidth, columnResizeState.resizingColumn]);
639
-
640
- if (isVirtualDragging) {
641
- otherProps.tabIndex = undefined;
642
- }
643
-
644
- let firstColumn = collection.columns[0];
645
- let scrollPadding = 0;
646
- if (firstColumn.props.isSelectionCell || firstColumn.props.isDragButtonCell) {
647
- scrollPadding = columnResizeState.getColumnWidth(firstColumn.key);
648
- }
649
-
650
- let visibleViews = renderChildren(null, state.visibleViews, renderWrapper);
651
-
652
- return (
653
- <VirtualizerContext.Provider value={resizingColumn}>
654
- <FocusScope>
655
- <div
656
- {...otherProps}
657
- ref={domRef}>
658
- <div
659
- role="presentation"
660
- className={classNames(styles, 'spectrum-Table-headWrapper')}
661
- style={{
662
- height: headerHeight,
663
- overflow: 'hidden',
664
- position: 'relative',
665
- willChange: state.isScrolling ? 'scroll-position' : undefined,
666
- scrollPaddingInlineStart: scrollPadding
667
- }}
668
- ref={headerRef}>
669
- <ResizeStateContext.Provider value={columnResizeState}>
670
- {visibleViews[0]}
671
- </ResizeStateContext.Provider>
672
- </div>
673
- <ScrollView
674
- className={
675
- classNames(
676
- styles,
677
- 'spectrum-Table-body',
678
- {
679
- 'focus-ring': isFocusVisible,
680
- 'spectrum-Table-body--resizerAtTableEdge': shouldHardCornerResizeCorner
681
- },
682
- classNames(
683
- stylesOverrides,
684
- 'react-spectrum-Table-body',
685
- {
686
- 'react-spectrum-Table-body--dropTarget': !!isRootDropTarget
687
- }
688
- )
689
- )
690
- }
691
- // Firefox and Chrome make generic elements using CSS overflow 'scroll' or 'auto' tabbable,
692
- // including them within the accessibility tree, which breaks the table structure in Firefox.
693
- // Using tabIndex={-1} prevents the ScrollView from being tabbable, and using role="rowgroup"
694
- // here and role="presentation" on the table body content fixes the table structure.
695
- role="rowgroup"
696
- tabIndex={isVirtualDragging ? undefined : -1}
697
- style={{
698
- flex: 1,
699
- scrollPaddingInlineStart: scrollPadding
700
- }}
701
- innerStyle={{overflow: 'visible'}}
702
- ref={bodyRef}
703
- contentSize={state.contentSize}
704
- onVisibleRectChange={onVisibleRectChangeMemo}
705
- onScrollStart={state.startScrolling}
706
- onScrollEnd={state.endScrolling}
707
- onScroll={onScroll}>
708
- {visibleViews[1]}
709
- <div
710
- className={classNames(styles, 'spectrum-Table-bodyResizeIndicator')}
711
- style={{[direction === 'ltr' ? 'left' : 'right']: `${resizerPosition}px`, height: `${Math.max(state.virtualizer.contentSize.height, state.virtualizer.visibleRect.height)}px`, display: columnResizeState.resizingColumn ? 'block' : 'none'}} />
712
- </ScrollView>
713
- </div>
714
- </FocusScope>
715
- </VirtualizerContext.Provider>
716
- );
717
- }
718
-
719
- function renderChildren<T extends object>(parent: View | null, views: View[], renderWrapper: NonNullable<TableVirtualizerProps<T>['renderWrapper']>) {
720
- return views.map(view => {
721
- return renderWrapper(
722
- parent,
723
- view,
724
- view.children ? Array.from(view.children) : [],
725
- childViews => renderChildren(view, childViews, renderWrapper)
726
- );
727
- });
728
- }
729
-
730
- function useStyle(layoutInfo: LayoutInfo, parent: LayoutInfo | null) {
731
- let {direction} = useLocale();
732
- let style = layoutInfoToStyle(layoutInfo, direction, parent);
733
- if (style.overflow === 'hidden') {
734
- style.overflow = 'visible'; // needed to support position: sticky
735
- }
736
- return style;
737
- }
738
-
739
- function TableHeader({children, layoutInfo, parent, ...otherProps}: {children: ReactNode, layoutInfo: LayoutInfo, parent: LayoutInfo | null}) {
740
- let {rowGroupProps} = useTableRowGroup();
741
- let style = useStyle(layoutInfo, parent);
742
-
743
- return (
744
- <div {...rowGroupProps} {...otherProps} className={classNames(styles, 'spectrum-Table-head')} style={style}>
745
- {children}
746
- </div>
747
- );
748
- }
749
-
750
- function TableColumnHeader(props) {
751
- let {column} = props;
752
- let ref = useRef<HTMLDivElement>(null);
753
- let {state, isEmpty} = useTableContext();
754
- let {pressProps, isPressed} = usePress({isDisabled: isEmpty});
755
- let columnProps = column.props as SpectrumColumnProps<unknown>;
756
- useEffect(() => {
757
- if (column.hasChildNodes && columnProps.allowsResizing && process.env.NODE_ENV !== 'production') {
758
- console.warn(`Column key: ${column.key}. Columns with child columns don't allow resizing.`);
759
- }
760
- }, [column.hasChildNodes, column.key, columnProps.allowsResizing]);
761
-
762
- let {columnHeaderProps} = useTableColumnHeader({
763
- node: column,
764
- isVirtualized: true
765
- }, state, ref);
766
-
767
- let {hoverProps, isHovered} = useHover({...props, isDisabled: isEmpty});
768
-
769
- const allProps = [columnHeaderProps, hoverProps, pressProps];
770
-
771
- return (
772
- <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
773
- <div
774
- {...mergeProps(...allProps)}
775
- ref={ref}
776
- className={
777
- classNames(
778
- styles,
779
- 'spectrum-Table-headCell',
780
- {
781
- 'is-active': isPressed,
782
- 'is-sortable': columnProps.allowsSorting,
783
- 'is-sorted-desc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'descending',
784
- 'is-sorted-asc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'ascending',
785
- 'is-hovered': isHovered,
786
- 'spectrum-Table-cell--hideHeader': columnProps.hideHeader
787
- },
788
- classNames(
789
- stylesOverrides,
790
- 'react-spectrum-Table-cell',
791
- {
792
- 'react-spectrum-Table-cell--alignCenter': columnProps.align === 'center' || column.colSpan > 1,
793
- 'react-spectrum-Table-cell--alignEnd': columnProps.align === 'end'
794
- }
795
- )
796
- )
797
- }>
798
- {columnProps.allowsSorting &&
799
- <ArrowDownSmall UNSAFE_className={classNames(styles, 'spectrum-Table-sortedIcon')} />
800
- }
801
- {columnProps.hideHeader ?
802
- <VisuallyHidden>{column.rendered}</VisuallyHidden> :
803
- <div className={classNames(styles, 'spectrum-Table-headCellContents')}>{column.rendered}</div>
804
- }
805
- </div>
806
- </FocusRing>
807
- );
808
- }
809
-
810
- let ForwardTableColumnHeaderButton = (props, ref: FocusableRef<HTMLDivElement>) => {
811
- let {focusProps, alignment, ...otherProps} = props;
812
- let {isEmpty} = useTableContext();
813
- let domRef = useFocusableRef(ref);
814
- let {buttonProps} = useButton({...otherProps, elementType: 'div', isDisabled: isEmpty}, domRef);
815
- let {hoverProps, isHovered} = useHover({...otherProps, isDisabled: isEmpty});
816
-
817
- return (
818
- <div
819
- className={
820
- classNames(
821
- styles,
822
- 'spectrum-Table-headCellContents',
823
- {
824
- 'is-hovered': isHovered
825
- }
826
- )
827
- }
828
- {...hoverProps}>
829
- <div
830
- className={
831
- classNames(
832
- styles,
833
- 'spectrum-Table-headCellButton',
834
- {
835
- 'spectrum-Table-headCellButton--alignStart': alignment === 'start',
836
- 'spectrum-Table-headCellButton--alignCenter': alignment === 'center',
837
- 'spectrum-Table-headCellButton--alignEnd': alignment === 'end'
838
- }
839
- )
840
- }
841
- {...mergeProps(buttonProps, focusProps)}
842
- ref={domRef}>
843
- {props.children}
844
- </div>
845
- </div>
846
- );
847
- };
848
- let TableColumnHeaderButton = React.forwardRef(ForwardTableColumnHeaderButton);
849
-
850
- function ResizableTableColumnHeader(props) {
851
- let {column} = props;
852
- let ref = useRef(null);
853
- let triggerRef = useRef(null);
854
- let resizingRef = useRef(null);
855
- let {
856
- state,
857
- onResizeStart,
858
- onResize,
859
- onResizeEnd,
860
- headerRowHovered,
861
- setIsInResizeMode,
862
- isEmpty,
863
- isInResizeMode,
864
- headerMenuOpen,
865
- setHeaderMenuOpen
866
- } = useTableContext();
867
- let columnResizeState = useContext(ResizeStateContext)!;
868
- let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/table');
869
- let {pressProps, isPressed} = usePress({isDisabled: isEmpty});
870
- let {columnHeaderProps} = useTableColumnHeader({
871
- node: column,
872
- isVirtualized: true
873
- }, state, ref);
874
-
875
- let {hoverProps, isHovered} = useHover({...props, isDisabled: isEmpty || headerMenuOpen});
876
-
877
- const allProps = [columnHeaderProps, pressProps, hoverProps];
878
-
879
- let columnProps = column.props as SpectrumColumnProps<unknown>;
880
-
881
- let {isFocusVisible, focusProps} = useFocusRing();
882
-
883
- const onMenuSelect = (key) => {
884
- switch (key) {
885
- case 'sort-asc':
886
- state.sort(column.key, 'ascending');
887
- break;
888
- case 'sort-desc':
889
- state.sort(column.key, 'descending');
890
- break;
891
- case 'resize':
892
- columnResizeState.startResize(column.key);
893
- setIsInResizeMode(true);
894
- state.setKeyboardNavigationDisabled(true);
895
- break;
896
- }
897
- };
898
- let allowsSorting = column.props?.allowsSorting;
899
- let items = useMemo(() => {
900
- let options: {label: string, id: string}[] = [];
901
- if (allowsSorting) {
902
- options.push({
903
- label: stringFormatter.format('sortAscending'),
904
- id: 'sort-asc'
905
- });
906
- options.push({
907
- label: stringFormatter.format('sortDescending'),
908
- id: 'sort-desc'
909
- });
910
- }
911
- options.push({
912
- label: stringFormatter.format('resizeColumn'),
913
- id: 'resize'
914
- });
915
- return options;
916
- // eslint-disable-next-line react-hooks/exhaustive-deps
917
- }, [allowsSorting]);
918
-
919
- let resizingColumn = columnResizeState.resizingColumn;
920
- let showResizer = !isEmpty && ((headerRowHovered && getInteractionModality() !== 'keyboard') || resizingColumn != null);
921
- let alignment = 'start';
922
- let menuAlign = 'start' as 'start' | 'end';
923
- if (columnProps.align === 'center' || column.colSpan > 1) {
924
- alignment = 'center';
925
- } else if (columnProps.align === 'end') {
926
- alignment = 'end';
927
- menuAlign = 'end';
928
- }
929
-
930
- return (
931
- <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
932
- <div
933
- {...mergeProps(...allProps)}
934
- ref={ref}
935
- className={
936
- classNames(
937
- styles,
938
- 'spectrum-Table-headCell',
939
- {
940
- 'is-active': isPressed,
941
- 'is-resizable': columnProps.allowsResizing,
942
- 'is-sortable': columnProps.allowsSorting,
943
- 'is-sorted-desc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'descending',
944
- 'is-sorted-asc': state.sortDescriptor?.column === column.key && state.sortDescriptor?.direction === 'ascending',
945
- 'is-hovered': isHovered,
946
- 'focus-ring': isFocusVisible,
947
- 'spectrum-Table-cell--hideHeader': columnProps.hideHeader
948
- },
949
- classNames(
950
- stylesOverrides,
951
- 'react-spectrum-Table-cell',
952
- {
953
- 'react-spectrum-Table-cell--alignCenter': alignment === 'center',
954
- 'react-spectrum-Table-cell--alignEnd': alignment === 'end'
955
- }
956
- )
957
- )
958
- }>
959
- <MenuTrigger onOpenChange={setHeaderMenuOpen} align={menuAlign}>
960
- <TableColumnHeaderButton alignment={alignment} ref={triggerRef} focusProps={focusProps}>
961
- {columnProps.allowsSorting &&
962
- <ArrowDownSmall UNSAFE_className={classNames(styles, 'spectrum-Table-sortedIcon')} />
963
- }
964
- {columnProps.hideHeader ?
965
- <VisuallyHidden>{column.rendered}</VisuallyHidden> :
966
- <div className={classNames(styles, 'spectrum-Table-headerCellText')}>{column.rendered}</div>
967
- }
968
- {
969
- columnProps.allowsResizing && <ChevronDownMedium UNSAFE_className={classNames(styles, 'spectrum-Table-menuChevron')} />
970
- }
971
- </TableColumnHeaderButton>
972
- <Menu onAction={onMenuSelect} minWidth="size-2000" items={items}>
973
- {(item) => (
974
- <Item>
975
- {item.label}
976
- </Item>
977
- )}
978
- </Menu>
979
- </MenuTrigger>
980
- <Resizer
981
- ref={resizingRef}
982
- column={column}
983
- showResizer={showResizer}
984
- onResizeStart={onResizeStart}
985
- onResize={onResize}
986
- onResizeEnd={onResizeEnd}
987
- triggerRef={useUnwrapDOMRef(triggerRef)} />
988
- <div
989
- aria-hidden
990
- className={classNames(
991
- styles,
992
- 'spectrum-Table-colResizeIndicator',
993
- {
994
- 'spectrum-Table-colResizeIndicator--visible': resizingColumn != null,
995
- 'spectrum-Table-colResizeIndicator--resizing': resizingColumn === column.key
996
- }
997
- )}>
998
- <div
999
- className={classNames(
1000
- styles,
1001
- 'spectrum-Table-colResizeNubbin',
1002
- {
1003
- 'spectrum-Table-colResizeNubbin--visible': isInResizeMode && resizingColumn === column.key
1004
- }
1005
- )}>
1006
- <Nubbin />
1007
- </div>
1008
- </div>
1009
- </div>
1010
- </FocusRing>
1011
- );
1012
- }
1013
-
1014
- function TableSelectAllCell({column}) {
1015
- let ref = useRef<HTMLDivElement | null>(null);
1016
- let {state} = useTableContext();
1017
- let isSingleSelectionMode = state.selectionManager.selectionMode === 'single';
1018
- let {columnHeaderProps} = useTableColumnHeader({
1019
- node: column,
1020
- isVirtualized: true
1021
- }, state, ref);
1022
-
1023
- let {checkboxProps} = useTableSelectAllCheckbox(state);
1024
- let {hoverProps, isHovered} = useHover({});
1025
-
1026
- return (
1027
- <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1028
- <div
1029
- {...mergeProps(columnHeaderProps, hoverProps)}
1030
- ref={ref}
1031
- className={
1032
- classNames(
1033
- styles,
1034
- 'spectrum-Table-headCell',
1035
- 'spectrum-Table-checkboxCell',
1036
- {
1037
- 'is-hovered': isHovered
1038
- }
1039
- )
1040
- }>
1041
- {
1042
- /*
1043
- In single selection mode, the checkbox will be hidden.
1044
- So to avoid leaving a column header with no accessible content,
1045
- we use a VisuallyHidden component to include the aria-label from the checkbox,
1046
- which for single selection will be "Select."
1047
- */
1048
- isSingleSelectionMode &&
1049
- <VisuallyHidden>{checkboxProps['aria-label']}</VisuallyHidden>
1050
- }
1051
- <Checkbox
1052
- {...checkboxProps}
1053
- data-testid="selectAll"
1054
- isEmphasized
1055
- UNSAFE_style={isSingleSelectionMode ? {visibility: 'hidden'} : undefined}
1056
- UNSAFE_className={classNames(styles, 'spectrum-Table-checkbox')} />
1057
- </div>
1058
- </FocusRing>
1059
- );
1060
- }
1061
-
1062
- function TableDragHeaderCell({column}) {
1063
- let ref = useRef<HTMLDivElement | null>(null);
1064
- let {state} = useTableContext();
1065
- let {columnHeaderProps} = useTableColumnHeader({
1066
- node: column,
1067
- isVirtualized: true
1068
- }, state, ref);
1069
- let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/table');
1070
-
1071
- return (
1072
- <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1073
- <div
1074
- {...columnHeaderProps}
1075
- ref={ref}
1076
- className={
1077
- classNames(
1078
- styles,
1079
- 'spectrum-Table-headCell',
1080
- classNames(
1081
- stylesOverrides,
1082
- 'react-spectrum-Table-headCell',
1083
- 'react-spectrum-Table-dragButtonHeadCell'
1084
- )
1085
- )
1086
- }>
1087
- <VisuallyHidden>{stringFormatter.format('drag')}</VisuallyHidden>
1088
- </div>
1089
- </FocusRing>
1090
- );
1091
- }
1092
-
1093
- function TableRowGroup({children, layoutInfo, parent, ...otherProps}: {children: ReactNode, layoutInfo: LayoutInfo, parent: LayoutInfo | null, role: string}) {
1094
- let {rowGroupProps} = useTableRowGroup();
1095
- let {isTableDroppable} = useContext(TableContext)!;
1096
- let style = useStyle(layoutInfo, parent);
1097
-
1098
- return (
1099
- <div {...rowGroupProps} style={style} {...otherProps}>
1100
- {isTableDroppable &&
1101
- <RootDropIndicator key="root" />
1102
- }
1103
- {children}
1104
- </div>
1105
- );
1106
- }
1107
-
1108
- function DragButton() {
1109
- let {dragButtonProps, dragButtonRef, isFocusVisibleWithin} = useTableRowContext();
1110
- let {visuallyHiddenProps} = useVisuallyHidden();
1111
- return (
1112
- <FocusRing focusRingClass={classNames(stylesOverrides, 'focus-ring')}>
1113
- <div
1114
- {...dragButtonProps as React.HTMLAttributes<HTMLElement>}
1115
- className={
1116
- classNames(
1117
- stylesOverrides,
1118
- 'react-spectrum-Table-dragButton'
1119
- )
1120
- }
1121
- style={!isFocusVisibleWithin ? {...visuallyHiddenProps.style} : {}}
1122
- ref={dragButtonRef}
1123
- draggable="true">
1124
- <ListGripper UNSAFE_className={classNames(stylesOverrides)} />
1125
- </div>
1126
- </FocusRing>
1127
- );
1128
- }
1129
-
1130
- interface TableRowContextValue {
1131
- dragButtonProps: React.HTMLAttributes<HTMLDivElement>,
1132
- dragButtonRef: React.RefObject<HTMLDivElement | null>,
1133
- isFocusVisibleWithin: boolean
1134
- }
1135
-
1136
-
1137
- const TableRowContext = React.createContext<TableRowContextValue | null>(null);
1138
- export function useTableRowContext(): TableRowContextValue {
1139
- return useContext(TableRowContext)!;
1140
- }
1141
-
1142
- function TableRow({item, children, layoutInfo, parent, ...otherProps}: {item: GridNode<unknown>, children: ReactNode, layoutInfo: LayoutInfo, parent: LayoutInfo | null}) {
1143
- let ref = useRef<HTMLDivElement | null>(null);
1144
- let {state, layout, dragAndDropHooks, isTableDraggable, isTableDroppable, dragState, dropState} = useTableContext();
1145
- let isSelected = state.selectionManager.isSelected(item.key);
1146
- let {rowProps, hasAction, allowsSelection} = useTableRow({
1147
- node: item,
1148
- isVirtualized: true,
1149
- shouldSelectOnPressUp: isTableDraggable
1150
- }, state, ref);
1151
-
1152
- let isDisabled = state.selectionManager.isDisabled(item.key);
1153
- let isInteractive = !isDisabled && (hasAction || allowsSelection || isTableDraggable);
1154
- let {pressProps, isPressed} = usePress({isDisabled: !isInteractive});
1155
-
1156
- // The row should show the focus background style when any cell inside it is focused.
1157
- // If the row itself is focused, then it should have a blue focus indicator on the left.
1158
- let {
1159
- isFocusVisible: isFocusVisibleWithin,
1160
- focusProps: focusWithinProps
1161
- } = useFocusRing({within: true});
1162
- let {isFocusVisible, focusProps} = useFocusRing();
1163
- let {hoverProps, isHovered} = useHover({isDisabled: !isInteractive});
1164
- let isFirstRow = state.collection.rows.find(row => row.level === 1)?.key === item.key;
1165
- let isLastRow = item.nextKey == null;
1166
- // Figure out if the TableView content is equal or greater in height to the container. If so, we'll need to round the bottom
1167
- // border corners of the last row when selected.
1168
- let isFlushWithContainerBottom = false;
1169
- if (isLastRow) {
1170
- if (layout.getContentSize()?.height >= (layout.virtualizer?.visibleRect.height ?? 0)) {
1171
- isFlushWithContainerBottom = true;
1172
- }
1173
- }
1174
-
1175
- let draggableItem: DraggableItemResult | null = null;
1176
- if (isTableDraggable && dragAndDropHooks && dragState) {
1177
- draggableItem = dragAndDropHooks.useDraggableItem!({key: item.key, hasDragButton: true}, dragState);
1178
- if (isDisabled) {
1179
- draggableItem = null;
1180
- }
1181
- }
1182
- let isDropTarget = false;
1183
- let dropIndicator: DropIndicatorAria | null = null;
1184
- let dropIndicatorRef = useRef<HTMLDivElement | null>(null);
1185
- if (isTableDroppable && dragAndDropHooks && dropState) {
1186
- let target = {type: 'item', key: item.key, dropPosition: 'on'} as DropTarget;
1187
- isDropTarget = dropState.isDropTarget(target);
1188
-
1189
- dropIndicator = dragAndDropHooks.useDropIndicator!({target}, dropState, dropIndicatorRef);
1190
- }
1191
-
1192
- let dragButtonRef = React.useRef<HTMLDivElement | null>(null);
1193
- let {buttonProps: dragButtonProps} = useButton({
1194
- ...draggableItem?.dragButtonProps,
1195
- elementType: 'div'
1196
- }, dragButtonRef);
1197
-
1198
- let style = useStyle(layoutInfo, parent);
1199
-
1200
- let props = mergeProps(
1201
- rowProps,
1202
- otherProps,
1203
- {style},
1204
- focusWithinProps,
1205
- focusProps,
1206
- hoverProps,
1207
- pressProps,
1208
- draggableItem?.dragProps,
1209
- // Remove tab index from list row if performing a screenreader drag. This prevents TalkBack from focusing the row,
1210
- // allowing for single swipe navigation between row drop indicator
1211
- dragAndDropHooks?.isVirtualDragging?.() ? {tabIndex: null} : null
1212
- ) as HTMLAttributes<HTMLElement> & DOMAttributes<FocusableElement>;
1213
-
1214
- let {visuallyHiddenProps} = useVisuallyHidden();
1215
-
1216
- return (
1217
- <TableRowContext.Provider value={{dragButtonProps, dragButtonRef, isFocusVisibleWithin}}>
1218
- {isTableDroppable && isFirstRow &&
1219
- <InsertionIndicator
1220
- rowProps={props}
1221
- key={`${item.key}-before`}
1222
- target={{key: item.key, type: 'item', dropPosition: 'before'}} />
1223
- }
1224
- {isTableDroppable && !dropIndicator?.isHidden &&
1225
- <div role="row" {...visuallyHiddenProps}>
1226
- <div role="gridcell">
1227
- <div role="button" {...dropIndicator?.dropIndicatorProps} ref={dropIndicatorRef} />
1228
- </div>
1229
- </div>
1230
- }
1231
- <div
1232
- {...props}
1233
- ref={ref}
1234
- className={
1235
- classNames(
1236
- styles,
1237
- 'spectrum-Table-row',
1238
- {
1239
- 'is-active': isPressed,
1240
- 'is-selected': isSelected,
1241
- 'spectrum-Table-row--highlightSelection': state.selectionManager.selectionBehavior === 'replace',
1242
- 'is-next-selected': item.nextKey != null && state.selectionManager.isSelected(item.nextKey),
1243
- 'is-focused': isFocusVisibleWithin,
1244
- 'focus-ring': isFocusVisible,
1245
- 'is-hovered': isHovered,
1246
- 'is-disabled': isDisabled,
1247
- 'spectrum-Table-row--firstRow': isFirstRow,
1248
- 'spectrum-Table-row--lastRow': isLastRow,
1249
- 'spectrum-Table-row--isFlushBottom': isFlushWithContainerBottom
1250
- },
1251
- classNames(
1252
- stylesOverrides,
1253
- 'react-spectrum-Table-row',
1254
- {'react-spectrum-Table-row--dropTarget': isDropTarget}
1255
- )
1256
- )
1257
- }>
1258
- {children}
1259
- </div>
1260
- {isTableDroppable &&
1261
- <InsertionIndicator
1262
- rowProps={props}
1263
- key={`${item.key}-after`}
1264
- target={{key: item.key, type: 'item', dropPosition: 'after'}} />
1265
- }
1266
- </TableRowContext.Provider>
1267
- );
1268
- }
1269
-
1270
- function TableHeaderRow({item, children, layoutInfo, parent, ...props}: {item: GridNode<unknown>, children: ReactNode, layoutInfo: LayoutInfo, parent: LayoutInfo | null} & HoverProps) {
1271
- let {state, headerMenuOpen} = useTableContext();
1272
- let ref = useRef<HTMLDivElement | null>(null);
1273
- let {rowProps} = useTableHeaderRow({node: item, isVirtualized: true}, state, ref);
1274
- let {hoverProps} = useHover({...props, isDisabled: headerMenuOpen});
1275
- let style = useStyle(layoutInfo, parent);
1276
-
1277
- return (
1278
- <div {...mergeProps(rowProps, hoverProps)} ref={ref} style={style}>
1279
- {children}
1280
- </div>
1281
- );
1282
- }
1283
-
1284
- function TableDragCell({cell}) {
1285
- let ref = useRef<HTMLDivElement | null>(null);
1286
- let {state, isTableDraggable} = useTableContext();
1287
- let isDisabled = state.selectionManager.isDisabled(cell.parentKey);
1288
- let {gridCellProps} = useTableCell({
1289
- node: cell,
1290
- isVirtualized: true
1291
- }, state, ref);
1292
-
1293
-
1294
- return (
1295
- <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1296
- <div
1297
- {...gridCellProps}
1298
- ref={ref}
1299
- className={
1300
- classNames(
1301
- styles,
1302
- 'spectrum-Table-cell',
1303
- {
1304
- 'is-disabled': isDisabled
1305
- },
1306
- classNames(
1307
- stylesOverrides,
1308
- 'react-spectrum-Table-cell',
1309
- 'react-spectrum-Table-dragButtonCell'
1310
- )
1311
- )
1312
- }>
1313
- {isTableDraggable && !isDisabled && <DragButton />}
1314
- </div>
1315
- </FocusRing>
1316
- );
1317
- }
1318
-
1319
- function TableCheckboxCell({cell}) {
1320
- let ref = useRef<HTMLDivElement | null>(null);
1321
- let {state} = useTableContext();
1322
- // The TableCheckbox should always render its disabled status if the row is disabled, regardless of disabledBehavior,
1323
- // but the cell itself should not render its disabled styles if disabledBehavior="selection" because the row might have actions on it.
1324
- let isSelectionDisabled = state.disabledKeys.has(cell.parentKey);
1325
- let isDisabled = state.selectionManager.isDisabled(cell.parentKey);
1326
- let {gridCellProps} = useTableCell({
1327
- node: cell,
1328
- isVirtualized: true
1329
- }, state, ref);
1330
-
1331
- let {checkboxProps} = useTableSelectionCheckbox({key: cell.parentKey}, state);
1332
-
1333
- return (
1334
- <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1335
- <div
1336
- {...gridCellProps}
1337
- ref={ref}
1338
- className={
1339
- classNames(
1340
- styles,
1341
- 'spectrum-Table-cell',
1342
- 'spectrum-Table-checkboxCell',
1343
- {
1344
- 'is-disabled': isDisabled
1345
- },
1346
- classNames(
1347
- stylesOverrides,
1348
- 'react-spectrum-Table-cell'
1349
- )
1350
- )
1351
- }>
1352
- {state.selectionManager.selectionMode !== 'none' &&
1353
- <Checkbox
1354
- {...checkboxProps}
1355
- isEmphasized
1356
- isDisabled={isSelectionDisabled}
1357
- UNSAFE_className={classNames(styles, 'spectrum-Table-checkbox')} />
1358
- }
1359
- </div>
1360
- </FocusRing>
1361
- );
1362
- }
1363
-
1364
- function TableCell({cell}) {
1365
- let {scale} = useProvider();
1366
- let {state} = useTableContext();
1367
- let isExpandableTable = 'expandedKeys' in state;
1368
- let ref = useRef<HTMLDivElement | null>(null);
1369
- let columnProps = cell.column.props as SpectrumColumnProps<unknown>;
1370
- let isDisabled = state.selectionManager.isDisabled(cell.parentKey);
1371
- let {gridCellProps} = useTableCell({
1372
- node: cell,
1373
- isVirtualized: true
1374
- }, state, ref);
1375
- let {id, ...otherGridCellProps} = gridCellProps;
1376
- let isFirstRowHeaderCell = state.collection.rowHeaderColumnKeys.keys().next().value === cell.column.key;
1377
- let isRowExpandable = false;
1378
- let showExpandCollapseButton = false;
1379
- let levelOffset = 0;
1380
-
1381
- if ('expandedKeys' in state) {
1382
- isRowExpandable = state.keyMap.get(cell.parentKey)?.props.UNSTABLE_childItems?.length > 0 || state.keyMap.get(cell.parentKey)?.props?.children?.length > state.userColumnCount;
1383
- showExpandCollapseButton = isFirstRowHeaderCell && isRowExpandable;
1384
- // Offset based on level, and add additional offset if there is no expand/collapse button on a row
1385
- levelOffset = (cell.level - 2) * LEVEL_OFFSET_WIDTH[scale] + (!showExpandCollapseButton ? LEVEL_OFFSET_WIDTH[scale] * 2 : 0);
1386
- }
1387
-
1388
- return (
1389
- <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
1390
- <div
1391
- {...otherGridCellProps}
1392
- aria-labelledby={id}
1393
- ref={ref}
1394
- style={isExpandableTable && isFirstRowHeaderCell ? {paddingInlineStart: levelOffset} : {}}
1395
- className={
1396
- classNames(
1397
- styles,
1398
- 'spectrum-Table-cell',
1399
- {
1400
- 'spectrum-Table-cell--divider': columnProps.showDivider && cell.column.nextKey !== null,
1401
- 'spectrum-Table-cell--hideHeader': columnProps.hideHeader,
1402
- 'spectrum-Table-cell--hasExpandCollapseButton': showExpandCollapseButton,
1403
- 'is-disabled': isDisabled
1404
- },
1405
- classNames(
1406
- stylesOverrides,
1407
- 'react-spectrum-Table-cell',
1408
- {
1409
- 'react-spectrum-Table-cell--alignStart': columnProps.align === 'start',
1410
- 'react-spectrum-Table-cell--alignCenter': columnProps.align === 'center',
1411
- 'react-spectrum-Table-cell--alignEnd': columnProps.align === 'end'
1412
- }
1413
- )
1414
- )
1415
- }>
1416
- {showExpandCollapseButton && <ExpandableRowChevron cell={cell} />}
1417
- <span
1418
- id={id}
1419
- className={
1420
- classNames(
1421
- styles,
1422
- 'spectrum-Table-cellContents'
1423
- )
1424
- }>
1425
- {cell.rendered}
1426
- </span>
1427
- </div>
1428
- </FocusRing>
1429
- );
1430
- }
1431
-
1432
- function TableCellWrapper({layoutInfo, virtualizer, parent, children}: {layoutInfo: LayoutInfo, virtualizer: any, parent: ReusableView<any, any>, children: ReactNode}) {
1433
- let {isTableDroppable, dropState} = useContext(TableContext)!;
1434
- let isDropTarget = false;
1435
- let isRootDroptarget = false;
1436
- if (isTableDroppable && dropState) {
1437
- if (parent.content) {
1438
- isDropTarget = dropState.isDropTarget({type: 'item', dropPosition: 'on', key: parent.content.key});
1439
- }
1440
- isRootDroptarget = dropState.isDropTarget({type: 'root'});
1441
- }
1442
-
1443
- return (
1444
- <VirtualizerItem
1445
- layoutInfo={layoutInfo}
1446
- virtualizer={virtualizer}
1447
- parent={parent?.layoutInfo}
1448
- className={
1449
- useMemo(() => classNames(
1450
- styles,
1451
- 'spectrum-Table-cellWrapper',
1452
- classNames(
1453
- stylesOverrides,
1454
- {
1455
- 'react-spectrum-Table-cellWrapper': !layoutInfo.estimatedSize,
1456
- 'react-spectrum-Table-cellWrapper--dropTarget': isDropTarget || isRootDroptarget
1457
- }
1458
- )
1459
- ), [layoutInfo.estimatedSize, isDropTarget, isRootDroptarget])
1460
- }>
1461
- {children}
1462
- </VirtualizerItem>
1463
- );
1464
- }
1465
-
1466
- function ExpandableRowChevron({cell}) {
1467
- // TODO: move some/all of the chevron button setup into a separate hook?
1468
- let {direction} = useLocale();
1469
- let {state} = useTableContext();
1470
- let expandButtonRef = useRef<HTMLSpanElement | null>(null);
1471
- let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/table');
1472
- let isExpanded;
1473
-
1474
- if ('expandedKeys' in state) {
1475
- isExpanded = state.expandedKeys === 'all' || state.expandedKeys.has(cell.parentKey);
1476
- }
1477
-
1478
- // 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
1479
- let {buttonProps} = useButton({
1480
- // Desktop and mobile both toggle expansion of a native expandable row on mouse/touch up
1481
- onPress: () => {
1482
- (state as TreeGridState<unknown>).toggleKey(cell.parentKey);
1483
- if (!isFocusVisible()) {
1484
- state.selectionManager.setFocused(true);
1485
- state.selectionManager.setFocusedKey(cell.parentKey);
1486
- }
1487
- },
1488
- elementType: 'span',
1489
- 'aria-label': isExpanded ? stringFormatter.format('collapse') : stringFormatter.format('expand')
1490
- }, expandButtonRef);
1491
-
1492
- return (
1493
- <span
1494
- {...buttonProps}
1495
- ref={expandButtonRef}
1496
- // Override tabindex so that grid keyboard nav skips over it. Needs -1 so android talkback can actually "focus" it
1497
- tabIndex={isAndroid() ? -1 : undefined}
1498
- className={
1499
- classNames(
1500
- styles,
1501
- 'spectrum-Table-expandButton',
1502
- {
1503
- 'is-open': isExpanded
1504
- }
1505
- )
1506
- }>
1507
- {direction === 'ltr' ? <ChevronRightMedium /> : <ChevronLeftMedium />}
1508
- </span>
1509
- );
1510
- }
1511
-
1512
- function LoadingState() {
1513
- let {state} = useContext(TableContext)!;
1514
- let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/table');
1515
- return (
1516
- <CenteredWrapper>
1517
- <ProgressCircle
1518
- isIndeterminate
1519
- aria-label={state.collection.size > 0 ? stringFormatter.format('loadingMore') : stringFormatter.format('loading')} />
1520
- </CenteredWrapper>
1521
- );
1522
- }
1523
-
1524
- function EmptyState() {
1525
- let {renderEmptyState} = useContext(TableContext)!;
1526
- let emptyState = renderEmptyState ? renderEmptyState() : null;
1527
- if (emptyState == null) {
1528
- return null;
1529
- }
1530
-
1531
- return (
1532
- <CenteredWrapper>
1533
- {emptyState}
1534
- </CenteredWrapper>
1535
- );
1536
- }
1537
-
1538
- function CenteredWrapper({children}) {
1539
- let {state} = useTableContext();
1540
- let rowProps;
1541
-
1542
- if ('expandedKeys' in state) {
1543
- let topLevelRowCount = [...state.collection.body.childNodes].length;
1544
- rowProps = {
1545
- 'aria-level': 1,
1546
- 'aria-posinset': topLevelRowCount + 1,
1547
- 'aria-setsize': topLevelRowCount + 1
1548
- };
1549
- } else {
1550
- rowProps = {
1551
- 'aria-rowindex': state.collection.headerRows.length + state.collection.size + 1
1552
- };
1553
- }
1554
-
1555
- return (
1556
- <div
1557
- role="row"
1558
- {...rowProps}
1559
- className={classNames(stylesOverrides, 'react-spectrum-Table-centeredWrapper')}>
1560
- <div role="rowheader" aria-colspan={state.collection.columns.length}>
1561
- {children}
1562
- </div>
1563
- </div>
1564
- );
1565
- }
1566
-
1567
- const ForwardTableViewBase = React.forwardRef(TableViewBase) as <T>(props: TableBaseProps<T> & {ref?: DOMRef<HTMLDivElement>}) => ReactElement;
1568
-
1569
- export {ForwardTableViewBase as TableViewBase};