react-aria-components 0.0.4 → 1.0.0-alpha.2

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 (91) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +12 -29
  3. package/dist/import.mjs +5223 -0
  4. package/dist/main.js +5345 -0
  5. package/dist/main.js.map +1 -0
  6. package/dist/module.js +5223 -0
  7. package/dist/module.js.map +1 -0
  8. package/dist/types.d.ts +1481 -0
  9. package/dist/types.d.ts.map +1 -0
  10. package/package.json +36 -44
  11. package/src/Breadcrumbs.tsx +90 -0
  12. package/src/Button.tsx +79 -0
  13. package/src/Calendar.tsx +471 -0
  14. package/src/Checkbox.tsx +240 -0
  15. package/src/Collection.tsx +787 -0
  16. package/src/ComboBox.tsx +129 -0
  17. package/src/DateField.tsx +244 -0
  18. package/src/DatePicker.tsx +178 -0
  19. package/src/Dialog.tsx +95 -0
  20. package/src/GridList.tsx +411 -0
  21. package/src/Group.tsx +69 -0
  22. package/src/Header.tsx +34 -0
  23. package/src/Heading.tsx +35 -0
  24. package/src/Input.tsx +73 -0
  25. package/src/Keyboard.tsx +24 -0
  26. package/src/Label.tsx +30 -0
  27. package/src/Link.tsx +98 -0
  28. package/src/ListBox.tsx +406 -0
  29. package/src/Menu.tsx +227 -0
  30. package/src/Meter.tsx +73 -0
  31. package/src/Modal.tsx +192 -0
  32. package/src/NumberField.tsx +79 -0
  33. package/src/OverlayArrow.tsx +70 -0
  34. package/src/Popover.tsx +125 -0
  35. package/src/ProgressBar.tsx +81 -0
  36. package/src/RadioGroup.tsx +219 -0
  37. package/src/SearchField.tsx +78 -0
  38. package/src/Select.tsx +180 -0
  39. package/src/Separator.tsx +53 -0
  40. package/src/Slider.tsx +217 -0
  41. package/src/Switch.tsx +127 -0
  42. package/src/Table.tsx +917 -0
  43. package/src/Tabs.tsx +282 -0
  44. package/src/Text.tsx +30 -0
  45. package/src/TextField.tsx +62 -0
  46. package/src/ToggleButton.tsx +59 -0
  47. package/src/Tooltip.tsx +135 -0
  48. package/src/index.ts +94 -0
  49. package/src/useDragAndDrop.tsx +172 -0
  50. package/src/utils.tsx +235 -0
  51. package/.babelrc +0 -17
  52. package/.eslintrc +0 -12
  53. package/src/accordion/accordion.css +0 -4
  54. package/src/accordion/accordion.js +0 -20
  55. package/src/accordion/index.js +0 -2
  56. package/src/accordion/section.css +0 -21
  57. package/src/accordion/section.js +0 -85
  58. package/src/examples/accordion-example.js +0 -73
  59. package/src/examples/example.js +0 -21
  60. package/src/examples/grid-cells/fancy-input-grid-cell.js +0 -206
  61. package/src/examples/grid-cells/input-grid-cell.js +0 -44
  62. package/src/examples/grid-example.css +0 -23
  63. package/src/examples/grid-example.js +0 -231
  64. package/src/examples/index.html +0 -10
  65. package/src/examples/index.js +0 -53
  66. package/src/examples/tabs-example.js +0 -52
  67. package/src/grid/column-header.css +0 -4
  68. package/src/grid/column-header.js +0 -29
  69. package/src/grid/grid-cell.css +0 -16
  70. package/src/grid/grid-cell.js +0 -104
  71. package/src/grid/grid-context.js +0 -3
  72. package/src/grid/grid.css +0 -4
  73. package/src/grid/grid.js +0 -110
  74. package/src/grid/index.js +0 -6
  75. package/src/grid/interactive-grid-cell.js +0 -96
  76. package/src/grid/row-headers.css +0 -6
  77. package/src/grid/row-headers.js +0 -22
  78. package/src/grid/row.css +0 -5
  79. package/src/grid/row.js +0 -21
  80. package/src/prop-types/ref.js +0 -3
  81. package/src/tabs/index.js +0 -4
  82. package/src/tabs/tab-list.css +0 -6
  83. package/src/tabs/tab-list.js +0 -103
  84. package/src/tabs/tab-panels.js +0 -35
  85. package/src/tabs/tab.css +0 -10
  86. package/src/tabs/tab.js +0 -45
  87. package/src/tabs/tabs.js +0 -52
  88. package/src/utils/debounce.js +0 -12
  89. package/src/utils/event-handlers-factory.js +0 -42
  90. package/src/utils/unique-id.js +0 -6
  91. package/webpack.config.js +0 -43
package/src/Table.tsx ADDED
@@ -0,0 +1,917 @@
1
+ import {AriaLabelingProps} from '@react-types/shared';
2
+ import {BaseCollection, CollectionContext, CollectionProps, CollectionRendererContext, ItemRenderProps, NodeValue, useCachedChildren, useCollection, useCollectionChildren} from './Collection';
3
+ import {buildHeaderRows} from '@react-stately/table';
4
+ import {ButtonContext} from './Button';
5
+ import {CheckboxContext} from './Checkbox';
6
+ import {ContextValue, defaultSlot, Provider, RenderProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps} from './utils';
7
+ import {DisabledBehavior, DraggableCollectionState, DroppableCollectionState, Node, SelectionBehavior, SelectionMode, SortDirection, TableState, useTableState} from 'react-stately';
8
+ import {DragAndDropHooks, DropIndicator, DropIndicatorContext, DropIndicatorProps} from './useDragAndDrop';
9
+ import {DraggableItemResult, DragPreviewRenderer, DropIndicatorAria, DroppableCollectionResult, FocusScope, ListKeyboardDelegate, mergeProps, useFocusRing, useHover, useTable, useTableCell, useTableColumnHeader, useTableHeaderRow, useTableRow, useTableRowGroup, useTableSelectAllCheckbox, useTableSelectionCheckbox, useVisuallyHidden} from 'react-aria';
10
+ import {filterDOMProps, useObjectRef} from '@react-aria/utils';
11
+ import {GridNode} from '@react-types/grid';
12
+ import {TableCollection as ITableCollection, TableProps as SharedTableProps} from '@react-types/table';
13
+ import React, {createContext, ForwardedRef, forwardRef, Key, ReactElement, ReactNode, RefObject, useCallback, useContext, useEffect, useMemo, useRef} from 'react';
14
+
15
+ class TableCollection<T> extends BaseCollection<T> implements ITableCollection<T> {
16
+ headerRows: GridNode<T>[] = [];
17
+ columns: GridNode<T>[] = [];
18
+ rowHeaderColumnKeys: Set<Key> = new Set();
19
+ head: NodeValue<T> = new NodeValue('tableheader', -1);
20
+ body: NodeValue<T> = new NodeValue('tablebody', -2);
21
+ columnsDirty = true;
22
+
23
+ addNode(node: NodeValue<T>) {
24
+ super.addNode(node);
25
+
26
+ this.columnsDirty ||= node.type === 'column';
27
+ if (node.type === 'tableheader') {
28
+ this.head = node;
29
+ }
30
+
31
+ if (node.type === 'tablebody') {
32
+ this.body = node;
33
+ }
34
+ }
35
+
36
+ commit(firstKey: Key, lastKey: Key) {
37
+ this.updateColumns();
38
+ super.commit(firstKey, lastKey);
39
+ }
40
+
41
+ private updateColumns() {
42
+ if (!this.columnsDirty) {
43
+ return;
44
+ }
45
+
46
+ this.rowHeaderColumnKeys = new Set();
47
+ this.columns = [];
48
+
49
+ let columnKeyMap = new Map();
50
+ let visit = (node: Node<T>) => {
51
+ switch (node.type) {
52
+ case 'column':
53
+ columnKeyMap.set(node.key, node);
54
+ if (!node.hasChildNodes) {
55
+ node.index = this.columns.length;
56
+ this.columns.push(node);
57
+
58
+ if (node.props.isRowHeader) {
59
+ this.rowHeaderColumnKeys.add(node.key);
60
+ }
61
+ }
62
+ break;
63
+ }
64
+ for (let child of this.getChildren(node.key)) {
65
+ visit(child);
66
+ }
67
+ };
68
+
69
+ for (let node of this.getChildren(this.head.key)) {
70
+ visit(node);
71
+ }
72
+
73
+ this.headerRows = buildHeaderRows(columnKeyMap, this.columns);
74
+ this.columnsDirty = false;
75
+ if (this.rowHeaderColumnKeys.size === 0 && this.columns.length > 0) {
76
+ throw new Error('A table must have at least one Column with the isRowHeader prop set to true');
77
+ }
78
+ }
79
+
80
+ get columnCount() {
81
+ return this.columns.length;
82
+ }
83
+
84
+ get rows() {
85
+ return [...this.getChildren(this.body.key)];
86
+ }
87
+
88
+ *[Symbol.iterator]() {
89
+ yield* this.getChildren(this.body.key);
90
+ }
91
+
92
+ get size() {
93
+ return [...this.getChildren(this.body.key)].length;
94
+ }
95
+
96
+ getFirstKey() {
97
+ return [...this.getChildren(this.body.key)][0]?.key;
98
+ }
99
+
100
+ getLastKey() {
101
+ let rows = [...this.getChildren(this.body.key)];
102
+ return rows[rows.length - 1]?.key;
103
+ }
104
+
105
+ getKeyAfter(key: Key) {
106
+ let node = this.getItem(key);
107
+ if (node?.type === 'column') {
108
+ return node.nextKey ?? null;
109
+ }
110
+
111
+ return super.getKeyAfter(key);
112
+ }
113
+
114
+ getKeyBefore(key: Key) {
115
+ let node = this.getItem(key);
116
+ if (node?.type === 'column') {
117
+ return node.prevKey ?? null;
118
+ }
119
+
120
+ let k = super.getKeyBefore(key);
121
+ if (k != null && this.getItem(k)?.type === 'tablebody') {
122
+ return null;
123
+ }
124
+
125
+ return k;
126
+ }
127
+
128
+ getChildren(key: Key): Iterable<Node<T>> {
129
+ if (!this.getItem(key)) {
130
+ for (let row of this.headerRows) {
131
+ if (row.key === key) {
132
+ return row.childNodes;
133
+ }
134
+ }
135
+ }
136
+
137
+ return super.getChildren(key);
138
+ }
139
+
140
+ clone() {
141
+ let collection = super.clone();
142
+ collection.headerRows = this.headerRows;
143
+ collection.columns = this.columns;
144
+ collection.rowHeaderColumnKeys = this.rowHeaderColumnKeys;
145
+ collection.head = this.head;
146
+ collection.body = this.body;
147
+ return collection;
148
+ }
149
+
150
+ getTextValue(key: Key): string {
151
+ let row = this.getItem(key);
152
+ if (!row) {
153
+ return '';
154
+ }
155
+
156
+ // If the row has a textValue, use that.
157
+ if (row.textValue) {
158
+ return row.textValue;
159
+ }
160
+
161
+ // Otherwise combine the text of each of the row header columns.
162
+ let rowHeaderColumnKeys = this.rowHeaderColumnKeys;
163
+ let text: string[] = [];
164
+ for (let cell of this.getChildren(key)) {
165
+ let column = this.columns[cell.index!];
166
+ if (rowHeaderColumnKeys.has(column.key) && cell.textValue) {
167
+ text.push(cell.textValue);
168
+ }
169
+
170
+ if (text.length === rowHeaderColumnKeys.size) {
171
+ break;
172
+ }
173
+ }
174
+
175
+ return text.join(' ');
176
+ }
177
+ }
178
+
179
+ interface InternalTableContextValue {
180
+ state: TableState<unknown>,
181
+ dragAndDropHooks?: DragAndDropHooks,
182
+ dragState?: DraggableCollectionState,
183
+ dropState?: DroppableCollectionState
184
+ }
185
+
186
+ export const TableContext = createContext<ContextValue<TableProps, HTMLTableElement>>(null);
187
+ const InternalTableContext = createContext<InternalTableContextValue | null>(null);
188
+
189
+ export interface TableRenderProps {
190
+ /**
191
+ * Whether the table is currently focused.
192
+ * @selector [data-focused]
193
+ */
194
+ isFocused: boolean,
195
+ /**
196
+ * Whether the table is currently keyboard focused.
197
+ * @selector [data-focus-visible]
198
+ */
199
+ isFocusVisible: boolean,
200
+ /**
201
+ * Whether the table is currently the active drop target.
202
+ * @selector [data-drop-target]
203
+ */
204
+ isDropTarget: boolean
205
+ }
206
+
207
+ export interface TableProps extends Omit<SharedTableProps<any>, 'children'>, StyleRenderProps<TableRenderProps>, SlotProps, AriaLabelingProps {
208
+ /** The elements that make up the table. Includes the TableHeader, TableBody, Columns, and Rows. */
209
+ children?: ReactNode,
210
+ /**
211
+ * How multiple selection should behave in the collection.
212
+ * @default "toggle"
213
+ */
214
+ selectionBehavior?: SelectionBehavior,
215
+ /**
216
+ * Whether `disabledKeys` applies to all interactions, or only selection.
217
+ * @default "selection"
218
+ */
219
+ disabledBehavior?: DisabledBehavior,
220
+ /** Handler that is called when a user performs an action on the row. */
221
+ onRowAction?: (key: Key) => void,
222
+ /** Handler that is called when a user performs an action on the cell. */
223
+ onCellAction?: (key: Key) => void,
224
+ /** The drag and drop hooks returned by `useDragAndDrop` used to enable drag and drop behavior for the Table. */
225
+ dragAndDropHooks?: DragAndDropHooks
226
+ }
227
+
228
+ function Table(props: TableProps, ref: ForwardedRef<HTMLTableElement>) {
229
+ [props, ref] = useContextProps(props, ref, TableContext);
230
+ let initialCollection = useMemo(() => new TableCollection<any>(), []);
231
+ let {portal, collection} = useCollection(props, initialCollection);
232
+ let state = useTableState({
233
+ ...props,
234
+ collection,
235
+ children: undefined
236
+ });
237
+
238
+ let {gridProps} = useTable(props, state, ref);
239
+
240
+ let {dragAndDropHooks} = props;
241
+ let selectionManager = state.selectionManager;
242
+ let isListDraggable = !!dragAndDropHooks?.useDraggableCollectionState;
243
+ let isListDroppable = !!dragAndDropHooks?.useDroppableCollectionState;
244
+ let dragHooksProvided = useRef(isListDraggable);
245
+ let dropHooksProvided = useRef(isListDroppable);
246
+ if (dragHooksProvided.current !== isListDraggable) {
247
+ console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
248
+ }
249
+ if (dropHooksProvided.current !== isListDroppable) {
250
+ console.warn('Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.');
251
+ }
252
+
253
+ let dragState: DraggableCollectionState | undefined = undefined;
254
+ let dropState: DroppableCollectionState | undefined = undefined;
255
+ let droppableCollection: DroppableCollectionResult | undefined = undefined;
256
+ let isRootDropTarget = false;
257
+ let dragPreview: JSX.Element | null = null;
258
+ let preview = useRef<DragPreviewRenderer>(null);
259
+
260
+ if (isListDraggable && dragAndDropHooks) {
261
+ dragState = dragAndDropHooks.useDraggableCollectionState!({
262
+ collection,
263
+ selectionManager,
264
+ preview: dragAndDropHooks.renderDragPreview ? preview : undefined
265
+ });
266
+ dragAndDropHooks.useDraggableCollection!({}, dragState, ref);
267
+
268
+ let DragPreview = dragAndDropHooks.DragPreview!;
269
+ dragPreview = dragAndDropHooks.renderDragPreview
270
+ ? <DragPreview ref={preview}>{dragAndDropHooks.renderDragPreview}</DragPreview>
271
+ : null;
272
+ }
273
+
274
+ if (isListDroppable && dragAndDropHooks) {
275
+ dropState = dragAndDropHooks.useDroppableCollectionState!({
276
+ collection,
277
+ selectionManager
278
+ });
279
+
280
+ let keyboardDelegate = new ListKeyboardDelegate(
281
+ collection,
282
+ selectionManager.disabledBehavior === 'selection' ? new Set() : selectionManager.disabledKeys,
283
+ ref
284
+ );
285
+ let dropTargetDelegate = dragAndDropHooks.dropTargetDelegate || new dragAndDropHooks.ListDropTargetDelegate(collection, ref);
286
+ droppableCollection = dragAndDropHooks.useDroppableCollection!({
287
+ keyboardDelegate,
288
+ dropTargetDelegate
289
+ }, dropState, ref);
290
+
291
+ isRootDropTarget = dropState.isDropTarget({type: 'root'});
292
+ }
293
+
294
+ let {focusProps, isFocused, isFocusVisible} = useFocusRing();
295
+ let renderProps = useRenderProps({
296
+ className: props.className,
297
+ style: props.style,
298
+ defaultClassName: 'react-aria-Table',
299
+ values: {
300
+ isDropTarget: isRootDropTarget,
301
+ isFocused,
302
+ isFocusVisible
303
+ }
304
+ });
305
+
306
+ let {selectionBehavior, selectionMode, disallowEmptySelection} = state.selectionManager;
307
+ let ctx = useMemo(() => ({
308
+ selectionBehavior: selectionMode === 'none' ? null : selectionBehavior,
309
+ selectionMode,
310
+ disallowEmptySelection,
311
+ allowsDragging: isListDraggable
312
+ }), [selectionBehavior, selectionMode, disallowEmptySelection, isListDraggable]);
313
+
314
+ return (
315
+ <>
316
+ <Provider
317
+ values={[
318
+ [InternalTableContext, {state, dragAndDropHooks, dragState, dropState}],
319
+ [DropIndicatorContext, {render: TableDropIndicator}]
320
+ ]}>
321
+ <FocusScope>
322
+ <table
323
+ {...filterDOMProps(props)}
324
+ {...renderProps}
325
+ {...mergeProps(gridProps, focusProps, droppableCollection?.collectionProps)}
326
+ ref={ref}
327
+ slot={props.slot}
328
+ data-drop-target={isRootDropTarget || undefined}
329
+ data-focused={isFocused || undefined}
330
+ data-focus-visible={isFocusVisible || undefined}>
331
+ <TableHeaderRowGroup collection={collection} />
332
+ <TableBodyRowGroup collection={collection} isDroppable={isListDroppable} />
333
+ </table>
334
+ </FocusScope>
335
+ {dragPreview}
336
+ </Provider>
337
+ <TableOptionsContext.Provider value={ctx}>
338
+ {portal}
339
+ </TableOptionsContext.Provider>
340
+ </>
341
+ );
342
+ }
343
+
344
+ /**
345
+ * A table displays data in rows and columns and enables a user to navigate its contents via directional navigation keys,
346
+ * and optionally supports row selection and sorting.
347
+ */
348
+ const _Table = forwardRef(Table);
349
+ export {_Table as Table};
350
+
351
+ export interface TableOptionsContextValue {
352
+ /** The type of selection that is allowed in the table. */
353
+ selectionMode: SelectionMode,
354
+ /** The selection behavior for the table. If selectionMode is `"none"`, this will be `null`. */
355
+ selectionBehavior: SelectionBehavior | null,
356
+ /** Whether the table allows empty selection. */
357
+ disallowEmptySelection: boolean,
358
+ /** Whether the table allows rows to be dragged. */
359
+ allowsDragging: boolean
360
+ }
361
+
362
+ const TableOptionsContext = createContext<TableOptionsContextValue | null>(null);
363
+
364
+ /**
365
+ * Returns options from the parent `<Table>` component.
366
+ */
367
+ export function useTableOptions(): TableOptionsContextValue {
368
+ return useContext(TableOptionsContext)!;
369
+ }
370
+
371
+ export interface TableHeaderProps<T> {
372
+ /** A list of table columns. */
373
+ columns?: T[],
374
+ /** A list of `Column(s)` or a function. If the latter, a list of columns must be provided using the `columns` prop. */
375
+ children?: ReactNode | ((item: T) => ReactElement)
376
+ }
377
+
378
+ /**
379
+ * A header within a `<Table>`, containing the table columns.
380
+ */
381
+ export function TableHeader<T extends object>(props: TableHeaderProps<T>) {
382
+ let children = useCollectionChildren({
383
+ children: props.children,
384
+ items: props.columns
385
+ });
386
+
387
+ let renderer = typeof props.children === 'function' ? props.children : null;
388
+ return (
389
+ <CollectionRendererContext.Provider value={renderer}>
390
+ {/* @ts-ignore */}
391
+ <tableheader multiple={props}>{children}</tableheader>
392
+ </CollectionRendererContext.Provider>
393
+ );
394
+ }
395
+
396
+ export interface ColumnRenderProps {
397
+ /**
398
+ * Whether the item is currently focused.
399
+ * @selector [data-focused]
400
+ */
401
+ isFocused: boolean,
402
+ /**
403
+ * Whether the item is currently keyboard focused.
404
+ * @selector [data-focus-visible]
405
+ */
406
+ isFocusVisible: boolean,
407
+ /**
408
+ * Whether the column allows sorting.
409
+ * @selector [aria-sort]
410
+ */
411
+ allowsSorting: boolean,
412
+ /**
413
+ * The current sort direction.
414
+ * @selector [aria-sort="ascending | descending"]
415
+ */
416
+ sortDirection?: SortDirection
417
+ }
418
+
419
+ export interface ColumnProps<T = object> extends RenderProps<ColumnRenderProps> {
420
+ id?: Key,
421
+ /** Rendered contents of the column if `children` contains child columns. */
422
+ title?: ReactNode,
423
+ /** A list of child columns used when dynamically rendering nested child columns. */
424
+ childColumns?: T[],
425
+ /** Whether the column allows sorting. */
426
+ allowsSorting?: boolean,
427
+ /** Whether a column is a [row header](https://www.w3.org/TR/wai-aria-1.1/#rowheader) and should be announced by assistive technology during row navigation. */
428
+ isRowHeader?: boolean,
429
+ /** A string representation of the column's contents, used for accessibility announcements. */
430
+ textValue?: string
431
+ }
432
+
433
+ /**
434
+ * A column within a `<Table>`.
435
+ */
436
+ export function Column<T extends object>(props: ColumnProps<T>): JSX.Element {
437
+ let render = useContext(CollectionRendererContext);
438
+ let childColumns = typeof render === 'function' ? render : props.children;
439
+ let children = useCollectionChildren({
440
+ children: (props.title || props.childColumns) ? childColumns : null,
441
+ items: props.childColumns
442
+ });
443
+
444
+ // @ts-ignore
445
+ return <column multiple={{...props, rendered: props.title ?? props.children}}>{children}</column>;
446
+ }
447
+
448
+ export interface TableBodyRenderProps {
449
+ /**
450
+ * Whether the table body has no rows and should display its empty state.
451
+ * @selector [data-empty]
452
+ */
453
+ isEmpty: boolean
454
+ }
455
+
456
+ export interface TableBodyProps<T> extends CollectionProps<T>, StyleRenderProps<TableBodyRenderProps> {
457
+ /** Provides content to display when there are no rows in the table. */
458
+ renderEmptyState?: () => ReactNode
459
+ }
460
+
461
+ /**
462
+ * The body of a `<Table>`, containing the table rows.
463
+ */
464
+ export function TableBody<T extends object>(props: TableBodyProps<T>) {
465
+ let children = useCollectionChildren(props);
466
+
467
+ // @ts-ignore
468
+ return <tablebody multiple={props}>{children}</tablebody>;
469
+ }
470
+
471
+ export interface RowRenderProps extends ItemRenderProps {}
472
+
473
+ export interface RowProps<T> extends RenderProps<RowRenderProps> {
474
+ id?: Key,
475
+ /** A list of columns used when dynamically rendering cells. */
476
+ columns?: Iterable<T>,
477
+ /** The cells within the row. Supports static items or a function for dynamic rendering. */
478
+ children?: ReactNode | ((item: T) => ReactElement),
479
+ /** A string representation of the row's contents, used for features like typeahead. */
480
+ textValue?: string
481
+ }
482
+
483
+ /**
484
+ * A row within a `<Table>`.
485
+ */
486
+ export function Row<T extends object>(props: RowProps<T>) {
487
+ let children = useCollectionChildren({
488
+ children: props.children,
489
+ items: props.columns,
490
+ idScope: props.id
491
+ });
492
+
493
+ let ctx = useMemo(() => ({idScope: props.id}), [props.id]);
494
+
495
+ return (
496
+ // @ts-ignore
497
+ <item multiple={props}>
498
+ <CollectionContext.Provider value={ctx}>
499
+ {children}
500
+ </CollectionContext.Provider>
501
+ {/* @ts-ignore */}
502
+ </item>
503
+ );
504
+ }
505
+
506
+ export interface CellRenderProps {
507
+ /**
508
+ * Whether the cell is currently in a pressed state.
509
+ * @selector [data-pressed]
510
+ */
511
+ isPressed: boolean,
512
+ /**
513
+ * Whether the cell is currently focused.
514
+ * @selector [data-focused]
515
+ */
516
+ isFocused: boolean,
517
+ /**
518
+ * Whether the cell is currently keyboard focused.
519
+ * @selector [data-focus-visible]
520
+ */
521
+ isFocusVisible: boolean
522
+ }
523
+
524
+ export interface CellProps extends RenderProps<CellRenderProps> {
525
+ id?: Key,
526
+ /** The contents of the cell. */
527
+ children: ReactNode,
528
+ /** A string representation of the cell's contents, used for features like typeahead. */
529
+ textValue?: string
530
+ }
531
+
532
+ /**
533
+ * A cell within a table row.
534
+ */
535
+ export function Cell(props: CellProps): JSX.Element {
536
+ // @ts-ignore
537
+ return <cell multiple={{...props, rendered: props.children}} />;
538
+ }
539
+
540
+ function TableHeaderRowGroup<T>({collection}: {collection: TableCollection<T>}) {
541
+ let headerRows = useCachedChildren({
542
+ items: collection.headerRows,
543
+ children: useCallback((item: Node<T>) => {
544
+ switch (item.type) {
545
+ case 'headerrow':
546
+ return <TableHeaderRow item={item} />;
547
+ default:
548
+ throw new Error('Unsupported node type in TableHeader: ' + item.type);
549
+ }
550
+ }, [])
551
+ });
552
+
553
+ let {rowGroupProps} = useTableRowGroup();
554
+ return (
555
+ <thead
556
+ {...filterDOMProps(collection.head.props)}
557
+ {...rowGroupProps}
558
+ className={collection.head.props.className ?? 'react-aria-TableHeader'}
559
+ style={collection.head.props.style}>
560
+ {headerRows}
561
+ </thead>
562
+ );
563
+ }
564
+
565
+ function TableBodyRowGroup<T>({collection, isDroppable}: {collection: TableCollection<T>, isDroppable: boolean}) {
566
+ let bodyRows = useCachedChildren({
567
+ items: collection.rows,
568
+ children: useCallback((item: Node<T>) => {
569
+ switch (item.type) {
570
+ case 'item':
571
+ return <TableRow item={item} />;
572
+ default:
573
+ throw new Error('Unsupported node type in TableBody: ' + item.type);
574
+ }
575
+ }, [])
576
+ });
577
+
578
+ let props: TableBodyProps<T> = collection.body.props;
579
+ let renderProps = useRenderProps({
580
+ ...props,
581
+ id: undefined,
582
+ children: undefined,
583
+ defaultClassName: 'react-aria-TableBody',
584
+ values: {
585
+ isEmpty: collection.size === 0
586
+ }
587
+ });
588
+
589
+ let emptyState;
590
+ if (collection.size === 0 && props.renderEmptyState) {
591
+ emptyState = (
592
+ <tr role="row">
593
+ <td role="gridcell" colSpan={collection.columnCount}>
594
+ {props.renderEmptyState()}
595
+ </td>
596
+ </tr>
597
+ );
598
+ }
599
+
600
+ let {rowGroupProps} = useTableRowGroup();
601
+ return (
602
+ <tbody
603
+ {...renderProps}
604
+ {...rowGroupProps}
605
+ data-empty={collection.size === 0 || undefined}>
606
+ {isDroppable && <RootDropIndicator />}
607
+ {bodyRows}
608
+ {emptyState}
609
+ </tbody>
610
+ );
611
+ }
612
+
613
+ function TableHeaderRow<T>({item}: {item: GridNode<T>}) {
614
+ let ref = useRef<HTMLTableRowElement>(null);
615
+ let {state} = useContext(InternalTableContext)!;
616
+ let {rowProps} = useTableHeaderRow({node: item}, state, ref);
617
+ let {checkboxProps} = useTableSelectAllCheckbox(state);
618
+
619
+ let cells = useCachedChildren({
620
+ items: state.collection.getChildren!(item.key),
621
+ children: (item) => {
622
+ switch (item.type) {
623
+ case 'column':
624
+ return <TableColumnHeader column={item} />;
625
+ default:
626
+ throw new Error('Unsupported node type in Row: ' + item.type);
627
+ }
628
+ }
629
+ });
630
+
631
+ return (
632
+ <tr {...rowProps} ref={ref}>
633
+ <Provider
634
+ values={[
635
+ [CheckboxContext, {
636
+ slots: {
637
+ selection: checkboxProps
638
+ }
639
+ }]
640
+ ]}>
641
+ {cells}
642
+ </Provider>
643
+ </tr>
644
+ );
645
+ }
646
+
647
+ function TableColumnHeader<T>({column}: {column: GridNode<T>}) {
648
+ let ref = useRef<HTMLTableHeaderCellElement>(null);
649
+ let {state} = useContext(InternalTableContext)!;
650
+ let {columnHeaderProps} = useTableColumnHeader(
651
+ {node: column},
652
+ state,
653
+ ref
654
+ );
655
+ let {isFocused, isFocusVisible, focusProps} = useFocusRing();
656
+
657
+ let props: ColumnProps<unknown> = column.props;
658
+ let renderProps = useRenderProps({
659
+ ...props,
660
+ id: undefined,
661
+ children: column.rendered,
662
+ defaultClassName: 'react-aria-Column',
663
+ values: {
664
+ isFocused,
665
+ isFocusVisible,
666
+ allowsSorting: column.props.allowsSorting,
667
+ sortDirection: state.sortDescriptor?.column === column.key
668
+ ? state.sortDescriptor.direction
669
+ : undefined
670
+ }
671
+ });
672
+
673
+ return (
674
+ <th
675
+ {...mergeProps(columnHeaderProps, focusProps)}
676
+ {...renderProps}
677
+ colSpan={column.colspan}
678
+ ref={ref}
679
+ data-focused={isFocused || undefined}
680
+ data-focus-visible={isFocusVisible || undefined}>
681
+ {renderProps.children}
682
+ </th>
683
+ );
684
+ }
685
+
686
+ function TableRow<T>({item}: {item: GridNode<T>}) {
687
+ let ref = useRef<HTMLTableRowElement>(null);
688
+ let {state, dragAndDropHooks, dragState, dropState} = useContext(InternalTableContext)!;
689
+ let {rowProps, ...states} = useTableRow(
690
+ {
691
+ node: item,
692
+ shouldSelectOnPressUp: !!dragState
693
+ },
694
+ state,
695
+ ref
696
+ );
697
+ let {isFocused, isFocusVisible, focusProps} = useFocusRing();
698
+ let {hoverProps, isHovered} = useHover({
699
+ isDisabled: !states.allowsSelection && !states.hasAction
700
+ });
701
+
702
+ let {checkboxProps} = useTableSelectionCheckbox(
703
+ {key: item.key},
704
+ state
705
+ );
706
+
707
+ let draggableItem: DraggableItemResult | undefined = undefined;
708
+ if (dragState && dragAndDropHooks) {
709
+ draggableItem = dragAndDropHooks.useDraggableItem!({key: item.key, hasDragButton: true}, dragState);
710
+ }
711
+
712
+ let dropIndicator: DropIndicatorAria | undefined = undefined;
713
+ let dropIndicatorRef = useRef<HTMLDivElement>(null);
714
+ let {visuallyHiddenProps} = useVisuallyHidden();
715
+ if (dropState && dragAndDropHooks) {
716
+ dropIndicator = dragAndDropHooks.useDropIndicator!({
717
+ target: {type: 'item', key: item.key, dropPosition: 'on'}
718
+ }, dropState, dropIndicatorRef);
719
+ }
720
+
721
+ let renderDropIndicator = dragAndDropHooks?.renderDropIndicator || (target => <DropIndicator target={target} />);
722
+ let dragButtonRef = useRef<HTMLButtonElement>(null);
723
+ useEffect(() => {
724
+ if (dragState && !dragButtonRef.current) {
725
+ console.warn('Draggable items in a Table must contain a <Button slot="drag"> element so that keyboard and screen reader users can drag them.');
726
+ }
727
+ // eslint-disable-next-line
728
+ }, []);
729
+
730
+ let props = item.props as RowProps<unknown>;
731
+ let isDragging = dragState && dragState.isDragging(item.key);
732
+ let renderProps = useRenderProps({
733
+ ...props,
734
+ id: undefined,
735
+ defaultClassName: 'react-aria-Row',
736
+ values: {
737
+ ...states,
738
+ isHovered,
739
+ isFocused,
740
+ isFocusVisible,
741
+ selectionMode: state.selectionManager.selectionMode,
742
+ selectionBehavior: state.selectionManager.selectionBehavior,
743
+ isDragging,
744
+ isDropTarget: dropIndicator?.isDropTarget
745
+ }
746
+ });
747
+
748
+ let cells = useCachedChildren({
749
+ items: state.collection.getChildren!(item.key),
750
+ children: (item: Node<unknown>) => {
751
+ switch (item.type) {
752
+ case 'cell':
753
+ return <TableCell cell={item} />;
754
+ default:
755
+ throw new Error('Unsupported node type in Row: ' + item.type);
756
+ }
757
+ }
758
+ });
759
+
760
+ return (
761
+ <>
762
+ {dragAndDropHooks?.useDropIndicator &&
763
+ renderDropIndicator({type: 'item', key: item.key, dropPosition: 'before'})
764
+ }
765
+ {dropIndicator && !dropIndicator.isHidden && (
766
+ <tr role="row" style={{height: 0}}>
767
+ <td role="gridcell" colSpan={state.collection.columnCount} style={{padding: 0}}>
768
+ <div role="button" {...visuallyHiddenProps} {...dropIndicator.dropIndicatorProps} ref={dropIndicatorRef} />
769
+ </td>
770
+ </tr>
771
+ )}
772
+ <tr
773
+ {...mergeProps(rowProps, focusProps, hoverProps, draggableItem?.dragProps)}
774
+ {...renderProps}
775
+ ref={ref}
776
+ data-hovered={isHovered || undefined}
777
+ data-focused={states.isFocused || undefined}
778
+ data-focus-visible={isFocusVisible || undefined}
779
+ data-pressed={states.isPressed || undefined}
780
+ data-dragging={isDragging || undefined}
781
+ data-drop-target={dropIndicator?.isDropTarget || undefined}>
782
+ <Provider
783
+ values={[
784
+ [CheckboxContext, {
785
+ slots: {
786
+ selection: checkboxProps
787
+ }
788
+ }],
789
+ [ButtonContext, {
790
+ slots: {
791
+ [defaultSlot]: {},
792
+ drag: {
793
+ ...draggableItem?.dragButtonProps,
794
+ ref: dragButtonRef,
795
+ style: {
796
+ pointerEvents: 'none'
797
+ }
798
+ }
799
+ }
800
+ }]
801
+ ]}>
802
+ {cells}
803
+ </Provider>
804
+ </tr>
805
+ {dragAndDropHooks?.useDropIndicator && state.collection.getKeyAfter(item.key) == null &&
806
+ renderDropIndicator({type: 'item', key: item.key, dropPosition: 'after'})
807
+ }
808
+ </>
809
+ );
810
+ }
811
+
812
+ function TableCell<T>({cell}: {cell: GridNode<T>}) {
813
+ let ref = useRef<HTMLTableCellElement>(null);
814
+ let {state, dragState} = useContext(InternalTableContext)!;
815
+
816
+ // @ts-ignore
817
+ cell.column = state.collection.columns[cell.index];
818
+
819
+ let {gridCellProps, isPressed} = useTableCell({
820
+ node: cell,
821
+ shouldSelectOnPressUp: !!dragState
822
+ }, state, ref);
823
+ let {isFocused, isFocusVisible, focusProps} = useFocusRing();
824
+
825
+ let props: CellProps = cell.props;
826
+ let renderProps = useRenderProps({
827
+ ...props,
828
+ id: undefined,
829
+ defaultClassName: 'react-aria-Cell',
830
+ values: {
831
+ isFocused,
832
+ isFocusVisible,
833
+ isPressed
834
+ }
835
+ });
836
+
837
+ return (
838
+ <td
839
+ {...mergeProps(gridCellProps, focusProps)}
840
+ {...renderProps}
841
+ ref={ref}
842
+ data-focused={isFocused || undefined}
843
+ data-focus-visible={isFocusVisible || undefined}
844
+ data-pressed={isPressed || undefined}>
845
+ {cell.rendered}
846
+ </td>
847
+ );
848
+ }
849
+
850
+ function TableDropIndicator(props: DropIndicatorProps, ref: ForwardedRef<HTMLElement>) {
851
+ ref = useObjectRef(ref);
852
+ let {state, dragAndDropHooks, dropState} = useContext(InternalTableContext)!;
853
+ let buttonRef = useRef<HTMLDivElement>(null);
854
+ let {dropIndicatorProps, isHidden, isDropTarget} = dragAndDropHooks!.useDropIndicator!(
855
+ props,
856
+ dropState!,
857
+ buttonRef
858
+ );
859
+
860
+ let {visuallyHiddenProps} = useVisuallyHidden();
861
+
862
+ if (isHidden) {
863
+ return null;
864
+ }
865
+
866
+ // eslint-disable-next-line react-hooks/rules-of-hooks
867
+ let renderProps = useRenderProps({
868
+ ...props,
869
+ defaultClassName: 'react-aria-DropIndicator',
870
+ values: {
871
+ isDropTarget
872
+ }
873
+ });
874
+
875
+ return (
876
+ <tr
877
+ {...renderProps}
878
+ role="row"
879
+ ref={ref as RefObject<HTMLTableRowElement>}
880
+ data-drop-target={isDropTarget || undefined}>
881
+ <td
882
+ role="gridcell"
883
+ colSpan={state.collection.columnCount}
884
+ style={{padding: 0}}>
885
+ <div {...visuallyHiddenProps} role="button" {...dropIndicatorProps} ref={buttonRef} />
886
+ </td>
887
+ </tr>
888
+ );
889
+ }
890
+
891
+ function RootDropIndicator() {
892
+ let {state, dragAndDropHooks, dropState} = useContext(InternalTableContext)!;
893
+ let ref = useRef<HTMLDivElement>(null);
894
+ let {dropIndicatorProps} = dragAndDropHooks!.useDropIndicator!({
895
+ target: {type: 'root'}
896
+ }, dropState!, ref);
897
+ let isDropTarget = dropState!.isDropTarget({type: 'root'});
898
+ let {visuallyHiddenProps} = useVisuallyHidden();
899
+
900
+ if (!isDropTarget && dropIndicatorProps['aria-hidden']) {
901
+ return null;
902
+ }
903
+
904
+ return (
905
+ <tr
906
+ role="row"
907
+ aria-hidden={dropIndicatorProps['aria-hidden']}
908
+ style={{height: 0}}>
909
+ <td
910
+ role="gridcell"
911
+ colSpan={state.collection.columnCount}
912
+ style={{padding: 0}}>
913
+ <div role="button" {...visuallyHiddenProps} {...dropIndicatorProps} ref={ref} />
914
+ </td>
915
+ </tr>
916
+ );
917
+ }