@octanejs/aria 0.0.5 → 0.0.6
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/package.json +3 -3
- package/src/components/Table.ts +2618 -0
- package/src/components/Tree.ts +1450 -0
- package/src/components/TreeDropTargetDelegate.ts +363 -0
- package/src/components/index.ts +57 -0
- package/src/grid/GridKeyboardDelegate.ts +514 -0
- package/src/grid/useGrid.ts +263 -0
- package/src/grid/useGridCell.ts +346 -0
- package/src/grid/useGridRow.ts +108 -0
- package/src/grid/useGridRowGroup.ts +29 -0
- package/src/grid/utils.ts +24 -0
- package/src/index.ts +38 -0
- package/src/intl/autocomplete/index.ts +9 -2
- package/src/intl/breadcrumbs/index.ts +9 -2
- package/src/intl/combobox/index.ts +9 -2
- package/src/intl/compileMessages.ts +356 -0
- package/src/intl/grid/index.ts +9 -2
- package/src/intl/gridlist/index.ts +9 -2
- package/src/intl/menu/index.ts +9 -2
- package/src/intl/numberfield/index.ts +9 -2
- package/src/intl/overlays/index.ts +9 -2
- package/src/intl/searchfield/index.ts +9 -2
- package/src/intl/spinbutton/index.ts +9 -2
- package/src/intl/table/ar-AE.json +13 -0
- package/src/intl/table/bg-BG.json +13 -0
- package/src/intl/table/cs-CZ.json +13 -0
- package/src/intl/table/da-DK.json +13 -0
- package/src/intl/table/de-DE.json +13 -0
- package/src/intl/table/el-GR.json +13 -0
- package/src/intl/table/en-US.json +13 -0
- package/src/intl/table/es-ES.json +13 -0
- package/src/intl/table/et-EE.json +13 -0
- package/src/intl/table/fi-FI.json +13 -0
- package/src/intl/table/fr-FR.json +13 -0
- package/src/intl/table/he-IL.json +13 -0
- package/src/intl/table/hr-HR.json +13 -0
- package/src/intl/table/hu-HU.json +13 -0
- package/src/intl/table/index.ts +82 -0
- package/src/intl/table/it-IT.json +13 -0
- package/src/intl/table/ja-JP.json +13 -0
- package/src/intl/table/ko-KR.json +13 -0
- package/src/intl/table/lt-LT.json +13 -0
- package/src/intl/table/lv-LV.json +13 -0
- package/src/intl/table/nb-NO.json +13 -0
- package/src/intl/table/nl-NL.json +13 -0
- package/src/intl/table/pl-PL.json +13 -0
- package/src/intl/table/pt-BR.json +13 -0
- package/src/intl/table/pt-PT.json +13 -0
- package/src/intl/table/ro-RO.json +13 -0
- package/src/intl/table/ru-RU.json +13 -0
- package/src/intl/table/sk-SK.json +13 -0
- package/src/intl/table/sl-SI.json +13 -0
- package/src/intl/table/sr-SP.json +13 -0
- package/src/intl/table/sv-SE.json +13 -0
- package/src/intl/table/tr-TR.json +13 -0
- package/src/intl/table/uk-UA.json +13 -0
- package/src/intl/table/zh-CN.json +13 -0
- package/src/intl/table/zh-TW.json +13 -0
- package/src/intl/tag/index.ts +9 -2
- package/src/intl/tree/ar-AE.json +4 -0
- package/src/intl/tree/bg-BG.json +4 -0
- package/src/intl/tree/cs-CZ.json +4 -0
- package/src/intl/tree/da-DK.json +4 -0
- package/src/intl/tree/de-DE.json +4 -0
- package/src/intl/tree/el-GR.json +4 -0
- package/src/intl/tree/en-US.json +4 -0
- package/src/intl/tree/es-ES.json +4 -0
- package/src/intl/tree/et-EE.json +4 -0
- package/src/intl/tree/fi-FI.json +4 -0
- package/src/intl/tree/fr-FR.json +4 -0
- package/src/intl/tree/he-IL.json +4 -0
- package/src/intl/tree/hr-HR.json +4 -0
- package/src/intl/tree/hu-HU.json +4 -0
- package/src/intl/tree/index.ts +82 -0
- package/src/intl/tree/it-IT.json +4 -0
- package/src/intl/tree/ja-JP.json +4 -0
- package/src/intl/tree/ko-KR.json +4 -0
- package/src/intl/tree/lt-LT.json +4 -0
- package/src/intl/tree/lv-LV.json +4 -0
- package/src/intl/tree/nb-NO.json +4 -0
- package/src/intl/tree/nl-NL.json +4 -0
- package/src/intl/tree/pl-PL.json +4 -0
- package/src/intl/tree/pt-BR.json +4 -0
- package/src/intl/tree/pt-PT.json +4 -0
- package/src/intl/tree/ro-RO.json +4 -0
- package/src/intl/tree/ru-RU.json +4 -0
- package/src/intl/tree/sk-SK.json +4 -0
- package/src/intl/tree/sl-SI.json +4 -0
- package/src/intl/tree/sr-SP.json +4 -0
- package/src/intl/tree/sv-SE.json +4 -0
- package/src/intl/tree/tr-TR.json +4 -0
- package/src/intl/tree/uk-UA.json +4 -0
- package/src/intl/tree/zh-CN.json +4 -0
- package/src/intl/tree/zh-TW.json +4 -0
- package/src/stately/grid/GridCollection.ts +200 -0
- package/src/stately/grid/useGridState.ts +160 -0
- package/src/stately/index.ts +43 -0
- package/src/stately/table/Cell.ts +49 -0
- package/src/stately/table/Column.ts +121 -0
- package/src/stately/table/Row.ts +145 -0
- package/src/stately/table/TableBody.ts +66 -0
- package/src/stately/table/TableCollection.ts +390 -0
- package/src/stately/table/TableColumnLayout.ts +148 -0
- package/src/stately/table/TableHeader.ts +65 -0
- package/src/stately/table/TableUtils.ts +269 -0
- package/src/stately/table/useTableColumnResizeState.ts +202 -0
- package/src/stately/table/useTableState.ts +235 -0
- package/src/stately/table/useTreeGridState.ts +248 -0
- package/src/table/TableKeyboardDelegate.ts +205 -0
- package/src/table/useTable.ts +175 -0
- package/src/table/useTableCell.ts +81 -0
- package/src/table/useTableColumnHeader.ts +155 -0
- package/src/table/useTableColumnResize.ts +374 -0
- package/src/table/useTableHeaderRow.ts +57 -0
- package/src/table/useTableRow.ts +198 -0
- package/src/table/useTableRowGroup.ts +13 -0
- package/src/table/useTableSelectionCheckbox.ts +105 -0
- package/src/table/utils.ts +42 -0
- package/src/tree/useTree.ts +76 -0
- package/src/tree/useTreeItem.ts +109 -0
|
@@ -0,0 +1,2618 @@
|
|
|
1
|
+
// Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/Table.tsx).
|
|
2
|
+
// octane adaptations: `.tsx` → `.ts`, JSX → `createElement`; NO forwardRef — the forwarded
|
|
3
|
+
// ref is `props.ref` (Table passes it into `useContextProps` explicitly; the element-type
|
|
4
|
+
// helpers, Column, Row, Cell, ColumnResizer and the drop-indicator wrapper adapt theirs
|
|
5
|
+
// with `useObjectRef` exactly like upstream's forwarded refs); the plain-`.ts` components
|
|
6
|
+
// use the S()/subSlot component-slot convention. The collection composes the Phase-4
|
|
7
|
+
// engine: `CollectionBuilder`/`createLeafComponent`/`createBranchComponent` from
|
|
8
|
+
// `../collections/CollectionBuilder` and the renderer's `CollectionRoot`/`CollectionBranch`
|
|
9
|
+
// via `CollectionRendererContext`. The RAC-local `TableCollection` subclass of
|
|
10
|
+
// `BaseCollection` is ported faithfully (columns/headerRows bookkeeping, flattened rows,
|
|
11
|
+
// expanded-key traversal); `buildHeaderRows` and `ITableCollection` come from the ported
|
|
12
|
+
// stately table area. Upstream's RAC-local `CollectionProps` import is our
|
|
13
|
+
// `ItemCollectionProps` (see ./Collection.ts). Upstream's `inertValue` (React <19 string
|
|
14
|
+
// compat) collapses to the plain boolean — octane follows React 19 `inert` semantics.
|
|
15
|
+
// `ReactDOM.createPortal` → octane's `createPortal` as a child value. The dnd branches use
|
|
16
|
+
// the PHASE-7 structural aliases from ./useDragAndDrop and stay inert until a consumer can
|
|
17
|
+
// construct `dragAndDropHooks` (that includes the `TreeDropTargetDelegate` wiring, which is
|
|
18
|
+
// only reachable through `hasDropHooks`). react-aria's private `useLoadMoreSentinel` comes
|
|
19
|
+
// from ../utils/useLoadMoreSentinel and `useCachedChildren` from
|
|
20
|
+
// ../collections/useCachedChildren. `TableLayout` (Virtualizer) is deferred to PHASE-7:
|
|
21
|
+
// the layout plumbing (`layoutDelegate`, `isVirtualized`) stays typed loosely and inert.
|
|
22
|
+
// The Parcel glob intl import becomes a module-local dictionary reduced to the one key
|
|
23
|
+
// this module reads (`tableResizer`). Explicit dep arrays are preserved verbatim. The
|
|
24
|
+
// ColumnResizer's render-phase "mouse released" setState idiom is kept verbatim (octane
|
|
25
|
+
// matches React's render-phase-update semantics).
|
|
26
|
+
import type {
|
|
27
|
+
AriaLabelingProps,
|
|
28
|
+
DisabledBehavior,
|
|
29
|
+
HoverEvents,
|
|
30
|
+
Key,
|
|
31
|
+
LinkDOMProps,
|
|
32
|
+
Node,
|
|
33
|
+
PressEvents,
|
|
34
|
+
SelectionBehavior,
|
|
35
|
+
SelectionMode,
|
|
36
|
+
SortDirection,
|
|
37
|
+
} from '@react-types/shared';
|
|
38
|
+
import {
|
|
39
|
+
Fragment,
|
|
40
|
+
createContext,
|
|
41
|
+
createElement,
|
|
42
|
+
createPortal,
|
|
43
|
+
useCallback,
|
|
44
|
+
useContext,
|
|
45
|
+
useEffect,
|
|
46
|
+
useMemo,
|
|
47
|
+
useRef,
|
|
48
|
+
useState,
|
|
49
|
+
} from 'octane';
|
|
50
|
+
|
|
51
|
+
import {
|
|
52
|
+
BaseCollection,
|
|
53
|
+
CollectionNode,
|
|
54
|
+
FilterableNode,
|
|
55
|
+
LoaderNode,
|
|
56
|
+
type Mutable,
|
|
57
|
+
} from '../collections/BaseCollection';
|
|
58
|
+
import {
|
|
59
|
+
CollectionBuilder,
|
|
60
|
+
createBranchComponent,
|
|
61
|
+
createLeafComponent,
|
|
62
|
+
} from '../collections/CollectionBuilder';
|
|
63
|
+
import { useCachedChildren } from '../collections/useCachedChildren';
|
|
64
|
+
import { FocusScope } from '../focus/FocusScope';
|
|
65
|
+
import { useFocusRing } from '../focus/useFocusRing';
|
|
66
|
+
import { useLocale } from '../i18n/I18nProvider';
|
|
67
|
+
import { useLocalizedStringFormatter } from '../i18n/useLocalizedStringFormatter';
|
|
68
|
+
import { useHover } from '../interactions/useHover';
|
|
69
|
+
import { S, subSlot } from '../internal';
|
|
70
|
+
import { ListKeyboardDelegate } from '../selection/ListKeyboardDelegate';
|
|
71
|
+
import type { GridNode } from '../stately/grid/GridCollection';
|
|
72
|
+
import type { ColumnSize, ColumnStaticSize } from '../stately/table/Column';
|
|
73
|
+
import { buildHeaderRows, type ITableCollection } from '../stately/table/TableCollection';
|
|
74
|
+
import type { MultipleSelectionState } from '../stately/selection/types';
|
|
75
|
+
import { useMultipleSelectionState } from '../stately/selection/useMultipleSelectionState';
|
|
76
|
+
import {
|
|
77
|
+
type TableProps as SharedTableProps,
|
|
78
|
+
type TableState,
|
|
79
|
+
UNSTABLE_useFilteredTableState,
|
|
80
|
+
useTableState,
|
|
81
|
+
} from '../stately/table/useTableState';
|
|
82
|
+
import {
|
|
83
|
+
type TableColumnResizeState,
|
|
84
|
+
useTableColumnResizeState,
|
|
85
|
+
} from '../stately/table/useTableColumnResizeState';
|
|
86
|
+
import { useControlledState } from '../stately/utils/useControlledState';
|
|
87
|
+
import { useTable } from '../table/useTable';
|
|
88
|
+
import { useTableCell } from '../table/useTableCell';
|
|
89
|
+
import { useTableColumnHeader } from '../table/useTableColumnHeader';
|
|
90
|
+
import { useTableColumnResize } from '../table/useTableColumnResize';
|
|
91
|
+
import { useTableHeaderRow } from '../table/useTableHeaderRow';
|
|
92
|
+
import { useTableRow } from '../table/useTableRow';
|
|
93
|
+
import { useTableRowGroup } from '../table/useTableRowGroup';
|
|
94
|
+
import {
|
|
95
|
+
useTableSelectAllCheckbox,
|
|
96
|
+
useTableSelectionCheckbox,
|
|
97
|
+
} from '../table/useTableSelectionCheckbox';
|
|
98
|
+
import { filterDOMProps } from '../utils/filterDOMProps';
|
|
99
|
+
import { isScrollable } from '../utils/isScrollable';
|
|
100
|
+
import { mergeProps } from '../utils/mergeProps';
|
|
101
|
+
import { mergeRefs } from '../utils/mergeRefs';
|
|
102
|
+
import { useLayoutEffect } from '../utils/useLayoutEffect';
|
|
103
|
+
import { type LoadMoreSentinelProps, useLoadMoreSentinel } from '../utils/useLoadMoreSentinel';
|
|
104
|
+
import { useObjectRef } from '../utils/useObjectRef';
|
|
105
|
+
import { useResizeObserver } from '../utils/useResizeObserver';
|
|
106
|
+
import { useVisuallyHidden } from '../visually-hidden/VisuallyHidden';
|
|
107
|
+
import {
|
|
108
|
+
FieldInputContext,
|
|
109
|
+
SelectableCollectionContext,
|
|
110
|
+
type SelectableCollectionContextValue,
|
|
111
|
+
} from './Autocomplete';
|
|
112
|
+
import { ButtonContext } from './Button';
|
|
113
|
+
import { CheckboxContext, CheckboxFieldContext } from './Checkbox';
|
|
114
|
+
import {
|
|
115
|
+
Collection,
|
|
116
|
+
CollectionRendererContext,
|
|
117
|
+
DefaultCollectionRenderer,
|
|
118
|
+
type ItemCollectionProps,
|
|
119
|
+
type ItemRenderProps,
|
|
120
|
+
} from './Collection';
|
|
121
|
+
import {
|
|
122
|
+
DragAndDropContext,
|
|
123
|
+
DropIndicatorContext,
|
|
124
|
+
type DropIndicatorProps,
|
|
125
|
+
useDndPersistedKeys,
|
|
126
|
+
useRenderDropIndicator,
|
|
127
|
+
} from './DragAndDrop';
|
|
128
|
+
import type {
|
|
129
|
+
DragAndDropHooks,
|
|
130
|
+
DraggableCollectionState,
|
|
131
|
+
DraggableItemResult,
|
|
132
|
+
DropIndicatorAria,
|
|
133
|
+
DroppableCollectionResult,
|
|
134
|
+
DroppableCollectionState,
|
|
135
|
+
} from './useDragAndDrop';
|
|
136
|
+
import { SelectionIndicatorContext } from './SelectionIndicator';
|
|
137
|
+
import { SharedElementTransition } from './SharedElementTransition';
|
|
138
|
+
import { TreeDropTargetDelegate } from './TreeDropTargetDelegate';
|
|
139
|
+
import {
|
|
140
|
+
type ClassNameOrFunction,
|
|
141
|
+
type ContextValue,
|
|
142
|
+
DEFAULT_SLOT,
|
|
143
|
+
dom,
|
|
144
|
+
type DOMProps,
|
|
145
|
+
type DOMRenderProps,
|
|
146
|
+
Provider,
|
|
147
|
+
type RenderProps,
|
|
148
|
+
type SlotProps,
|
|
149
|
+
type StyleProps,
|
|
150
|
+
type StyleRenderProps,
|
|
151
|
+
useContextProps,
|
|
152
|
+
useRenderProps,
|
|
153
|
+
} from './utils';
|
|
154
|
+
|
|
155
|
+
// octane adaptation: structural bag (upstream's `GlobalDOMAttributes` drags React handler types).
|
|
156
|
+
type GlobalDOMAttributes = Record<string, any>;
|
|
157
|
+
type RefObject<T> = { current: T };
|
|
158
|
+
type ReactNode = any;
|
|
159
|
+
type HTMLAttributes = Record<string, any>;
|
|
160
|
+
type DragPreviewRenderer = any;
|
|
161
|
+
|
|
162
|
+
// The react-aria-components package intl dictionary, reduced to the key this module
|
|
163
|
+
// reads. Strings are copied VERBATIM from .react-spectrum/packages/react-aria-components/
|
|
164
|
+
// intl/*.json; refresh from the pinned checkout on version bumps, never hand-edit.
|
|
165
|
+
const intlMessages = {
|
|
166
|
+
'ar-AE': { tableResizer: 'أداة تغيير الحجم' },
|
|
167
|
+
'bg-BG': { tableResizer: 'Преоразмерител' },
|
|
168
|
+
'cs-CZ': { tableResizer: 'Změna velikosti' },
|
|
169
|
+
'da-DK': { tableResizer: 'Størrelsesændring' },
|
|
170
|
+
'de-DE': { tableResizer: 'Größenanpassung' },
|
|
171
|
+
'el-GR': { tableResizer: 'Αλλαγή μεγέθους' },
|
|
172
|
+
'en-US': { tableResizer: 'Resizer' },
|
|
173
|
+
'es-ES': { tableResizer: 'Cambiador de tamaño' },
|
|
174
|
+
'et-EE': { tableResizer: 'Suuruse muutja' },
|
|
175
|
+
'fi-FI': { tableResizer: 'Koon muuttaja' },
|
|
176
|
+
'fr-FR': { tableResizer: 'Redimensionneur' },
|
|
177
|
+
'he-IL': { tableResizer: 'שינוי גודל' },
|
|
178
|
+
'hr-HR': { tableResizer: 'Promjena veličine' },
|
|
179
|
+
'hu-HU': { tableResizer: 'Átméretező' },
|
|
180
|
+
'it-IT': { tableResizer: 'Ridimensionamento' },
|
|
181
|
+
'ja-JP': { tableResizer: 'サイズ変更ツール' },
|
|
182
|
+
'ko-KR': { tableResizer: '크기 조정기' },
|
|
183
|
+
'lt-LT': { tableResizer: 'Dydžio keitiklis' },
|
|
184
|
+
'lv-LV': { tableResizer: 'Izmēra mainītājs' },
|
|
185
|
+
'nb-NO': { tableResizer: 'Størrelsesendrer' },
|
|
186
|
+
'nl-NL': { tableResizer: 'Resizer' },
|
|
187
|
+
'pl-PL': { tableResizer: 'Zmiana rozmiaru' },
|
|
188
|
+
'pt-BR': { tableResizer: 'Redimensionador' },
|
|
189
|
+
'pt-PT': { tableResizer: 'Redimensionador' },
|
|
190
|
+
'ro-RO': { tableResizer: 'Instrument de redimensionare' },
|
|
191
|
+
'ru-RU': { tableResizer: 'Средство изменения размера' },
|
|
192
|
+
'sk-SK': { tableResizer: 'Nástroj na zmenu veľkosti' },
|
|
193
|
+
'sl-SI': { tableResizer: 'Spreminjanje velikosti' },
|
|
194
|
+
'sr-SP': { tableResizer: 'Promena veličine' },
|
|
195
|
+
'sv-SE': { tableResizer: 'Storleksändrare' },
|
|
196
|
+
'tr-TR': { tableResizer: 'Yeniden boyutlandırıcı' },
|
|
197
|
+
'uk-UA': { tableResizer: 'Засіб змінення розміру' },
|
|
198
|
+
'zh-CN': { tableResizer: '尺寸调整器' },
|
|
199
|
+
'zh-TW': { tableResizer: '大小調整器' },
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
class TableCollection<T> extends BaseCollection<T> implements ITableCollection<T> {
|
|
203
|
+
headerRows: GridNode<T>[] = [];
|
|
204
|
+
columns: GridNode<T>[] = [];
|
|
205
|
+
rows: GridNode<T>[] = [];
|
|
206
|
+
rowHeaderColumnKeys: Set<Key> = new Set();
|
|
207
|
+
head: CollectionNode<T> = new TableHeaderNode<T>(-1);
|
|
208
|
+
columnsDirty = true;
|
|
209
|
+
private expandedKeys: Set<Key> = new Set();
|
|
210
|
+
|
|
211
|
+
withExpandedKeys(expandedKeys: Set<Key>): TableCollection<T> {
|
|
212
|
+
let collection = this.clone();
|
|
213
|
+
collection.expandedKeys = expandedKeys;
|
|
214
|
+
collection.frozen = this.frozen;
|
|
215
|
+
collection.rows = Array.from(collection.getRows());
|
|
216
|
+
return collection;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
addNode(node: CollectionNode<T>): void {
|
|
220
|
+
super.addNode(node);
|
|
221
|
+
|
|
222
|
+
this.columnsDirty ||= node.type === 'column';
|
|
223
|
+
if (node.type === 'tableheader') {
|
|
224
|
+
this.head = node;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private getRows(): GridNode<T>[] {
|
|
229
|
+
let rows: GridNode<T>[] = [];
|
|
230
|
+
for (let child of this) {
|
|
231
|
+
if (child.type === 'tablebody' || child.type === 'tablefooter') {
|
|
232
|
+
rows.push(...(this.getChildren(child.key) as Iterable<GridNode<T>>));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return rows;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// backward compatibility
|
|
239
|
+
get body(): GridNode<T> {
|
|
240
|
+
for (let child of this) {
|
|
241
|
+
if (child.type === 'tablebody') {
|
|
242
|
+
return child as GridNode<T>;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return new TableBodyNode<T>(-2) as GridNode<T>;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
commit(firstKey: Key | null, lastKey: Key | null, isSSR = false): void {
|
|
249
|
+
this.updateColumns(isSSR);
|
|
250
|
+
|
|
251
|
+
this.firstKey = firstKey;
|
|
252
|
+
this.lastKey = lastKey;
|
|
253
|
+
this.rows = [];
|
|
254
|
+
for (let row of this.getRows()) {
|
|
255
|
+
let lastChildKey = (row as CollectionNode<T>).lastChildKey;
|
|
256
|
+
if (lastChildKey != null) {
|
|
257
|
+
let lastCell = this.getItem(lastChildKey) as GridNode<T> | null;
|
|
258
|
+
while (lastCell && lastCell.type !== 'cell') {
|
|
259
|
+
lastCell =
|
|
260
|
+
lastCell.prevKey != null
|
|
261
|
+
? (this.getItem(lastCell.prevKey) as GridNode<T> | null)
|
|
262
|
+
: null;
|
|
263
|
+
}
|
|
264
|
+
if (lastCell) {
|
|
265
|
+
let numberOfCellsInRow = (lastCell.colIndex ?? lastCell.index) + (lastCell.colSpan ?? 1);
|
|
266
|
+
if (numberOfCellsInRow !== this.columns.length && !isSSR) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Cell count must match column count. Found ${numberOfCellsInRow} cells and ${this.columns.length} columns.`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
this.rows.push(row);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
super.commit(firstKey, lastKey, isSSR);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private updateColumns(isSSR: boolean): void {
|
|
280
|
+
if (!this.columnsDirty) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.rowHeaderColumnKeys = new Set();
|
|
285
|
+
this.columns = [];
|
|
286
|
+
let columnKeyMap = new Map();
|
|
287
|
+
let visit = (node: Node<T>) => {
|
|
288
|
+
switch (node.type) {
|
|
289
|
+
case 'column':
|
|
290
|
+
columnKeyMap.set(node.key, node);
|
|
291
|
+
if (!node.hasChildNodes) {
|
|
292
|
+
(node as Mutable<Node<T>>).index = this.columns.length;
|
|
293
|
+
this.columns.push(node as GridNode<T>);
|
|
294
|
+
|
|
295
|
+
if (node.props.isRowHeader) {
|
|
296
|
+
this.rowHeaderColumnKeys.add(node.key);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
for (let child of this.getChildren(node.key)) {
|
|
302
|
+
visit(child);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
for (let node of this.getChildren(this.head.key)) {
|
|
307
|
+
visit(node);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
this.headerRows = buildHeaderRows(columnKeyMap, this.columns);
|
|
311
|
+
this.columnsDirty = false;
|
|
312
|
+
if (this.rowHeaderColumnKeys.size === 0 && this.columns.length > 0 && !isSSR) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
'A table must have at least one Column with the isRowHeader prop set to true',
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
get columnCount(): number {
|
|
320
|
+
return this.columns.length;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
*[Symbol.iterator](): IterableIterator<Node<T>> {
|
|
324
|
+
let key = this.firstKey;
|
|
325
|
+
while (key != null) {
|
|
326
|
+
let node = this.getItem(key);
|
|
327
|
+
if (node) {
|
|
328
|
+
yield node;
|
|
329
|
+
}
|
|
330
|
+
key = node?.nextKey ?? null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
getFirstKey(): Key | null {
|
|
335
|
+
for (let child of this) {
|
|
336
|
+
if (child.type === 'tablebody') {
|
|
337
|
+
return (child as CollectionNode<T>).firstChildKey ?? null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
getLastKey(): Key | null {
|
|
344
|
+
let key = this.lastKey;
|
|
345
|
+
if (key == null) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let node = this.getItem(key) as CollectionNode<T>;
|
|
350
|
+
|
|
351
|
+
while (
|
|
352
|
+
node?.lastChildKey != null &&
|
|
353
|
+
(node.type !== 'item' || this.expandedKeys.has(node.key))
|
|
354
|
+
) {
|
|
355
|
+
node = this.getItem(node.lastChildKey) as CollectionNode<T>;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return node?.key;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
getKeyAfter(key: Key): Key | null {
|
|
362
|
+
let node = this.getItem(key) as CollectionNode<T>;
|
|
363
|
+
if (node?.type === 'column') {
|
|
364
|
+
return node.nextKey ?? null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!node) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// If this is an expanded item, return the first child item if any.
|
|
372
|
+
if (node.type === 'item' && node.firstChildKey != null && this.expandedKeys.has(node.key)) {
|
|
373
|
+
let child = this.getItem(node.firstChildKey) as CollectionNode<T> | null;
|
|
374
|
+
while (child) {
|
|
375
|
+
if (child.type === 'item') {
|
|
376
|
+
return child.key;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
child = child.nextKey != null ? (this.getItem(child.nextKey) as CollectionNode<T>) : null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return super.getKeyAfter(key);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
getKeyBefore(key: Key): Key | null {
|
|
387
|
+
let node = this.getItem(key) as CollectionNode<T>;
|
|
388
|
+
if (node?.type === 'column') {
|
|
389
|
+
return node.prevKey ?? null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!node) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let k: Key | null = null;
|
|
397
|
+
if (node.prevKey != null) {
|
|
398
|
+
node = this.getItem(node.prevKey) as CollectionNode<T>;
|
|
399
|
+
|
|
400
|
+
// Traverse to the deepest expanded child.
|
|
401
|
+
while (
|
|
402
|
+
node &&
|
|
403
|
+
(node.type !== 'item' || this.expandedKeys.has(node.key)) &&
|
|
404
|
+
node.lastChildKey != null
|
|
405
|
+
) {
|
|
406
|
+
node = this.getItem(node.lastChildKey) as CollectionNode<T>;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
k = node?.key ?? null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (k == null) {
|
|
413
|
+
k = node.parentKey;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (k != null && this.getItem(k)?.type === 'tableheader') {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return k;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
getChildren(key: Key): Iterable<Node<T>> {
|
|
424
|
+
let item = this.getItem(key);
|
|
425
|
+
if (!item) {
|
|
426
|
+
for (let row of this.headerRows) {
|
|
427
|
+
if (row.key === key) {
|
|
428
|
+
return row.childNodes;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Flatten all rows into the body.
|
|
434
|
+
let self = this;
|
|
435
|
+
if (item?.type === 'tablebody' || item?.type === 'tablefooter') {
|
|
436
|
+
return {
|
|
437
|
+
*[Symbol.iterator]() {
|
|
438
|
+
let firstKey = (item as CollectionNode<T>).firstChildKey;
|
|
439
|
+
let node: Node<T> | null = firstKey != null ? self.getItem(firstKey) : null;
|
|
440
|
+
|
|
441
|
+
while (node) {
|
|
442
|
+
yield node as Node<T>;
|
|
443
|
+
let key = self.getKeyAfter(node.key);
|
|
444
|
+
node = key != null ? self.getItem(key) : null;
|
|
445
|
+
if (node && node.parentKey === item.parentKey) {
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return {
|
|
454
|
+
*[Symbol.iterator]() {
|
|
455
|
+
let parent = self.getItem(key) as CollectionNode<T> | null;
|
|
456
|
+
let node =
|
|
457
|
+
parent?.firstChildKey != null
|
|
458
|
+
? (self.getItem(parent.firstChildKey) as CollectionNode<T> | null)
|
|
459
|
+
: null;
|
|
460
|
+
while (node) {
|
|
461
|
+
yield node as Node<T>;
|
|
462
|
+
node =
|
|
463
|
+
node.nextKey != null ? (self.getItem(node.nextKey) as CollectionNode<T> | null) : null;
|
|
464
|
+
|
|
465
|
+
// Return only cells as children of rows (nested rows are flattened into the body).
|
|
466
|
+
if (parent?.type === 'item' && node?.type !== 'cell') {
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
clone(): this {
|
|
475
|
+
let collection = super.clone();
|
|
476
|
+
collection.headerRows = this.headerRows;
|
|
477
|
+
collection.columns = this.columns;
|
|
478
|
+
collection.rows = this.rows;
|
|
479
|
+
collection.rowHeaderColumnKeys = this.rowHeaderColumnKeys;
|
|
480
|
+
collection.head = this.head;
|
|
481
|
+
return collection;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
getTextValue(key: Key): string {
|
|
485
|
+
let row = this.getItem(key);
|
|
486
|
+
if (!row) {
|
|
487
|
+
return '';
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// If the row has a textValue, use that.
|
|
491
|
+
if (row.textValue) {
|
|
492
|
+
return row.textValue;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Otherwise combine the text of each of the row header columns.
|
|
496
|
+
let rowHeaderColumnKeys = this.rowHeaderColumnKeys;
|
|
497
|
+
let text: string[] = [];
|
|
498
|
+
for (let cell of this.getChildren(key)) {
|
|
499
|
+
let column = this.columns[cell.index!];
|
|
500
|
+
if (rowHeaderColumnKeys.has(column.key) && cell.textValue) {
|
|
501
|
+
text.push(cell.textValue);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (text.length === rowHeaderColumnKeys.size) {
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return text.join(' ');
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
interface ResizableTableContainerContextValue {
|
|
514
|
+
tableWidth: number;
|
|
515
|
+
tableRef: RefObject<HTMLTableElement | null>;
|
|
516
|
+
scrollRef: RefObject<HTMLElement | null>;
|
|
517
|
+
// Dependency inject useTableColumnResizeState so it doesn't affect bundle size unless you're using ResizableTableContainer.
|
|
518
|
+
useTableColumnResizeState: typeof useTableColumnResizeState;
|
|
519
|
+
onResizeStart?: (widths: Map<Key, ColumnSize>) => void;
|
|
520
|
+
onResize?: (widths: Map<Key, ColumnSize>) => void;
|
|
521
|
+
onResizeEnd?: (widths: Map<Key, ColumnSize>) => void;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const ResizableTableContainerContext = createContext<ResizableTableContainerContextValue | null>(
|
|
525
|
+
null,
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
export interface ResizableTableContainerProps
|
|
529
|
+
extends DOMProps, DOMRenderProps<'div', undefined>, GlobalDOMAttributes {
|
|
530
|
+
/**
|
|
531
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
532
|
+
* element.
|
|
533
|
+
*
|
|
534
|
+
* @default 'react-aria-ResizableTableContainer'
|
|
535
|
+
*/
|
|
536
|
+
className?: string;
|
|
537
|
+
/**
|
|
538
|
+
* Handler that is called when a user starts a column resize.
|
|
539
|
+
*/
|
|
540
|
+
onResizeStart?: (widths: Map<Key, ColumnSize>) => void;
|
|
541
|
+
/**
|
|
542
|
+
* Handler that is called when a user performs a column resize.
|
|
543
|
+
* Can be used with the width property on columns to put the column widths into
|
|
544
|
+
* a controlled state.
|
|
545
|
+
*/
|
|
546
|
+
onResize?: (widths: Map<Key, ColumnSize>) => void;
|
|
547
|
+
/**
|
|
548
|
+
* Handler that is called after a user performs a column resize.
|
|
549
|
+
* Can be used to store the widths of columns for another future session.
|
|
550
|
+
*/
|
|
551
|
+
onResizeEnd?: (widths: Map<Key, ColumnSize>) => void;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export function ResizableTableContainer(props: ResizableTableContainerProps & { ref?: any }): any {
|
|
555
|
+
const slot = S('ResizableTableContainer');
|
|
556
|
+
let containerRef = useObjectRef<HTMLDivElement>((props as any).ref, subSlot(slot, 'container'));
|
|
557
|
+
let tableRef = useRef<HTMLTableElement | null>(null, subSlot(slot, 'table'));
|
|
558
|
+
let scrollRef = useRef<HTMLElement | null>(null, subSlot(slot, 'scroll'));
|
|
559
|
+
let [width, setWidth] = useState(0, subSlot(slot, 'width'));
|
|
560
|
+
|
|
561
|
+
useLayoutEffect(
|
|
562
|
+
() => {
|
|
563
|
+
// Walk up the DOM from the Table to the ResizableTableContainer and stop
|
|
564
|
+
// when we reach the first scrollable element. This is what we'll measure
|
|
565
|
+
// to determine column widths (important due to width of scrollbars).
|
|
566
|
+
// This will usually be the ResizableTableContainer for native tables, and
|
|
567
|
+
// the Table itself for virtualized tables.
|
|
568
|
+
let table = tableRef.current as HTMLElement | null;
|
|
569
|
+
while (table && table !== containerRef.current && !isScrollable(table)) {
|
|
570
|
+
table = table.parentElement;
|
|
571
|
+
}
|
|
572
|
+
scrollRef.current = table;
|
|
573
|
+
},
|
|
574
|
+
[containerRef],
|
|
575
|
+
subSlot(slot, 'scrollWalk'),
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
useResizeObserver(
|
|
579
|
+
{
|
|
580
|
+
ref: scrollRef,
|
|
581
|
+
box: 'border-box',
|
|
582
|
+
onResize() {
|
|
583
|
+
setWidth(scrollRef.current?.clientWidth ?? 0);
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
subSlot(slot, 'resizeObserver'),
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
useLayoutEffect(
|
|
590
|
+
() => {
|
|
591
|
+
setWidth(scrollRef.current?.clientWidth ?? 0);
|
|
592
|
+
},
|
|
593
|
+
[],
|
|
594
|
+
subSlot(slot, 'initialWidth'),
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
let ctx = useMemo(
|
|
598
|
+
() => ({
|
|
599
|
+
tableRef,
|
|
600
|
+
scrollRef,
|
|
601
|
+
tableWidth: width,
|
|
602
|
+
useTableColumnResizeState,
|
|
603
|
+
onResizeStart: props.onResizeStart,
|
|
604
|
+
onResize: props.onResize,
|
|
605
|
+
onResizeEnd: props.onResizeEnd,
|
|
606
|
+
}),
|
|
607
|
+
[tableRef, width, props.onResizeStart, props.onResize, props.onResizeEnd],
|
|
608
|
+
subSlot(slot, 'ctx'),
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
return createElement(dom.div, {
|
|
612
|
+
render: props.render,
|
|
613
|
+
...filterDOMProps(props, { global: true }),
|
|
614
|
+
ref: containerRef,
|
|
615
|
+
className: props.className || 'react-aria-ResizableTableContainer',
|
|
616
|
+
style: props.style,
|
|
617
|
+
onScroll: (props as any).onScroll,
|
|
618
|
+
children: createElement(ResizableTableContainerContext.Provider, {
|
|
619
|
+
value: ctx,
|
|
620
|
+
children: props.children,
|
|
621
|
+
}),
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export const TableContext =
|
|
626
|
+
createContext<ContextValue<TableProps, HTMLTableElement | HTMLDivElement>>(null);
|
|
627
|
+
export const TableStateContext = createContext<TableState<any> | null>(null);
|
|
628
|
+
export const TableColumnResizeStateContext = createContext<TableColumnResizeState<unknown> | null>(
|
|
629
|
+
null,
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
export interface TableRenderProps {
|
|
633
|
+
/**
|
|
634
|
+
* Whether the table is currently focused.
|
|
635
|
+
*
|
|
636
|
+
* @selector [data-focused]
|
|
637
|
+
*/
|
|
638
|
+
isFocused: boolean;
|
|
639
|
+
/**
|
|
640
|
+
* Whether the table is currently keyboard focused.
|
|
641
|
+
*
|
|
642
|
+
* @selector [data-focus-visible]
|
|
643
|
+
*/
|
|
644
|
+
isFocusVisible: boolean;
|
|
645
|
+
/**
|
|
646
|
+
* Whether the table is currently the active drop target.
|
|
647
|
+
*
|
|
648
|
+
* @selector [data-drop-target]
|
|
649
|
+
*/
|
|
650
|
+
isDropTarget: boolean;
|
|
651
|
+
/**
|
|
652
|
+
* State of the table.
|
|
653
|
+
*/
|
|
654
|
+
state: TableState<unknown>;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export interface TableProps
|
|
658
|
+
extends
|
|
659
|
+
Omit<SharedTableProps<any>, 'children'>,
|
|
660
|
+
StyleRenderProps<TableRenderProps, 'table' | 'div'>,
|
|
661
|
+
SlotProps,
|
|
662
|
+
AriaLabelingProps,
|
|
663
|
+
GlobalDOMAttributes {
|
|
664
|
+
/**
|
|
665
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
666
|
+
* element. A function may be provided to compute the class based on component state.
|
|
667
|
+
*
|
|
668
|
+
* @default 'react-aria-Table'
|
|
669
|
+
*/
|
|
670
|
+
className?: ClassNameOrFunction<TableRenderProps>;
|
|
671
|
+
/** The elements that make up the table. Includes the TableHeader, TableBody, Columns, and Rows. */
|
|
672
|
+
children?: ReactNode;
|
|
673
|
+
/**
|
|
674
|
+
* How multiple selection should behave in the collection.
|
|
675
|
+
*
|
|
676
|
+
* @default 'toggle'
|
|
677
|
+
*/
|
|
678
|
+
selectionBehavior?: SelectionBehavior;
|
|
679
|
+
/**
|
|
680
|
+
* Whether `disabledKeys` applies to all interactions, or only selection.
|
|
681
|
+
*
|
|
682
|
+
* @default 'all'
|
|
683
|
+
*/
|
|
684
|
+
disabledBehavior?: DisabledBehavior;
|
|
685
|
+
/** Handler that is called when a user performs an action on the row. */
|
|
686
|
+
onRowAction?: (key: Key) => void;
|
|
687
|
+
/**
|
|
688
|
+
* The drag and drop hooks returned by `useDragAndDrop` used to enable drag and drop behavior for
|
|
689
|
+
* the Table.
|
|
690
|
+
*/
|
|
691
|
+
dragAndDropHooks?: DragAndDropHooks;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* A table displays data in rows and columns and enables a user to navigate its contents via
|
|
696
|
+
* directional navigation keys, and optionally supports row selection and sorting.
|
|
697
|
+
*/
|
|
698
|
+
export function Table(props: TableProps): any {
|
|
699
|
+
const slot = S('Table');
|
|
700
|
+
let ref: any;
|
|
701
|
+
[props, ref] = useContextProps(props, (props as any).ref, TableContext, subSlot(slot, 'ctx'));
|
|
702
|
+
|
|
703
|
+
// Separate selection state so we have access to it from collection components via useTableOptions.
|
|
704
|
+
let selectionState = useMultipleSelectionState(props, subSlot(slot, 'selection'));
|
|
705
|
+
let { selectionBehavior, selectionMode, disallowEmptySelection } = selectionState;
|
|
706
|
+
let hasDragHooks = !!props.dragAndDropHooks?.useDraggableCollectionState;
|
|
707
|
+
let ctx = useMemo(
|
|
708
|
+
() => ({
|
|
709
|
+
selectionBehavior: selectionMode === 'none' ? null : selectionBehavior,
|
|
710
|
+
selectionMode,
|
|
711
|
+
disallowEmptySelection,
|
|
712
|
+
allowsDragging: hasDragHooks,
|
|
713
|
+
}),
|
|
714
|
+
[selectionBehavior, selectionMode, disallowEmptySelection, hasDragHooks],
|
|
715
|
+
subSlot(slot, 'options'),
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
let content = createElement(TableOptionsContext.Provider, {
|
|
719
|
+
value: ctx,
|
|
720
|
+
children: createElement(Collection, props as any),
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
return createElement(CollectionBuilder, {
|
|
724
|
+
content,
|
|
725
|
+
createCollection: () => new TableCollection<any>(),
|
|
726
|
+
children: (collection: TableCollection<Node<object>>) =>
|
|
727
|
+
createElement(TableInner, {
|
|
728
|
+
props,
|
|
729
|
+
forwardedRef: ref,
|
|
730
|
+
selectionState,
|
|
731
|
+
collection,
|
|
732
|
+
}),
|
|
733
|
+
} as any);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
interface TableInnerProps {
|
|
737
|
+
props: TableProps & SelectableCollectionContextValue<unknown>;
|
|
738
|
+
forwardedRef: RefObject<HTMLElement | null>;
|
|
739
|
+
selectionState: MultipleSelectionState;
|
|
740
|
+
collection: TableCollection<Node<object>>;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function TableElementType(props: any): any {
|
|
744
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
745
|
+
if (isVirtualized) {
|
|
746
|
+
return createElement(dom.div, props);
|
|
747
|
+
}
|
|
748
|
+
return createElement(dom.table, props);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const EXPANSION_KEYS = {
|
|
752
|
+
expand: {
|
|
753
|
+
ltr: 'ArrowRight',
|
|
754
|
+
rtl: 'ArrowLeft',
|
|
755
|
+
},
|
|
756
|
+
collapse: {
|
|
757
|
+
ltr: 'ArrowLeft',
|
|
758
|
+
rtl: 'ArrowRight',
|
|
759
|
+
},
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
function TableInner({ props, forwardedRef, selectionState, collection }: TableInnerProps): any {
|
|
763
|
+
const slot = S('TableInner');
|
|
764
|
+
let ref: any = forwardedRef;
|
|
765
|
+
[props, ref] = useContextProps(
|
|
766
|
+
props,
|
|
767
|
+
ref,
|
|
768
|
+
SelectableCollectionContext,
|
|
769
|
+
subSlot(slot, 'ctx'),
|
|
770
|
+
) as any;
|
|
771
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
772
|
+
let { shouldUseVirtualFocus, disallowTypeAhead, filter, ...DOMCollectionProps } = props;
|
|
773
|
+
let tableContainerContext = useContext(ResizableTableContainerContext);
|
|
774
|
+
ref = useObjectRef(
|
|
775
|
+
useMemo(
|
|
776
|
+
() => mergeRefs(ref, tableContainerContext?.tableRef as any),
|
|
777
|
+
[ref, tableContainerContext?.tableRef],
|
|
778
|
+
subSlot(slot, 'mergedRef'),
|
|
779
|
+
),
|
|
780
|
+
subSlot(slot, 'objectRef'),
|
|
781
|
+
);
|
|
782
|
+
let [expandedKeys, setExpandedKeys] = useControlledState(
|
|
783
|
+
props.expandedKeys ? new Set(props.expandedKeys) : undefined,
|
|
784
|
+
props.defaultExpandedKeys ? new Set(props.defaultExpandedKeys) : new Set(),
|
|
785
|
+
props.onExpandedChange as any,
|
|
786
|
+
subSlot(slot, 'expandedKeys'),
|
|
787
|
+
);
|
|
788
|
+
collection = useMemo(
|
|
789
|
+
() => collection.withExpandedKeys(expandedKeys),
|
|
790
|
+
[collection, expandedKeys],
|
|
791
|
+
subSlot(slot, 'collection'),
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
let tableState = useTableState(
|
|
795
|
+
{
|
|
796
|
+
...DOMCollectionProps,
|
|
797
|
+
collection,
|
|
798
|
+
children: undefined,
|
|
799
|
+
UNSAFE_selectionState: selectionState,
|
|
800
|
+
expandedKeys,
|
|
801
|
+
onExpandedChange: setExpandedKeys,
|
|
802
|
+
} as any,
|
|
803
|
+
subSlot(slot, 'state'),
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
let filteredState = UNSTABLE_useFilteredTableState(tableState, filter, subSlot(slot, 'filtered'));
|
|
807
|
+
let {
|
|
808
|
+
isVirtualized,
|
|
809
|
+
layoutDelegate,
|
|
810
|
+
dropTargetDelegate: ctxDropTargetDelegate,
|
|
811
|
+
CollectionRoot,
|
|
812
|
+
} = useContext(CollectionRendererContext);
|
|
813
|
+
let { dragAndDropHooks } = props;
|
|
814
|
+
let { gridProps } = useTable(
|
|
815
|
+
{
|
|
816
|
+
...DOMCollectionProps,
|
|
817
|
+
layoutDelegate,
|
|
818
|
+
isVirtualized,
|
|
819
|
+
} as any,
|
|
820
|
+
filteredState,
|
|
821
|
+
ref,
|
|
822
|
+
subSlot(slot, 'table'),
|
|
823
|
+
);
|
|
824
|
+
let selectionManager = filteredState.selectionManager;
|
|
825
|
+
let hasDragHooks = !!dragAndDropHooks?.useDraggableCollectionState;
|
|
826
|
+
let hasDropHooks = !!dragAndDropHooks?.useDroppableCollectionState;
|
|
827
|
+
let dragHooksProvided = useRef(hasDragHooks, subSlot(slot, 'dragProvided'));
|
|
828
|
+
let dropHooksProvided = useRef(hasDropHooks, subSlot(slot, 'dropProvided'));
|
|
829
|
+
useEffect(
|
|
830
|
+
() => {
|
|
831
|
+
if (process.env.NODE_ENV === 'production') {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (dragHooksProvided.current !== hasDragHooks) {
|
|
835
|
+
console.warn(
|
|
836
|
+
'Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.',
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
if (dropHooksProvided.current !== hasDropHooks) {
|
|
840
|
+
console.warn(
|
|
841
|
+
'Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.',
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
[hasDragHooks, hasDropHooks],
|
|
846
|
+
subSlot(slot, 'dndWarn'),
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
let dragState: DraggableCollectionState | undefined = undefined;
|
|
850
|
+
let dropState: DroppableCollectionState | undefined = undefined;
|
|
851
|
+
let droppableCollection: DroppableCollectionResult | undefined = undefined;
|
|
852
|
+
let isRootDropTarget = false;
|
|
853
|
+
let dragPreview: any = null;
|
|
854
|
+
let preview = useRef<DragPreviewRenderer>(null, subSlot(slot, 'preview'));
|
|
855
|
+
let { direction } = useLocale(subSlot(slot, 'locale'));
|
|
856
|
+
let [treeDropTargetDelegate] = useState(
|
|
857
|
+
() => new TreeDropTargetDelegate(),
|
|
858
|
+
subSlot(slot, 'dropDelegate'),
|
|
859
|
+
);
|
|
860
|
+
|
|
861
|
+
// PHASE-7: these branches call consumer-provided dnd hooks (upstream contract); they are
|
|
862
|
+
// unreachable until `useDragAndDrop` is ported and a consumer can construct hooks.
|
|
863
|
+
if (hasDragHooks && dragAndDropHooks) {
|
|
864
|
+
dragState = dragAndDropHooks.useDraggableCollectionState!({
|
|
865
|
+
collection: filteredState.collection,
|
|
866
|
+
selectionManager,
|
|
867
|
+
preview: dragAndDropHooks.renderDragPreview ? preview : undefined,
|
|
868
|
+
});
|
|
869
|
+
dragAndDropHooks.useDraggableCollection!({}, dragState, ref);
|
|
870
|
+
|
|
871
|
+
let DragPreview = dragAndDropHooks.DragPreview!;
|
|
872
|
+
dragPreview = dragAndDropHooks.renderDragPreview
|
|
873
|
+
? createElement(DragPreview, { ref: preview, children: dragAndDropHooks.renderDragPreview })
|
|
874
|
+
: null;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (hasDropHooks && dragAndDropHooks) {
|
|
878
|
+
dropState = dragAndDropHooks.useDroppableCollectionState!({
|
|
879
|
+
collection: filteredState.collection,
|
|
880
|
+
selectionManager,
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
let keyboardDelegate = new ListKeyboardDelegate({
|
|
884
|
+
collection: filteredState.collection,
|
|
885
|
+
disabledKeys: selectionManager.disabledKeys,
|
|
886
|
+
disabledBehavior: selectionManager.disabledBehavior,
|
|
887
|
+
ref,
|
|
888
|
+
layoutDelegate,
|
|
889
|
+
});
|
|
890
|
+
let dropTargetDelegate =
|
|
891
|
+
dragAndDropHooks.dropTargetDelegate ||
|
|
892
|
+
ctxDropTargetDelegate ||
|
|
893
|
+
new dragAndDropHooks.ListDropTargetDelegate(collection.rows, ref);
|
|
894
|
+
treeDropTargetDelegate.setup(dropTargetDelegate, tableState as any, direction);
|
|
895
|
+
droppableCollection = dragAndDropHooks.useDroppableCollection!(
|
|
896
|
+
{
|
|
897
|
+
keyboardDelegate,
|
|
898
|
+
dropTargetDelegate: treeDropTargetDelegate,
|
|
899
|
+
onDropActivate: (e: any) => {
|
|
900
|
+
// Expand collapsed item when dragging over. For keyboard, allow collapsing.
|
|
901
|
+
if (e.target.type === 'item') {
|
|
902
|
+
let key = e.target.key;
|
|
903
|
+
let item = tableState.collection.getItem(key);
|
|
904
|
+
let isExpanded = expandedKeys.has(key);
|
|
905
|
+
if (
|
|
906
|
+
item &&
|
|
907
|
+
item.hasChildNodes &&
|
|
908
|
+
(!isExpanded || dragAndDropHooks?.isVirtualDragging?.())
|
|
909
|
+
) {
|
|
910
|
+
tableState.toggleKey(key);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
onKeyDown: (e: KeyboardEvent) => {
|
|
915
|
+
let target = dropState?.target;
|
|
916
|
+
if (target && target.type === 'item' && target.dropPosition === 'on') {
|
|
917
|
+
let item = tableState.collection.getItem(target.key);
|
|
918
|
+
if (
|
|
919
|
+
e.key === EXPANSION_KEYS['expand'][direction] &&
|
|
920
|
+
item?.hasChildNodes &&
|
|
921
|
+
!tableState.expandedKeys.has(target.key)
|
|
922
|
+
) {
|
|
923
|
+
tableState.toggleKey(target.key);
|
|
924
|
+
} else if (
|
|
925
|
+
e.key === EXPANSION_KEYS['collapse'][direction] &&
|
|
926
|
+
item?.hasChildNodes &&
|
|
927
|
+
tableState.expandedKeys.has(target.key)
|
|
928
|
+
) {
|
|
929
|
+
tableState.toggleKey(target.key);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
dropState,
|
|
935
|
+
ref,
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
isRootDropTarget = dropState.isDropTarget({ type: 'root' });
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
let { focusProps, isFocused, isFocusVisible } = useFocusRing(
|
|
942
|
+
undefined,
|
|
943
|
+
subSlot(slot, 'focusRing'),
|
|
944
|
+
);
|
|
945
|
+
let renderProps = useRenderProps(
|
|
946
|
+
{
|
|
947
|
+
...props,
|
|
948
|
+
children: undefined,
|
|
949
|
+
defaultClassName: 'react-aria-Table',
|
|
950
|
+
values: {
|
|
951
|
+
isDropTarget: isRootDropTarget,
|
|
952
|
+
isFocused,
|
|
953
|
+
isFocusVisible,
|
|
954
|
+
state: filteredState,
|
|
955
|
+
},
|
|
956
|
+
} as any,
|
|
957
|
+
subSlot(slot, 'render'),
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
let isListDraggable = !!(hasDragHooks && !dragState?.isDisabled);
|
|
961
|
+
|
|
962
|
+
let style = renderProps.style;
|
|
963
|
+
let layoutState: TableColumnResizeState<unknown> | null = null;
|
|
964
|
+
if (tableContainerContext) {
|
|
965
|
+
layoutState = tableContainerContext.useTableColumnResizeState(
|
|
966
|
+
{
|
|
967
|
+
tableWidth: tableContainerContext.tableWidth,
|
|
968
|
+
},
|
|
969
|
+
filteredState,
|
|
970
|
+
subSlot(slot, 'layoutState'),
|
|
971
|
+
);
|
|
972
|
+
if (!isVirtualized) {
|
|
973
|
+
style = {
|
|
974
|
+
...style,
|
|
975
|
+
tableLayout: 'fixed',
|
|
976
|
+
// due to https://bugzilla.mozilla.org/show_bug.cgi?id=1959353, we can't use "fit-content".
|
|
977
|
+
// Causes the table columns to grow to fill the available space in Firefox, ignoring user set column widths
|
|
978
|
+
width: 'min-content',
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
let DOMProps = filterDOMProps(props, { global: true });
|
|
984
|
+
|
|
985
|
+
// octane adaptation: hooks hoisted out of the createElement argument list (upstream calls
|
|
986
|
+
// them inline in JSX attribute position).
|
|
987
|
+
let persistedKeys = useDndPersistedKeys(
|
|
988
|
+
selectionManager,
|
|
989
|
+
dragAndDropHooks,
|
|
990
|
+
dropState,
|
|
991
|
+
subSlot(slot, 'persisted'),
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
return createElement(
|
|
995
|
+
Provider,
|
|
996
|
+
{
|
|
997
|
+
values: [
|
|
998
|
+
[TableStateContext, filteredState],
|
|
999
|
+
[TableColumnResizeStateContext, layoutState],
|
|
1000
|
+
[DragAndDropContext, { dragAndDropHooks, dragState, dropState }],
|
|
1001
|
+
[DropIndicatorContext, { render: TableDropIndicatorWrapper }],
|
|
1002
|
+
[SelectableCollectionContext, null],
|
|
1003
|
+
[FieldInputContext, null],
|
|
1004
|
+
] as any,
|
|
1005
|
+
} as any,
|
|
1006
|
+
createElement(FocusScope, {
|
|
1007
|
+
children: createElement(TableElementType, {
|
|
1008
|
+
...mergeProps(
|
|
1009
|
+
DOMProps,
|
|
1010
|
+
renderProps,
|
|
1011
|
+
gridProps,
|
|
1012
|
+
focusProps,
|
|
1013
|
+
droppableCollection?.collectionProps,
|
|
1014
|
+
),
|
|
1015
|
+
style,
|
|
1016
|
+
ref,
|
|
1017
|
+
slot: props.slot || undefined,
|
|
1018
|
+
onScroll: (props as any).onScroll,
|
|
1019
|
+
'data-allows-dragging': isListDraggable || undefined,
|
|
1020
|
+
'data-drop-target': isRootDropTarget || undefined,
|
|
1021
|
+
'data-focused': isFocused || undefined,
|
|
1022
|
+
'data-focus-visible': isFocusVisible || undefined,
|
|
1023
|
+
children: createElement(SharedElementTransition, {
|
|
1024
|
+
children: createElement(CollectionRoot, {
|
|
1025
|
+
collection: filteredState.collection,
|
|
1026
|
+
scrollRef: tableContainerContext?.scrollRef ?? ref,
|
|
1027
|
+
persistedKeys,
|
|
1028
|
+
}),
|
|
1029
|
+
}),
|
|
1030
|
+
}),
|
|
1031
|
+
}),
|
|
1032
|
+
dragPreview,
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
export interface TableOptionsContextValue {
|
|
1037
|
+
/** The type of selection that is allowed in the table. */
|
|
1038
|
+
selectionMode: SelectionMode;
|
|
1039
|
+
/** The selection behavior for the table. If selectionMode is `"none"`, this will be `null`. */
|
|
1040
|
+
selectionBehavior: SelectionBehavior | null;
|
|
1041
|
+
/** Whether the table allows empty selection. */
|
|
1042
|
+
disallowEmptySelection: boolean;
|
|
1043
|
+
/** Whether the table allows rows to be dragged. */
|
|
1044
|
+
allowsDragging: boolean;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const TableOptionsContext = createContext<TableOptionsContextValue | null>(null);
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Returns options from the parent `<Table>` component.
|
|
1051
|
+
*/
|
|
1052
|
+
export function useTableOptions(): TableOptionsContextValue {
|
|
1053
|
+
return useContext(TableOptionsContext)!;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export interface TableHeaderRenderProps {
|
|
1057
|
+
/**
|
|
1058
|
+
* Whether the table header is currently hovered with a mouse.
|
|
1059
|
+
*
|
|
1060
|
+
* @selector [data-hovered]
|
|
1061
|
+
*/
|
|
1062
|
+
isHovered: boolean;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
export interface TableHeaderProps<T>
|
|
1066
|
+
extends
|
|
1067
|
+
StyleRenderProps<TableHeaderRenderProps, 'thead' | 'div'>,
|
|
1068
|
+
HoverEvents,
|
|
1069
|
+
GlobalDOMAttributes {
|
|
1070
|
+
/**
|
|
1071
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
1072
|
+
* element. A function may be provided to compute the class based on component state.
|
|
1073
|
+
*
|
|
1074
|
+
* @default 'react-aria-TableHeader'
|
|
1075
|
+
*/
|
|
1076
|
+
className?: ClassNameOrFunction<TableHeaderRenderProps>;
|
|
1077
|
+
/** A list of table columns. */
|
|
1078
|
+
columns?: Iterable<T>;
|
|
1079
|
+
/**
|
|
1080
|
+
* A list of `Column(s)` or a function. If the latter, a list of columns must be provided using
|
|
1081
|
+
* the `columns` prop.
|
|
1082
|
+
*/
|
|
1083
|
+
children?: ReactNode | ((item: T) => any);
|
|
1084
|
+
/** Values that should invalidate the column cache when using dynamic collections. */
|
|
1085
|
+
dependencies?: ReadonlyArray<any>;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
class TableHeaderNode<T> extends CollectionNode<T> {
|
|
1089
|
+
static readonly type = 'tableheader';
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function THeadElementType(props: any): any {
|
|
1093
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
1094
|
+
if (isVirtualized) {
|
|
1095
|
+
return createElement(dom.div, props);
|
|
1096
|
+
}
|
|
1097
|
+
return createElement(dom.thead, props);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* A header within a `<Table>`, containing the table columns.
|
|
1102
|
+
*/
|
|
1103
|
+
export const TableHeader: <T extends object = object>(
|
|
1104
|
+
props: TableHeaderProps<T> & { ref?: any },
|
|
1105
|
+
) => any = /*#__PURE__*/ createBranchComponent(
|
|
1106
|
+
TableHeaderNode,
|
|
1107
|
+
function TableHeader<T>(props: TableHeaderProps<T>, ref: any): any {
|
|
1108
|
+
const slot = S('TableHeader');
|
|
1109
|
+
let collection = useContext(TableStateContext)!.collection as TableCollection<unknown>;
|
|
1110
|
+
let headerRows = useCachedChildren(
|
|
1111
|
+
{
|
|
1112
|
+
items: collection.headerRows,
|
|
1113
|
+
children: useCallback(
|
|
1114
|
+
(item: Node<unknown>) => {
|
|
1115
|
+
switch (item.type) {
|
|
1116
|
+
case 'headerrow':
|
|
1117
|
+
return createElement(TableHeaderRow, { item });
|
|
1118
|
+
default:
|
|
1119
|
+
throw new Error('Unsupported node type in TableHeader: ' + item.type);
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
1122
|
+
[],
|
|
1123
|
+
subSlot(slot, 'headerRowFn'),
|
|
1124
|
+
),
|
|
1125
|
+
},
|
|
1126
|
+
subSlot(slot, 'headerRows'),
|
|
1127
|
+
);
|
|
1128
|
+
|
|
1129
|
+
let { rowGroupProps } = useTableRowGroup(subSlot(slot, 'rowGroup'));
|
|
1130
|
+
let { hoverProps, isHovered } = useHover(
|
|
1131
|
+
{
|
|
1132
|
+
onHoverStart: props.onHoverStart,
|
|
1133
|
+
onHoverChange: props.onHoverChange,
|
|
1134
|
+
onHoverEnd: props.onHoverEnd,
|
|
1135
|
+
},
|
|
1136
|
+
subSlot(slot, 'hover'),
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
let renderProps = useRenderProps(
|
|
1140
|
+
{
|
|
1141
|
+
...props,
|
|
1142
|
+
children: undefined,
|
|
1143
|
+
defaultClassName: 'react-aria-TableHeader',
|
|
1144
|
+
values: {
|
|
1145
|
+
isHovered,
|
|
1146
|
+
},
|
|
1147
|
+
} as any,
|
|
1148
|
+
subSlot(slot, 'render'),
|
|
1149
|
+
);
|
|
1150
|
+
|
|
1151
|
+
return createElement(THeadElementType, {
|
|
1152
|
+
...mergeProps(filterDOMProps(props, { global: true }), rowGroupProps, hoverProps),
|
|
1153
|
+
...renderProps,
|
|
1154
|
+
ref,
|
|
1155
|
+
'data-hovered': isHovered || undefined,
|
|
1156
|
+
children: headerRows,
|
|
1157
|
+
});
|
|
1158
|
+
},
|
|
1159
|
+
(props) =>
|
|
1160
|
+
createElement(Collection, {
|
|
1161
|
+
dependencies: props.dependencies,
|
|
1162
|
+
items: props.columns,
|
|
1163
|
+
children: props.children,
|
|
1164
|
+
}),
|
|
1165
|
+
);
|
|
1166
|
+
|
|
1167
|
+
function TableHeaderRowElementType(props: any): any {
|
|
1168
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
1169
|
+
if (isVirtualized) {
|
|
1170
|
+
return createElement('div', props);
|
|
1171
|
+
}
|
|
1172
|
+
return createElement('tr', props);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function TableHeaderRow({ item }: { item: GridNode<any> }): any {
|
|
1176
|
+
const slot = S('TableHeaderRow');
|
|
1177
|
+
let ref = useRef<HTMLTableRowElement | null>(null, subSlot(slot, 'ref'));
|
|
1178
|
+
let state = useContext(TableStateContext)!;
|
|
1179
|
+
let { isVirtualized, CollectionBranch } = useContext(CollectionRendererContext);
|
|
1180
|
+
let { rowProps } = useTableHeaderRow(
|
|
1181
|
+
{ node: item, isVirtualized },
|
|
1182
|
+
state,
|
|
1183
|
+
ref,
|
|
1184
|
+
subSlot(slot, 'headerRow'),
|
|
1185
|
+
);
|
|
1186
|
+
let { checkboxProps } = useTableSelectAllCheckbox(state, subSlot(slot, 'selectAll'));
|
|
1187
|
+
|
|
1188
|
+
return createElement(TableHeaderRowElementType, {
|
|
1189
|
+
...rowProps,
|
|
1190
|
+
ref,
|
|
1191
|
+
children: createElement(Provider, {
|
|
1192
|
+
values: [
|
|
1193
|
+
[
|
|
1194
|
+
CheckboxContext,
|
|
1195
|
+
{
|
|
1196
|
+
slots: {
|
|
1197
|
+
selection: checkboxProps,
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
],
|
|
1201
|
+
[
|
|
1202
|
+
CheckboxFieldContext,
|
|
1203
|
+
{
|
|
1204
|
+
slots: {
|
|
1205
|
+
selection: checkboxProps,
|
|
1206
|
+
},
|
|
1207
|
+
},
|
|
1208
|
+
],
|
|
1209
|
+
] as any,
|
|
1210
|
+
children: createElement(CollectionBranch, {
|
|
1211
|
+
collection: state.collection,
|
|
1212
|
+
parent: item,
|
|
1213
|
+
}),
|
|
1214
|
+
}),
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
export interface ColumnRenderProps {
|
|
1219
|
+
/**
|
|
1220
|
+
* Whether the column is currently hovered with a mouse.
|
|
1221
|
+
*
|
|
1222
|
+
* @selector [data-hovered]
|
|
1223
|
+
*/
|
|
1224
|
+
isHovered: boolean;
|
|
1225
|
+
/**
|
|
1226
|
+
* Whether the column is currently in a pressed state.
|
|
1227
|
+
*
|
|
1228
|
+
* @selector [data-pressed]
|
|
1229
|
+
*/
|
|
1230
|
+
isPressed: boolean;
|
|
1231
|
+
/**
|
|
1232
|
+
* Whether the column is currently focused.
|
|
1233
|
+
*
|
|
1234
|
+
* @selector [data-focused]
|
|
1235
|
+
*/
|
|
1236
|
+
isFocused: boolean;
|
|
1237
|
+
/**
|
|
1238
|
+
* Whether the column is currently keyboard focused.
|
|
1239
|
+
*
|
|
1240
|
+
* @selector [data-focus-visible]
|
|
1241
|
+
*/
|
|
1242
|
+
isFocusVisible: boolean;
|
|
1243
|
+
/**
|
|
1244
|
+
* Whether the column allows sorting.
|
|
1245
|
+
*
|
|
1246
|
+
* @selector [data-allows-sorting]
|
|
1247
|
+
*/
|
|
1248
|
+
allowsSorting: boolean;
|
|
1249
|
+
/**
|
|
1250
|
+
* The current sort direction.
|
|
1251
|
+
*
|
|
1252
|
+
* @selector [data-sort-direction="ascending | descending"]
|
|
1253
|
+
*/
|
|
1254
|
+
sortDirection: SortDirection | undefined;
|
|
1255
|
+
/**
|
|
1256
|
+
* Whether the column is currently being resized.
|
|
1257
|
+
*
|
|
1258
|
+
* @selector [data-resizing]
|
|
1259
|
+
*/
|
|
1260
|
+
isResizing: boolean;
|
|
1261
|
+
/**
|
|
1262
|
+
* Triggers sorting for this column in the given direction.
|
|
1263
|
+
*/
|
|
1264
|
+
sort(direction: SortDirection): void;
|
|
1265
|
+
/**
|
|
1266
|
+
* Starts column resizing if the table is contained in a `<ResizableTableContainer>` element.
|
|
1267
|
+
*/
|
|
1268
|
+
startResize(): void;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
export interface ColumnProps
|
|
1272
|
+
extends RenderProps<ColumnRenderProps, 'th' | 'div'>, GlobalDOMAttributes {
|
|
1273
|
+
/**
|
|
1274
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
1275
|
+
* element. A function may be provided to compute the class based on component state.
|
|
1276
|
+
*
|
|
1277
|
+
* @default 'react-aria-Column'
|
|
1278
|
+
*/
|
|
1279
|
+
className?: ClassNameOrFunction<ColumnRenderProps>;
|
|
1280
|
+
/** The unique id of the column. */
|
|
1281
|
+
id?: Key;
|
|
1282
|
+
/** Whether the column allows sorting. */
|
|
1283
|
+
allowsSorting?: boolean;
|
|
1284
|
+
/**
|
|
1285
|
+
* Whether a column is a [row header](https://www.w3.org/TR/wai-aria-1.1/#rowheader) and should be
|
|
1286
|
+
* announced by assistive technology during row navigation.
|
|
1287
|
+
*/
|
|
1288
|
+
isRowHeader?: boolean;
|
|
1289
|
+
/** A string representation of the column's contents, used for accessibility announcements. */
|
|
1290
|
+
textValue?: string;
|
|
1291
|
+
/**
|
|
1292
|
+
* The width of the column. This prop only applies when the `<Table>` is wrapped in a
|
|
1293
|
+
* `<ResizableTableContainer>`.
|
|
1294
|
+
*/
|
|
1295
|
+
width?: ColumnSize | null;
|
|
1296
|
+
/**
|
|
1297
|
+
* The default width of the column. This prop only applies when the `<Table>` is wrapped in a
|
|
1298
|
+
* `<ResizableTableContainer>`.
|
|
1299
|
+
*/
|
|
1300
|
+
defaultWidth?: ColumnSize | null;
|
|
1301
|
+
/**
|
|
1302
|
+
* The minimum width of the column. This prop only applies when the `<Table>` is wrapped in a
|
|
1303
|
+
* `<ResizableTableContainer>`.
|
|
1304
|
+
*/
|
|
1305
|
+
minWidth?: ColumnStaticSize | null;
|
|
1306
|
+
/**
|
|
1307
|
+
* The maximum width of the column. This prop only applies when the `<Table>` is wrapped in a
|
|
1308
|
+
* `<ResizableTableContainer>`.
|
|
1309
|
+
*/
|
|
1310
|
+
maxWidth?: ColumnStaticSize | null;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
class TableColumnNode extends CollectionNode<unknown> {
|
|
1314
|
+
static readonly type = 'column';
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function ColumnElementType(props: any): any {
|
|
1318
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
1319
|
+
if (isVirtualized) {
|
|
1320
|
+
return createElement(dom.div, props);
|
|
1321
|
+
}
|
|
1322
|
+
return createElement(dom.th, props);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* A column within a `<Table>`.
|
|
1327
|
+
*/
|
|
1328
|
+
export const Column: (props: ColumnProps & { ref?: any }) => any =
|
|
1329
|
+
/*#__PURE__*/ createLeafComponent(
|
|
1330
|
+
TableColumnNode,
|
|
1331
|
+
function Column(props: ColumnProps, forwardedRef: any, column?: Node<unknown>): any {
|
|
1332
|
+
const slot = S('Column');
|
|
1333
|
+
let ref = useObjectRef<HTMLTableCellElement | HTMLDivElement>(
|
|
1334
|
+
forwardedRef,
|
|
1335
|
+
subSlot(slot, 'objectRef'),
|
|
1336
|
+
);
|
|
1337
|
+
let state = useContext(TableStateContext)!;
|
|
1338
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
1339
|
+
let { columnHeaderProps, isPressed } = useTableColumnHeader(
|
|
1340
|
+
{ node: column as GridNode<unknown>, isVirtualized },
|
|
1341
|
+
state,
|
|
1342
|
+
ref,
|
|
1343
|
+
subSlot(slot, 'columnHeader'),
|
|
1344
|
+
);
|
|
1345
|
+
let { isFocused, isFocusVisible, focusProps } = useFocusRing(
|
|
1346
|
+
undefined,
|
|
1347
|
+
subSlot(slot, 'focusRing'),
|
|
1348
|
+
);
|
|
1349
|
+
|
|
1350
|
+
let layoutState = useContext(TableColumnResizeStateContext);
|
|
1351
|
+
let isResizing = false;
|
|
1352
|
+
if (layoutState) {
|
|
1353
|
+
isResizing = layoutState.resizingColumn === column!.key;
|
|
1354
|
+
} else if (process.env.NODE_ENV !== 'production') {
|
|
1355
|
+
for (let prop in ['width', 'defaultWidth', 'minWidth', 'maxWidth']) {
|
|
1356
|
+
if (prop in column!.props) {
|
|
1357
|
+
console.warn(
|
|
1358
|
+
`The ${prop} prop on a <Column> only applies when a <Table> is wrapped in a <ResizableTableContainer>. If you aren't using column resizing, you can set the width of a column with CSS.`,
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
let { hoverProps, isHovered } = useHover(
|
|
1365
|
+
{ isDisabled: !props.allowsSorting },
|
|
1366
|
+
subSlot(slot, 'hover'),
|
|
1367
|
+
);
|
|
1368
|
+
let renderProps = useRenderProps(
|
|
1369
|
+
{
|
|
1370
|
+
...props,
|
|
1371
|
+
id: undefined,
|
|
1372
|
+
children: column!.rendered,
|
|
1373
|
+
defaultClassName: 'react-aria-Column',
|
|
1374
|
+
values: {
|
|
1375
|
+
isHovered,
|
|
1376
|
+
isPressed,
|
|
1377
|
+
isFocused,
|
|
1378
|
+
isFocusVisible,
|
|
1379
|
+
allowsSorting: column!.props.allowsSorting,
|
|
1380
|
+
sortDirection:
|
|
1381
|
+
state.sortDescriptor?.column === column!.key
|
|
1382
|
+
? state.sortDescriptor.direction
|
|
1383
|
+
: undefined,
|
|
1384
|
+
isResizing,
|
|
1385
|
+
startResize: () => {
|
|
1386
|
+
if (layoutState) {
|
|
1387
|
+
layoutState.startResize(column!.key);
|
|
1388
|
+
state.setKeyboardNavigationDisabled(true);
|
|
1389
|
+
} else {
|
|
1390
|
+
throw new Error(
|
|
1391
|
+
'Wrap your <Table> in a <ResizableTableContainer> to enable column resizing',
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
},
|
|
1395
|
+
sort: (direction: SortDirection) => {
|
|
1396
|
+
state.sort(column!.key, direction);
|
|
1397
|
+
},
|
|
1398
|
+
},
|
|
1399
|
+
} as any,
|
|
1400
|
+
subSlot(slot, 'render'),
|
|
1401
|
+
);
|
|
1402
|
+
|
|
1403
|
+
let style = renderProps.style;
|
|
1404
|
+
if (layoutState) {
|
|
1405
|
+
style = { ...style, width: layoutState.getColumnWidth(column!.key) };
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
let DOMProps = filterDOMProps(props as any, { global: true });
|
|
1409
|
+
delete DOMProps.id;
|
|
1410
|
+
|
|
1411
|
+
return createElement(ColumnElementType, {
|
|
1412
|
+
...mergeProps(DOMProps, columnHeaderProps, focusProps, hoverProps),
|
|
1413
|
+
...renderProps,
|
|
1414
|
+
style,
|
|
1415
|
+
ref,
|
|
1416
|
+
'data-hovered': isHovered || undefined,
|
|
1417
|
+
'data-pressed': isPressed || undefined,
|
|
1418
|
+
'data-focused': isFocused || undefined,
|
|
1419
|
+
'data-focus-visible': isFocusVisible || undefined,
|
|
1420
|
+
'data-resizing': isResizing || undefined,
|
|
1421
|
+
'data-allows-sorting': column!.props.allowsSorting || undefined,
|
|
1422
|
+
'data-sort-direction':
|
|
1423
|
+
state.sortDescriptor?.column === column!.key ? state.sortDescriptor.direction : undefined,
|
|
1424
|
+
children: createElement(Provider, {
|
|
1425
|
+
values: [
|
|
1426
|
+
[ColumnResizerContext, { column: column as GridNode<unknown>, triggerRef: ref }],
|
|
1427
|
+
[CollectionRendererContext, DefaultCollectionRenderer],
|
|
1428
|
+
] as any,
|
|
1429
|
+
children: renderProps.children,
|
|
1430
|
+
}),
|
|
1431
|
+
});
|
|
1432
|
+
},
|
|
1433
|
+
);
|
|
1434
|
+
|
|
1435
|
+
export interface ColumnResizerRenderProps {
|
|
1436
|
+
/**
|
|
1437
|
+
* Whether the resizer is currently hovered with a mouse.
|
|
1438
|
+
*
|
|
1439
|
+
* @selector [data-hovered]
|
|
1440
|
+
*/
|
|
1441
|
+
isHovered: boolean;
|
|
1442
|
+
/**
|
|
1443
|
+
* Whether the resizer is currently focused.
|
|
1444
|
+
*
|
|
1445
|
+
* @selector [data-focused]
|
|
1446
|
+
*/
|
|
1447
|
+
isFocused: boolean;
|
|
1448
|
+
/**
|
|
1449
|
+
* Whether the resizer is currently keyboard focused.
|
|
1450
|
+
*
|
|
1451
|
+
* @selector [data-focus-visible]
|
|
1452
|
+
*/
|
|
1453
|
+
isFocusVisible: boolean;
|
|
1454
|
+
/**
|
|
1455
|
+
* Whether the resizer is currently being resized.
|
|
1456
|
+
*
|
|
1457
|
+
* @selector [data-resizing]
|
|
1458
|
+
*/
|
|
1459
|
+
isResizing: boolean;
|
|
1460
|
+
/**
|
|
1461
|
+
* The direction that the column is currently resizable.
|
|
1462
|
+
*
|
|
1463
|
+
* @selector [data-resizable-direction="right | left | both"]
|
|
1464
|
+
*/
|
|
1465
|
+
resizableDirection: 'right' | 'left' | 'both';
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
export interface ColumnResizerProps
|
|
1469
|
+
extends HoverEvents, RenderProps<ColumnResizerRenderProps>, GlobalDOMAttributes {
|
|
1470
|
+
/**
|
|
1471
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
1472
|
+
* element. A function may be provided to compute the class based on component state.
|
|
1473
|
+
*
|
|
1474
|
+
* @default 'react-aria-ColumnResizer'
|
|
1475
|
+
*/
|
|
1476
|
+
className?: ClassNameOrFunction<ColumnResizerRenderProps>;
|
|
1477
|
+
/** A custom accessibility label for the resizer. */
|
|
1478
|
+
'aria-label'?: string;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
interface ColumnResizerContextValue {
|
|
1482
|
+
column: GridNode<unknown>;
|
|
1483
|
+
triggerRef: RefObject<HTMLDivElement | null>;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
const ColumnResizerContext = createContext<ColumnResizerContextValue | null>(null);
|
|
1487
|
+
|
|
1488
|
+
export function ColumnResizer(props: ColumnResizerProps & { ref?: any }): any {
|
|
1489
|
+
const slot = S('ColumnResizer');
|
|
1490
|
+
let layoutState = useContext(TableColumnResizeStateContext);
|
|
1491
|
+
if (!layoutState) {
|
|
1492
|
+
throw new Error('Wrap your <Table> in a <ResizableTableContainer> to enable column resizing');
|
|
1493
|
+
}
|
|
1494
|
+
let stringFormatter = useLocalizedStringFormatter(
|
|
1495
|
+
intlMessages,
|
|
1496
|
+
'react-aria-components',
|
|
1497
|
+
subSlot(slot, 'strings'),
|
|
1498
|
+
);
|
|
1499
|
+
|
|
1500
|
+
let { onResizeStart, onResize, onResizeEnd } = useContext(ResizableTableContainerContext)!;
|
|
1501
|
+
let { column, triggerRef } = useContext(ColumnResizerContext)!;
|
|
1502
|
+
let inputRef = useRef<HTMLInputElement | null>(null, subSlot(slot, 'input'));
|
|
1503
|
+
let { resizerProps, inputProps, isResizing } = useTableColumnResize(
|
|
1504
|
+
{
|
|
1505
|
+
column,
|
|
1506
|
+
'aria-label': props['aria-label'] || stringFormatter.format('tableResizer'),
|
|
1507
|
+
onResizeStart,
|
|
1508
|
+
onResize,
|
|
1509
|
+
onResizeEnd,
|
|
1510
|
+
triggerRef,
|
|
1511
|
+
},
|
|
1512
|
+
layoutState,
|
|
1513
|
+
inputRef,
|
|
1514
|
+
subSlot(slot, 'resize'),
|
|
1515
|
+
);
|
|
1516
|
+
let { focusProps, isFocused, isFocusVisible } = useFocusRing(
|
|
1517
|
+
undefined,
|
|
1518
|
+
subSlot(slot, 'focusRing'),
|
|
1519
|
+
);
|
|
1520
|
+
let { hoverProps, isHovered } = useHover(props, subSlot(slot, 'hover'));
|
|
1521
|
+
|
|
1522
|
+
let isEResizable =
|
|
1523
|
+
layoutState.getColumnMinWidth(column.key) >= layoutState.getColumnWidth(column.key);
|
|
1524
|
+
let isWResizable =
|
|
1525
|
+
layoutState.getColumnMaxWidth(column.key) <= layoutState.getColumnWidth(column.key);
|
|
1526
|
+
let { direction } = useLocale(subSlot(slot, 'locale'));
|
|
1527
|
+
let resizableDirection: ColumnResizerRenderProps['resizableDirection'] = 'both';
|
|
1528
|
+
if (isEResizable) {
|
|
1529
|
+
resizableDirection = direction === 'rtl' ? 'right' : 'left';
|
|
1530
|
+
} else if (isWResizable) {
|
|
1531
|
+
resizableDirection = direction === 'rtl' ? 'left' : 'right';
|
|
1532
|
+
} else {
|
|
1533
|
+
resizableDirection = 'both';
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
let objectRef = useObjectRef<HTMLDivElement>((props as any).ref, subSlot(slot, 'objectRef'));
|
|
1537
|
+
let [cursor, setCursor] = useState('', subSlot(slot, 'cursor'));
|
|
1538
|
+
useEffect(
|
|
1539
|
+
() => {
|
|
1540
|
+
if (!objectRef.current) {
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
let style = window.getComputedStyle(objectRef.current);
|
|
1544
|
+
setCursor(style.cursor);
|
|
1545
|
+
},
|
|
1546
|
+
[objectRef, resizableDirection],
|
|
1547
|
+
subSlot(slot, 'cursorEffect'),
|
|
1548
|
+
);
|
|
1549
|
+
|
|
1550
|
+
let renderProps = useRenderProps(
|
|
1551
|
+
{
|
|
1552
|
+
...props,
|
|
1553
|
+
defaultClassName: 'react-aria-ColumnResizer',
|
|
1554
|
+
values: {
|
|
1555
|
+
isFocused,
|
|
1556
|
+
isFocusVisible,
|
|
1557
|
+
isResizing,
|
|
1558
|
+
isHovered,
|
|
1559
|
+
resizableDirection,
|
|
1560
|
+
},
|
|
1561
|
+
} as any,
|
|
1562
|
+
subSlot(slot, 'render'),
|
|
1563
|
+
);
|
|
1564
|
+
|
|
1565
|
+
let [isMouseDown, setMouseDown] = useState(false, subSlot(slot, 'mouseDown'));
|
|
1566
|
+
let onPointerDown = (e: PointerEvent) => {
|
|
1567
|
+
if (e.pointerType === 'mouse') {
|
|
1568
|
+
setMouseDown(true);
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1572
|
+
if (!isResizing && isMouseDown) {
|
|
1573
|
+
setMouseDown(false);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
let DOMProps = filterDOMProps(props, { global: true });
|
|
1577
|
+
|
|
1578
|
+
// Positional (non-array) children: the resizer content, the visually hidden input, and
|
|
1579
|
+
// the transient full-viewport cursor overlay (a portal while mouse-resizing).
|
|
1580
|
+
return createElement(
|
|
1581
|
+
dom.div,
|
|
1582
|
+
{
|
|
1583
|
+
ref: objectRef,
|
|
1584
|
+
role: 'presentation',
|
|
1585
|
+
...mergeProps(DOMProps, renderProps, resizerProps, { onPointerDown }, hoverProps),
|
|
1586
|
+
'data-hovered': isHovered || undefined,
|
|
1587
|
+
'data-focused': isFocused || undefined,
|
|
1588
|
+
'data-focus-visible': isFocusVisible || undefined,
|
|
1589
|
+
'data-resizing': isResizing || undefined,
|
|
1590
|
+
'data-resizable-direction': resizableDirection,
|
|
1591
|
+
},
|
|
1592
|
+
renderProps.children,
|
|
1593
|
+
createElement('input', { ref: inputRef, ...mergeProps(inputProps, focusProps) }),
|
|
1594
|
+
isResizing && isMouseDown
|
|
1595
|
+
? createPortal(
|
|
1596
|
+
createElement('div', {
|
|
1597
|
+
style: { position: 'fixed', top: 0, left: 0, bottom: 0, right: 0, cursor },
|
|
1598
|
+
}),
|
|
1599
|
+
document.body,
|
|
1600
|
+
)
|
|
1601
|
+
: null,
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
export interface TableBodyRenderProps {
|
|
1606
|
+
/**
|
|
1607
|
+
* Whether the table body has no rows and should display its empty state.
|
|
1608
|
+
*
|
|
1609
|
+
* @selector [data-empty]
|
|
1610
|
+
*/
|
|
1611
|
+
isEmpty: boolean;
|
|
1612
|
+
/**
|
|
1613
|
+
* Whether the Table is currently the active drop target.
|
|
1614
|
+
*
|
|
1615
|
+
* @selector [data-drop-target]
|
|
1616
|
+
*/
|
|
1617
|
+
isDropTarget: boolean;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
export interface TableBodyProps<T>
|
|
1621
|
+
extends
|
|
1622
|
+
Omit<ItemCollectionProps<T>, 'disabledKeys'>,
|
|
1623
|
+
StyleRenderProps<TableBodyRenderProps, 'tbody' | 'div'>,
|
|
1624
|
+
GlobalDOMAttributes {
|
|
1625
|
+
/**
|
|
1626
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
1627
|
+
* element. A function may be provided to compute the class based on component state.
|
|
1628
|
+
*
|
|
1629
|
+
* @default 'react-aria-TableBody'
|
|
1630
|
+
*/
|
|
1631
|
+
className?: ClassNameOrFunction<TableBodyRenderProps>;
|
|
1632
|
+
/** Provides content to display when there are no rows in the table. */
|
|
1633
|
+
renderEmptyState?: (props: TableBodyRenderProps) => ReactNode;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
class TableBodyNode<T> extends FilterableNode<T> {
|
|
1637
|
+
static readonly type = 'tablebody';
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function TableBodyElementType(props: any): any {
|
|
1641
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
1642
|
+
if (isVirtualized) {
|
|
1643
|
+
return createElement(dom.div, props);
|
|
1644
|
+
}
|
|
1645
|
+
return createElement(dom.tbody, props);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
/**
|
|
1649
|
+
* The body of a `<Table>`, containing the table rows.
|
|
1650
|
+
*/
|
|
1651
|
+
export const TableBody: <T extends object = object>(
|
|
1652
|
+
props: TableBodyProps<T> & { ref?: any },
|
|
1653
|
+
) => any = /*#__PURE__*/ createBranchComponent(TableBodyNode, function TableBody<
|
|
1654
|
+
T,
|
|
1655
|
+
>(props: TableBodyProps<T>, ref: any, node?: Node<T>): any {
|
|
1656
|
+
const slot = S('TableBody');
|
|
1657
|
+
let state = useContext(TableStateContext)!;
|
|
1658
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
1659
|
+
let collection = state.collection;
|
|
1660
|
+
let { CollectionBranch } = useContext(CollectionRendererContext);
|
|
1661
|
+
let { dragAndDropHooks, dropState } = useContext(DragAndDropContext);
|
|
1662
|
+
let isDroppable = !!dragAndDropHooks?.useDroppableCollectionState && !dropState?.isDisabled;
|
|
1663
|
+
let isRootDropTarget =
|
|
1664
|
+
isDroppable && !!dropState && (dropState.isDropTarget({ type: 'root' }) ?? false);
|
|
1665
|
+
|
|
1666
|
+
let isEmpty = collection.size === 0;
|
|
1667
|
+
let renderValues = {
|
|
1668
|
+
isDropTarget: isRootDropTarget,
|
|
1669
|
+
isEmpty,
|
|
1670
|
+
};
|
|
1671
|
+
let renderProps = useRenderProps(
|
|
1672
|
+
{
|
|
1673
|
+
...props,
|
|
1674
|
+
id: undefined,
|
|
1675
|
+
children: undefined,
|
|
1676
|
+
defaultClassName: 'react-aria-TableBody',
|
|
1677
|
+
values: renderValues,
|
|
1678
|
+
} as any,
|
|
1679
|
+
subSlot(slot, 'render'),
|
|
1680
|
+
);
|
|
1681
|
+
|
|
1682
|
+
let emptyState: any = null;
|
|
1683
|
+
let numColumns = (collection as ITableCollection<any>).columnCount;
|
|
1684
|
+
|
|
1685
|
+
if (isEmpty && props.renderEmptyState && state) {
|
|
1686
|
+
let rowProps: Record<string, any> = {};
|
|
1687
|
+
let rowHeaderProps: Record<string, any> = {};
|
|
1688
|
+
let style: Record<string, any> = {};
|
|
1689
|
+
if (isVirtualized) {
|
|
1690
|
+
rowHeaderProps['aria-colspan'] = numColumns;
|
|
1691
|
+
style = { display: 'contents' };
|
|
1692
|
+
} else {
|
|
1693
|
+
rowHeaderProps['colSpan'] = numColumns;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
emptyState = createElement(TableRowElementType, {
|
|
1697
|
+
role: 'row',
|
|
1698
|
+
...rowProps,
|
|
1699
|
+
style,
|
|
1700
|
+
children: createElement(TableCellElementType, {
|
|
1701
|
+
role: 'rowheader',
|
|
1702
|
+
...rowHeaderProps,
|
|
1703
|
+
style,
|
|
1704
|
+
children: props.renderEmptyState(renderValues),
|
|
1705
|
+
}),
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
let { rowGroupProps } = useTableRowGroup(subSlot(slot, 'rowGroup'));
|
|
1710
|
+
|
|
1711
|
+
let DOMProps = filterDOMProps(props, { global: true });
|
|
1712
|
+
|
|
1713
|
+
// octane adaptation: hook hoisted out of the createElement argument list.
|
|
1714
|
+
let renderDropIndicator = useRenderDropIndicator(
|
|
1715
|
+
dragAndDropHooks,
|
|
1716
|
+
dropState,
|
|
1717
|
+
subSlot(slot, 'dropIndicator'),
|
|
1718
|
+
);
|
|
1719
|
+
|
|
1720
|
+
// TODO: TableBody doesn't support being the scrollable body of the table yet, to revisit if needed. Would need to
|
|
1721
|
+
// call useLoadMore here and walk up the DOM to the nearest scrollable element to set scrollRef
|
|
1722
|
+
return createElement(TableBodyElementType, {
|
|
1723
|
+
...mergeProps(DOMProps, renderProps, rowGroupProps),
|
|
1724
|
+
ref,
|
|
1725
|
+
'data-empty': isEmpty || undefined,
|
|
1726
|
+
// Positional (non-array) children, exactly like upstream's JSX.
|
|
1727
|
+
children: createElement(
|
|
1728
|
+
Fragment,
|
|
1729
|
+
null,
|
|
1730
|
+
isDroppable ? createElement(RootDropIndicator, {}) : null,
|
|
1731
|
+
createElement(CollectionBranch, {
|
|
1732
|
+
collection,
|
|
1733
|
+
parent: node!,
|
|
1734
|
+
renderDropIndicator,
|
|
1735
|
+
}),
|
|
1736
|
+
emptyState,
|
|
1737
|
+
),
|
|
1738
|
+
});
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1741
|
+
class TableFooterNode<T> extends FilterableNode<T> {
|
|
1742
|
+
static readonly type = 'tablefooter';
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
export interface TableFooterProps<T>
|
|
1746
|
+
extends Omit<ItemCollectionProps<T>, 'disabledKeys'>, StyleProps, GlobalDOMAttributes {
|
|
1747
|
+
/**
|
|
1748
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
1749
|
+
* element.
|
|
1750
|
+
*
|
|
1751
|
+
* @default 'react-aria-TableFooter'
|
|
1752
|
+
*/
|
|
1753
|
+
className?: string;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
function TableFooterElementType(props: any): any {
|
|
1757
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
1758
|
+
if (isVirtualized) {
|
|
1759
|
+
return createElement(dom.div, props);
|
|
1760
|
+
}
|
|
1761
|
+
return createElement(dom.tfoot, props);
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
/**
|
|
1765
|
+
* The footer of a `<Table>`, containing table rows.
|
|
1766
|
+
*/
|
|
1767
|
+
export const TableFooter: <T extends object = object>(
|
|
1768
|
+
props: TableFooterProps<T> & { ref?: any },
|
|
1769
|
+
) => any = /*#__PURE__*/ createBranchComponent(TableFooterNode, function TableFooter<
|
|
1770
|
+
T,
|
|
1771
|
+
>(props: TableFooterProps<T>, ref: any, node?: Node<T>): any {
|
|
1772
|
+
const slot = S('TableFooter');
|
|
1773
|
+
let state = useContext(TableStateContext)!;
|
|
1774
|
+
let collection = state.collection as TableCollection<T>;
|
|
1775
|
+
let { CollectionBranch } = useContext(CollectionRendererContext);
|
|
1776
|
+
let { dragAndDropHooks, dropState } = useContext(DragAndDropContext);
|
|
1777
|
+
|
|
1778
|
+
let { rowGroupProps } = useTableRowGroup(subSlot(slot, 'rowGroup'));
|
|
1779
|
+
let DOMProps = filterDOMProps(props, { global: true });
|
|
1780
|
+
let renderProps = useRenderProps(
|
|
1781
|
+
{
|
|
1782
|
+
style: props.style,
|
|
1783
|
+
className: props.className,
|
|
1784
|
+
defaultClassName: 'react-aria-TableFooter',
|
|
1785
|
+
values: {},
|
|
1786
|
+
} as any,
|
|
1787
|
+
subSlot(slot, 'render'),
|
|
1788
|
+
);
|
|
1789
|
+
|
|
1790
|
+
// octane adaptation: hook hoisted out of the createElement argument list.
|
|
1791
|
+
let renderDropIndicator = useRenderDropIndicator(
|
|
1792
|
+
dragAndDropHooks,
|
|
1793
|
+
dropState,
|
|
1794
|
+
subSlot(slot, 'dropIndicator'),
|
|
1795
|
+
);
|
|
1796
|
+
|
|
1797
|
+
return createElement(TableFooterElementType, {
|
|
1798
|
+
...mergeProps(DOMProps, renderProps, rowGroupProps),
|
|
1799
|
+
ref,
|
|
1800
|
+
children: createElement(CollectionBranch, {
|
|
1801
|
+
collection,
|
|
1802
|
+
parent: node!,
|
|
1803
|
+
renderDropIndicator,
|
|
1804
|
+
}),
|
|
1805
|
+
});
|
|
1806
|
+
});
|
|
1807
|
+
|
|
1808
|
+
export interface RowRenderProps extends ItemRenderProps {
|
|
1809
|
+
/**
|
|
1810
|
+
* Whether the row's children have keyboard focus.
|
|
1811
|
+
*
|
|
1812
|
+
* @selector [data-focus-visible-within]
|
|
1813
|
+
*/
|
|
1814
|
+
isFocusVisibleWithin: boolean;
|
|
1815
|
+
/** The unique id of the row. */
|
|
1816
|
+
id?: Key;
|
|
1817
|
+
/**
|
|
1818
|
+
* Whether the row is expanded.
|
|
1819
|
+
*
|
|
1820
|
+
* @selector [data-expanded]
|
|
1821
|
+
*/
|
|
1822
|
+
isExpanded: boolean;
|
|
1823
|
+
/**
|
|
1824
|
+
* Whether the row has child rows.
|
|
1825
|
+
*
|
|
1826
|
+
* @selector [data-has-child-items]
|
|
1827
|
+
*/
|
|
1828
|
+
hasChildItems: boolean;
|
|
1829
|
+
/**
|
|
1830
|
+
* What level the row has within the table.
|
|
1831
|
+
*
|
|
1832
|
+
* @selector [data-level]
|
|
1833
|
+
*/
|
|
1834
|
+
level: number;
|
|
1835
|
+
/**
|
|
1836
|
+
* State of the table.
|
|
1837
|
+
*/
|
|
1838
|
+
state: TableState<unknown>;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
export interface RowFocusContextValue {
|
|
1842
|
+
isFocusVisibleWithinRow: boolean;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
export const RowFocusContext = createContext<RowFocusContextValue>({
|
|
1846
|
+
isFocusVisibleWithinRow: false,
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
export interface RowProps<T>
|
|
1850
|
+
extends
|
|
1851
|
+
StyleRenderProps<RowRenderProps, 'tr' | 'div'>,
|
|
1852
|
+
LinkDOMProps,
|
|
1853
|
+
HoverEvents,
|
|
1854
|
+
PressEvents,
|
|
1855
|
+
Omit<GlobalDOMAttributes, 'onClick'> {
|
|
1856
|
+
/**
|
|
1857
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
1858
|
+
* element. A function may be provided to compute the class based on component state.
|
|
1859
|
+
*
|
|
1860
|
+
* @default 'react-aria-Row'
|
|
1861
|
+
*/
|
|
1862
|
+
className?: ClassNameOrFunction<RowRenderProps>;
|
|
1863
|
+
/** A list of columns used when dynamically rendering cells. */
|
|
1864
|
+
columns?: Iterable<T>;
|
|
1865
|
+
/** The cells within the row. Supports static items or a function for dynamic rendering. */
|
|
1866
|
+
children?: ReactNode | ((item: T) => any);
|
|
1867
|
+
/**
|
|
1868
|
+
* The object value that this row represents. When using dynamic collections, this is set
|
|
1869
|
+
* automatically.
|
|
1870
|
+
*/
|
|
1871
|
+
value?: T;
|
|
1872
|
+
/** Values that should invalidate the cell cache when using dynamic collections. */
|
|
1873
|
+
dependencies?: ReadonlyArray<any>;
|
|
1874
|
+
/** A string representation of the row's contents, used for features like typeahead. */
|
|
1875
|
+
textValue?: string;
|
|
1876
|
+
/** Whether the row is disabled. */
|
|
1877
|
+
isDisabled?: boolean;
|
|
1878
|
+
/** Whether `disabledKeys` applies to all interactions, or only selection. */
|
|
1879
|
+
disabledBehavior?: DisabledBehavior;
|
|
1880
|
+
/**
|
|
1881
|
+
* Handler that is called when a user performs an action on the row. The exact user event depends
|
|
1882
|
+
* on the collection's `selectionBehavior` prop and the interaction modality.
|
|
1883
|
+
*/
|
|
1884
|
+
onAction?: () => void;
|
|
1885
|
+
/** The unique id of the row. */
|
|
1886
|
+
id?: Key;
|
|
1887
|
+
/** Whether this row has children, even if not loaded yet. */
|
|
1888
|
+
hasChildItems?: boolean;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
class TableRowNode<T> extends CollectionNode<T> {
|
|
1892
|
+
static readonly type = 'item';
|
|
1893
|
+
|
|
1894
|
+
filter(
|
|
1895
|
+
collection: BaseCollection<T>,
|
|
1896
|
+
newCollection: BaseCollection<T>,
|
|
1897
|
+
filterFn: (textValue: string, node: Node<T>) => boolean,
|
|
1898
|
+
): TableRowNode<T> | null {
|
|
1899
|
+
let cells = collection.getChildren(this.key);
|
|
1900
|
+
for (let cell of cells) {
|
|
1901
|
+
if (filterFn(cell.textValue, cell)) {
|
|
1902
|
+
let clone = this.clone();
|
|
1903
|
+
newCollection.addDescendants(clone, collection);
|
|
1904
|
+
return clone;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
function TableRowElementType(props: any): any {
|
|
1913
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
1914
|
+
if (isVirtualized) {
|
|
1915
|
+
return createElement(dom.div, props);
|
|
1916
|
+
}
|
|
1917
|
+
return createElement(dom.tr, props);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
/**
|
|
1921
|
+
* A row within a `<Table>`.
|
|
1922
|
+
*/
|
|
1923
|
+
export const Row: <T extends object = object>(props: RowProps<T> & { ref?: any }) => any =
|
|
1924
|
+
/*#__PURE__*/ createBranchComponent(
|
|
1925
|
+
TableRowNode,
|
|
1926
|
+
function Row<T>(props: RowProps<T>, forwardedRef: any, item?: Node<T>): any {
|
|
1927
|
+
const slot = S('TableRow');
|
|
1928
|
+
let ref = useObjectRef<HTMLTableRowElement | HTMLDivElement>(
|
|
1929
|
+
forwardedRef,
|
|
1930
|
+
subSlot(slot, 'objectRef'),
|
|
1931
|
+
);
|
|
1932
|
+
let state = useContext(TableStateContext)!;
|
|
1933
|
+
let { dragAndDropHooks, dragState, dropState } = useContext(DragAndDropContext);
|
|
1934
|
+
let { isVirtualized, CollectionBranch } = useContext(CollectionRendererContext);
|
|
1935
|
+
let isDraggable =
|
|
1936
|
+
dragState && !(dragState.isDisabled || dragState.selectionManager.isDisabled(item!.key));
|
|
1937
|
+
let { rowProps, expandButtonProps, ...states } = useTableRow(
|
|
1938
|
+
{
|
|
1939
|
+
node: item as GridNode<T>,
|
|
1940
|
+
shouldSelectOnPressUp: !!dragState,
|
|
1941
|
+
isVirtualized,
|
|
1942
|
+
},
|
|
1943
|
+
state,
|
|
1944
|
+
ref,
|
|
1945
|
+
subSlot(slot, 'row'),
|
|
1946
|
+
);
|
|
1947
|
+
let { isFocused, isFocusVisible, focusProps } = useFocusRing(
|
|
1948
|
+
undefined,
|
|
1949
|
+
subSlot(slot, 'focusRing'),
|
|
1950
|
+
);
|
|
1951
|
+
let { isFocusVisible: isFocusVisibleWithin, focusProps: focusWithinProps } = useFocusRing(
|
|
1952
|
+
{
|
|
1953
|
+
within: true,
|
|
1954
|
+
},
|
|
1955
|
+
subSlot(slot, 'focusRingWithin'),
|
|
1956
|
+
);
|
|
1957
|
+
let { hoverProps, isHovered } = useHover(
|
|
1958
|
+
{
|
|
1959
|
+
// because of https://bugs.webkit.org/show_bug.cgi?id=214609, supporting hover styles when a item is ONLY isDraggable
|
|
1960
|
+
// results in hover styles sticking around after a reorder/drop operation...
|
|
1961
|
+
isDisabled: !states.allowsSelection && !states.hasAction && !isDraggable,
|
|
1962
|
+
onHoverStart: props.onHoverStart,
|
|
1963
|
+
onHoverChange: props.onHoverChange,
|
|
1964
|
+
onHoverEnd: props.onHoverEnd,
|
|
1965
|
+
},
|
|
1966
|
+
subSlot(slot, 'hover'),
|
|
1967
|
+
);
|
|
1968
|
+
|
|
1969
|
+
let { checkboxProps } = useTableSelectionCheckbox(
|
|
1970
|
+
{ key: item!.key },
|
|
1971
|
+
state,
|
|
1972
|
+
subSlot(slot, 'checkbox'),
|
|
1973
|
+
);
|
|
1974
|
+
|
|
1975
|
+
// PHASE-7: consumer-provided dnd item hooks (unreachable until useDragAndDrop is ported).
|
|
1976
|
+
let draggableItem: DraggableItemResult | undefined = undefined;
|
|
1977
|
+
if (dragState && dragAndDropHooks) {
|
|
1978
|
+
draggableItem = dragAndDropHooks.useDraggableItem!(
|
|
1979
|
+
{ key: item!.key, hasDragButton: true },
|
|
1980
|
+
dragState,
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
let dropIndicator: DropIndicatorAria | undefined = undefined;
|
|
1985
|
+
let dropIndicatorRef = useRef<HTMLDivElement | null>(null, subSlot(slot, 'dropIndicatorRef'));
|
|
1986
|
+
let { visuallyHiddenProps } = useVisuallyHidden(undefined, subSlot(slot, 'visuallyHidden'));
|
|
1987
|
+
if (dropState && dragAndDropHooks) {
|
|
1988
|
+
dropIndicator = dragAndDropHooks.useDropIndicator!(
|
|
1989
|
+
{
|
|
1990
|
+
target: { type: 'item', key: item!.key, dropPosition: 'on' },
|
|
1991
|
+
},
|
|
1992
|
+
dropState,
|
|
1993
|
+
dropIndicatorRef,
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
let dragButtonRef = useRef<HTMLButtonElement | null>(null, subSlot(slot, 'dragButton'));
|
|
1998
|
+
useEffect(
|
|
1999
|
+
() => {
|
|
2000
|
+
if (dragState && !dragButtonRef.current && process.env.NODE_ENV !== 'production') {
|
|
2001
|
+
console.warn(
|
|
2002
|
+
'Draggable items in a Table must contain a <Button slot="drag"> element so that keyboard and screen reader users can drag them.',
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
// eslint-disable-next-line
|
|
2006
|
+
},
|
|
2007
|
+
[],
|
|
2008
|
+
subSlot(slot, 'dragButtonWarn'),
|
|
2009
|
+
);
|
|
2010
|
+
|
|
2011
|
+
let isDragging = dragState && dragState.isDragging(item!.key);
|
|
2012
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2013
|
+
let { children: _, ...restProps } = props;
|
|
2014
|
+
let hasChildItems =
|
|
2015
|
+
props.hasChildItems ||
|
|
2016
|
+
state.collection.getItem((item as CollectionNode<T>).lastChildKey!)?.type !== 'cell';
|
|
2017
|
+
let isExpanded = hasChildItems && state.expandedKeys.has(item!.key);
|
|
2018
|
+
let renderProps = useRenderProps(
|
|
2019
|
+
{
|
|
2020
|
+
...restProps,
|
|
2021
|
+
id: undefined,
|
|
2022
|
+
defaultClassName: 'react-aria-Row',
|
|
2023
|
+
defaultStyle: {
|
|
2024
|
+
'--table-row-level': item!.level + 1,
|
|
2025
|
+
},
|
|
2026
|
+
values: {
|
|
2027
|
+
...states,
|
|
2028
|
+
state,
|
|
2029
|
+
isHovered,
|
|
2030
|
+
isFocused,
|
|
2031
|
+
isFocusVisible,
|
|
2032
|
+
selectionMode: state.selectionManager.selectionMode,
|
|
2033
|
+
selectionBehavior: state.selectionManager.selectionBehavior,
|
|
2034
|
+
isDragging,
|
|
2035
|
+
isDropTarget: dropIndicator?.isDropTarget,
|
|
2036
|
+
isFocusVisibleWithin,
|
|
2037
|
+
id: item!.key,
|
|
2038
|
+
hasChildItems,
|
|
2039
|
+
isExpanded,
|
|
2040
|
+
level: item!.level + 1,
|
|
2041
|
+
},
|
|
2042
|
+
} as any,
|
|
2043
|
+
subSlot(slot, 'render'),
|
|
2044
|
+
);
|
|
2045
|
+
|
|
2046
|
+
let DOMProps = filterDOMProps(props as any, { global: true });
|
|
2047
|
+
delete DOMProps.id;
|
|
2048
|
+
delete DOMProps.onClick;
|
|
2049
|
+
|
|
2050
|
+
return createElement(
|
|
2051
|
+
Fragment,
|
|
2052
|
+
null,
|
|
2053
|
+
dropIndicator && !dropIndicator.isHidden
|
|
2054
|
+
? createElement(TableRowElementType, {
|
|
2055
|
+
role: 'row',
|
|
2056
|
+
style: { height: 0 },
|
|
2057
|
+
children: createElement(TableCellElementType, {
|
|
2058
|
+
role: 'gridcell',
|
|
2059
|
+
colSpan: (state.collection as ITableCollection<any>).columnCount,
|
|
2060
|
+
style: { padding: 0 },
|
|
2061
|
+
children: createElement('div', {
|
|
2062
|
+
role: 'button',
|
|
2063
|
+
...visuallyHiddenProps,
|
|
2064
|
+
...dropIndicator.dropIndicatorProps,
|
|
2065
|
+
ref: dropIndicatorRef,
|
|
2066
|
+
}),
|
|
2067
|
+
}),
|
|
2068
|
+
})
|
|
2069
|
+
: null,
|
|
2070
|
+
createElement(TableRowElementType, {
|
|
2071
|
+
...mergeProps(
|
|
2072
|
+
DOMProps,
|
|
2073
|
+
renderProps,
|
|
2074
|
+
rowProps,
|
|
2075
|
+
focusProps,
|
|
2076
|
+
hoverProps,
|
|
2077
|
+
draggableItem?.dragProps,
|
|
2078
|
+
focusWithinProps,
|
|
2079
|
+
),
|
|
2080
|
+
ref,
|
|
2081
|
+
'data-disabled': states.isDisabled || undefined,
|
|
2082
|
+
'data-selected': states.isSelected || undefined,
|
|
2083
|
+
'data-hovered': isHovered || undefined,
|
|
2084
|
+
'data-focused': states.isFocused || undefined,
|
|
2085
|
+
'data-focus-visible': isFocusVisible || undefined,
|
|
2086
|
+
'data-pressed': states.isPressed || undefined,
|
|
2087
|
+
'data-dragging': isDragging || undefined,
|
|
2088
|
+
'data-drop-target': dropIndicator?.isDropTarget || undefined,
|
|
2089
|
+
'data-selection-mode':
|
|
2090
|
+
state.selectionManager.selectionMode === 'none'
|
|
2091
|
+
? undefined
|
|
2092
|
+
: state.selectionManager.selectionMode,
|
|
2093
|
+
'data-focus-visible-within': isFocusVisibleWithin || undefined,
|
|
2094
|
+
'data-expanded': isExpanded || undefined,
|
|
2095
|
+
'data-has-child-items': hasChildItems || undefined,
|
|
2096
|
+
'data-level': item!.level + 1,
|
|
2097
|
+
children: createElement(Provider, {
|
|
2098
|
+
values: [
|
|
2099
|
+
[
|
|
2100
|
+
CheckboxContext,
|
|
2101
|
+
{
|
|
2102
|
+
slots: {
|
|
2103
|
+
[DEFAULT_SLOT]: {},
|
|
2104
|
+
selection: checkboxProps,
|
|
2105
|
+
},
|
|
2106
|
+
},
|
|
2107
|
+
],
|
|
2108
|
+
[
|
|
2109
|
+
CheckboxFieldContext,
|
|
2110
|
+
{
|
|
2111
|
+
slots: {
|
|
2112
|
+
[DEFAULT_SLOT]: {},
|
|
2113
|
+
selection: checkboxProps,
|
|
2114
|
+
},
|
|
2115
|
+
},
|
|
2116
|
+
],
|
|
2117
|
+
[
|
|
2118
|
+
ButtonContext,
|
|
2119
|
+
{
|
|
2120
|
+
slots: {
|
|
2121
|
+
[DEFAULT_SLOT]: {},
|
|
2122
|
+
chevron: expandButtonProps,
|
|
2123
|
+
drag: {
|
|
2124
|
+
...draggableItem?.dragButtonProps,
|
|
2125
|
+
ref: dragButtonRef,
|
|
2126
|
+
style: {
|
|
2127
|
+
pointerEvents: 'none',
|
|
2128
|
+
},
|
|
2129
|
+
},
|
|
2130
|
+
},
|
|
2131
|
+
},
|
|
2132
|
+
],
|
|
2133
|
+
[SelectionIndicatorContext, { isSelected: states.isSelected }],
|
|
2134
|
+
[RowFocusContext, { isFocusVisibleWithinRow: isFocusVisibleWithin }],
|
|
2135
|
+
] as any,
|
|
2136
|
+
children: createElement(CollectionBranch, {
|
|
2137
|
+
collection: state.collection,
|
|
2138
|
+
parent: item!,
|
|
2139
|
+
}),
|
|
2140
|
+
}),
|
|
2141
|
+
}),
|
|
2142
|
+
);
|
|
2143
|
+
},
|
|
2144
|
+
(props) => {
|
|
2145
|
+
if (props.id == null && typeof props.children === 'function') {
|
|
2146
|
+
throw new Error(
|
|
2147
|
+
'No id detected for the Row element. The Row element requires a id to be provided to it when the cells are rendered dynamically.',
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
let dependencies = [props.value].concat(props.dependencies as any);
|
|
2152
|
+
return createElement(Collection, {
|
|
2153
|
+
dependencies,
|
|
2154
|
+
items: props.columns,
|
|
2155
|
+
idScope: props.id,
|
|
2156
|
+
children: props.children,
|
|
2157
|
+
});
|
|
2158
|
+
},
|
|
2159
|
+
);
|
|
2160
|
+
|
|
2161
|
+
export interface CellRenderProps {
|
|
2162
|
+
/**
|
|
2163
|
+
* Whether the cell is currently in a pressed state.
|
|
2164
|
+
*
|
|
2165
|
+
* @selector [data-pressed]
|
|
2166
|
+
*/
|
|
2167
|
+
isPressed: boolean;
|
|
2168
|
+
/**
|
|
2169
|
+
* Whether the cell is currently focused.
|
|
2170
|
+
*
|
|
2171
|
+
* @selector [data-focused]
|
|
2172
|
+
*/
|
|
2173
|
+
isFocused: boolean;
|
|
2174
|
+
/**
|
|
2175
|
+
* Whether the cell is currently keyboard focused.
|
|
2176
|
+
*
|
|
2177
|
+
* @selector [data-focus-visible]
|
|
2178
|
+
*/
|
|
2179
|
+
isFocusVisible: boolean;
|
|
2180
|
+
/**
|
|
2181
|
+
* Whether the cell is currently hovered with a mouse.
|
|
2182
|
+
*
|
|
2183
|
+
* @selector [data-hovered]
|
|
2184
|
+
*/
|
|
2185
|
+
isHovered: boolean;
|
|
2186
|
+
/**
|
|
2187
|
+
* Whether the parent row is currently selected.
|
|
2188
|
+
*
|
|
2189
|
+
* @selector [data-selected]
|
|
2190
|
+
*/
|
|
2191
|
+
isSelected: boolean;
|
|
2192
|
+
/**
|
|
2193
|
+
* Whether the parent row is non-interactive, i.e. both selection and actions are disabled and the
|
|
2194
|
+
* item may not be focused. Dependent on `disabledKeys` and `disabledBehavior`.
|
|
2195
|
+
*
|
|
2196
|
+
* @selector [data-disabled]
|
|
2197
|
+
*/
|
|
2198
|
+
isDisabled: boolean;
|
|
2199
|
+
/**
|
|
2200
|
+
* Whether keyboard focus is visible anywhere within the parent row.
|
|
2201
|
+
*
|
|
2202
|
+
* @selector [data-focus-visible-within-row]
|
|
2203
|
+
*/
|
|
2204
|
+
isFocusVisibleWithinRow: boolean;
|
|
2205
|
+
/**
|
|
2206
|
+
* The unique id of the cell.
|
|
2207
|
+
*/
|
|
2208
|
+
id?: Key;
|
|
2209
|
+
/**
|
|
2210
|
+
* The index of the column that this cell belongs to. Respects col spanning.
|
|
2211
|
+
*/
|
|
2212
|
+
columnIndex?: number | null;
|
|
2213
|
+
/**
|
|
2214
|
+
* Whether the column displays hierarchical data.
|
|
2215
|
+
*
|
|
2216
|
+
* @selector [data-tree-column]
|
|
2217
|
+
*/
|
|
2218
|
+
isTreeColumn: boolean;
|
|
2219
|
+
/**
|
|
2220
|
+
* Whether the parent row is expanded.
|
|
2221
|
+
*
|
|
2222
|
+
* @selector [data-expanded]
|
|
2223
|
+
*/
|
|
2224
|
+
isExpanded: boolean;
|
|
2225
|
+
/**
|
|
2226
|
+
* Whether the parent row has child rows.
|
|
2227
|
+
*
|
|
2228
|
+
* @selector [data-has-child-items]
|
|
2229
|
+
*/
|
|
2230
|
+
hasChildItems: boolean;
|
|
2231
|
+
/**
|
|
2232
|
+
* What level the parent row has within the table.
|
|
2233
|
+
*
|
|
2234
|
+
* @selector [data-level]
|
|
2235
|
+
*/
|
|
2236
|
+
level: number;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
export interface CellProps extends RenderProps<CellRenderProps, 'td' | 'div'>, GlobalDOMAttributes {
|
|
2240
|
+
/**
|
|
2241
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
2242
|
+
* element. A function may be provided to compute the class based on component state.
|
|
2243
|
+
*
|
|
2244
|
+
* @default 'react-aria-Cell'
|
|
2245
|
+
*/
|
|
2246
|
+
className?: ClassNameOrFunction<CellRenderProps>;
|
|
2247
|
+
/** The unique id of the cell. */
|
|
2248
|
+
id?: Key;
|
|
2249
|
+
/** A string representation of the cell's contents, used for features like typeahead. */
|
|
2250
|
+
textValue?: string;
|
|
2251
|
+
/** Indicates how many columns the data cell spans. */
|
|
2252
|
+
colSpan?: number;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
class TableCellNode extends CollectionNode<unknown> {
|
|
2256
|
+
static readonly type = 'cell';
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
function TableCellElementType(props: any): any {
|
|
2260
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
2261
|
+
if (isVirtualized) {
|
|
2262
|
+
return createElement(dom.div, props);
|
|
2263
|
+
}
|
|
2264
|
+
return createElement(dom.td, props);
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
/**
|
|
2268
|
+
* A cell within a table row.
|
|
2269
|
+
*/
|
|
2270
|
+
export const Cell: (props: CellProps & { ref?: any }) => any = /*#__PURE__*/ createLeafComponent(
|
|
2271
|
+
TableCellNode,
|
|
2272
|
+
function Cell(props: CellProps, forwardedRef: any, cellNode?: Node<unknown>): any {
|
|
2273
|
+
const slot = S('TableCell');
|
|
2274
|
+
let cell = cellNode as GridNode<unknown>;
|
|
2275
|
+
let ref = useObjectRef<HTMLTableCellElement | HTMLDivElement>(
|
|
2276
|
+
forwardedRef,
|
|
2277
|
+
subSlot(slot, 'objectRef'),
|
|
2278
|
+
);
|
|
2279
|
+
let state = useContext(TableStateContext)!;
|
|
2280
|
+
let { dragState } = useContext(DragAndDropContext);
|
|
2281
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
2282
|
+
|
|
2283
|
+
cell.column = state.collection.columns[cell.index];
|
|
2284
|
+
|
|
2285
|
+
let { gridCellProps, isPressed } = useTableCell(
|
|
2286
|
+
{
|
|
2287
|
+
node: cell,
|
|
2288
|
+
shouldSelectOnPressUp: !!dragState,
|
|
2289
|
+
isVirtualized,
|
|
2290
|
+
},
|
|
2291
|
+
state,
|
|
2292
|
+
ref,
|
|
2293
|
+
subSlot(slot, 'cell'),
|
|
2294
|
+
);
|
|
2295
|
+
let { isFocused, isFocusVisible, focusProps } = useFocusRing(
|
|
2296
|
+
undefined,
|
|
2297
|
+
subSlot(slot, 'focusRing'),
|
|
2298
|
+
);
|
|
2299
|
+
let { hoverProps, isHovered } = useHover({}, subSlot(slot, 'hover'));
|
|
2300
|
+
let { isFocusVisibleWithinRow } = useContext(RowFocusContext);
|
|
2301
|
+
let isSelected =
|
|
2302
|
+
cell.parentKey != null ? state.selectionManager.isSelected(cell.parentKey) : false;
|
|
2303
|
+
// colIndex is null, when there is so span, falling back to using the index
|
|
2304
|
+
let columnIndex = cell.colIndex || cell.index;
|
|
2305
|
+
|
|
2306
|
+
let row = state.collection.getItem(cell.parentKey!)! as GridNode<unknown>;
|
|
2307
|
+
let hasChildItems =
|
|
2308
|
+
row.props.hasChildItems ||
|
|
2309
|
+
state.collection.getItem((row as CollectionNode<unknown>).lastChildKey!)?.type !== 'cell';
|
|
2310
|
+
let isExpanded = hasChildItems && state.expandedKeys.has(cell.parentKey!);
|
|
2311
|
+
let isDisabled = state.selectionManager.isDisabled(cell.parentKey!);
|
|
2312
|
+
let renderProps = useRenderProps(
|
|
2313
|
+
{
|
|
2314
|
+
...props,
|
|
2315
|
+
id: undefined,
|
|
2316
|
+
defaultClassName: 'react-aria-Cell',
|
|
2317
|
+
values: {
|
|
2318
|
+
isFocused,
|
|
2319
|
+
isFocusVisible,
|
|
2320
|
+
isFocusVisibleWithinRow,
|
|
2321
|
+
isPressed,
|
|
2322
|
+
isHovered,
|
|
2323
|
+
isSelected,
|
|
2324
|
+
id: cell.key,
|
|
2325
|
+
columnIndex,
|
|
2326
|
+
hasChildItems,
|
|
2327
|
+
isExpanded,
|
|
2328
|
+
isDisabled,
|
|
2329
|
+
level: row.level + 1,
|
|
2330
|
+
isTreeColumn: cell.column!.key === state.treeColumn,
|
|
2331
|
+
},
|
|
2332
|
+
} as any,
|
|
2333
|
+
subSlot(slot, 'render'),
|
|
2334
|
+
);
|
|
2335
|
+
|
|
2336
|
+
let DOMProps = filterDOMProps(props as any, { global: true });
|
|
2337
|
+
delete DOMProps.id;
|
|
2338
|
+
|
|
2339
|
+
return createElement(TableCellElementType, {
|
|
2340
|
+
...mergeProps(DOMProps, renderProps, gridCellProps, focusProps, hoverProps),
|
|
2341
|
+
ref,
|
|
2342
|
+
'data-focused': isFocused || undefined,
|
|
2343
|
+
'data-focus-visible': isFocusVisible || undefined,
|
|
2344
|
+
'data-focus-visible-within-row': isFocusVisibleWithinRow || undefined,
|
|
2345
|
+
'data-pressed': isPressed || undefined,
|
|
2346
|
+
'data-selected': isSelected || undefined,
|
|
2347
|
+
'data-column-index': columnIndex,
|
|
2348
|
+
'data-expanded': isExpanded || undefined,
|
|
2349
|
+
'data-has-child-items': hasChildItems || undefined,
|
|
2350
|
+
'data-level': row.level + 1,
|
|
2351
|
+
'data-tree-column': cell.column!.key === state.treeColumn || undefined,
|
|
2352
|
+
'data-disabled': isDisabled || undefined,
|
|
2353
|
+
children: createElement(CollectionRendererContext.Provider, {
|
|
2354
|
+
value: DefaultCollectionRenderer,
|
|
2355
|
+
children: renderProps.children,
|
|
2356
|
+
}),
|
|
2357
|
+
});
|
|
2358
|
+
},
|
|
2359
|
+
);
|
|
2360
|
+
|
|
2361
|
+
function TableDropIndicatorWrapper(props: DropIndicatorProps, forwardedRef: any): any {
|
|
2362
|
+
const slot = S('TableDropIndicatorWrapper');
|
|
2363
|
+
let ref = useObjectRef<HTMLElement>(forwardedRef, subSlot(slot, 'objectRef'));
|
|
2364
|
+
let { dragAndDropHooks, dropState } = useContext(DragAndDropContext);
|
|
2365
|
+
let buttonRef = useRef<HTMLDivElement | null>(null, subSlot(slot, 'buttonRef'));
|
|
2366
|
+
let { dropIndicatorProps, isHidden, isDropTarget } = dragAndDropHooks!.useDropIndicator!(
|
|
2367
|
+
props,
|
|
2368
|
+
dropState!,
|
|
2369
|
+
buttonRef,
|
|
2370
|
+
);
|
|
2371
|
+
|
|
2372
|
+
if (isHidden) {
|
|
2373
|
+
return null;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
let level =
|
|
2377
|
+
dropState && props.target.type === 'item'
|
|
2378
|
+
? (dropState.collection.getItem(props.target.key)?.level || 0) + 1
|
|
2379
|
+
: 1;
|
|
2380
|
+
return createElement(TableDropIndicator, {
|
|
2381
|
+
...props,
|
|
2382
|
+
dropIndicatorProps,
|
|
2383
|
+
isDropTarget,
|
|
2384
|
+
buttonRef,
|
|
2385
|
+
level,
|
|
2386
|
+
ref,
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
interface TableDropIndicatorProps extends DropIndicatorProps, GlobalDOMAttributes {
|
|
2391
|
+
dropIndicatorProps: HTMLAttributes;
|
|
2392
|
+
isDropTarget: boolean;
|
|
2393
|
+
buttonRef: RefObject<HTMLDivElement | null>;
|
|
2394
|
+
level: number;
|
|
2395
|
+
ref?: any;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
function TableDropIndicatorRowElementType(props: any): any {
|
|
2399
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
2400
|
+
if (isVirtualized) {
|
|
2401
|
+
return createElement(dom.div, props);
|
|
2402
|
+
}
|
|
2403
|
+
return createElement(dom.tr, props);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
function TableDropIndicatorTDElementType(props: any): any {
|
|
2407
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
2408
|
+
if (isVirtualized) {
|
|
2409
|
+
return createElement(dom.div, props);
|
|
2410
|
+
}
|
|
2411
|
+
return createElement(dom.td, props);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// octane adaptation: no forwardRef — the forwarded ref arrives as `props.ref` (upstream wraps
|
|
2415
|
+
// this in `forwardRef` as TableDropIndicatorForwardRef).
|
|
2416
|
+
function TableDropIndicator(props: TableDropIndicatorProps): any {
|
|
2417
|
+
const slot = S('TableDropIndicator');
|
|
2418
|
+
let { dropIndicatorProps, isDropTarget, buttonRef, level, ref, ...otherProps } = props;
|
|
2419
|
+
|
|
2420
|
+
let state = useContext(TableStateContext)!;
|
|
2421
|
+
let { visuallyHiddenProps } = useVisuallyHidden(undefined, subSlot(slot, 'visuallyHidden'));
|
|
2422
|
+
let renderProps = useRenderProps(
|
|
2423
|
+
{
|
|
2424
|
+
...otherProps,
|
|
2425
|
+
defaultClassName: 'react-aria-DropIndicator',
|
|
2426
|
+
defaultStyle: {
|
|
2427
|
+
'--table-row-level': level + 1,
|
|
2428
|
+
},
|
|
2429
|
+
values: {
|
|
2430
|
+
isDropTarget,
|
|
2431
|
+
},
|
|
2432
|
+
} as any,
|
|
2433
|
+
subSlot(slot, 'render'),
|
|
2434
|
+
);
|
|
2435
|
+
|
|
2436
|
+
return createElement(TableDropIndicatorRowElementType, {
|
|
2437
|
+
...filterDOMProps(props as any, { global: true }),
|
|
2438
|
+
...renderProps,
|
|
2439
|
+
role: 'row',
|
|
2440
|
+
ref,
|
|
2441
|
+
'data-drop-target': isDropTarget || undefined,
|
|
2442
|
+
'aria-level': level,
|
|
2443
|
+
children: createElement(TableDropIndicatorTDElementType, {
|
|
2444
|
+
role: 'gridcell',
|
|
2445
|
+
colSpan: (state.collection as ITableCollection<any>).columnCount,
|
|
2446
|
+
style: { padding: 0 },
|
|
2447
|
+
// Positional (non-array) children, exactly like upstream's JSX.
|
|
2448
|
+
children: createElement(
|
|
2449
|
+
Fragment,
|
|
2450
|
+
null,
|
|
2451
|
+
createElement('div', {
|
|
2452
|
+
...visuallyHiddenProps,
|
|
2453
|
+
role: 'button',
|
|
2454
|
+
...dropIndicatorProps,
|
|
2455
|
+
ref: buttonRef,
|
|
2456
|
+
}),
|
|
2457
|
+
renderProps.children,
|
|
2458
|
+
),
|
|
2459
|
+
}),
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
function RootDropIndicator(): any {
|
|
2464
|
+
const slot = S('TableRootDropIndicator');
|
|
2465
|
+
let state = useContext(TableStateContext)!;
|
|
2466
|
+
let { dragAndDropHooks, dropState } = useContext(DragAndDropContext);
|
|
2467
|
+
let ref = useRef<HTMLDivElement | null>(null, subSlot(slot, 'ref'));
|
|
2468
|
+
let { dropIndicatorProps } = dragAndDropHooks!.useDropIndicator!(
|
|
2469
|
+
{
|
|
2470
|
+
target: { type: 'root' },
|
|
2471
|
+
},
|
|
2472
|
+
dropState!,
|
|
2473
|
+
ref,
|
|
2474
|
+
);
|
|
2475
|
+
let isDropTarget = dropState!.isDropTarget({ type: 'root' });
|
|
2476
|
+
let { visuallyHiddenProps } = useVisuallyHidden(undefined, subSlot(slot, 'visuallyHidden'));
|
|
2477
|
+
|
|
2478
|
+
if (!isDropTarget && dropIndicatorProps['aria-hidden']) {
|
|
2479
|
+
return null;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
return createElement(TableRowElementType, {
|
|
2483
|
+
role: 'row',
|
|
2484
|
+
'aria-hidden': dropIndicatorProps['aria-hidden'],
|
|
2485
|
+
style: { height: 0 },
|
|
2486
|
+
children: createElement(TableCellElementType, {
|
|
2487
|
+
role: 'gridcell',
|
|
2488
|
+
colSpan: (state.collection as ITableCollection<any>).columnCount,
|
|
2489
|
+
style: { padding: 0 },
|
|
2490
|
+
children: createElement('div', {
|
|
2491
|
+
role: 'button',
|
|
2492
|
+
...visuallyHiddenProps,
|
|
2493
|
+
...dropIndicatorProps,
|
|
2494
|
+
ref,
|
|
2495
|
+
}),
|
|
2496
|
+
}),
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
export interface TableLoadMoreItemProps
|
|
2501
|
+
extends
|
|
2502
|
+
Omit<LoadMoreSentinelProps, 'collection'>,
|
|
2503
|
+
StyleProps,
|
|
2504
|
+
DOMRenderProps<'tr' | 'div', undefined>,
|
|
2505
|
+
GlobalDOMAttributes {
|
|
2506
|
+
/**
|
|
2507
|
+
* The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
|
|
2508
|
+
* element.
|
|
2509
|
+
*
|
|
2510
|
+
* @default 'react-aria-TableLoadMoreItem'
|
|
2511
|
+
*/
|
|
2512
|
+
className?: string;
|
|
2513
|
+
/**
|
|
2514
|
+
* The load more spinner to render when loading additional items.
|
|
2515
|
+
*/
|
|
2516
|
+
children?: ReactNode;
|
|
2517
|
+
/**
|
|
2518
|
+
* Whether or not the loading spinner should be rendered or not.
|
|
2519
|
+
*/
|
|
2520
|
+
isLoading?: boolean;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
export const TableLoadMoreItem: (props: TableLoadMoreItemProps & { ref?: any }) => any =
|
|
2524
|
+
createLeafComponent(
|
|
2525
|
+
LoaderNode,
|
|
2526
|
+
function TableLoadingIndicator(
|
|
2527
|
+
props: TableLoadMoreItemProps,
|
|
2528
|
+
ref: any,
|
|
2529
|
+
item?: Node<object>,
|
|
2530
|
+
): any {
|
|
2531
|
+
const slot = S('TableLoadMoreItem');
|
|
2532
|
+
let state = useContext(TableStateContext)!;
|
|
2533
|
+
let { isVirtualized } = useContext(CollectionRendererContext);
|
|
2534
|
+
let { isLoading, onLoadMore, scrollOffset, ...otherProps } = props;
|
|
2535
|
+
let numColumns = state.collection.columns.length;
|
|
2536
|
+
|
|
2537
|
+
let sentinelRef = useRef<HTMLDivElement | null>(null, subSlot(slot, 'sentinel'));
|
|
2538
|
+
let memoedLoadMoreProps = useMemo(
|
|
2539
|
+
() => ({
|
|
2540
|
+
onLoadMore,
|
|
2541
|
+
collection: state?.collection,
|
|
2542
|
+
sentinelRef,
|
|
2543
|
+
scrollOffset,
|
|
2544
|
+
}),
|
|
2545
|
+
[onLoadMore, scrollOffset, state?.collection],
|
|
2546
|
+
subSlot(slot, 'loadMoreProps'),
|
|
2547
|
+
);
|
|
2548
|
+
useLoadMoreSentinel(
|
|
2549
|
+
memoedLoadMoreProps as any,
|
|
2550
|
+
sentinelRef as any,
|
|
2551
|
+
subSlot(slot, 'loadMore'),
|
|
2552
|
+
);
|
|
2553
|
+
|
|
2554
|
+
let renderProps = useRenderProps(
|
|
2555
|
+
{
|
|
2556
|
+
...otherProps,
|
|
2557
|
+
id: undefined,
|
|
2558
|
+
children: item!.rendered,
|
|
2559
|
+
defaultClassName: 'react-aria-TableLoadingIndicator',
|
|
2560
|
+
defaultStyle: {
|
|
2561
|
+
'--table-row-level': item!.level + 1,
|
|
2562
|
+
},
|
|
2563
|
+
values: undefined,
|
|
2564
|
+
} as any,
|
|
2565
|
+
subSlot(slot, 'render'),
|
|
2566
|
+
);
|
|
2567
|
+
let rowProps: Record<string, any> = {};
|
|
2568
|
+
let rowHeaderProps: Record<string, any> = {};
|
|
2569
|
+
let style: Record<string, any> = {};
|
|
2570
|
+
|
|
2571
|
+
if (isVirtualized) {
|
|
2572
|
+
// For now don't include aria-rowindex on loader since they aren't keyboard focusable
|
|
2573
|
+
// Arguably shouldn't include them ever since it might be confusing to the user to include the loaders as part of the
|
|
2574
|
+
// row count
|
|
2575
|
+
rowHeaderProps['aria-colspan'] = numColumns;
|
|
2576
|
+
style = { display: 'contents' };
|
|
2577
|
+
} else {
|
|
2578
|
+
rowHeaderProps['colSpan'] = numColumns;
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
return createElement(
|
|
2582
|
+
Fragment,
|
|
2583
|
+
null,
|
|
2584
|
+
// Alway render the sentinel. For now onus is on the user for styling when using flex + gap
|
|
2585
|
+
// (this would introduce a gap even though it doesn't take room). octane adaptation:
|
|
2586
|
+
// upstream `inertValue` is React <19 string compat; octane follows React 19 boolean
|
|
2587
|
+
// `inert` semantics.
|
|
2588
|
+
createElement(TableRowElementType, {
|
|
2589
|
+
style: { height: 0 },
|
|
2590
|
+
inert: true,
|
|
2591
|
+
children: createElement(TableCellElementType, {
|
|
2592
|
+
style: { padding: 0, border: 0 },
|
|
2593
|
+
children: createElement('div', {
|
|
2594
|
+
'data-testid': 'loadMoreSentinel',
|
|
2595
|
+
ref: sentinelRef,
|
|
2596
|
+
style: { position: 'relative', height: 1, width: 1 },
|
|
2597
|
+
}),
|
|
2598
|
+
}),
|
|
2599
|
+
}),
|
|
2600
|
+
isLoading && renderProps.children
|
|
2601
|
+
? createElement(TableRowElementType, {
|
|
2602
|
+
...mergeProps(filterDOMProps(props, { global: true }), rowProps),
|
|
2603
|
+
...renderProps,
|
|
2604
|
+
role: 'row',
|
|
2605
|
+
ref,
|
|
2606
|
+
'aria-level': item!.level + 1,
|
|
2607
|
+
'data-level': item!.level + 1,
|
|
2608
|
+
children: createElement(TableCellElementType, {
|
|
2609
|
+
role: 'rowheader',
|
|
2610
|
+
...rowHeaderProps,
|
|
2611
|
+
style,
|
|
2612
|
+
children: renderProps.children,
|
|
2613
|
+
}),
|
|
2614
|
+
})
|
|
2615
|
+
: null,
|
|
2616
|
+
);
|
|
2617
|
+
},
|
|
2618
|
+
);
|