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