@dxos/react-ui-table 0.6.14-staging.e15392e → 0.7.1-staging.599df14

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 +1274 -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 +1380 -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 +1399 -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 +13 -15
  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 +14 -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 +177 -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 +87 -0
  160. package/src/components/Toolbar/common.tsx +71 -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
@@ -0,0 +1,565 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { computed, effect, signal, type ReadonlySignal } from '@preact/signals-core';
6
+ import orderBy from 'lodash.orderby';
7
+
8
+ import { Resource } from '@dxos/context';
9
+ import { getValue, setValue, FormatEnum, type JsonProp } from '@dxos/echo-schema';
10
+ import { invariant } from '@dxos/invariant';
11
+ import { PublicKey } from '@dxos/react-client';
12
+ import {
13
+ cellClassesForFieldType,
14
+ cellClassesForRowSelection,
15
+ formatForDisplay,
16
+ formatForEditing,
17
+ parseValue,
18
+ } from '@dxos/react-ui-form';
19
+ import {
20
+ type DxGridAxisMeta,
21
+ type DxGridCellValue,
22
+ type DxGridPlane,
23
+ type DxGridPlaneCells,
24
+ type DxGridPlaneRange,
25
+ } from '@dxos/react-ui-grid';
26
+ import { mx } from '@dxos/react-ui-theme';
27
+ import { VIEW_FIELD_LIMIT, type ViewProjection, type FieldType } from '@dxos/schema';
28
+
29
+ import { ModalController } from './modal-controller';
30
+ import { SelectionModel } from './selection-model';
31
+ import { type TableType } from '../types';
32
+ import { fromGridCell, type GridCell } from '../util';
33
+ import { tableButtons, touch } from '../util';
34
+ import { tableControls } from '../util/table-controls';
35
+
36
+ export type SortDirection = 'asc' | 'desc';
37
+ export type SortConfig = { fieldId: string; direction: SortDirection };
38
+
39
+ export type BaseTableRow = Record<JsonProp, any> & { id: string };
40
+
41
+ export type TableModelProps<T extends BaseTableRow = { id: string }> = {
42
+ table: TableType;
43
+ projection: ViewProjection;
44
+ sorting?: SortConfig[];
45
+ pinnedRows?: { top: number[]; bottom: number[] };
46
+ onInsertRow?: (index?: number) => void;
47
+ onDeleteRows?: (index: number, obj: T[]) => void;
48
+ onDeleteColumn?: (fieldId: string) => void;
49
+ onCellUpdate?: (cell: GridCell) => void;
50
+ onRowOrderChanged?: () => void;
51
+ };
52
+
53
+ export class TableModel<T extends BaseTableRow = { id: string }> extends Resource {
54
+ public readonly id = `table-model-${PublicKey.random().truncate()}`;
55
+
56
+ private readonly _table: TableType;
57
+ private readonly _projection: ViewProjection;
58
+
59
+ private readonly _visibleRange = signal<DxGridPlaneRange>({
60
+ start: { row: 0, col: 0 },
61
+ end: { row: 0, col: 0 },
62
+ });
63
+
64
+ /**
65
+ * Maps display indices to data indices.
66
+ * Used for translating between sorted/displayed order and original data order.
67
+ * Keys are display indices, values are corresponding data indices.
68
+ */
69
+ private readonly _displayToDataIndex: Map<number, number> = new Map();
70
+
71
+ private readonly _onInsertRow?: TableModelProps<T>['onInsertRow'];
72
+ private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
73
+ private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
74
+ private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
75
+ private readonly _onRowOrderChanged?: TableModelProps<T>['onRowOrderChanged'];
76
+
77
+ private readonly _sorting = signal<SortConfig | undefined>(undefined);
78
+ private _sortedRows!: ReadonlySignal<T[]>;
79
+ private _rows = signal<T[]>([]);
80
+
81
+ private _pinnedRows: NonNullable<TableModelProps<T>['pinnedRows']>;
82
+
83
+ private _selection!: SelectionModel<T>;
84
+ private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
85
+ private readonly _modalController = new ModalController();
86
+
87
+ constructor({
88
+ table,
89
+ projection,
90
+ sorting = [],
91
+ pinnedRows = { top: [], bottom: [] },
92
+ onCellUpdate,
93
+ onDeleteColumn,
94
+ onDeleteRows,
95
+ onInsertRow,
96
+ onRowOrderChanged,
97
+ }: TableModelProps<T>) {
98
+ super();
99
+ this._table = table;
100
+ this._projection = projection;
101
+
102
+ this._sorting.value = sorting.at(0);
103
+ this._pinnedRows = pinnedRows;
104
+ this._onInsertRow = onInsertRow;
105
+ this._onDeleteRows = onDeleteRows;
106
+ this._onDeleteColumn = onDeleteColumn;
107
+ this._onCellUpdate = onCellUpdate;
108
+ this._onRowOrderChanged = onRowOrderChanged;
109
+ }
110
+
111
+ public get table() {
112
+ return this._table;
113
+ }
114
+
115
+ public get projection() {
116
+ return this._projection;
117
+ }
118
+
119
+ public get pinnedRows(): NonNullable<TableModelProps<T>['pinnedRows']> {
120
+ return this._pinnedRows;
121
+ }
122
+
123
+ public get columnMeta(): ReadonlySignal<DxGridAxisMeta> {
124
+ invariant(this._columnMeta);
125
+ return this._columnMeta;
126
+ }
127
+
128
+ public get sorting(): SortConfig | undefined {
129
+ return this._sorting.value;
130
+ }
131
+
132
+ public get modalController() {
133
+ return this._modalController;
134
+ }
135
+
136
+ public get selection() {
137
+ return this._selection;
138
+ }
139
+
140
+ //
141
+ // Initialisation
142
+ //
143
+
144
+ protected override async _open() {
145
+ this.initializeColumnMeta();
146
+ this.initializeSorting();
147
+ this.initializeEffects();
148
+ this._selection = new SelectionModel(this._sortedRows, () => this._onRowOrderChanged?.());
149
+ await this._selection.open(this._ctx);
150
+ }
151
+
152
+ private initializeColumnMeta(): void {
153
+ this._columnMeta = computed(() => {
154
+ const fields = this._table.view?.fields ?? [];
155
+ const meta = Object.fromEntries(
156
+ fields.map((field, index: number) => [index, { size: field?.size ?? 256, resizeable: true }]),
157
+ );
158
+
159
+ return {
160
+ grid: meta,
161
+ frozenColsStart: { 0: { size: 30, resizeable: false } },
162
+ frozenColsEnd: { 0: { size: 40, resizeable: false } },
163
+ };
164
+ });
165
+ }
166
+
167
+ private initializeSorting(): void {
168
+ this._sortedRows = computed(() => {
169
+ this._displayToDataIndex.clear();
170
+ const sort = this._sorting.value;
171
+ if (!sort) {
172
+ return this._rows.value;
173
+ }
174
+
175
+ const field = this._table.view?.fields.find((field) => field.id === sort.fieldId);
176
+ if (!field) {
177
+ return this._rows.value;
178
+ }
179
+
180
+ const dataWithIndices = this._rows.value.map((item, index) => {
181
+ const value = getValue(item, field.path);
182
+ return { item, index, value, isEmpty: value == null || value === '' };
183
+ });
184
+ const sorted = orderBy(dataWithIndices, ['isEmpty', 'value'], ['asc', sort.direction]);
185
+
186
+ for (let displayIndex = 0; displayIndex < sorted.length; displayIndex++) {
187
+ const { index: dataIndex } = sorted[displayIndex];
188
+ if (displayIndex !== dataIndex) {
189
+ this._displayToDataIndex.set(displayIndex, dataIndex);
190
+ }
191
+ }
192
+
193
+ return sorted.map(({ item }) => item);
194
+ });
195
+ }
196
+
197
+ private initializeEffects(): void {
198
+ const rowOrderWatcher = effect(() => {
199
+ touch(this._sortedRows.value);
200
+ this._onRowOrderChanged?.();
201
+ });
202
+ this._ctx.onDispose(rowOrderWatcher);
203
+
204
+ /**
205
+ * Creates reactive effects for each row in the visible range.
206
+ * Subscribes to changes in the visible range, recreating row effects when it changes.
207
+ * When a row's data changes, invokes onCellUpdate to notify subscribers and trigger UI updates.
208
+ */
209
+ const rowEffectManager = effect(() => {
210
+ const { start, end } = touch(this._visibleRange.value);
211
+ const rowEffects: ReturnType<typeof effect>[] = [];
212
+ for (let row = start.row; row <= end.row; row++) {
213
+ rowEffects.push(
214
+ effect(() => {
215
+ const obj = this._sortedRows.value[row];
216
+ this?._table?.view?.fields.forEach((field) => touch(getValue(obj, field.path)));
217
+ this._onCellUpdate?.({ row, col: start.col });
218
+ }),
219
+ );
220
+ }
221
+
222
+ return () => rowEffects.forEach((cleanup) => cleanup());
223
+ });
224
+ this._ctx.onDispose(rowEffectManager);
225
+ }
226
+
227
+ //
228
+ // Get Cells
229
+ //
230
+
231
+ public getCells = (range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells => {
232
+ switch (plane) {
233
+ case 'grid': {
234
+ this._visibleRange.value = range;
235
+ return this.getMainGridCells(range);
236
+ }
237
+ case 'frozenRowsStart': {
238
+ return this.getHeaderCells(range);
239
+ }
240
+ case 'frozenColsStart': {
241
+ return this.getSelectionColumnCells(range);
242
+ }
243
+ case 'frozenColsEnd': {
244
+ return this.getActionColumnCells(range);
245
+ }
246
+ case 'fixedStartStart': {
247
+ return this.getSelectAllCell();
248
+ }
249
+ case 'fixedStartEnd': {
250
+ return this.getNewColumnCell();
251
+ }
252
+ default: {
253
+ return {};
254
+ }
255
+ }
256
+ };
257
+
258
+ private getMainGridCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
259
+ const values: DxGridPlaneCells = {};
260
+ const fields = this._table.view?.fields ?? [];
261
+
262
+ const addCell = (obj: T, field: FieldType, colIndex: number, displayIndex: number): void => {
263
+ const { props } = this._projection.getFieldProjection(field.id);
264
+ const value: DxGridCellValue = {
265
+ get value() {
266
+ const value = getValue(obj, field.path);
267
+ if (value == null) {
268
+ return '';
269
+ }
270
+
271
+ switch (props.format) {
272
+ case FormatEnum.Ref: {
273
+ if (!field.referencePath) {
274
+ return ''; // TODO(burdon): Show error.
275
+ }
276
+
277
+ return getValue(value, field.referencePath);
278
+ }
279
+
280
+ default: {
281
+ return formatForDisplay({ type: props.type, format: props.format, value });
282
+ }
283
+ }
284
+ },
285
+ };
286
+
287
+ const classes = [];
288
+ const formatClasses = cellClassesForFieldType({ type: props.type, format: props.format });
289
+ if (formatClasses) {
290
+ classes.push(formatClasses);
291
+ }
292
+ const rowSelectionClasses = cellClassesForRowSelection(this._selection.isObjectSelected(obj));
293
+ if (rowSelectionClasses) {
294
+ classes.push(rowSelectionClasses);
295
+ }
296
+ if (classes.length > 0) {
297
+ value.className = mx(classes.flat());
298
+ }
299
+
300
+ const idx = fromGridCell({ col: colIndex, row: displayIndex });
301
+ values[idx] = value;
302
+ };
303
+
304
+ for (let row = range.start.row; row <= range.end.row && row < this._sortedRows.value.length; row++) {
305
+ for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
306
+ const field = fields[col];
307
+ if (!field) {
308
+ continue;
309
+ }
310
+
311
+ addCell(this._sortedRows.value[row], field, col, row);
312
+ }
313
+ }
314
+
315
+ return values;
316
+ };
317
+
318
+ private getHeaderCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
319
+ const values: DxGridPlaneCells = {};
320
+ const fields = this.table.view?.fields ?? [];
321
+ for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
322
+ const { field, props } = this._projection.getFieldProjection(fields[col].id);
323
+ values[fromGridCell({ col, row: 0 })] = {
324
+ value: props.title ?? field.path,
325
+ readonly: true,
326
+ resizeHandle: 'col',
327
+ accessoryHtml: tableButtons.columnSettings.render({ fieldId: field.id }),
328
+ };
329
+ }
330
+
331
+ return values;
332
+ };
333
+
334
+ private getSelectionColumnCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
335
+ const values: DxGridPlaneCells = {};
336
+ for (let row = range.start.row; row <= range.end.row && row < this._rows.value.length; row++) {
337
+ const isSelected = this._selection.isRowIndexSelected(row);
338
+ const classes = cellClassesForRowSelection(isSelected);
339
+ values[fromGridCell({ col: 0, row })] = {
340
+ value: '',
341
+ readonly: true,
342
+ className: classes ? mx(classes) : undefined,
343
+ accessoryHtml: tableControls.checkbox.render({ rowIndex: row, checked: isSelected }),
344
+ };
345
+ }
346
+
347
+ return values;
348
+ };
349
+
350
+ private getActionColumnCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
351
+ const values: DxGridPlaneCells = {};
352
+ for (let row = range.start.row; row <= range.end.row && row < this._rows.value.length; row++) {
353
+ const isSelected = this._selection.isRowIndexSelected(row);
354
+ const classes = cellClassesForRowSelection(isSelected);
355
+ values[fromGridCell({ col: 0, row })] = {
356
+ value: '',
357
+ readonly: true,
358
+ className: classes ? mx(classes) : undefined,
359
+ accessoryHtml: tableButtons.rowMenu.render({ rowIndex: row }),
360
+ };
361
+ }
362
+
363
+ return values;
364
+ };
365
+
366
+ private getSelectAllCell = (): DxGridPlaneCells => {
367
+ return {
368
+ [fromGridCell({ col: 0, row: 0 })]: {
369
+ value: '',
370
+ accessoryHtml: tableControls.checkbox.render({
371
+ rowIndex: 0,
372
+ header: true,
373
+ checked: this._selection.allRowsSeleted.value,
374
+ }),
375
+ readonly: true,
376
+ },
377
+ };
378
+ };
379
+
380
+ private getNewColumnCell = (): DxGridPlaneCells => {
381
+ return {
382
+ [fromGridCell({ col: 0, row: 0 })]: {
383
+ value: '',
384
+ accessoryHtml: tableButtons.addColumn.render({
385
+ disabled: (this._table.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
386
+ }),
387
+ readonly: true,
388
+ },
389
+ };
390
+ };
391
+
392
+ //
393
+ // Data
394
+ //
395
+
396
+ public setRows = (obj: T[]): void => {
397
+ this._rows.value = obj;
398
+ };
399
+
400
+ public getRowCount = (): number => this._rows.value.length;
401
+
402
+ public getColumnCount = (): number => this.table.view?.fields.length ?? 0;
403
+
404
+ public insertRow = (rowIndex?: number): void => {
405
+ const row = rowIndex !== undefined ? this._displayToDataIndex.get(rowIndex) ?? rowIndex : this._rows.value.length;
406
+ this._onInsertRow?.(row);
407
+ };
408
+
409
+ public deleteRow = (rowIndex: number): void => {
410
+ const row = this._displayToDataIndex.get(rowIndex) ?? rowIndex;
411
+ const obj = this._rows.value[row];
412
+ const objectsToDelete = [];
413
+
414
+ if (this._selection.hasSelection.value) {
415
+ const selectedRows = this._selection.getSelectedRows();
416
+ objectsToDelete.push(...selectedRows);
417
+ } else {
418
+ objectsToDelete.push(obj);
419
+ }
420
+
421
+ this._onDeleteRows?.(row, objectsToDelete);
422
+ };
423
+
424
+ public getCellData = ({ col, row }: GridCell): any => {
425
+ const fields = this.table.view?.fields ?? [];
426
+ if (col < 0 || col >= fields.length) {
427
+ return undefined;
428
+ }
429
+
430
+ const field = fields[col];
431
+ const dataIndex = this._displayToDataIndex.get(row) ?? row;
432
+ const value = getValue(this._rows.value[dataIndex], field.path);
433
+ if (value == null) {
434
+ return '';
435
+ }
436
+
437
+ const { props } = this._projection.getFieldProjection(field.id);
438
+ switch (props.format) {
439
+ case FormatEnum.Ref: {
440
+ if (!field.referencePath) {
441
+ return ''; // TODO(burdon): Show error.
442
+ }
443
+
444
+ return getValue(value, field.referencePath);
445
+ }
446
+
447
+ default: {
448
+ return formatForEditing({ type: props.type, format: props.format, value });
449
+ }
450
+ }
451
+ };
452
+
453
+ public setCellData = ({ col, row }: GridCell, value: any): void => {
454
+ const rowIdx = this._displayToDataIndex.get(row) ?? row;
455
+ const fields = this.table.view?.fields ?? [];
456
+ if (col < 0 || col >= fields.length) {
457
+ return;
458
+ }
459
+
460
+ const field = fields[col];
461
+ const { props } = this._projection.getFieldProjection(field.id);
462
+ switch (props.format) {
463
+ case FormatEnum.Ref: {
464
+ setValue(this._rows.value[rowIdx], field.path, value);
465
+ break;
466
+ }
467
+
468
+ default: {
469
+ setValue(
470
+ this._rows.value[rowIdx],
471
+ field.path,
472
+ parseValue({
473
+ type: props.type,
474
+ format: props.format,
475
+ value,
476
+ }),
477
+ );
478
+ break;
479
+ }
480
+ }
481
+ };
482
+
483
+ //
484
+ // Column Operations
485
+ //
486
+
487
+ public deleteColumn(fieldId: string): void {
488
+ if (!this.table.view) {
489
+ return;
490
+ }
491
+
492
+ const field = this.table.view.fields.find((field) => field.id === fieldId);
493
+ if (field && this._onDeleteColumn) {
494
+ if (this._sorting.value?.fieldId === fieldId) {
495
+ this.clearSort();
496
+ }
497
+ this._onDeleteColumn(field.id);
498
+ }
499
+ }
500
+
501
+ //
502
+ // Interactions
503
+ //
504
+ public handleGridClick = (event: React.MouseEvent): void => {
505
+ if (!this._modalController.handleClick(event)) {
506
+ const target = event.target as HTMLElement;
507
+
508
+ const selectionCheckbox = target.closest(`input[${tableControls.checkbox.attributes.checkbox}]`);
509
+ if (selectionCheckbox) {
510
+ const isHeader = selectionCheckbox.hasAttribute(tableControls.checkbox.attributes.header);
511
+ if (isHeader) {
512
+ if (this._selection.allRowsSeleted.value) {
513
+ this._selection.setSelection('none');
514
+ } else {
515
+ this._selection.setSelection('all');
516
+ }
517
+ } else {
518
+ const rowIndex = Number(selectionCheckbox.getAttribute(tableControls.checkbox.attributes.checkbox));
519
+ this._selection.toggleSelectionForRowIndex(rowIndex);
520
+ }
521
+ }
522
+ }
523
+ };
524
+
525
+ //
526
+ // Resize
527
+ //
528
+
529
+ public setColumnWidth(columnIndex: number, width: number): void {
530
+ const fields = this.table.view?.fields ?? [];
531
+ if (columnIndex < fields.length) {
532
+ const newWidth = Math.max(0, width);
533
+ const field = fields[columnIndex];
534
+ if (field) {
535
+ field.size = newWidth;
536
+ }
537
+ }
538
+ }
539
+
540
+ //
541
+ // Sorting
542
+ //
543
+
544
+ public setSort(fieldId: string, direction: SortDirection): void {
545
+ this._sorting.value = { fieldId, direction };
546
+ }
547
+
548
+ public clearSort(): void {
549
+ this._sorting.value = undefined;
550
+ }
551
+
552
+ //
553
+ // Pinning
554
+ //
555
+
556
+ // TODO(burdon): Change to setPinned(on/off).
557
+ public pinRow(rowIndex: number, side: 'top' | 'bottom'): void {
558
+ this._pinnedRows[side].push(rowIndex);
559
+ }
560
+
561
+ public unpinRow(rowIndex: number): void {
562
+ this._pinnedRows.top = this._pinnedRows.top.filter((index: number) => index !== rowIndex);
563
+ this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index: number) => index !== rowIndex);
564
+ }
565
+ }
@@ -8,19 +8,26 @@ export default [
8
8
  {
9
9
  'en-US': {
10
10
  [translationKey]: {
11
- 'no selection label': ' ',
12
- 'number column type label': 'Number',
13
- 'boolean column type label': 'Boolean',
14
- 'date column type label': 'Date',
15
- 'string column type label': 'Text',
16
- 'ref column type label': 'Reference',
17
- 'digits label': 'Decimal places',
18
- 'save label': 'Save',
19
- 'delete label': 'Delete',
20
- 'column label label': 'Label',
21
- 'property key label': 'Property',
22
- 'column type label': 'Type',
11
+ 'object placeholder': 'New table',
12
+ 'create object label': 'Create table',
13
+ 'table name placeholder': 'Table name',
14
+ 'settings title': 'Table settings',
15
+ 'table schema label': 'Schema',
16
+ 'new schema': 'New schema',
17
+ 'continue label': 'Continue',
18
+ 'create stack section label': 'Create table',
19
+ 'add row': 'Add row',
20
+ 'create comment': 'Create thread',
21
+ 'column action sort descending': 'Sort Descending',
22
+ 'column action sort ascending': 'Sort Ascending',
23
+ 'column action clear sorting': 'Clear Sorting',
24
+ 'column action settings': 'Column Settings',
25
+ 'column action delete': 'Delete Column',
26
+ 'delete row label': 'Delete row',
27
+ 'bulk delete row label': 'Delete selected rows',
28
+ 'column deleted label': 'Column deleted',
29
+ 'new column button label': 'Create column',
23
30
  },
24
31
  },
25
32
  },
26
- ];
33
+ ] as const;
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './table';
@@ -0,0 +1,13 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { ref, S, TypedObject } from '@dxos/echo-schema';
6
+ import { ThreadType } from '@dxos/plugin-space';
7
+ import { ViewType } from '@dxos/schema';
8
+
9
+ export class TableType extends TypedObject({ typename: 'dxos.org/type/Table', version: '0.1.0' })({
10
+ name: S.optional(S.String),
11
+ view: S.optional(ref(ViewType)),
12
+ threads: S.optional(S.mutable(S.Array(ref(ThreadType)))),
13
+ }) {}
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type DxGridCellIndex } from '@dxos/react-ui-grid';
6
+
7
+ // TODO(burdon): Factor out to grid.
8
+ export type GridCell = { col: number; row: number };
9
+
10
+ /**
11
+ * Generates a string key for a cell based on its column and row indices.
12
+ */
13
+ export const fromGridCell = ({ col, row }: GridCell): DxGridCellIndex => {
14
+ return `${col},${row}` as const; // TODO(burdon): ":" delim.
15
+ };
16
+
17
+ /**
18
+ * Converts a cell key string back into column and row coordinates.
19
+ */
20
+ export const toGridCell = (cell: DxGridCellIndex): GridCell => {
21
+ const [col, row] = cell.split(',').map(Number);
22
+ return { row, col };
23
+ };
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './grid';
6
+ export * from './signals';
7
+ export * from './table';
8
+ export * from './table-buttons';
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ /**
6
+ * Explicitly marks signal dependencies for tracking within effects.
7
+ * Returns the passed value to enable inline usage.
8
+ */
9
+ export const touch = <T>(value: T) => value;