@dxos/react-ui-table 0.4.10-main.c75170d → 0.4.10-main.c8e5c39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/lib/browser/index.mjs +219 -139
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/ColumnMenu/ColumnMenu.d.ts.map +1 -1
  5. package/dist/types/src/components/Table/Table.d.ts +17 -4
  6. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  7. package/dist/types/src/components/Table/Table.stories.d.ts +29 -1
  8. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/Table/TableBody.d.ts.map +1 -1
  10. package/dist/types/src/components/Table/TableRootContext.d.ts +17 -0
  11. package/dist/types/src/components/Table/TableRootContext.d.ts.map +1 -0
  12. package/dist/types/src/components/Table/index.d.ts +1 -0
  13. package/dist/types/src/components/Table/index.d.ts.map +1 -1
  14. package/dist/types/src/components/Table/props.d.ts +2 -3
  15. package/dist/types/src/components/Table/props.d.ts.map +1 -1
  16. package/dist/types/src/helpers.d.ts.map +1 -1
  17. package/dist/types/src/hooks/index.d.ts +2 -0
  18. package/dist/types/src/hooks/index.d.ts.map +1 -1
  19. package/dist/types/src/hooks/useColumnSizing.d.ts +12 -0
  20. package/dist/types/src/hooks/useColumnSizing.d.ts.map +1 -0
  21. package/dist/types/src/hooks/usePinLastRow.d.ts +1 -1
  22. package/dist/types/src/hooks/usePinLastRow.d.ts.map +1 -1
  23. package/dist/types/src/hooks/useRowSelection.d.ts +7 -0
  24. package/dist/types/src/hooks/useRowSelection.d.ts.map +1 -0
  25. package/dist/types/src/schema/SchemaToTable.stories.d.ts.map +1 -1
  26. package/package.json +18 -15
  27. package/src/components/ColumnMenu/ColumnMenu.tsx +34 -32
  28. package/src/components/Table/Table.stories.tsx +204 -89
  29. package/src/components/Table/Table.tsx +70 -101
  30. package/src/components/Table/TableBody.tsx +6 -1
  31. package/src/components/Table/TableRootContext.ts +39 -0
  32. package/src/components/Table/index.ts +1 -0
  33. package/src/components/Table/props.ts +17 -24
  34. package/src/helpers.tsx +9 -7
  35. package/src/hooks/index.ts +2 -0
  36. package/src/hooks/useColumnSizing.ts +45 -0
  37. package/src/hooks/usePinLastRow.ts +10 -10
  38. package/src/hooks/useRowSelection.ts +39 -0
  39. package/src/schema/SchemaToTable.stories.tsx +15 -16
@@ -5,12 +5,13 @@
5
5
  import '@dxosTheme';
6
6
 
7
7
  import { Plugs, PlugsConnected } from '@phosphor-icons/react';
8
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
9
9
 
10
- import { create } from '@dxos/echo-schema/schema';
10
+ import { create } from '@dxos/echo-schema';
11
+ import { registerSignalRuntime } from '@dxos/echo-signals/react';
11
12
  import { PublicKey } from '@dxos/keys';
12
13
  import { faker } from '@dxos/random';
13
- import { AnchoredOverflow, Button, DensityProvider } from '@dxos/react-ui';
14
+ import { Button, DensityProvider } from '@dxos/react-ui';
14
15
  import { withTheme } from '@dxos/storybook-utils';
15
16
  import { range } from '@dxos/util';
16
17
 
@@ -115,9 +116,19 @@ const makeColumns = (onUpdate?: ValueUpdater<Item, any>): TableColumnDef<Item, a
115
116
  // Tests
116
117
  //
117
118
 
119
+ const MinimalTable = (props: any) => {
120
+ return (
121
+ <Table.Root>
122
+ <Table.Viewport classNames='fixed inset-0 overflow-auto'>
123
+ <Table.Table<any> {...props} />
124
+ </Table.Viewport>
125
+ </Table.Root>
126
+ );
127
+ };
128
+
118
129
  export default {
119
130
  title: 'react-ui-table/Table',
120
- component: Table,
131
+ component: MinimalTable,
121
132
  args: {
122
133
  header: true,
123
134
  keyAccessor: (item: Item) => item.publicKey.toHex(),
@@ -186,7 +197,6 @@ export const Empty = {
186
197
 
187
198
  export const Dynamic = {
188
199
  render: () => {
189
- const containerRef = useRef<HTMLDivElement | null>(null);
190
200
  const [items, setItems] = useState<Item[]>(createItems(50));
191
201
 
192
202
  useEffect(() => {
@@ -204,26 +214,27 @@ export const Dynamic = {
204
214
  const columns = useMemo(() => makeColumns(), []);
205
215
 
206
216
  return (
207
- <AnchoredOverflow.Root classNames='max-bs-[80dvh]' ref={containerRef}>
208
- <Table<Item>
209
- rowsSelectable='multi'
210
- keyAccessor={(row) => row.publicKey.toHex()}
211
- columns={columns}
212
- data={items}
213
- fullWidth
214
- footer
215
- stickyHeader
216
- getScrollElement={() => containerRef.current}
217
- />
218
- <AnchoredOverflow.Anchor />
219
- </AnchoredOverflow.Root>
217
+ <Table.Root>
218
+ <Table.Viewport classNames='fixed inset-0 overflow-auto'>
219
+ <Table.Table<Item>
220
+ role='grid'
221
+ rowsSelectable='multi'
222
+ keyAccessor={(row) => row.publicKey.toHex()}
223
+ columns={columns}
224
+ data={items}
225
+ fullWidth
226
+ stickyHeader
227
+ border
228
+ pinLastRow
229
+ />
230
+ </Table.Viewport>
231
+ </Table.Root>
220
232
  );
221
233
  },
222
234
  };
223
235
 
224
236
  export const Editable = {
225
237
  render: () => {
226
- const containerRef = useRef<HTMLDivElement | null>(null);
227
238
  const [items, setItems] = useState<Item[]>(createItems(200));
228
239
 
229
240
  const onUpdate: ValueUpdater<Item, any> = useCallback(
@@ -234,26 +245,27 @@ export const Editable = {
234
245
  const columns = useMemo(() => makeColumns(onUpdate), [onUpdate]);
235
246
 
236
247
  return (
237
- <div ref={containerRef} className='fixed inset-0 overflow-auto'>
238
- <Table<Item>
239
- role='grid'
240
- rowsSelectable='multi'
241
- keyAccessor={(row) => row.publicKey.toHex()}
242
- columns={columns}
243
- data={items}
244
- fullWidth
245
- stickyHeader
246
- border
247
- getScrollElement={() => containerRef.current}
248
- />
249
- </div>
248
+ <Table.Root>
249
+ <Table.Viewport classNames='fixed inset-0 overflow-auto'>
250
+ <Table.Table<Item>
251
+ role='grid'
252
+ rowsSelectable='multi'
253
+ keyAccessor={(row) => row.publicKey.toHex()}
254
+ columns={columns}
255
+ data={items}
256
+ fullWidth
257
+ stickyHeader
258
+ border
259
+ pinLastRow
260
+ />
261
+ </Table.Viewport>
262
+ </Table.Root>
250
263
  );
251
264
  },
252
265
  };
253
266
 
254
267
  export const PinnedLastRow = {
255
268
  render: () => {
256
- const containerRef = useRef<HTMLDivElement | null>(null);
257
269
  const [items, setItems] = useState<Item[]>(createItems(200));
258
270
 
259
271
  const onUpdate: ValueUpdater<Item, any> = useCallback(
@@ -264,27 +276,27 @@ export const PinnedLastRow = {
264
276
  const columns = useMemo(() => makeColumns(onUpdate), [onUpdate]);
265
277
 
266
278
  return (
267
- <div ref={containerRef} className='fixed inset-0 overflow-auto'>
268
- <Table<Item>
269
- role='grid'
270
- rowsSelectable='multi'
271
- keyAccessor={(row) => row.publicKey.toHex()}
272
- columns={columns}
273
- data={items}
274
- fullWidth
275
- stickyHeader
276
- border
277
- getScrollElement={() => containerRef.current}
278
- pinLastRow
279
- />
280
- </div>
279
+ <Table.Root>
280
+ <Table.Viewport classNames='fixed inset-0 overflow-auto'>
281
+ <Table.Table<Item>
282
+ role='grid'
283
+ rowsSelectable='multi'
284
+ keyAccessor={(row) => row.publicKey.toHex()}
285
+ columns={columns}
286
+ data={items}
287
+ fullWidth
288
+ stickyHeader
289
+ border
290
+ pinLastRow
291
+ />
292
+ </Table.Viewport>
293
+ </Table.Root>
281
294
  );
282
295
  },
283
296
  };
284
297
 
285
298
  export const InsertDelete = {
286
299
  render: () => {
287
- const containerRef = useRef<HTMLDivElement | null>(null);
288
300
  const [items, setItems] = useState<Item[]>(createItems(10));
289
301
 
290
302
  const onUpdate: ValueUpdater<Item, any> = useCallback(
@@ -318,20 +330,21 @@ export const InsertDelete = {
318
330
  <Button onClick={onDeleteFirst}>Delete first</Button>
319
331
  <Button onClick={onDeleteLast}>Delete last</Button>
320
332
  </div>
321
- <div ref={containerRef} className='fixed inset-0 top-[72px] overflow-auto'>
322
- <Table<Item>
323
- role='grid'
324
- rowsSelectable='multi'
325
- keyAccessor={(row) => row.publicKey.toHex()}
326
- columns={columns}
327
- data={items}
328
- fullWidth
329
- stickyHeader
330
- border
331
- getScrollElement={() => containerRef.current}
332
- pinLastRow
333
- />
334
- </div>
333
+ <Table.Root>
334
+ <Table.Viewport classNames='fixed inset-0 overflow-auto'>
335
+ <Table.Table<Item>
336
+ role='grid'
337
+ rowsSelectable='multi'
338
+ keyAccessor={(row) => row.publicKey.toHex()}
339
+ columns={columns}
340
+ data={items}
341
+ fullWidth
342
+ stickyHeader
343
+ border
344
+ pinLastRow
345
+ />
346
+ </Table.Viewport>
347
+ </Table.Root>
335
348
  </div>
336
349
  );
337
350
  },
@@ -339,7 +352,6 @@ export const InsertDelete = {
339
352
 
340
353
  export const Resizable = {
341
354
  render: () => {
342
- const containerRef = useRef<HTMLDivElement | null>(null);
343
355
  const [items, setItems] = useState<Item[]>(createItems(10));
344
356
 
345
357
  const onUpdate: ValueUpdater<Item, any> = useCallback(
@@ -348,25 +360,24 @@ export const Resizable = {
348
360
  );
349
361
 
350
362
  return (
351
- <div ref={containerRef} className='fixed inset-0 overflow-auto'>
352
- <Table<Item>
353
- rowsSelectable='multi'
354
- keyAccessor={(row) => row.publicKey.toHex()}
355
- columns={makeColumns(onUpdate)}
356
- data={items}
357
- fullWidth
358
- stickyHeader
359
- getScrollElement={() => containerRef.current}
360
- // onColumnResize={handleColumnResize}
361
- />
362
- </div>
363
+ <Table.Root>
364
+ <Table.Viewport classNames='fixed inset-0 overflow-auto'>
365
+ <Table.Table<Item>
366
+ rowsSelectable='multi'
367
+ keyAccessor={(row) => row.publicKey.toHex()}
368
+ columns={makeColumns(onUpdate)}
369
+ data={items}
370
+ fullWidth
371
+ stickyHeader
372
+ />
373
+ </Table.Viewport>
374
+ </Table.Root>
363
375
  );
364
376
  },
365
377
  };
366
378
 
367
379
  export const TenThousandRows = {
368
380
  render: () => {
369
- const containerRef = useRef<HTMLDivElement | null>(null);
370
381
  const [items, setItems] = useState<Item[]>(createItems(10000));
371
382
 
372
383
  const onUpdate: ValueUpdater<Item, any> = useCallback(
@@ -377,19 +388,123 @@ export const TenThousandRows = {
377
388
  const columns = useMemo(() => makeColumns(onUpdate), [onUpdate]);
378
389
 
379
390
  return (
380
- <div ref={containerRef} className='fixed inset-0 overflow-auto'>
381
- <Table<Item>
382
- role='grid'
383
- rowsSelectable='multi'
384
- keyAccessor={(row) => row.publicKey.toHex()}
385
- columns={columns}
386
- data={items}
387
- fullWidth
388
- stickyHeader
389
- border
390
- getScrollElement={() => containerRef.current}
391
- />
392
- </div>
391
+ <Table.Root>
392
+ <Table.Viewport classNames='fixed inset-0 overflow-auto'>
393
+ <Table.Table<Item>
394
+ role='grid'
395
+ rowsSelectable='multi'
396
+ keyAccessor={(row) => row.publicKey.toHex()}
397
+ columns={columns}
398
+ data={items}
399
+ fullWidth
400
+ stickyHeader
401
+ border
402
+ />{' '}
403
+ </Table.Viewport>
404
+ </Table.Root>
405
+ );
406
+ },
407
+ };
408
+
409
+ registerSignalRuntime();
410
+ const state = create({ items: createItems(10) });
411
+
412
+ export const RealTimeUpdates = {
413
+ args: {
414
+ periodicMutations: true,
415
+ mutationInterval: 1000,
416
+ periodicDeletions: false,
417
+ deletionInterval: 5000,
418
+ periodicInsertions: false,
419
+ insertionInterval: 3000,
420
+ },
421
+ argTypes: {
422
+ mutationInterval: { control: 'number' },
423
+ periodicDeletions: { control: 'boolean' },
424
+ deletionInterval: { control: 'number' },
425
+ periodicInsertions: { control: 'boolean' },
426
+ insertionInterval: { control: 'number' },
427
+ },
428
+ render: ({
429
+ periodicMutations,
430
+ mutationInterval,
431
+ periodicDeletions,
432
+ deletionInterval,
433
+ periodicInsertions,
434
+ insertionInterval,
435
+ }: any) => {
436
+ useEffect(() => {
437
+ if (!periodicMutations) {
438
+ return;
439
+ }
440
+
441
+ const interval = setInterval(() => {
442
+ if (state.items.length === 0) {
443
+ return;
444
+ }
445
+
446
+ const randomIndex = Math.floor(Math.random() * state.items.length);
447
+ console.log('Mutating row', randomIndex);
448
+ state.items[randomIndex].name = faker.commerce.productName();
449
+ state.items[randomIndex].count = Math.floor(Math.random() * 1000);
450
+ state.items[randomIndex].started = new Date();
451
+ }, mutationInterval);
452
+
453
+ return () => clearInterval(interval);
454
+ }, [periodicMutations, mutationInterval, state.items]);
455
+
456
+ useEffect(() => {
457
+ if (!periodicInsertions) {
458
+ return;
459
+ }
460
+
461
+ const interval = setInterval(() => {
462
+ console.log('Inserting...');
463
+ state.items.push(createItems(1)[0]);
464
+ }, insertionInterval);
465
+
466
+ return () => clearInterval(interval);
467
+ }, [periodicInsertions, insertionInterval, state.items]);
468
+
469
+ useEffect(() => {
470
+ if (!periodicDeletions) {
471
+ return;
472
+ }
473
+
474
+ const interval = setInterval(() => {
475
+ if (state.items.length === 0) {
476
+ return;
477
+ }
478
+
479
+ console.log('Deleting');
480
+
481
+ // Randomly delete a row from state.items
482
+ const randomIndex = Math.floor(Math.random() * state.items.length);
483
+ state.items.splice(randomIndex, 1);
484
+ }, deletionInterval);
485
+
486
+ return () => clearInterval(interval);
487
+ }, [periodicDeletions, deletionInterval, state.items]);
488
+
489
+ const onUpdate = useCallback((...args: any[]) => {}, []);
490
+ const columns = useMemo(() => makeColumns(onUpdate), []);
491
+
492
+ return (
493
+ <Table.Root>
494
+ <Table.Viewport classNames='fixed inset-0 overflow-auto'>
495
+ <Table.Table<Item>
496
+ role='grid'
497
+ rowsSelectable='multi'
498
+ keyAccessor={(row) => row.publicKey.toHex()}
499
+ columns={columns}
500
+ data={state.items}
501
+ fullWidth
502
+ stickyHeader
503
+ border
504
+ pinLastRow
505
+ />
506
+ </Table.Viewport>
507
+ </Table.Root>
393
508
  );
394
509
  },
395
510
  };
@@ -1,36 +1,60 @@
1
1
  //
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
- import { useControllableState } from '@radix-ui/react-use-controllable-state';
4
+ import { type Primitive } from '@radix-ui/react-primitive';
5
+ import { Slot } from '@radix-ui/react-slot';
5
6
  import {
6
7
  getCoreRowModel,
7
8
  getSortedRowModel,
8
9
  useReactTable,
9
- type ColumnSizingInfoState,
10
10
  type GroupingState,
11
11
  type RowData,
12
- type ColumnSizingState,
13
- type OnChangeFn,
14
- type RowSelectionState,
15
12
  getGroupedRowModel,
16
13
  getExpandedRowModel,
17
14
  type Row,
18
15
  } from '@tanstack/react-table';
19
- import { useVirtualizer, type VirtualizerOptions } from '@tanstack/react-virtual';
20
- import React, { Fragment, useCallback, useEffect, useState } from 'react';
16
+ import React, { type ComponentPropsWithoutRef, Fragment, useCallback, useEffect, useState, useContext } from 'react';
21
17
 
22
- import { log } from '@dxos/log';
23
- import { useDefaultValue, useOnTransition } from '@dxos/react-ui';
18
+ import { type ThemedClassName, useDefaultValue } from '@dxos/react-ui';
19
+ import { mx } from '@dxos/react-ui-theme';
24
20
 
25
21
  import { TableBody } from './TableBody';
26
22
  import { type TypedTableProvider, TableProvider as UntypedTableProvider, useTableContext } from './TableContext';
27
23
  import { TableFooter } from './TableFooter';
28
24
  import { TableHead } from './TableHead';
25
+ import { TableRootContext, useTableRootContext } from './TableRootContext';
29
26
  import { type TableProps } from './props';
30
- import { usePinLastRow } from '../../hooks';
27
+ import { useColumnResizing, usePinLastRow, useRowSelection } from '../../hooks';
31
28
  import { groupTh, tableRoot } from '../../theme';
32
29
 
33
- export const Table = <TData extends RowData>(props: TableProps<TData>) => {
30
+ type TableRootProps = { children: React.ReactNode };
31
+
32
+ const TableRoot = ({ children }: TableRootProps) => {
33
+ const contextValue = useTableRootContext();
34
+ return <TableRootContext.Provider value={contextValue}>{children}</TableRootContext.Provider>;
35
+ };
36
+
37
+ type TableViewportProps = ThemedClassName<ComponentPropsWithoutRef<typeof Primitive.div>> & {
38
+ asChild?: boolean;
39
+ };
40
+
41
+ const TableViewport = ({ children, classNames, asChild, ...props }: TableViewportProps) => {
42
+ const { scrollContextRef } = useContext(TableRootContext);
43
+
44
+ const classes = mx(classNames);
45
+
46
+ return asChild ? (
47
+ <Slot ref={scrollContextRef} {...props}>
48
+ {children}
49
+ </Slot>
50
+ ) : (
51
+ <div role='none' className={classes} ref={scrollContextRef} {...props}>
52
+ {children}
53
+ </div>
54
+ );
55
+ };
56
+
57
+ export const TablePrimitive = <TData extends RowData>(props: TableProps<TData>) => {
34
58
  const {
35
59
  role,
36
60
  onColumnResize,
@@ -39,62 +63,26 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
39
63
  rowsSelectable,
40
64
  debug,
41
65
  onDataSelectionChange,
42
- getScrollElement,
43
66
  pinLastRow,
44
67
  } = props;
45
68
 
46
69
  const columns = useDefaultValue(props.columns, []);
47
- const data = useDefaultValue(props.data, []);
70
+ const incomingData = useDefaultValue(props.data, []);
48
71
 
49
- const TableProvider = UntypedTableProvider as TypedTableProvider<TData>;
50
-
51
- const [columnsInitialised, setColumnsInitialised] = useState(false);
52
- const [columnSizing, setColumnSizing] = useState<ColumnSizingState>({});
53
-
54
- useEffect(() => {
55
- if (columnsInitialised) {
56
- return;
57
- }
58
-
59
- setColumnSizing(
60
- columns
61
- .filter((column) => !!column.size && (column as any).prop !== undefined)
62
- .reduce<ColumnSizingState>((state, column) => {
63
- state[(column as any).prop] = column.size!;
64
- return state;
65
- }, {}),
66
- );
72
+ const [data, setData] = useState([...incomingData]);
67
73
 
68
- setColumnsInitialised(true);
69
- }, [columns, setColumnSizing]);
74
+ // Reactivity workaround: https://github.com/dxos/dxos/issues/6376.
75
+ const forceUpdate = useCallback(() => setData([...incomingData]), [incomingData]);
76
+ useEffect(() => forceUpdate(), [JSON.stringify(incomingData), forceUpdate]);
70
77
 
71
- const [columnSizingInfo, setColumnSizingInfo] = useState<ColumnSizingInfoState>({} as ColumnSizingInfoState);
72
-
73
- // Notify on column resize.
74
- const notifyColumnResize = useCallback(() => onColumnResize?.(columnSizing), [onColumnResize, columnSizing]);
75
- useOnTransition(columnSizingInfo.isResizingColumn, (v) => typeof v === 'string', false, notifyColumnResize);
78
+ const TableProvider = UntypedTableProvider as TypedTableProvider<TData>;
76
79
 
77
- const [rowSelection = {}, setRowSelection] = useControllableState({
78
- prop: props.rowSelection,
79
- onChange: props.onRowSelectionChange,
80
- defaultProp: props.defaultRowSelection,
80
+ const { columnSizing, setColumnSizing, columnSizingInfo, setColumnSizingInfo } = useColumnResizing({
81
+ columns,
82
+ onColumnResize,
81
83
  });
82
84
 
83
- // TODO(thure): Does @tanstack/react-table really need this intervention? It did seem necessary to enforce single-selection...
84
- const handleRowSelectionChange = useCallback<OnChangeFn<RowSelectionState>>(
85
- (updaterOrValue) => {
86
- const nextRowSelection = typeof updaterOrValue === 'function' ? updaterOrValue(rowSelection) : updaterOrValue;
87
- if (rowsSelectable === 'multi') {
88
- setRowSelection(nextRowSelection);
89
- } else if (rowsSelectable) {
90
- const nextRowSelectionKey = Object.keys(nextRowSelection).filter((id) => !rowSelection[id])[0];
91
- setRowSelection(nextRowSelectionKey ? { [nextRowSelectionKey]: true } : {});
92
- } else {
93
- setRowSelection({});
94
- }
95
- },
96
- [rowsSelectable, setRowSelection, rowSelection],
97
- );
85
+ const { rowSelection, handleRowSelectionChange } = useRowSelection(props);
98
86
 
99
87
  const [grouping, handleGroupingChange] = useState<GroupingState>(props.grouping ?? []);
100
88
  useEffect(() => handleGroupingChange(props.grouping ?? []), [handleGroupingChange, props.grouping]);
@@ -151,15 +139,11 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
151
139
  onDataSelectionChange?.(Object.keys(rowSelection).map((id) => table.getRowModel().rowsById[id].original));
152
140
  }, [onDataSelectionChange, rowSelection, table]);
153
141
 
154
- usePinLastRow(pinLastRow, table, data, getScrollElement);
142
+ usePinLastRow(pinLastRow, table, data);
155
143
 
156
144
  // Create additional expansion column if all columns have fixed width.
157
145
  const expand = false; // columns.map((column) => column.size).filter(Boolean).length === columns?.length;
158
146
 
159
- if (!getScrollElement) {
160
- log.warn('Table: getScrollElement is not set. This is required for virtualized tables.');
161
- }
162
-
163
147
  return (
164
148
  <TableProvider
165
149
  {...props}
@@ -168,16 +152,17 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
168
152
  expand={expand}
169
153
  isGrid={role === 'grid' || role === 'treegrid'}
170
154
  >
171
- <TableImpl<TData> debug={false} getScrollElement={getScrollElement} {...props} />
155
+ <TableImpl<TData> debug={false} {...props} />
172
156
  </TableProvider>
173
157
  );
174
158
  };
175
159
 
160
+ // TODO(Zan): Smush this into the Table component.
176
161
  /**
177
162
  * Pure implementation of table outside of context set-up.
178
163
  */
179
164
  const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
180
- const { debug, classNames, getScrollElement, role, footer, grouping, fullWidth } = props;
165
+ const { debug, classNames, role, footer, grouping, fullWidth } = props;
181
166
  const { table } = useTableContext<TData>();
182
167
 
183
168
  if (debug) {
@@ -188,9 +173,10 @@ const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
188
173
  );
189
174
  }
190
175
 
191
- const virtualisable = getScrollElement !== undefined;
192
176
  const isResizingColumn = table.getState().columnSizingInfo.isResizingColumn;
193
177
 
178
+ const TableComponent = isResizingColumn ? MemoizedVirtualisedTableContent : VirtualizedTableContent;
179
+
194
180
  return (
195
181
  <table
196
182
  role={role}
@@ -199,44 +185,24 @@ const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
199
185
  >
200
186
  <TableHead />
201
187
 
202
- {grouping?.length !== 0 ? (
203
- virtualisable ? (
204
- isResizingColumn ? (
205
- <MemoizedVirtualisedTableContent getScrollElement={getScrollElement} />
206
- ) : (
207
- <VirtualizedTableContent getScrollElement={getScrollElement} />
208
- )
209
- ) : (
210
- <UnvirtualizedTableContent />
211
- )
212
- ) : (
213
- <GroupedTableContent />
214
- )}
188
+ {grouping?.length !== 0 ? <TableComponent /> : <GroupedTableContent />}
215
189
 
216
190
  {footer && <TableFooter />}
217
191
  </table>
218
192
  );
219
193
  };
220
194
 
221
- const UnvirtualizedTableContent = () => {
222
- const { table } = useTableContext();
223
-
224
- const centerRows = table.getCenterRows();
225
- const pinnedRows = table.getBottomRows();
226
-
227
- const rows = [...centerRows, ...pinnedRows];
228
-
229
- return <TableBody rows={rows} />;
230
- };
231
-
232
- const VirtualizedTableContent = ({
233
- getScrollElement,
234
- }: Pick<VirtualizerOptions<Element, Element>, 'getScrollElement'>) => {
195
+ const VirtualizedTableContent = () => {
235
196
  const { table } = useTableContext();
197
+ const { virtualizer, dispatch } = useContext(TableRootContext);
236
198
 
237
199
  const centerRows = table.getCenterRows();
238
200
  let pinnedRows = [] as Row<unknown>[];
239
201
 
202
+ useEffect(() => {
203
+ dispatch({ type: 'updateTableCount', count: centerRows.length });
204
+ }, [centerRows.length]);
205
+
240
206
  try {
241
207
  // TODO(zan): Work out how to sync the row pinning with rendering
242
208
  // This is a hack because the first call to this function after a row is deleted will throw an error
@@ -245,12 +211,7 @@ const VirtualizedTableContent = ({
245
211
  pinnedRows = table.getBottomRows();
246
212
  } catch (_) {} // Ignore error
247
213
 
248
- const { getTotalSize, getVirtualItems } = useVirtualizer({
249
- getScrollElement,
250
- count: centerRows.length,
251
- overscan: 8,
252
- estimateSize: () => 40,
253
- });
214
+ const { getTotalSize, getVirtualItems } = virtualizer;
254
215
 
255
216
  const virtualRows = getVirtualItems();
256
217
  const totalSize = getTotalSize();
@@ -258,10 +219,12 @@ const VirtualizedTableContent = ({
258
219
  const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
259
220
  const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0) : 0;
260
221
 
261
- const rowsToRender = [...virtualRows.map((virtualRow) => centerRows[virtualRow.index]), ...pinnedRows];
222
+ const rowsToRender = [...virtualRows.map((virtualRow) => centerRows[virtualRow.index]), ...pinnedRows].filter(
223
+ (r) => !!r,
224
+ );
262
225
 
263
226
  return (
264
- <>
227
+ <Fragment>
265
228
  {paddingTop > 0 && (
266
229
  <tbody role='none'>
267
230
  <tr role='none'>
@@ -277,7 +240,7 @@ const VirtualizedTableContent = ({
277
240
  </tr>
278
241
  </tbody>
279
242
  )}
280
- </>
243
+ </Fragment>
281
244
  );
282
245
  };
283
246
 
@@ -314,3 +277,9 @@ const GroupedTableContent = () => {
314
277
  </>
315
278
  );
316
279
  };
280
+
281
+ export const Table = {
282
+ Root: TableRoot,
283
+ Table: TablePrimitive,
284
+ Viewport: TableViewport,
285
+ };