@dxos/react-ui-table 0.6.14-staging.e15392e → 0.7.0

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 (242) hide show
  1. package/dist/lib/browser/_deprecated/index.mjs +1483 -0
  2. package/dist/lib/browser/_deprecated/index.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-A5W4XWPD.mjs +18 -0
  4. package/dist/lib/browser/chunk-A5W4XWPD.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +1275 -1356
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/types/index.mjs +7 -0
  9. package/dist/lib/browser/types/index.mjs.map +7 -0
  10. package/dist/lib/node/_deprecated/index.cjs +1511 -0
  11. package/dist/lib/node/_deprecated/index.cjs.map +7 -0
  12. package/dist/lib/node/chunk-32IGMRME.cjs +40 -0
  13. package/dist/lib/node/chunk-32IGMRME.cjs.map +7 -0
  14. package/dist/lib/node/index.cjs +1381 -0
  15. package/dist/lib/node/index.cjs.map +7 -0
  16. package/dist/lib/node/meta.json +1 -0
  17. package/dist/lib/node/types/index.cjs +29 -0
  18. package/dist/lib/node/types/index.cjs.map +7 -0
  19. package/dist/lib/node-esm/_deprecated/index.mjs +1485 -0
  20. package/dist/lib/node-esm/_deprecated/index.mjs.map +7 -0
  21. package/dist/lib/node-esm/chunk-DWQ4KGLD.mjs +20 -0
  22. package/dist/lib/node-esm/chunk-DWQ4KGLD.mjs.map +7 -0
  23. package/dist/lib/node-esm/index.mjs +1400 -0
  24. package/dist/lib/node-esm/index.mjs.map +7 -0
  25. package/dist/lib/node-esm/meta.json +1 -0
  26. package/dist/lib/node-esm/types/index.mjs +8 -0
  27. package/dist/lib/node-esm/types/index.mjs.map +7 -0
  28. package/dist/types/src/_deprecated/components/Cell/Cell.d.ts.map +1 -0
  29. package/dist/types/src/_deprecated/components/Cell/CellCombobox.d.ts.map +1 -0
  30. package/dist/types/src/_deprecated/components/Cell/index.d.ts.map +1 -0
  31. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.d.ts.map +1 -0
  32. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.stories.d.ts.map +1 -0
  33. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.d.ts.map +1 -0
  34. package/dist/types/src/{components → _deprecated/components}/ColumnMenu/ColumnSettings.stories.d.ts +4 -4
  35. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.stories.d.ts.map +1 -0
  36. package/dist/types/src/_deprecated/components/ColumnMenu/index.d.ts.map +1 -0
  37. package/dist/types/src/_deprecated/components/Table/Table.d.ts +20 -0
  38. package/dist/types/src/_deprecated/components/Table/Table.d.ts.map +1 -0
  39. package/dist/types/src/_deprecated/components/Table/Table.stories.d.ts +116 -0
  40. package/dist/types/src/_deprecated/components/Table/Table.stories.d.ts.map +1 -0
  41. package/dist/types/src/_deprecated/components/Table/TableBody.d.ts.map +1 -0
  42. package/dist/types/src/_deprecated/components/Table/TableContext.d.ts.map +1 -0
  43. package/dist/types/src/_deprecated/components/Table/TableHead.d.ts.map +1 -0
  44. package/dist/types/src/_deprecated/components/Table/TableRootContext.d.ts.map +1 -0
  45. package/dist/types/src/_deprecated/components/Table/index.d.ts +5 -0
  46. package/dist/types/src/_deprecated/components/Table/index.d.ts.map +1 -0
  47. package/dist/types/src/_deprecated/components/Table/props.d.ts.map +1 -0
  48. package/dist/types/src/_deprecated/components/index.d.ts +4 -0
  49. package/dist/types/src/_deprecated/components/index.d.ts.map +1 -0
  50. package/dist/types/src/_deprecated/helpers.d.ts.map +1 -0
  51. package/dist/types/src/_deprecated/hooks/index.d.ts +5 -0
  52. package/dist/types/src/_deprecated/hooks/index.d.ts.map +1 -0
  53. package/dist/types/src/_deprecated/hooks/useColumnSizing.d.ts.map +1 -0
  54. package/dist/types/src/{hooks → _deprecated/hooks}/useColumnSorting.d.ts +1 -1
  55. package/dist/types/src/_deprecated/hooks/useColumnSorting.d.ts.map +1 -0
  56. package/dist/types/src/_deprecated/hooks/usePinLastRow.d.ts.map +1 -0
  57. package/dist/types/src/_deprecated/hooks/useRowSelection.d.ts.map +1 -0
  58. package/dist/types/src/_deprecated/index.d.ts +8 -0
  59. package/dist/types/src/_deprecated/index.d.ts.map +1 -0
  60. package/dist/types/src/_deprecated/schema/Schema.stories.d.ts.map +1 -0
  61. package/dist/types/src/{schema → _deprecated/schema}/column-utils.d.ts +3 -0
  62. package/dist/types/src/_deprecated/schema/column-utils.d.ts.map +1 -0
  63. package/dist/types/src/_deprecated/schema/index.d.ts.map +1 -0
  64. package/dist/types/src/{schema → _deprecated/schema}/types.d.ts +3 -2
  65. package/dist/types/src/_deprecated/schema/types.d.ts.map +1 -0
  66. package/dist/types/src/_deprecated/theme.d.ts.map +1 -0
  67. package/dist/types/src/_deprecated/translations.d.ts +21 -0
  68. package/dist/types/src/_deprecated/translations.d.ts.map +1 -0
  69. package/dist/types/src/_deprecated/types.d.ts.map +1 -0
  70. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +8 -0
  71. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -0
  72. package/dist/types/src/components/Table/ColumnSettings.d.ts +8 -0
  73. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -0
  74. package/dist/types/src/components/Table/RowActionsMenu.d.ts +8 -0
  75. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -0
  76. package/dist/types/src/components/Table/Table.d.ts +11 -14
  77. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  78. package/dist/types/src/components/Table/Table.stories.d.ts +10 -113
  79. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  80. package/dist/types/src/components/Table/index.d.ts +0 -3
  81. package/dist/types/src/components/Table/index.d.ts.map +1 -1
  82. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +17 -0
  83. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -0
  84. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +11 -0
  85. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -0
  86. package/dist/types/src/components/TableCellEditor/extension.d.ts +12 -0
  87. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -0
  88. package/dist/types/src/components/TableCellEditor/index.d.ts +2 -0
  89. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -0
  90. package/dist/types/src/components/Toolbar/Toolbar.d.ts +21 -0
  91. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  92. package/dist/types/src/components/Toolbar/common.d.ts +20 -0
  93. package/dist/types/src/components/Toolbar/common.d.ts.map +1 -0
  94. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  95. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  96. package/dist/types/src/components/index.d.ts +1 -2
  97. package/dist/types/src/components/index.d.ts.map +1 -1
  98. package/dist/types/src/components/testing.d.ts +97 -0
  99. package/dist/types/src/components/testing.d.ts.map +1 -0
  100. package/dist/types/src/hooks/index.d.ts +1 -4
  101. package/dist/types/src/hooks/index.d.ts.map +1 -1
  102. package/dist/types/src/hooks/useTableModel.d.ts +15 -0
  103. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -0
  104. package/dist/types/src/index.d.ts +3 -4
  105. package/dist/types/src/index.d.ts.map +1 -1
  106. package/dist/types/src/model/index.d.ts +3 -0
  107. package/dist/types/src/model/index.d.ts.map +1 -0
  108. package/dist/types/src/model/modal-controller.d.ts +29 -0
  109. package/dist/types/src/model/modal-controller.d.ts.map +1 -0
  110. package/dist/types/src/model/selection-model.d.ts +22 -0
  111. package/dist/types/src/model/selection-model.d.ts.map +1 -0
  112. package/dist/types/src/model/table-model.d.ts +93 -0
  113. package/dist/types/src/model/table-model.d.ts.map +1 -0
  114. package/dist/types/src/model/table-model.test.d.ts +2 -0
  115. package/dist/types/src/model/table-model.test.d.ts.map +1 -0
  116. package/dist/types/src/translations.d.ts +23 -16
  117. package/dist/types/src/translations.d.ts.map +1 -1
  118. package/dist/types/src/types/index.d.ts +2 -0
  119. package/dist/types/src/types/index.d.ts.map +1 -0
  120. package/dist/types/src/types/table.d.ts +106 -0
  121. package/dist/types/src/types/table.d.ts.map +1 -0
  122. package/dist/types/src/util/grid.d.ts +14 -0
  123. package/dist/types/src/util/grid.d.ts.map +1 -0
  124. package/dist/types/src/util/index.d.ts +5 -0
  125. package/dist/types/src/util/index.d.ts.map +1 -0
  126. package/dist/types/src/util/signals.d.ts +6 -0
  127. package/dist/types/src/util/signals.d.ts.map +1 -0
  128. package/dist/types/src/util/table-buttons.d.ts +24 -0
  129. package/dist/types/src/util/table-buttons.d.ts.map +1 -0
  130. package/dist/types/src/util/table-controls.d.ts +21 -0
  131. package/dist/types/src/util/table-controls.d.ts.map +1 -0
  132. package/dist/types/src/util/table.d.ts +8 -0
  133. package/dist/types/src/util/table.d.ts.map +1 -0
  134. package/package.json +56 -22
  135. package/src/{components → _deprecated/components}/ColumnMenu/ColumnMenu.stories.tsx +4 -4
  136. package/src/{components → _deprecated/components}/ColumnMenu/ColumnSettings.stories.tsx +9 -9
  137. package/src/{components → _deprecated/components}/ColumnMenu/ColumnSettings.tsx +3 -3
  138. package/src/_deprecated/components/Table/Table.stories.tsx +508 -0
  139. package/src/_deprecated/components/Table/Table.tsx +301 -0
  140. package/src/{components → _deprecated/components}/Table/TableHead.tsx +3 -1
  141. package/src/_deprecated/components/Table/index.ts +8 -0
  142. package/src/_deprecated/components/index.ts +7 -0
  143. package/src/{helpers.tsx → _deprecated/helpers.tsx} +4 -3
  144. package/src/_deprecated/hooks/index.ts +8 -0
  145. package/src/_deprecated/index.ts +12 -0
  146. package/src/_deprecated/schema/column-utils.ts +54 -0
  147. package/src/{schema → _deprecated/schema}/types.ts +3 -2
  148. package/src/_deprecated/translations.ts +26 -0
  149. package/src/components/Table/ColumnActionsMenu.tsx +60 -0
  150. package/src/components/Table/ColumnSettings.tsx +76 -0
  151. package/src/components/Table/RowActionsMenu.tsx +36 -0
  152. package/src/components/Table/Table.stories.tsx +197 -467
  153. package/src/components/Table/Table.tsx +172 -256
  154. package/src/components/Table/index.ts +1 -4
  155. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +114 -0
  156. package/src/components/TableCellEditor/TableCellEditor.tsx +155 -0
  157. package/src/components/TableCellEditor/extension.ts +62 -0
  158. package/src/components/TableCellEditor/index.ts +5 -0
  159. package/src/components/Toolbar/Toolbar.tsx +86 -0
  160. package/src/components/Toolbar/common.tsx +72 -0
  161. package/src/components/Toolbar/index.ts +5 -0
  162. package/src/components/index.ts +2 -3
  163. package/src/components/testing.ts +114 -0
  164. package/src/hooks/index.ts +1 -4
  165. package/src/hooks/useTableModel.ts +52 -0
  166. package/src/index.ts +5 -6
  167. package/src/model/index.ts +6 -0
  168. package/src/model/modal-controller.ts +83 -0
  169. package/src/model/selection-model.ts +99 -0
  170. package/src/model/table-model.test.ts +182 -0
  171. package/src/model/table-model.ts +565 -0
  172. package/src/translations.ts +20 -13
  173. package/src/types/index.ts +5 -0
  174. package/src/types/table.ts +13 -0
  175. package/src/util/grid.ts +23 -0
  176. package/src/util/index.ts +8 -0
  177. package/src/util/signals.ts +9 -0
  178. package/src/util/table-buttons.ts +48 -0
  179. package/src/util/table-controls.ts +27 -0
  180. package/src/util/table.ts +77 -0
  181. package/dist/types/src/components/Cell/Cell.d.ts.map +0 -1
  182. package/dist/types/src/components/Cell/CellCombobox.d.ts.map +0 -1
  183. package/dist/types/src/components/Cell/index.d.ts.map +0 -1
  184. package/dist/types/src/components/ColumnMenu/ColumnMenu.d.ts.map +0 -1
  185. package/dist/types/src/components/ColumnMenu/ColumnMenu.stories.d.ts.map +0 -1
  186. package/dist/types/src/components/ColumnMenu/ColumnSettings.d.ts.map +0 -1
  187. package/dist/types/src/components/ColumnMenu/ColumnSettings.stories.d.ts.map +0 -1
  188. package/dist/types/src/components/ColumnMenu/index.d.ts.map +0 -1
  189. package/dist/types/src/components/Table/TableBody.d.ts.map +0 -1
  190. package/dist/types/src/components/Table/TableContext.d.ts.map +0 -1
  191. package/dist/types/src/components/Table/TableHead.d.ts.map +0 -1
  192. package/dist/types/src/components/Table/TableRootContext.d.ts.map +0 -1
  193. package/dist/types/src/components/Table/props.d.ts.map +0 -1
  194. package/dist/types/src/helpers.d.ts.map +0 -1
  195. package/dist/types/src/hooks/useColumnSizing.d.ts.map +0 -1
  196. package/dist/types/src/hooks/useColumnSorting.d.ts.map +0 -1
  197. package/dist/types/src/hooks/usePinLastRow.d.ts.map +0 -1
  198. package/dist/types/src/hooks/useRowSelection.d.ts.map +0 -1
  199. package/dist/types/src/schema/Schema.stories.d.ts.map +0 -1
  200. package/dist/types/src/schema/column-utils.d.ts.map +0 -1
  201. package/dist/types/src/schema/index.d.ts.map +0 -1
  202. package/dist/types/src/schema/types.d.ts.map +0 -1
  203. package/dist/types/src/theme.d.ts.map +0 -1
  204. package/dist/types/src/types.d.ts.map +0 -1
  205. package/src/schema/column-utils.ts +0 -48
  206. /package/dist/types/src/{components → _deprecated/components}/Cell/Cell.d.ts +0 -0
  207. /package/dist/types/src/{components → _deprecated/components}/Cell/CellCombobox.d.ts +0 -0
  208. /package/dist/types/src/{components → _deprecated/components}/Cell/index.d.ts +0 -0
  209. /package/dist/types/src/{components → _deprecated/components}/ColumnMenu/ColumnMenu.d.ts +0 -0
  210. /package/dist/types/src/{components → _deprecated/components}/ColumnMenu/ColumnMenu.stories.d.ts +0 -0
  211. /package/dist/types/src/{components → _deprecated/components}/ColumnMenu/ColumnSettings.d.ts +0 -0
  212. /package/dist/types/src/{components → _deprecated/components}/ColumnMenu/index.d.ts +0 -0
  213. /package/dist/types/src/{components → _deprecated/components}/Table/TableBody.d.ts +0 -0
  214. /package/dist/types/src/{components → _deprecated/components}/Table/TableContext.d.ts +0 -0
  215. /package/dist/types/src/{components → _deprecated/components}/Table/TableHead.d.ts +0 -0
  216. /package/dist/types/src/{components → _deprecated/components}/Table/TableRootContext.d.ts +0 -0
  217. /package/dist/types/src/{components → _deprecated/components}/Table/props.d.ts +0 -0
  218. /package/dist/types/src/{helpers.d.ts → _deprecated/helpers.d.ts} +0 -0
  219. /package/dist/types/src/{hooks → _deprecated/hooks}/useColumnSizing.d.ts +0 -0
  220. /package/dist/types/src/{hooks → _deprecated/hooks}/usePinLastRow.d.ts +0 -0
  221. /package/dist/types/src/{hooks → _deprecated/hooks}/useRowSelection.d.ts +0 -0
  222. /package/dist/types/src/{schema → _deprecated/schema}/Schema.stories.d.ts +0 -0
  223. /package/dist/types/src/{schema → _deprecated/schema}/index.d.ts +0 -0
  224. /package/dist/types/src/{theme.d.ts → _deprecated/theme.d.ts} +0 -0
  225. /package/dist/types/src/{types.d.ts → _deprecated/types.d.ts} +0 -0
  226. /package/src/{components → _deprecated/components}/Cell/Cell.tsx +0 -0
  227. /package/src/{components → _deprecated/components}/Cell/CellCombobox.tsx +0 -0
  228. /package/src/{components → _deprecated/components}/Cell/index.ts +0 -0
  229. /package/src/{components → _deprecated/components}/ColumnMenu/ColumnMenu.tsx +0 -0
  230. /package/src/{components → _deprecated/components}/ColumnMenu/index.ts +0 -0
  231. /package/src/{components → _deprecated/components}/Table/TableBody.tsx +0 -0
  232. /package/src/{components → _deprecated/components}/Table/TableContext.tsx +0 -0
  233. /package/src/{components → _deprecated/components}/Table/TableRootContext.ts +0 -0
  234. /package/src/{components → _deprecated/components}/Table/props.ts +0 -0
  235. /package/src/{hooks → _deprecated/hooks}/useColumnSizing.ts +0 -0
  236. /package/src/{hooks → _deprecated/hooks}/useColumnSorting.ts +0 -0
  237. /package/src/{hooks → _deprecated/hooks}/usePinLastRow.ts +0 -0
  238. /package/src/{hooks → _deprecated/hooks}/useRowSelection.ts +0 -0
  239. /package/src/{schema → _deprecated/schema}/Schema.stories.tsx +0 -0
  240. /package/src/{schema → _deprecated/schema}/index.ts +0 -0
  241. /package/src/{theme.ts → _deprecated/theme.ts} +0 -0
  242. /package/src/{types.ts → _deprecated/types.ts} +0 -0
@@ -1,301 +1,217 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Primitive } from '@radix-ui/react-primitive';
6
- import { Slot } from '@radix-ui/react-slot';
7
- import {
8
- type GroupingState,
9
- type Row,
10
- type RowData,
11
- getCoreRowModel,
12
- getExpandedRowModel,
13
- getGroupedRowModel,
14
- getSortedRowModel,
15
- useReactTable,
16
- } from '@tanstack/react-table';
17
5
  import React, {
18
- type ComponentPropsWithoutRef,
19
- type ReactNode,
20
- Fragment,
21
- memo,
6
+ forwardRef,
7
+ type PropsWithChildren,
22
8
  useCallback,
23
- useEffect,
9
+ useImperativeHandle,
24
10
  useState,
25
- useContext,
11
+ type WheelEvent,
26
12
  } from 'react';
27
13
 
28
- import { type ThemedClassName, useDefaultValue } from '@dxos/react-ui';
14
+ import { getValue } from '@dxos/echo-schema';
15
+ import { invariant } from '@dxos/invariant';
16
+ import { Filter, getSpace, fullyQualifiedId } from '@dxos/react-client/echo';
17
+ import { useAttention } from '@dxos/react-ui-attention';
18
+ import { type DxGridElement, Grid, type GridContentProps, closestCell } from '@dxos/react-ui-grid';
29
19
  import { mx } from '@dxos/react-ui-theme';
20
+ import { isNotFalsy } from '@dxos/util';
30
21
 
31
- import { TableBody } from './TableBody';
32
- import { type TypedTableProvider, TableProvider as UntypedTableProvider, useTableContext } from './TableContext';
33
- import { TableHead } from './TableHead';
34
- import { TableRootContext, useTableRootContext } from './TableRootContext';
35
- import { type TableProps } from './props';
36
- import { useColumnResizing, useColumnSorting, usePinLastRow, useRowSelection } from '../../hooks';
37
- import { groupTh, tableRoot } from '../../theme';
22
+ import { ColumnActionsMenu } from './ColumnActionsMenu';
23
+ import { ColumnSettings } from './ColumnSettings';
24
+ import { RowActionsMenu } from './RowActionsMenu';
25
+ import { type TableModel } from '../../model';
26
+ import { type GridCell } from '../../util';
27
+ import { TableCellEditor, type TableCellEditorProps } from '../TableCellEditor';
38
28
 
39
- type TableRootProps = { children: ReactNode };
29
+ // NOTE(Zan): These fragments add border to inline-end and block-end of the grid using pseudo-elements.
30
+ // These are offset by 1px to avoid double borders in planks.
31
+ const inlineEndLine =
32
+ '[&>.dx-grid]:relative [&>.dx-grid]:after:absolute [&>.dx-grid]:after:inset-block-0 [&>.dx-grid]:after:-inline-end-px [&>.dx-grid]:after:is-px [&>.dx-grid]:after:bg-separator';
33
+ const blockEndLine =
34
+ '[&>.dx-grid]:before:absolute [&>.dx-grid]:before:inset-inline-0 [&>.dx-grid]:before:-block-end-px [&>.dx-grid]:before:bs-px [&>.dx-grid]:before:bg-separator';
40
35
 
41
- const TableRoot = ({ children }: TableRootProps) => {
42
- const contextValue = useTableRootContext();
43
- return <TableRootContext.Provider value={contextValue}>{children}</TableRootContext.Provider>;
44
- };
36
+ const frozen = { frozenRowsStart: 1, frozenColsStart: 1, frozenColsEnd: 1 };
45
37
 
46
- type TableViewportProps = ThemedClassName<ComponentPropsWithoutRef<typeof Primitive.div>> & {
47
- asChild?: boolean;
48
- };
38
+ //
39
+ // Root
40
+ //
49
41
 
50
- const TableViewport = ({ children, classNames, asChild, ...props }: TableViewportProps) => {
51
- const { scrollContextRef } = useContext(TableRootContext);
42
+ export type TableRootProps = PropsWithChildren<{ role?: string }>;
52
43
 
53
- const classes = mx(classNames, 'overflow-auto');
54
- return asChild ? (
55
- <Slot ref={scrollContextRef} className={classes} {...props}>
56
- {children}
57
- </Slot>
58
- ) : (
59
- <div role='none' className={classes} ref={scrollContextRef} {...props}>
44
+ const TableRoot = ({ children }: TableRootProps) => {
45
+ return (
46
+ <div role='none' className='relative flex flex-col'>
60
47
  {children}
61
48
  </div>
62
49
  );
63
50
  };
64
51
 
65
- export const TablePrimitive = <TData extends RowData>(props: TableProps<TData>) => {
66
- const {
67
- role,
68
- header = true,
69
- rowsSelectable,
70
- debug,
71
- pinLastRow,
72
- columnVisibility,
73
- onColumnResize,
74
- onDataSelectionChange,
75
- } = props;
76
-
77
- const columns = useDefaultValue(props.columns, () => []);
78
- const incomingData = useDefaultValue(props.data, () => []);
79
-
80
- const [data, setData] = useState([...incomingData]);
81
-
82
- // Reactivity workaround: https://github.com/dxos/dxos/issues/6376.
83
- const forceUpdate = useCallback(() => setData([...incomingData]), [incomingData]);
84
- useEffect(() => forceUpdate(), [JSON.stringify(incomingData), forceUpdate]);
85
-
86
- const TableProvider = UntypedTableProvider as TypedTableProvider<TData>;
87
-
88
- const { columnSizing, setColumnSizing, columnSizingInfo, setColumnSizingInfo } = useColumnResizing({
89
- columns,
90
- onColumnResize,
91
- });
52
+ //
53
+ // Main
54
+ //
92
55
 
93
- const { rowSelection, handleRowSelectionChange } = useRowSelection(props);
56
+ export type TableController = {
57
+ update?: (cell?: GridCell) => void;
58
+ };
94
59
 
95
- const [grouping, handleGroupingChange] = useState<GroupingState>(props.grouping ?? []);
96
- useEffect(() => handleGroupingChange(props.grouping ?? []), [handleGroupingChange, props.grouping]);
60
+ export type TableMainProps = {
61
+ model?: TableModel;
62
+ };
97
63
 
98
- const columnSorting = useColumnSorting();
64
+ const TableMain = forwardRef<TableController, TableMainProps>(({ model }, forwardedRef) => {
65
+ const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
66
+
67
+ const { hasAttention } = useAttention(model?.table ? fullyQualifiedId(model.table) : 'table');
68
+
69
+ /**
70
+ * Provides an external controller that can be called to repaint the table.
71
+ */
72
+ useImperativeHandle<TableController, TableController>(
73
+ forwardedRef,
74
+ () => {
75
+ if (!model || !dxGrid) {
76
+ return {};
77
+ }
78
+
79
+ dxGrid.getCells = model.getCells.bind(model);
80
+ return {
81
+ update: (cell) => {
82
+ if (cell) {
83
+ dxGrid.updateIfWithinBounds(cell);
84
+ } else {
85
+ dxGrid.updateCells(true);
86
+ }
87
+ },
88
+ };
89
+ },
90
+ [model, dxGrid],
91
+ );
99
92
 
100
- const table = useReactTable({
101
- // Data
102
- meta: {},
103
- data,
93
+ const handleFocus: TableCellEditorProps['onFocus'] = (increment, delta) => {
94
+ dxGrid?.refocus(increment, delta);
95
+ };
104
96
 
105
- // Columns
106
- columns,
107
- defaultColumn: {
108
- size: 400, // Required in order remove default width.
109
- maxSize: 1024,
97
+ const handleEnter = useCallback<NonNullable<TableCellEditorProps['onEnter']>>(
98
+ (cell) => {
99
+ // TODO(burdon): Insert row only if bottom row isn't completely blank already.
100
+ if (model && cell.row === model.getRowCount() - 1) {
101
+ model.insertRow(cell.row);
102
+ }
110
103
  },
104
+ [model],
105
+ );
111
106
 
112
- // State
113
- state: {
114
- columnVisibility,
115
- columnSizing,
116
- columnSizingInfo,
117
- sorting: columnSorting.sorting,
118
- rowSelection,
119
- grouping,
107
+ const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
108
+ (event) => {
109
+ const cell = closestCell(event.target);
110
+ if (!model || !cell) {
111
+ return;
112
+ }
113
+
114
+ switch (event.key) {
115
+ case 'Backspace':
116
+ case 'Delete': {
117
+ model.setCellData(cell, undefined);
118
+ break;
119
+ }
120
+ }
120
121
  },
121
-
122
- // Resize columns
123
- columnResizeMode: 'onChange',
124
- enableColumnResizing: true,
125
- onColumnSizingChange: setColumnSizing,
126
- onColumnSizingInfoChange: setColumnSizingInfo,
127
-
128
- // Rows
129
- getCoreRowModel: getCoreRowModel(),
130
- getSortedRowModel: getSortedRowModel(),
131
- getExpandedRowModel: getExpandedRowModel(),
132
-
133
- // Grouping
134
- getGroupedRowModel: grouping.length > 1 ? getGroupedRowModel() : undefined,
135
- onGroupingChange: handleGroupingChange,
136
-
137
- enableMultiRowSelection: rowsSelectable === 'multi' ? true : undefined,
138
- enableRowSelection: rowsSelectable === true ? true : undefined,
139
-
140
- onRowSelectionChange: handleRowSelectionChange,
141
-
142
- enableSorting: true,
143
- enableColumnPinning: true,
144
-
145
- // Debug
146
- debugTable: debug,
147
- });
148
-
149
- useEffect(() => {
150
- onDataSelectionChange?.(Object.keys(rowSelection).map((id) => table.getRowModel().rowsById[id].original));
151
- }, [onDataSelectionChange, rowSelection, table]);
152
-
153
- usePinLastRow(pinLastRow, table, data);
154
-
155
- // Create additional expansion column if all columns have fixed width.
156
- const expand = false; // columns.map((column) => column.size).filter(Boolean).length === columns?.length;
157
-
158
- return (
159
- <TableProvider
160
- {...{ ...props, ...columnSorting }}
161
- table={table}
162
- header={header}
163
- expand={expand}
164
- isGrid={role === 'grid' || role === 'treegrid'}
165
- >
166
- <TableImpl<TData> debug={false} {...props} />
167
- </TableProvider>
122
+ [model],
168
123
  );
169
- };
170
-
171
- // TODO(zan): Smush this into the Table component.
172
- /**
173
- * Pure implementation of table outside of context set-up.
174
- */
175
- const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
176
- const { debug, classNames, role, grouping, fullWidth } = props;
177
- const { table } = useTableContext<TData>();
178
-
179
- if (debug) {
180
- return (
181
- <pre className='font-mono text-xs text-neutral-500 m-1 p-2 ring'>
182
- <code>{JSON.stringify(table.getState(), undefined, 2)}</code>
183
- </pre>
184
- );
185
- }
186
-
187
- const isResizingColumn = table.getState().columnSizingInfo.isResizingColumn;
188
-
189
- const TableComponent = isResizingColumn ? MemoizedVirtualizedTableContent : VirtualizedTableContent;
190
124
 
191
- return (
192
- <table
193
- role={role}
194
- className={tableRoot(props, classNames)}
195
- style={{
196
- // borderCollapse: 'separate',
197
- borderSpacing: 0,
198
- ...(!fullWidth && { width: table.getTotalSize() }),
199
- }}
200
- >
201
- <TableHead />
202
-
203
- {grouping?.length !== 0 ? <TableComponent /> : <GroupedTableContent />}
204
- </table>
125
+ const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
126
+ (event) => {
127
+ if (event.axis === 'col') {
128
+ const columnIndex = parseInt(event.index, 10);
129
+ model?.setColumnWidth(columnIndex, event.size);
130
+ }
131
+ },
132
+ [model],
205
133
  );
206
- };
207
-
208
- const VirtualizedTableContent = () => {
209
- const { table } = useTableContext();
210
- const { virtualizer, dispatch } = useContext(TableRootContext);
211
-
212
- const centerRows = table.getCenterRows();
213
- let pinnedRows = [] as Row<unknown>[];
214
-
215
- useEffect(() => {
216
- dispatch({ type: 'updateTableCount', count: centerRows.length });
217
- }, [centerRows.length]);
218
134
 
219
- try {
220
- // TODO(zan): Work out how to sync the row pinning with rendering
221
- // This is a hack because the first call to this function after a row is deleted will throw an error
222
- // because the row index is no longer in the table
223
- // The pin last row hook updates the pinned row key after the row is deleted
224
- pinnedRows = table.getBottomRows();
225
- } catch (_) {} // Ignore error
226
-
227
- const { getTotalSize, getVirtualItems } = virtualizer;
228
-
229
- const virtualRows = getVirtualItems();
230
- const totalSize = getTotalSize();
231
-
232
- const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
233
- const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0) : 0;
234
-
235
- const rowsToRender = [...virtualRows.map((virtualRow) => centerRows[virtualRow.index]), ...pinnedRows].filter(
236
- (r) => !!r,
135
+ const handleWheel = useCallback(
136
+ (event: WheelEvent) => {
137
+ if (!hasAttention) {
138
+ event.stopPropagation();
139
+ }
140
+ },
141
+ [hasAttention],
237
142
  );
238
143
 
239
- return (
240
- <Fragment>
241
- {paddingTop > 0 && (
242
- <tbody role='none'>
243
- <tr role='none'>
244
- <td style={{ height: `${paddingTop}px` }} role='none' />
245
- </tr>
246
- </tbody>
247
- )}
248
- <TableBody rows={rowsToRender} />
249
- {paddingBottom > 0 && (
250
- <tbody role='none'>
251
- <tr role='none'>
252
- <td style={{ height: `${paddingBottom}px` }} role='none' />
253
- </tr>
254
- </tbody>
255
- )}
256
- </Fragment>
144
+ // TODO(burdon): Factor out?
145
+ const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
146
+ async ({ field, props }, _text) => {
147
+ if (model && props.referenceSchema && field.referencePath) {
148
+ const space = getSpace(model.table);
149
+ invariant(space);
150
+ const schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
151
+ if (schema) {
152
+ // TODO(burdon): Cache/filter.
153
+ const { objects } = await space.db.query(Filter.schema(schema)).run();
154
+ return objects
155
+ .map((obj) => {
156
+ const value = getValue(obj, field.referencePath!);
157
+ if (!value || typeof value !== 'string') {
158
+ return undefined;
159
+ }
160
+
161
+ return {
162
+ label: value,
163
+ data: obj,
164
+ };
165
+ })
166
+ .filter(isNotFalsy);
167
+ }
168
+ }
169
+
170
+ return [];
171
+ },
172
+ [model],
257
173
  );
258
- };
259
-
260
- export const MemoizedVirtualizedTableContent = memo(VirtualizedTableContent);
261
174
 
262
- const GroupedTableContent = () => {
263
- const {
264
- table: { getGroupedRowModel, getHeaderGroups, getState },
265
- debug,
266
- } = useTableContext();
175
+ if (!model) {
176
+ return <span role='none' className='attention-surface' />;
177
+ }
267
178
 
268
179
  return (
269
180
  <>
270
- {getGroupedRowModel().rows.map((row, i) => {
271
- return (
272
- <Fragment key={i}>
273
- {/* TODO(burdon): Customize group header renderer. */}
274
- <thead>
275
- <tr>
276
- {debug && <th />}
277
- <th
278
- // TODO(burdon): Calculate row span.
279
- colSpan={getHeaderGroups()[0].headers.length}
280
- className={groupTh({})}
281
- >
282
- {getState().grouping[0]}[{String(row.getGroupingValue(getState().grouping[0]))}]
283
- </th>
284
- </tr>
285
- </thead>
286
- <TableBody rows={row.subRows} />
287
- </Fragment>
288
- );
289
- })}
181
+ <Grid.Root id={model.table.id ?? 'table-grid'}>
182
+ <TableCellEditor model={model} onEnter={handleEnter} onFocus={handleFocus} onQuery={handleQuery} />
183
+
184
+ <Grid.Content
185
+ onWheelCapture={handleWheel}
186
+ className={mx(
187
+ '[--dx-grid-base:var(--surface-bg)] [&_.dx-grid]:grow [&_.dx-grid]:max-is-max [&_.dx-grid]:bs-0 [&_.dx-grid]:max-bs-max',
188
+ inlineEndLine,
189
+ blockEndLine,
190
+ )}
191
+ frozen={frozen}
192
+ columns={model.columnMeta.value}
193
+ limitRows={model.getRowCount() ?? 0}
194
+ limitColumns={model.table.view?.fields?.length ?? 0}
195
+ onAxisResize={handleAxisResize}
196
+ onClick={model?.handleGridClick}
197
+ onKeyDown={handleKeyDown}
198
+ overscroll='trap'
199
+ ref={setDxGrid}
200
+ />
201
+ </Grid.Root>
202
+
203
+ <RowActionsMenu model={model} />
204
+ <ColumnActionsMenu model={model} />
205
+ <ColumnSettings model={model} />
290
206
  </>
291
207
  );
292
- };
208
+ });
209
+
210
+ //
211
+ // CellEditor
212
+ //
293
213
 
294
- // TODO(burdon): Floating "add row" jumps by 1 pixel on scroll.
295
- // TODO(burdon): Blue focus highlight is clipped by 1px on the left.
296
- // TODO(burdon): Focused row and focus ring passes over the sticky header.
297
214
  export const Table = {
298
215
  Root: TableRoot,
299
- Viewport: TableViewport,
300
- Main: TablePrimitive,
216
+ Main: TableMain,
301
217
  };
@@ -1,8 +1,5 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './props';
6
5
  export * from './Table';
7
- export * from './TableContext';
8
- export * from './TableRootContext';
@@ -0,0 +1,114 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type Meta, type StoryObj } from '@storybook/react';
8
+ import React, { useEffect, useMemo, useState } from 'react';
9
+
10
+ import { type MutableSchema } from '@dxos/echo-schema';
11
+ import { invariant } from '@dxos/invariant';
12
+ import { faker } from '@dxos/random';
13
+ import { Filter, useSpaces, useQuery, create } from '@dxos/react-client/echo';
14
+ import { withClientProvider } from '@dxos/react-client/testing';
15
+ import { defaultSizeRow, Grid, type GridEditing } from '@dxos/react-ui-grid';
16
+ import { ViewProjection, ViewType } from '@dxos/schema';
17
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
18
+
19
+ import { TableCellEditor, type TableCellEditorProps } from './TableCellEditor';
20
+ import { useTableModel } from '../../hooks';
21
+ import translations from '../../translations';
22
+ import { TableType } from '../../types';
23
+ import { initializeTable } from '../../util';
24
+
25
+ type StoryProps = {
26
+ editing: GridEditing;
27
+ };
28
+
29
+ const DefaultStory = ({ editing }: StoryProps) => {
30
+ const spaces = useSpaces();
31
+ const space = spaces[spaces.length - 1];
32
+ const tables = useQuery(space, Filter.schema(TableType));
33
+ const [table, setTable] = useState<TableType>();
34
+ const [schema, setSchema] = useState<MutableSchema>();
35
+ useEffect(() => {
36
+ if (tables.length && !table) {
37
+ const table = tables[0];
38
+ invariant(table.view);
39
+ setTable(table);
40
+ setSchema(space.db.schemaRegistry.getSchema(table.view.query.type));
41
+ }
42
+ }, [tables]);
43
+
44
+ const projection = useMemo(() => {
45
+ if (schema && table?.view) {
46
+ return new ViewProjection(schema, table.view);
47
+ }
48
+ }, [schema, table?.view]);
49
+
50
+ const model = useTableModel({ table, projection });
51
+
52
+ const handleQuery: TableCellEditorProps['onQuery'] = async ({ field }) => {
53
+ const { objects } = await space.db.query(schema).run();
54
+ return objects.map((obj) => {
55
+ const label = obj[field.referencePath ?? 'id'];
56
+ return {
57
+ label,
58
+ data: obj,
59
+ };
60
+ });
61
+ };
62
+
63
+ if (!model || !schema || !table) {
64
+ return <div />;
65
+ }
66
+
67
+ return (
68
+ <div className='flex w-[300px] border border-separator' style={{ height: defaultSizeRow }}>
69
+ <Grid.Root id='test' editing={editing}>
70
+ <TableCellEditor model={model} onQuery={handleQuery} />
71
+ </Grid.Root>
72
+ </div>
73
+ );
74
+ };
75
+
76
+ const meta: Meta<StoryProps> = {
77
+ title: 'plugins/plugin-table/TableCellEditor',
78
+ component: DefaultStory,
79
+ render: DefaultStory,
80
+ parameters: { translations, layout: 'centered' },
81
+ decorators: [
82
+ withClientProvider({
83
+ types: [TableType, ViewType],
84
+ createIdentity: true,
85
+ createSpace: true,
86
+ onSpaceCreated: ({ space }) => {
87
+ const table = space.db.add(create(TableType, {}));
88
+ const schema = initializeTable({ space, table });
89
+ Array.from({ length: 10 }).forEach(() => {
90
+ space.db.add(
91
+ create(schema, {
92
+ name: faker.person.fullName(),
93
+ }),
94
+ );
95
+ });
96
+ },
97
+ }),
98
+ withTheme,
99
+ withLayout({ tooltips: true }),
100
+ ],
101
+ };
102
+
103
+ export default meta;
104
+
105
+ type Story = StoryObj<StoryProps>;
106
+
107
+ export const Default: Story = {
108
+ args: {
109
+ editing: {
110
+ index: '0,3',
111
+ initialContent: 'Test',
112
+ },
113
+ },
114
+ };