@dxos/react-ui-table 0.4.10-main.fe71b4c → 0.4.10-next.169e4e3

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
@@ -4,9 +4,10 @@
4
4
 
5
5
  import { useArrowNavigationGroup } from '@fluentui/react-tabster';
6
6
  import { flexRender, type Row, type RowData } from '@tanstack/react-table';
7
- import React from 'react';
7
+ import React, { useContext } from 'react';
8
8
 
9
9
  import { useTableContext } from './TableContext';
10
+ import { TableRootContext } from './TableRootContext';
10
11
  import { tbodyTr } from '../../theme';
11
12
  import { Cell } from '../Cell/Cell';
12
13
 
@@ -23,6 +24,8 @@ const TableBody = ({ rows }: TableBodyProps) => {
23
24
  const domAttributes = useArrowNavigationGroup({ axis: 'grid' });
24
25
  const canBeCurrent = !isGrid && !!onDatumClick;
25
26
 
27
+ const { virtualizer } = useContext(TableRootContext);
28
+
26
29
  return (
27
30
  <tbody {...(isGrid && domAttributes)}>
28
31
  {rows.map((row) => {
@@ -37,6 +40,8 @@ const TableBody = ({ rows }: TableBodyProps) => {
37
40
  <tr
38
41
  key={keyAccessor ? keyAccessor(row.original) : row.id}
39
42
  className={classNames}
43
+ data-index={row.index}
44
+ ref={virtualizer.measureElement}
40
45
  {...(isCurrent && { 'aria-current': 'location' })}
41
46
  {...(isSelected && { 'aria-selected': 'true' })}
42
47
  {...(canBeCurrent && {
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { useVirtualizer } from '@tanstack/react-virtual';
6
+ import { createContext, useRef, useState } from 'react';
7
+
8
+ type TableRootContextActions = { type: 'updateTableCount'; count: number };
9
+
10
+ export const useTableRootContext = () => {
11
+ const scrollContextRef = useRef<HTMLDivElement>(null);
12
+ const [tableCount, setTableCount] = useState(0);
13
+
14
+ const virtualizer = useVirtualizer({
15
+ getScrollElement: () => scrollContextRef?.current ?? null,
16
+ count: tableCount,
17
+ overscan: 8,
18
+ estimateSize: () => 40,
19
+ });
20
+
21
+ const dispatch = (action: TableRootContextActions) => {
22
+ switch (action.type) {
23
+ case 'updateTableCount':
24
+ setTableCount(action.count);
25
+ break;
26
+
27
+ default:
28
+ break;
29
+ }
30
+ };
31
+
32
+ return { scrollContextRef, virtualizer, dispatch };
33
+ };
34
+
35
+ export type TableRootContextValue = ReturnType<typeof useTableRootContext>;
36
+
37
+ // Create the context without a default value.
38
+ // The expectation is that the provider will always supply a value, so no need to define a default here.
39
+ export const TableRootContext = createContext<TableRootContextValue>({} as TableRootContextValue);
@@ -4,3 +4,4 @@
4
4
 
5
5
  export * from './Table';
6
6
  export * from './props';
7
+ export * from './TableRootContext';
@@ -3,7 +3,6 @@
3
3
  //
4
4
 
5
5
  import { type RowData, type RowSelectionState, type Table, type VisibilityState } from '@tanstack/react-table';
6
- import { type VirtualizerOptions } from '@tanstack/react-virtual';
7
6
 
8
7
  import { type ClassNameValue } from '@dxos/react-ui-types';
9
8
 
@@ -31,30 +30,24 @@ export type TableCurrent<TData extends RowData> = Partial<{
31
30
  onDatumClick: (datum: TData) => void;
32
31
  }>;
33
32
 
34
- export type TableProps<
35
- TData extends RowData,
36
- ScrollElement extends Element | Window = Element,
37
- ItemElement extends Element = HTMLTableRowElement,
38
- > = TableFlags &
33
+ export type TableProps<TData extends RowData> = TableFlags &
39
34
  TableCurrent<TData> &
40
- Partial<
41
- {
42
- keyAccessor: KeyValue<TData>;
43
- data: TData[];
44
- columns: TableColumnDef<TData>[];
45
- onColumnResize: (state: Record<string, number>) => void;
46
- columnVisibility: VisibilityState;
47
- // Controllable row selection
48
- rowSelection: RowSelectionState;
49
- defaultRowSelection: RowSelectionState;
50
- onRowSelectionChange: (rowSelection: RowSelectionState) => void;
51
- // Derived from row selection
52
- onDataSelectionChange: (dataSelection: TData[]) => void;
53
- // `table` element props
54
- classNames: ClassNameValue;
55
- pinLastRow: boolean;
56
- } & Pick<VirtualizerOptions<ScrollElement, ItemElement>, 'getScrollElement'>
57
- >;
35
+ Partial<{
36
+ keyAccessor: KeyValue<TData>;
37
+ data: TData[];
38
+ columns: TableColumnDef<TData>[];
39
+ onColumnResize: (state: Record<string, number>) => void;
40
+ columnVisibility: VisibilityState;
41
+ // Controllable row selection
42
+ rowSelection: RowSelectionState;
43
+ defaultRowSelection: RowSelectionState;
44
+ onRowSelectionChange: (rowSelection: RowSelectionState) => void;
45
+ // Derived from row selection
46
+ onDataSelectionChange: (dataSelection: TData[]) => void;
47
+ // `table` element props
48
+ classNames: ClassNameValue;
49
+ pinLastRow: boolean;
50
+ }>;
58
51
 
59
52
  export type { RowSelectionState, VisibilityState, TableColumnDef, KeyValue };
60
53
 
package/src/helpers.tsx CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  } from '@tanstack/react-table';
15
15
  import { format } from 'date-fns/format';
16
16
  import { formatDistanceToNow } from 'date-fns/formatDistanceToNow';
17
- import React, { useEffect, useRef, useState } from 'react';
17
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
18
18
 
19
19
  import { type PublicKey } from '@dxos/keys';
20
20
  import { log } from '@dxos/log';
@@ -102,14 +102,16 @@ const StringBuilderCell = <TData extends RowData>(cellContext: CellContext<TData
102
102
 
103
103
  // https://tanstack.com/table/v8/docs/examples/react/editable-data
104
104
  const initialValue = cellContext.getValue();
105
- const [value, setValue] = useState(() => {
105
+
106
+ const getInitialValue = useCallback(() => {
106
107
  // TODO(burdon): Temporary shim for automerge.
107
- if ((initialValue as any)?.content) {
108
- return (initialValue as any)?.content;
109
- }
108
+ return (initialValue as any)?.content ?? initialValue;
109
+ }, [initialValue]);
110
+
111
+ const [value, setValue] = useState(getInitialValue);
110
112
 
111
- return initialValue;
112
- });
113
+ // Update value if initialValue changes externally (cell reactivity).
114
+ useEffect(() => setValue(getInitialValue()), [getInitialValue]);
113
115
 
114
116
  const handleSave = () => {
115
117
  ref.current && document.activeElement === ref.current && findPrevFocusable(ref.current)?.focus();
@@ -2,5 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ export * from './useColumnSizing';
5
6
  export * from './useColumnSorting';
6
7
  export * from './usePinLastRow';
8
+ export * from './useRowSelection';
@@ -0,0 +1,45 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+ import { type ColumnSizingInfoState, type ColumnSizingState } from '@tanstack/react-table';
5
+ import { useCallback, useEffect, useState } from 'react';
6
+
7
+ import { useOnTransition } from '@dxos/react-ui';
8
+
9
+ import { type TableColumnDef } from '../components/Table';
10
+
11
+ export const useColumnResizing = ({
12
+ columns,
13
+ onColumnResize,
14
+ }: {
15
+ columns: TableColumnDef<any>[];
16
+ onColumnResize: ((state: Record<string, number>) => void) | undefined;
17
+ }) => {
18
+ const [columnsInitialised, setColumnsInitialised] = useState(false);
19
+ const [columnSizing, setColumnSizing] = useState<ColumnSizingState>({});
20
+
21
+ useEffect(() => {
22
+ if (columnsInitialised) {
23
+ return;
24
+ }
25
+
26
+ setColumnSizing(
27
+ columns
28
+ .filter((column) => !!column.size && (column as any).prop !== undefined)
29
+ .reduce<ColumnSizingState>((state, column) => {
30
+ state[(column as any).prop] = column.size!;
31
+ return state;
32
+ }, {}),
33
+ );
34
+
35
+ setColumnsInitialised(true);
36
+ }, [columns, setColumnSizing]);
37
+
38
+ const [columnSizingInfo, setColumnSizingInfo] = useState<ColumnSizingInfoState>({} as ColumnSizingInfoState);
39
+
40
+ // Notify on column resize.
41
+ const notifyColumnResize = useCallback(() => onColumnResize?.(columnSizing), [onColumnResize, columnSizing]);
42
+ useOnTransition(columnSizingInfo.isResizingColumn, (v) => typeof v === 'string', false, notifyColumnResize);
43
+
44
+ return { columnSizing, columnSizingInfo, setColumnSizing, setColumnSizingInfo };
45
+ };
@@ -5,21 +5,21 @@
5
5
  import { type Row, type Table } from '@tanstack/react-table';
6
6
  import { useCallback, useEffect, useState } from 'react';
7
7
 
8
- export const usePinLastRow = (
9
- pinLastRow: boolean | undefined,
10
- table: Table<any>,
11
- data: any[],
12
- getScrollElement?: () => Element | null,
13
- ) => {
8
+ import { useTableRootContext } from '../components';
9
+
10
+ export const usePinLastRow = (pinLastRow: boolean | undefined, table: Table<any>, data: any[]) => {
11
+ const { scrollContextRef } = useTableRootContext();
14
12
  const [pinnedRowKey, setPinnedRowKey] = useState<string>();
15
13
 
16
14
  // Scrolls to the bottom of a scrollable element.
17
15
  const scrollToBottom = useCallback(() => {
18
- const scrollElement = getScrollElement?.();
19
- if (scrollElement) {
20
- requestAnimationFrame(() => scrollElement.scrollTo({ top: scrollElement.scrollHeight }));
16
+ const element = scrollContextRef.current;
17
+ const scrollHeight = element?.scrollHeight;
18
+
19
+ if (element && scrollHeight) {
20
+ requestAnimationFrame(() => element.scrollTo({ top: scrollHeight }));
21
21
  }
22
- }, [getScrollElement]);
22
+ }, []);
23
23
 
24
24
  const pinRow = useCallback(
25
25
  (rowToPin: Row<any>) => {
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+ import { useControllableState } from '@radix-ui/react-use-controllable-state';
5
+ import { type OnChangeFn, type RowSelectionState } from '@tanstack/react-table';
6
+ import { useCallback } from 'react';
7
+
8
+ import { type TableProps } from '../components/Table';
9
+
10
+ export const useRowSelection = (
11
+ props: Pick<
12
+ TableProps<any>,
13
+ 'rowsSelectable' | 'defaultRowSelection' | 'rowSelection' | 'onRowSelectionChange' | 'rowsSelectable'
14
+ >,
15
+ ) => {
16
+ const [rowSelection = {}, setRowSelection] = useControllableState({
17
+ prop: props.rowSelection,
18
+ onChange: props.onRowSelectionChange,
19
+ defaultProp: props.defaultRowSelection,
20
+ });
21
+
22
+ // TODO(thure): Does @tanstack/react-table really need this intervention? It did seem necessary to enforce single-selection...
23
+ const handleRowSelectionChange = useCallback<OnChangeFn<RowSelectionState>>(
24
+ (updaterOrValue) => {
25
+ const nextRowSelection = typeof updaterOrValue === 'function' ? updaterOrValue(rowSelection) : updaterOrValue;
26
+ if (props.rowsSelectable === 'multi') {
27
+ setRowSelection(nextRowSelection);
28
+ } else if (props.rowsSelectable) {
29
+ const nextRowSelectionKey = Object.keys(nextRowSelection).filter((id) => !rowSelection[id])[0];
30
+ setRowSelection(nextRowSelectionKey ? { [nextRowSelectionKey]: true } : {});
31
+ } else {
32
+ setRowSelection({});
33
+ }
34
+ },
35
+ [props.rowsSelectable, setRowSelection, rowSelection],
36
+ );
37
+
38
+ return { rowSelection, handleRowSelectionChange };
39
+ };
@@ -6,7 +6,7 @@ import '@dxosTheme';
6
6
 
7
7
  import * as Arbitrary from '@effect/schema/Arbitrary';
8
8
  import * as fc from 'fast-check';
9
- import React, { useRef } from 'react';
9
+ import React from 'react';
10
10
 
11
11
  import { S } from '@dxos/echo-schema';
12
12
  import { DensityProvider } from '@dxos/react-ui';
@@ -51,22 +51,21 @@ const items = fc.sample(exampleSchemaArbitrary, 10);
51
51
 
52
52
  export const SchemaTable = {
53
53
  render: () => {
54
- const containerRef = useRef<HTMLDivElement | null>(null);
55
-
56
54
  return (
57
- <div ref={containerRef} className='fixed inset-0 overflow-auto'>
58
- <Table<ExampleSchema>
59
- role='grid'
60
- rowsSelectable='multi'
61
- keyAccessor={(row) => JSON.stringify(row)}
62
- columns={columns}
63
- data={items}
64
- fullWidth
65
- stickyHeader
66
- border
67
- getScrollElement={() => containerRef.current}
68
- />
69
- </div>
55
+ <Table.Root>
56
+ <Table.Viewport classNames='inset-0 fixed'>
57
+ <Table.Table<ExampleSchema>
58
+ role='grid'
59
+ rowsSelectable='multi'
60
+ keyAccessor={(row) => JSON.stringify(row)}
61
+ columns={columns}
62
+ data={items}
63
+ fullWidth
64
+ stickyHeader
65
+ border
66
+ />
67
+ </Table.Viewport>
68
+ </Table.Root>
70
69
  );
71
70
  },
72
71
  };