@dxos/react-ui-table 0.8.1-main.ba2dec9 → 0.8.1-staging.31c3ee1

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 (191) hide show
  1. package/dist/lib/browser/{chunk-3FR6NHPD.mjs → chunk-YWNRUF4J.mjs} +8 -6
  2. package/dist/lib/browser/chunk-YWNRUF4J.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +625 -225
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types/index.mjs +1 -1
  7. package/dist/lib/node/{chunk-NB3SFHGA.cjs → chunk-TGG6OZ54.cjs} +10 -8
  8. package/dist/lib/node/chunk-TGG6OZ54.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +618 -220
  10. package/dist/lib/node/index.cjs.map +4 -4
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/types/index.cjs +3 -3
  13. package/dist/lib/node/types/index.cjs.map +1 -1
  14. package/dist/lib/node-esm/{chunk-RLVVOZW5.mjs → chunk-KKLP54L3.mjs} +8 -6
  15. package/dist/lib/node-esm/chunk-KKLP54L3.mjs.map +7 -0
  16. package/dist/lib/node-esm/index.mjs +625 -225
  17. package/dist/lib/node-esm/index.mjs.map +4 -4
  18. package/dist/lib/node-esm/meta.json +1 -1
  19. package/dist/lib/node-esm/types/index.mjs +1 -1
  20. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  21. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  22. package/dist/types/src/components/Table/DynamicTable.d.ts +24 -0
  23. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -0
  24. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +10 -0
  25. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -0
  26. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/Table.d.ts +1 -0
  28. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Table.stories.d.ts +4 -3
  30. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/index.d.ts +1 -0
  32. package/dist/types/src/components/Table/index.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/relations.stories.d.ts +9 -0
  34. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -0
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/testing.d.ts +7 -7
  38. package/dist/types/src/components/testing.d.ts.map +1 -1
  39. package/dist/types/src/hooks/useTableModel.d.ts +8 -9
  40. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  41. package/dist/types/src/model/index.d.ts +1 -0
  42. package/dist/types/src/model/index.d.ts.map +1 -1
  43. package/dist/types/src/model/selection-model.d.ts +6 -3
  44. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  45. package/dist/types/src/model/table-model.d.ts +42 -16
  46. package/dist/types/src/model/table-model.d.ts.map +1 -1
  47. package/dist/types/src/model/table-presentation.d.ts +4 -4
  48. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  49. package/dist/types/src/model/table-sorting.d.ts +2 -2
  50. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  51. package/dist/types/src/types/table.d.ts +7 -32
  52. package/dist/types/src/types/table.d.ts.map +1 -1
  53. package/dist/types/src/util/dynamic-table.d.ts +31 -0
  54. package/dist/types/src/util/dynamic-table.d.ts.map +1 -0
  55. package/dist/types/src/util/index.d.ts +1 -0
  56. package/dist/types/src/util/index.d.ts.map +1 -1
  57. package/dist/types/src/util/table-controls.d.ts +2 -2
  58. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  59. package/dist/types/src/util/table.d.ts +5 -3
  60. package/dist/types/src/util/table.d.ts.map +1 -1
  61. package/package.json +38 -37
  62. package/src/components/Table/ColumnActionsMenu.tsx +6 -4
  63. package/src/components/Table/ColumnSettings.tsx +5 -6
  64. package/src/components/Table/CreateRefPanel.tsx +3 -3
  65. package/src/components/Table/DynamicTable.stories.tsx +155 -0
  66. package/src/components/Table/DynamicTable.tsx +104 -0
  67. package/src/components/Table/RefPanel.tsx +3 -3
  68. package/src/components/Table/RowActionsMenu.tsx +18 -3
  69. package/src/components/Table/Table.stories.tsx +170 -115
  70. package/src/components/Table/Table.tsx +71 -26
  71. package/src/components/Table/index.ts +1 -0
  72. package/src/components/Table/relations.stories.tsx +113 -0
  73. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +15 -5
  74. package/src/components/TableCellEditor/TableCellEditor.tsx +102 -4
  75. package/src/components/testing.ts +1 -1
  76. package/src/hooks/useTableModel.ts +35 -14
  77. package/src/model/index.ts +1 -0
  78. package/src/model/selection-model.ts +12 -2
  79. package/src/model/table-model.test.ts +2 -2
  80. package/src/model/table-model.ts +117 -32
  81. package/src/model/table-presentation.test.ts +2 -2
  82. package/src/model/table-presentation.ts +94 -22
  83. package/src/model/table-sorting.ts +3 -2
  84. package/src/playwright/smoke.spec.ts +2 -3
  85. package/src/types/table.ts +9 -10
  86. package/src/util/dynamic-table.ts +103 -0
  87. package/src/util/index.ts +1 -0
  88. package/src/util/table-controls.ts +4 -4
  89. package/src/util/table.ts +68 -65
  90. package/dist/lib/browser/_deprecated/index.mjs +0 -1483
  91. package/dist/lib/browser/_deprecated/index.mjs.map +0 -7
  92. package/dist/lib/browser/chunk-3FR6NHPD.mjs.map +0 -7
  93. package/dist/lib/node/_deprecated/index.cjs +0 -1511
  94. package/dist/lib/node/_deprecated/index.cjs.map +0 -7
  95. package/dist/lib/node/chunk-NB3SFHGA.cjs.map +0 -7
  96. package/dist/lib/node-esm/_deprecated/index.mjs +0 -1485
  97. package/dist/lib/node-esm/_deprecated/index.mjs.map +0 -7
  98. package/dist/lib/node-esm/chunk-RLVVOZW5.mjs.map +0 -7
  99. package/dist/types/src/_deprecated/components/Cell/Cell.d.ts +0 -12
  100. package/dist/types/src/_deprecated/components/Cell/Cell.d.ts.map +0 -1
  101. package/dist/types/src/_deprecated/components/Cell/CellCombobox.d.ts +0 -13
  102. package/dist/types/src/_deprecated/components/Cell/CellCombobox.d.ts.map +0 -1
  103. package/dist/types/src/_deprecated/components/Cell/index.d.ts +0 -2
  104. package/dist/types/src/_deprecated/components/Cell/index.d.ts.map +0 -1
  105. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.d.ts +0 -23
  106. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.d.ts.map +0 -1
  107. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.stories.d.ts +0 -36
  108. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.stories.d.ts.map +0 -1
  109. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.d.ts +0 -15
  110. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.d.ts.map +0 -1
  111. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.stories.d.ts +0 -84
  112. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.stories.d.ts.map +0 -1
  113. package/dist/types/src/_deprecated/components/ColumnMenu/index.d.ts +0 -3
  114. package/dist/types/src/_deprecated/components/ColumnMenu/index.d.ts.map +0 -1
  115. package/dist/types/src/_deprecated/components/Table/Table.d.ts +0 -20
  116. package/dist/types/src/_deprecated/components/Table/Table.d.ts.map +0 -1
  117. package/dist/types/src/_deprecated/components/Table/Table.stories.d.ts +0 -116
  118. package/dist/types/src/_deprecated/components/Table/Table.stories.d.ts.map +0 -1
  119. package/dist/types/src/_deprecated/components/Table/TableBody.d.ts +0 -12
  120. package/dist/types/src/_deprecated/components/Table/TableBody.d.ts.map +0 -1
  121. package/dist/types/src/_deprecated/components/Table/TableContext.d.ts +0 -13
  122. package/dist/types/src/_deprecated/components/Table/TableContext.d.ts.map +0 -1
  123. package/dist/types/src/_deprecated/components/Table/TableHead.d.ts +0 -9
  124. package/dist/types/src/_deprecated/components/Table/TableHead.d.ts.map +0 -1
  125. package/dist/types/src/_deprecated/components/Table/TableRootContext.d.ts +0 -17
  126. package/dist/types/src/_deprecated/components/Table/TableRootContext.d.ts.map +0 -1
  127. package/dist/types/src/_deprecated/components/Table/index.d.ts +0 -5
  128. package/dist/types/src/_deprecated/components/Table/index.d.ts.map +0 -1
  129. package/dist/types/src/_deprecated/components/Table/props.d.ts +0 -42
  130. package/dist/types/src/_deprecated/components/Table/props.d.ts.map +0 -1
  131. package/dist/types/src/_deprecated/components/index.d.ts +0 -4
  132. package/dist/types/src/_deprecated/components/index.d.ts.map +0 -1
  133. package/dist/types/src/_deprecated/helpers.d.ts +0 -91
  134. package/dist/types/src/_deprecated/helpers.d.ts.map +0 -1
  135. package/dist/types/src/_deprecated/hooks/index.d.ts +0 -5
  136. package/dist/types/src/_deprecated/hooks/index.d.ts.map +0 -1
  137. package/dist/types/src/_deprecated/hooks/useColumnSizing.d.ts +0 -12
  138. package/dist/types/src/_deprecated/hooks/useColumnSizing.d.ts.map +0 -1
  139. package/dist/types/src/_deprecated/hooks/useColumnSorting.d.ts +0 -15
  140. package/dist/types/src/_deprecated/hooks/useColumnSorting.d.ts.map +0 -1
  141. package/dist/types/src/_deprecated/hooks/usePinLastRow.d.ts +0 -3
  142. package/dist/types/src/_deprecated/hooks/usePinLastRow.d.ts.map +0 -1
  143. package/dist/types/src/_deprecated/hooks/useRowSelection.d.ts +0 -7
  144. package/dist/types/src/_deprecated/hooks/useRowSelection.d.ts.map +0 -1
  145. package/dist/types/src/_deprecated/index.d.ts +0 -8
  146. package/dist/types/src/_deprecated/index.d.ts.map +0 -1
  147. package/dist/types/src/_deprecated/schema/Schema.stories.d.ts +0 -11
  148. package/dist/types/src/_deprecated/schema/Schema.stories.d.ts.map +0 -1
  149. package/dist/types/src/_deprecated/schema/column-utils.d.ts +0 -7
  150. package/dist/types/src/_deprecated/schema/column-utils.d.ts.map +0 -1
  151. package/dist/types/src/_deprecated/schema/index.d.ts +0 -3
  152. package/dist/types/src/_deprecated/schema/index.d.ts.map +0 -1
  153. package/dist/types/src/_deprecated/schema/types.d.ts +0 -27
  154. package/dist/types/src/_deprecated/schema/types.d.ts.map +0 -1
  155. package/dist/types/src/_deprecated/theme.d.ts +0 -29
  156. package/dist/types/src/_deprecated/theme.d.ts.map +0 -1
  157. package/dist/types/src/_deprecated/translations.d.ts +0 -21
  158. package/dist/types/src/_deprecated/translations.d.ts.map +0 -1
  159. package/dist/types/src/_deprecated/types.d.ts +0 -26
  160. package/dist/types/src/_deprecated/types.d.ts.map +0 -1
  161. package/src/_deprecated/components/Cell/Cell.tsx +0 -88
  162. package/src/_deprecated/components/Cell/CellCombobox.tsx +0 -103
  163. package/src/_deprecated/components/Cell/index.ts +0 -5
  164. package/src/_deprecated/components/ColumnMenu/ColumnMenu.stories.tsx +0 -129
  165. package/src/_deprecated/components/ColumnMenu/ColumnMenu.tsx +0 -182
  166. package/src/_deprecated/components/ColumnMenu/ColumnSettings.stories.tsx +0 -108
  167. package/src/_deprecated/components/ColumnMenu/ColumnSettings.tsx +0 -207
  168. package/src/_deprecated/components/ColumnMenu/index.ts +0 -6
  169. package/src/_deprecated/components/Table/Table.stories.tsx +0 -508
  170. package/src/_deprecated/components/Table/Table.tsx +0 -301
  171. package/src/_deprecated/components/Table/TableBody.tsx +0 -82
  172. package/src/_deprecated/components/Table/TableContext.tsx +0 -34
  173. package/src/_deprecated/components/Table/TableHead.tsx +0 -78
  174. package/src/_deprecated/components/Table/TableRootContext.ts +0 -39
  175. package/src/_deprecated/components/Table/index.ts +0 -8
  176. package/src/_deprecated/components/Table/props.ts +0 -60
  177. package/src/_deprecated/components/index.ts +0 -7
  178. package/src/_deprecated/helpers.tsx +0 -526
  179. package/src/_deprecated/hooks/index.ts +0 -8
  180. package/src/_deprecated/hooks/useColumnSizing.ts +0 -46
  181. package/src/_deprecated/hooks/useColumnSorting.ts +0 -64
  182. package/src/_deprecated/hooks/usePinLastRow.ts +0 -47
  183. package/src/_deprecated/hooks/useRowSelection.ts +0 -40
  184. package/src/_deprecated/index.ts +0 -12
  185. package/src/_deprecated/schema/Schema.stories.tsx +0 -59
  186. package/src/_deprecated/schema/column-utils.ts +0 -54
  187. package/src/_deprecated/schema/index.ts +0 -6
  188. package/src/_deprecated/schema/types.ts +0 -40
  189. package/src/_deprecated/theme.ts +0 -82
  190. package/src/_deprecated/translations.ts +0 -26
  191. package/src/_deprecated/types.ts +0 -38
@@ -4,11 +4,11 @@
4
4
 
5
5
  import { computed, effect, signal, type ReadonlySignal } from '@preact/signals-core';
6
6
 
7
+ import { type Space } from '@dxos/client/echo';
7
8
  import { Resource } from '@dxos/context';
8
9
  import { type FieldSortType, FormatEnum, getValue, setValue, type JsonProp } from '@dxos/echo-schema';
9
10
  import { invariant } from '@dxos/invariant';
10
11
  import { isReactiveObject, makeRef } from '@dxos/live-object';
11
- import { PublicKey } from '@dxos/react-client';
12
12
  import { formatForEditing, parseValue } from '@dxos/react-ui-form';
13
13
  import {
14
14
  type DxGridAxisMeta,
@@ -16,31 +16,53 @@ import {
16
16
  type DxGridPlanePosition,
17
17
  type DxGridPosition,
18
18
  } from '@dxos/react-ui-grid';
19
- import { type ViewProjection } from '@dxos/schema';
19
+ import { type ViewType, type ViewProjection } from '@dxos/schema';
20
20
 
21
- import { SelectionModel } from './selection-model';
21
+ import { type SelectionMode, SelectionModel } from './selection-model';
22
22
  import { TableSorting } from './table-sorting';
23
- import { type TableType } from '../types';
24
23
  import { touch } from '../util';
25
24
 
26
- export type BaseTableRow = Record<JsonProp, any> & { id: string };
25
+ // TODO(burdon): Use schema types.
26
+ export type TableRow = Record<JsonProp, any> & { id: string };
27
27
 
28
- export type TableModelProps<T extends BaseTableRow = { id: string }> = {
29
- table: TableType;
28
+ export type TableRowAction = {
29
+ id: string;
30
+ translationKey: string;
31
+ };
32
+
33
+ export type TableFeatures = {
34
+ selection: { enabled: boolean; mode?: SelectionMode };
35
+ dataEditable: boolean;
36
+ schemaEditable: boolean;
37
+ };
38
+
39
+ const defaultFeatures: TableFeatures = {
40
+ selection: { enabled: true, mode: 'multiple' },
41
+ dataEditable: false,
42
+ schemaEditable: false,
43
+ };
44
+
45
+ export type TableModelProps<T extends TableRow = TableRow> = {
46
+ id?: string;
47
+ space?: Space;
48
+ view?: ViewType;
30
49
  projection: ViewProjection;
50
+ features?: Partial<TableFeatures>;
31
51
  sorting?: FieldSortType[];
32
52
  pinnedRows?: { top: number[]; bottom: number[] };
53
+ rowActions?: TableRowAction[];
33
54
  onInsertRow?: (index?: number) => void;
34
55
  onDeleteRows?: (index: number, obj: T[]) => void;
35
56
  onDeleteColumn?: (fieldId: string) => void;
36
57
  onCellUpdate?: (cell: DxGridPosition) => void;
37
- onRowOrderChanged?: () => void;
58
+ onRowOrderChange?: () => void;
59
+ onRowAction?: (actionId: string, data: T) => void;
38
60
  };
39
61
 
40
- export class TableModel<T extends BaseTableRow = { id: string }> extends Resource {
41
- public readonly id = `table-model-${PublicKey.random().truncate()}`;
42
-
43
- private readonly _table: TableType;
62
+ export class TableModel<T extends TableRow = TableRow> extends Resource {
63
+ private readonly _id: string | undefined;
64
+ private readonly _space: Space | undefined;
65
+ private readonly _view: ViewType | undefined;
44
66
  private readonly _projection: ViewProjection;
45
67
 
46
68
  private readonly _visibleRange = signal<DxGridPlaneRange>({
@@ -52,7 +74,10 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
52
74
  private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
53
75
  private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
54
76
  private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
55
- private readonly _onRowOrderChanged?: TableModelProps<T>['onRowOrderChanged'];
77
+ private readonly _onRowOrderChange?: TableModelProps<T>['onRowOrderChange'];
78
+ private readonly _onRowAction?: TableModelProps<T>['onRowAction'];
79
+ private readonly _rowActions: TableRowAction[];
80
+ private readonly _features: TableFeatures;
56
81
 
57
82
  private readonly _rows = signal<T[]>([]);
58
83
  private readonly _sorting: TableSorting<T>;
@@ -62,20 +87,36 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
62
87
  private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
63
88
 
64
89
  constructor({
65
- table,
90
+ id,
91
+ space,
92
+ view,
66
93
  projection,
94
+ features = {},
67
95
  sorting = [],
68
96
  pinnedRows = { top: [], bottom: [] },
97
+ rowActions = [],
69
98
  onCellUpdate,
70
99
  onDeleteColumn,
71
100
  onDeleteRows,
72
101
  onInsertRow,
73
- onRowOrderChanged,
102
+ onRowOrderChange,
103
+ onRowAction,
74
104
  }: TableModelProps<T>) {
75
105
  super();
76
- this._table = table;
106
+ this._id = id;
107
+ this._space = space;
108
+ this._view = view;
77
109
  this._projection = projection;
78
- this._sorting = new TableSorting(this._rows, table.view?.target, projection);
110
+
111
+ // TODO(ZaymonFC): Use our more robust config merging module?
112
+ this._features = { ...defaultFeatures, ...features };
113
+
114
+ invariant(
115
+ !(this._features.dataEditable && this._features.selection.enabled && this._features.selection.mode === 'single'),
116
+ 'Single selection is not compatible with editable tables.',
117
+ );
118
+
119
+ this._sorting = new TableSorting(this._rows, this._view, projection);
79
120
 
80
121
  if (sorting.length > 0) {
81
122
  const [sort] = sorting;
@@ -83,15 +124,25 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
83
124
  }
84
125
 
85
126
  this._pinnedRows = pinnedRows;
127
+ this._rowActions = rowActions;
86
128
  this._onInsertRow = onInsertRow;
87
129
  this._onDeleteRows = onDeleteRows;
88
130
  this._onDeleteColumn = onDeleteColumn;
89
131
  this._onCellUpdate = onCellUpdate;
90
- this._onRowOrderChanged = onRowOrderChanged;
132
+ this._onRowOrderChange = onRowOrderChange;
133
+ this._onRowAction = onRowAction;
134
+ }
135
+
136
+ public get id() {
137
+ return this._id;
138
+ }
139
+
140
+ public get space() {
141
+ return this._space;
91
142
  }
92
143
 
93
- public get table() {
94
- return this._table;
144
+ public get view() {
145
+ return this._view;
95
146
  }
96
147
 
97
148
  public get projection() {
@@ -102,6 +153,10 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
102
153
  return this._sorting.sortedRows;
103
154
  }
104
155
 
156
+ public get features(): TableFeatures {
157
+ return this._features;
158
+ }
159
+
105
160
  /**
106
161
  * @reactive
107
162
  */
@@ -109,6 +164,17 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
109
164
  return this._sorting;
110
165
  }
111
166
 
167
+ /**
168
+ * Gets the row data at the specified display index.
169
+ */
170
+ public getRowAt(displayIndex: number): T | undefined {
171
+ if (displayIndex < 0 || displayIndex >= this._sorting.sortedRows.value.length) {
172
+ return undefined;
173
+ }
174
+
175
+ return this._sorting.sortedRows.value[displayIndex];
176
+ }
177
+
112
178
  public get pinnedRows(): NonNullable<TableModelProps<T>['pinnedRows']> {
113
179
  return this._pinnedRows;
114
180
  }
@@ -127,6 +193,10 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
127
193
  return this._sorting.isDirty;
128
194
  }
129
195
 
196
+ public get rowActions(): TableRowAction[] {
197
+ return this._rowActions;
198
+ }
199
+
130
200
  //
131
201
  // Initialisation
132
202
  //
@@ -134,13 +204,15 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
134
204
  protected override async _open() {
135
205
  this.initializeColumnMeta();
136
206
  this.initializeEffects();
137
- this._selection = new SelectionModel(this._sorting.sortedRows, () => this._onRowOrderChanged?.());
207
+ this._selection = new SelectionModel(this._sorting.sortedRows, this._features.selection.mode ?? 'multiple', () =>
208
+ this._onRowOrderChange?.(),
209
+ );
138
210
  await this._selection.open(this._ctx);
139
211
  }
140
212
 
141
213
  private initializeColumnMeta(): void {
142
214
  this._columnMeta = computed(() => {
143
- const fields = this._table.view?.target?.fields ?? [];
215
+ const fields = this._view?.fields ?? [];
144
216
  const meta = Object.fromEntries(
145
217
  fields.map((field, index: number) => [index, { size: field?.size ?? 256, resizeable: true }]),
146
218
  );
@@ -156,7 +228,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
156
228
  private initializeEffects(): void {
157
229
  const rowOrderWatcher = effect(() => {
158
230
  touch(this._sorting.sortedRows.value);
159
- this._onRowOrderChanged?.();
231
+ this._onRowOrderChange?.();
160
232
  });
161
233
  this._ctx.onDispose(rowOrderWatcher);
162
234
 
@@ -172,7 +244,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
172
244
  rowEffects.push(
173
245
  effect(() => {
174
246
  const obj = this._sorting.sortedRows.value[row];
175
- this?._table?.view?.target?.fields.forEach((field) => touch(getValue(obj, field.path)));
247
+ this._view?.fields.forEach((field) => touch(getValue(obj, field.path)));
176
248
  this._onCellUpdate?.({ row, col: start.col, plane: 'grid' });
177
249
  }),
178
250
  );
@@ -193,7 +265,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
193
265
 
194
266
  public getRowCount = (): number => this._rows.value.length;
195
267
 
196
- public getColumnCount = (): number => this.table.view?.target?.fields.length ?? 0;
268
+ public getColumnCount = (): number => this._view?.fields.length ?? 0;
197
269
 
198
270
  public insertRow = (rowIndex?: number): void => {
199
271
  const row = rowIndex !== undefined ? this._sorting.getDataIndex(rowIndex) : this._rows.value.length;
@@ -205,7 +277,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
205
277
  const obj = this._rows.value[row];
206
278
  const objectsToDelete = [];
207
279
 
208
- if (this._selection.hasSelection.value) {
280
+ if (this.features.selection.enabled && this._selection.hasSelection.value) {
209
281
  const selectedRows = this._selection.getSelectedRows();
210
282
  objectsToDelete.push(...selectedRows);
211
283
  } else {
@@ -215,8 +287,21 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
215
287
  this._onDeleteRows?.(row, objectsToDelete);
216
288
  };
217
289
 
290
+ public handleRowAction = (actionId: string, rowIndex: number): void => {
291
+ if (!this._onRowAction) {
292
+ return;
293
+ }
294
+
295
+ const row = this._sorting.getDataIndex(rowIndex);
296
+ const data = this._rows.value[row];
297
+
298
+ if (data) {
299
+ this._onRowAction(actionId, data);
300
+ }
301
+ };
302
+
218
303
  public getCellData = ({ col, row }: DxGridPlanePosition): any => {
219
- const fields = this.table.view?.target?.fields ?? [];
304
+ const fields = this._view?.fields ?? [];
220
305
  if (col < 0 || col >= fields.length) {
221
306
  return undefined;
222
307
  }
@@ -246,7 +331,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
246
331
 
247
332
  public setCellData = ({ col, row }: DxGridPlanePosition, value: any): void => {
248
333
  const rowIdx = this._sorting.getDataIndex(row);
249
- const fields = this.table.view?.target?.fields ?? [];
334
+ const fields = this._view?.fields ?? [];
250
335
  if (col < 0 || col >= fields.length) {
251
336
  return;
252
337
  }
@@ -285,7 +370,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
285
370
  */
286
371
  public updateCellData({ col, row }: DxGridPlanePosition, update: (value: any) => any): void {
287
372
  const dataRow = this._sorting.getDataIndex(row);
288
- const fields = this.table.view?.target?.fields ?? [];
373
+ const fields = this._view?.fields ?? [];
289
374
  const field = fields[col];
290
375
 
291
376
  const value = getValue(this._rows.value[dataRow], field.path);
@@ -298,11 +383,11 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
298
383
  //
299
384
 
300
385
  public deleteColumn(fieldId: string): void {
301
- if (!this.table.view) {
386
+ if (!this._view) {
302
387
  return;
303
388
  }
304
389
 
305
- const field = this.table.view.target?.fields.find((field) => field.id === fieldId);
390
+ const field = this._view?.fields.find((field) => field.id === fieldId);
306
391
  if (field && this._onDeleteColumn) {
307
392
  this._onDeleteColumn(field.id);
308
393
  }
@@ -313,7 +398,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
313
398
  //
314
399
 
315
400
  public setColumnWidth(columnIndex: number, width: number): void {
316
- const fields = this.table.view?.target?.fields ?? [];
401
+ const fields = this._view?.fields ?? [];
317
402
  if (columnIndex < fields.length) {
318
403
  const newWidth = Math.max(0, width);
319
404
  const field = fields[columnIndex];
@@ -96,7 +96,7 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
96
96
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
97
97
  const schema = createEchoSchema(Test);
98
98
  const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: schema.jsonSchema });
99
- const projection = new ViewProjection(schema, view);
99
+ const projection = new ViewProjection(schema.jsonSchema, view);
100
100
  const table = create(TableType, { view: makeRef(view) });
101
- return new TableModel({ table, projection, ...props });
101
+ return new TableModel({ id: table.id, view, projection, ...props });
102
102
  };
@@ -5,18 +5,19 @@
5
5
  import { signal } from '@preact/signals-core';
6
6
 
7
7
  import { FormatEnum, getValue } from '@dxos/echo-schema';
8
- import { cellClassesForFieldType, cellClassesForRowSelection, formatForDisplay } from '@dxos/react-ui-form';
8
+ import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
9
9
  import {
10
10
  type DxGridPlane,
11
11
  type DxGridPlaneCells,
12
- toPlaneCellIndex,
13
12
  type DxGridPlaneRange,
14
13
  type DxGridCellValue,
14
+ toPlaneCellIndex,
15
15
  } from '@dxos/react-ui-grid';
16
16
  import { mx } from '@dxos/react-ui-theme';
17
17
  import { VIEW_FIELD_LIMIT, type FieldType } from '@dxos/schema';
18
18
 
19
- import { type BaseTableRow, type TableModel } from './table-model';
19
+ import { type SelectionMode } from './selection-model';
20
+ import { type TableRow, type TableModel } from './table-model';
20
21
  import { tableButtons, tableControls } from '../util';
21
22
 
22
23
  /**
@@ -24,7 +25,7 @@ import { tableButtons, tableControls } from '../util';
24
25
  * Manages rendering of table cells, headers, selection columns, and action columns across
25
26
  * different grid planes.
26
27
  */
27
- export class TablePresentation<T extends BaseTableRow = { id: string }> {
28
+ export class TablePresentation<T extends TableRow = TableRow> {
28
29
  constructor(
29
30
  private readonly model: TableModel<T>,
30
31
  private readonly _visibleRange = signal<DxGridPlaneRange>({
@@ -34,28 +35,44 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
34
35
  ) {}
35
36
 
36
37
  public getCells(range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells {
38
+ let cells: DxGridPlaneCells;
39
+
37
40
  switch (plane) {
38
41
  case 'grid':
39
42
  this._visibleRange.value = range;
40
- return this.getMainGridCells(range);
43
+ cells = this.getMainGridCells(range);
44
+ break;
41
45
  case 'frozenRowsStart':
42
- return this.getHeaderCells(range);
46
+ cells = this.getHeaderCells(range);
47
+ break;
43
48
  case 'frozenColsStart':
44
- return this.getSelectionColumnCells(range);
49
+ cells = this.getSelectionColumnCells(range);
50
+ break;
45
51
  case 'frozenColsEnd':
46
- return this.getActionColumnCells(range);
52
+ cells = this.getActionColumnCells(range);
53
+ break;
47
54
  case 'fixedStartStart':
48
- return this.getSelectAllCell();
55
+ cells = this.getSelectAllCell();
56
+ break;
49
57
  case 'fixedStartEnd':
50
- return this.getNewColumnCell();
58
+ cells = this.getNewColumnCell();
59
+ break;
51
60
  default:
52
- return {};
61
+ cells = {};
53
62
  }
63
+
64
+ if (plane === 'grid' && this.model.features.dataEditable === false) {
65
+ Object.values(cells).forEach((cell) => {
66
+ cell.readonly = 'text-select';
67
+ });
68
+ }
69
+
70
+ return cells;
54
71
  }
55
72
 
56
73
  private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
57
74
  const cells: DxGridPlaneCells = {};
58
- const fields = this.model.table.view?.target?.fields ?? [];
75
+ const fields = this.model.view?.fields ?? [];
59
76
 
60
77
  const addCell = (obj: T, field: FieldType, colIndex: number, displayIndex: number): void => {
61
78
  const { props } = this.model.projection.getFieldProjection(field.id);
@@ -69,7 +86,8 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
69
86
 
70
87
  switch (props.format) {
71
88
  case FormatEnum.Boolean:
72
- case FormatEnum.SingleSelect: {
89
+ case FormatEnum.SingleSelect:
90
+ case FormatEnum.MultiSelect: {
73
91
  return '';
74
92
  }
75
93
  case FormatEnum.Ref: {
@@ -92,7 +110,10 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
92
110
  if (formatClasses) {
93
111
  classes.push(formatClasses);
94
112
  }
95
- const rowSelectionClasses = cellClassesForRowSelection(this.model.selection.isObjectSelected(obj));
113
+ const rowSelectionClasses = cellClassesForRowSelection(
114
+ this.model.selection.isObjectSelected(obj),
115
+ this.model.selection.selectionMode,
116
+ );
96
117
  if (rowSelectionClasses) {
97
118
  classes.push(rowSelectionClasses);
98
119
  }
@@ -117,7 +138,7 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
117
138
  rowIndex: displayIndex,
118
139
  checked: value ?? false,
119
140
  });
120
- cell.readonly = true;
141
+ cell.readonly = 'no-text-select';
121
142
  }
122
143
 
123
144
  if (props.format === FormatEnum.SingleSelect) {
@@ -131,6 +152,27 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
131
152
  }
132
153
  }
133
154
 
155
+ if (props.format === FormatEnum.MultiSelect) {
156
+ const values = getValue(obj, field.path) as string[] | undefined;
157
+ const options = this.model.projection.getFieldProjection(field.id).props.options;
158
+ if (options && values && values.length > 0) {
159
+ const tags = values
160
+ .map((value) => {
161
+ const option = options.find((o) => o.id === value);
162
+ if (option) {
163
+ return `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
164
+ }
165
+ return null;
166
+ })
167
+ .filter(Boolean)
168
+ .join(' ');
169
+
170
+ if (tags) {
171
+ cell.accessoryHtml = tags;
172
+ }
173
+ }
174
+ }
175
+
134
176
  const idx = toPlaneCellIndex({ col: colIndex, row: displayIndex });
135
177
  cells[idx] = cell;
136
178
  };
@@ -151,7 +193,7 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
151
193
 
152
194
  private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
153
195
  const cells: DxGridPlaneCells = {};
154
- const fields = this.model.table.view?.target?.fields ?? [];
196
+ const fields = this.model.view?.fields ?? [];
155
197
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
156
198
  const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
157
199
  const sorting = this.model.sorting?.sorting;
@@ -177,10 +219,10 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
177
219
  const cells: DxGridPlaneCells = {};
178
220
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
179
221
  const isSelected = this.model.selection.isRowIndexSelected(row);
180
- const classes = cellClassesForRowSelection(isSelected);
222
+ const classes = cellClassesForRowSelection(isSelected, this.model.selection.selectionMode);
181
223
  cells[toPlaneCellIndex({ col: 0, row })] = {
182
224
  value: '',
183
- readonly: true,
225
+ readonly: 'no-text-select',
184
226
  className: classes ? mx(classes) : undefined,
185
227
  accessoryHtml: tableControls.checkbox.render({ rowIndex: row, checked: isSelected }),
186
228
  };
@@ -193,7 +235,7 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
193
235
  const cells: DxGridPlaneCells = {};
194
236
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
195
237
  const isSelected = this.model.selection.isRowIndexSelected(row);
196
- const classes = cellClassesForRowSelection(isSelected);
238
+ const classes = cellClassesForRowSelection(isSelected, this.model.selection.selectionMode);
197
239
  cells[toPlaneCellIndex({ col: 0, row })] = {
198
240
  value: '',
199
241
  readonly: true,
@@ -206,6 +248,16 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
206
248
  }
207
249
 
208
250
  private getSelectAllCell(): DxGridPlaneCells {
251
+ if (!this.model.features.selection.enabled || this.model.selection.selectionMode === 'single') {
252
+ return {
253
+ [toPlaneCellIndex({ col: 0, row: 0 })]: {
254
+ className: '!bg-gridHeader',
255
+ readonly: true,
256
+ value: '',
257
+ },
258
+ };
259
+ }
260
+
209
261
  return {
210
262
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
211
263
  accessoryHtml: tableControls.checkbox.render({
@@ -223,9 +275,11 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
223
275
  private getNewColumnCell(): DxGridPlaneCells {
224
276
  return {
225
277
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
226
- accessoryHtml: tableButtons.addColumn.render({
227
- disabled: (this.model.table.view?.target?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
228
- }),
278
+ accessoryHtml: this.model.features.schemaEditable
279
+ ? tableButtons.addColumn.render({
280
+ disabled: (this.model.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
281
+ })
282
+ : undefined,
229
283
  className: '!bg-gridHeader',
230
284
  readonly: true,
231
285
  value: '',
@@ -233,3 +287,21 @@ export class TablePresentation<T extends BaseTableRow = { id: string }> {
233
287
  };
234
288
  }
235
289
  }
290
+
291
+ export const cellClassesForRowSelection = (selected: boolean, selectionMode: SelectionMode) => {
292
+ if (!selected) {
293
+ if (selectionMode === 'single') {
294
+ return ['!cursor-pointer'];
295
+ } else {
296
+ return undefined;
297
+ }
298
+ }
299
+
300
+ switch (selectionMode) {
301
+ case 'single':
302
+ // TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
303
+ return ['!bg-currentRelated hover:bg-hoverSurface', '!cursor-pointer'];
304
+ case 'multiple':
305
+ return ['!bg-gridCellSelected'];
306
+ }
307
+ };
@@ -9,7 +9,7 @@ import { getValue, FormatEnum, TypeEnum, type SortDirectionType, type FieldSortT
9
9
  import { formatForDisplay } from '@dxos/react-ui-form';
10
10
  import type { PropertyType, FieldType, ViewProjection, ViewType } from '@dxos/schema';
11
11
 
12
- import { type BaseTableRow } from './table-model';
12
+ import { type TableRow } from './table-model';
13
13
 
14
14
  /**
15
15
  * Represents the local sort state.
@@ -40,7 +40,7 @@ type LocalSort = { type: 'cleared' } | { type: 'active'; sort: FieldSortType } |
40
40
  * │ 2: "Cherry" │ => │ 2: "Cherry" │ => │ 2 => 2 │
41
41
  * └──────────────┘ └──────────────┘ └────────┘
42
42
  */
43
- export class TableSorting<T extends BaseTableRow> {
43
+ export class TableSorting<T extends TableRow> {
44
44
  private readonly _displayToDataIndex = new Map<number, number>();
45
45
  private readonly _rows: Signal<T[]>;
46
46
  private readonly _localSort = signal<LocalSort>(undefined);
@@ -82,6 +82,7 @@ export class TableSorting<T extends BaseTableRow> {
82
82
  if (local?.type === 'active') {
83
83
  return local.sort;
84
84
  }
85
+
85
86
  return this._view?.query.sort?.[0];
86
87
  }
87
88
 
@@ -4,12 +4,11 @@
4
4
 
5
5
  import { expect, test } from '@playwright/test';
6
6
 
7
- import { setupPage } from '@dxos/test-utils/playwright';
7
+ import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
8
8
 
9
9
  import { TableManager } from './TableManager';
10
10
 
11
- const storyId = 'ui-react-ui-table-table';
12
- const storyUrl = `http://localhost:9009/iframe.html?id=${storyId}&viewMode=story`;
11
+ const storyUrl = storybookUrl('ui-react-ui-table-table--default');
13
12
 
14
13
  // NOTE(ZaymonFC): This test suite relies on the faker seed being set to 0 in the story.
15
14
  test.describe('Table', () => {
@@ -2,21 +2,20 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Ref, ObjectId, S, TypedObject, Expando } from '@dxos/echo-schema';
5
+ import { EchoObject, Expando, LabelAnnotationId, ObjectId, Ref, S } from '@dxos/echo-schema';
6
6
  // import { ThreadType } from '@dxos/plugin-space/types';
7
7
  import { ViewType } from '@dxos/schema';
8
8
 
9
9
  export const TableSchema = S.Struct({
10
- id: ObjectId, // TODO(burdon): Where should this be?
10
+ id: ObjectId,
11
11
  name: S.optional(S.String),
12
12
  view: S.optional(Ref(ViewType)),
13
- // TODO(burdon): Should not import from plugin. Either factor out type or use reverse deps when supported.
14
- threads: S.optional(S.Array(Ref(Expando /* ThreadType */))), // TODO(dmaretskyi): Breaks edge because plugin-space depends on react-client.
13
+ // TODO(burdon): Document why threads is included here?
14
+ threads: S.optional(S.Array(Ref(Expando /* ThreadType */))),
15
+ }).annotations({
16
+ // TODO(burdon): Move annotation to property.
17
+ [LabelAnnotationId]: 'name',
15
18
  });
16
19
 
17
- // type TableType = S.Schema.Type<typeof TableSchema>;
18
-
19
- export class TableType extends TypedObject({
20
- typename: 'dxos.org/type/Table',
21
- version: '0.1.0',
22
- })(TableSchema.fields) {}
20
+ export const TableType = TableSchema.pipe(EchoObject({ typename: 'dxos.org/type/Table', version: '0.1.0' }));
21
+ export interface TableType extends S.Schema.Type<typeof TableType> {}