@dxos/react-ui-table 0.8.3 → 0.8.4-main.f9ba587

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 (68) hide show
  1. package/dist/lib/browser/{chunk-TL7CKQQV.mjs → chunk-Q5QQXJUO.mjs} +2 -2
  2. package/dist/lib/browser/index.mjs +552 -227
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/types/index.mjs +1 -1
  6. package/dist/lib/node-esm/{chunk-GBT6GD4G.mjs → chunk-ZNOU6LHG.mjs} +2 -2
  7. package/dist/lib/node-esm/index.mjs +552 -227
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/lib/node-esm/types/index.mjs +1 -1
  11. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
  12. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  14. package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
  15. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/Table/relations.stories.d.ts +1 -1
  17. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
  18. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
  19. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  20. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  21. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -1
  22. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  23. package/dist/types/src/components/testing.d.ts +2 -2
  24. package/dist/types/src/hooks/index.d.ts +1 -0
  25. package/dist/types/src/hooks/index.d.ts.map +1 -1
  26. package/dist/types/src/hooks/useAddRow.d.ts +13 -0
  27. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
  28. package/dist/types/src/index.d.ts +1 -1
  29. package/dist/types/src/index.d.ts.map +1 -1
  30. package/dist/types/src/model/table-model.d.ts +42 -6
  31. package/dist/types/src/model/table-model.d.ts.map +1 -1
  32. package/dist/types/src/model/table-presentation.d.ts +8 -0
  33. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  34. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  35. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  36. package/dist/types/src/translations.d.ts +3 -4
  37. package/dist/types/src/translations.d.ts.map +1 -1
  38. package/dist/types/src/types/table.d.ts +4 -4
  39. package/dist/types/src/util/table-buttons.d.ts +15 -1
  40. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  41. package/dist/types/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +38 -40
  43. package/src/components/Table/DynamicTable.stories.tsx +2 -2
  44. package/src/components/Table/Table.stories.tsx +18 -12
  45. package/src/components/Table/Table.tsx +17 -4
  46. package/src/components/Table/relations.stories.tsx +4 -5
  47. package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -1
  48. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +3 -3
  49. package/src/components/TableCellEditor/TableCellEditor.tsx +54 -15
  50. package/src/hooks/index.ts +1 -0
  51. package/src/hooks/useAddRow.ts +36 -0
  52. package/src/index.ts +1 -1
  53. package/src/model/table-model.ts +220 -34
  54. package/src/model/table-presentation.ts +213 -130
  55. package/src/playwright/playwright.config.ts +17 -0
  56. package/src/playwright/smoke.spec.ts +9 -7
  57. package/src/translations.ts +5 -3
  58. package/src/util/table-buttons.ts +24 -1
  59. package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
  60. package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
  61. package/dist/lib/node/index.cjs +0 -2739
  62. package/dist/lib/node/index.cjs.map +0 -7
  63. package/dist/lib/node/meta.json +0 -1
  64. package/dist/lib/node/types/index.cjs +0 -31
  65. package/dist/lib/node/types/index.cjs.map +0 -7
  66. package/src/playwright/playwright.config.cts +0 -18
  67. /package/dist/lib/browser/{chunk-TL7CKQQV.mjs.map → chunk-Q5QQXJUO.mjs.map} +0 -0
  68. /package/dist/lib/node-esm/{chunk-GBT6GD4G.mjs.map → chunk-ZNOU6LHG.mjs.map} +0 -0
@@ -5,13 +5,14 @@
5
5
  import { signal } from '@preact/signals-core';
6
6
  import { isNotNullable } from 'effect/Predicate';
7
7
 
8
- import { FormatEnum, getObjectDXN, getValue, TypeEnum } from '@dxos/echo-schema';
8
+ import { Obj } from '@dxos/echo';
9
+ import { FormatEnum, getValue, TypeEnum } from '@dxos/echo-schema';
9
10
  import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
10
11
  import {
12
+ type DxGridCellValue,
11
13
  type DxGridPlane,
12
14
  type DxGridPlaneCells,
13
15
  type DxGridPlaneRange,
14
- type DxGridCellValue,
15
16
  toPlaneCellIndex,
16
17
  } from '@dxos/react-ui-grid';
17
18
  import { mx } from '@dxos/react-ui-theme';
@@ -27,6 +28,8 @@ import { tableButtons, tableControls } from '../util';
27
28
  * different grid planes.
28
29
  */
29
30
  export class TablePresentation<T extends TableRow = TableRow> {
31
+ private fieldProjectionCache = new Map<string, ReturnType<typeof this.model.projection.getFieldProjection>>();
32
+
30
33
  constructor(
31
34
  private readonly model: TableModel<T>,
32
35
  private readonly _visibleRange = signal<DxGridPlaneRange>({
@@ -36,6 +39,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
36
39
  ) {}
37
40
 
38
41
  public getCells(range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells {
42
+ // Clear cache at the start of each render pass
43
+ this.fieldProjectionCache.clear();
44
+
39
45
  let cells: DxGridPlaneCells;
40
46
 
41
47
  switch (plane) {
@@ -58,12 +64,18 @@ export class TablePresentation<T extends TableRow = TableRow> {
58
64
  case 'fixedStartEnd':
59
65
  cells = this.getNewColumnCell();
60
66
  break;
67
+ case 'frozenRowsEnd':
68
+ cells = this.getDraftRowCells(range);
69
+ break;
70
+ case 'fixedEndEnd':
71
+ cells = this.getDraftActionCells(range);
72
+ break;
61
73
  default:
62
74
  cells = {};
63
75
  }
64
76
 
65
77
  if (plane === 'grid' && this.model.features.dataEditable === false) {
66
- Object.values(cells).forEach((cell) => {
78
+ Object.values<DxGridCellValue>(cells).forEach((cell) => {
67
79
  cell.readonly = 'text-select';
68
80
  });
69
81
  }
@@ -71,164 +83,217 @@ export class TablePresentation<T extends TableRow = TableRow> {
71
83
  return cells;
72
84
  }
73
85
 
74
- private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
75
- const cells: DxGridPlaneCells = {};
76
- const fields = this.model.view?.fields ?? [];
86
+ /**
87
+ * Gets a field projection, using cache if available within the current render pass.
88
+ */
89
+ private getFieldProjection(fieldId: string) {
90
+ let projection = this.fieldProjectionCache.get(fieldId);
91
+ if (!projection) {
92
+ projection = this.model.projection.getFieldProjection(fieldId);
93
+ this.fieldProjectionCache.set(fieldId, projection);
94
+ }
95
+ return projection;
96
+ }
77
97
 
78
- const addCell = (obj: T, field: FieldType, colIndex: number, displayIndex: number): void => {
79
- const { props } = this.model.projection.getFieldProjection(field.id);
98
+ private createDataCell(
99
+ cells: DxGridPlaneCells,
100
+ obj: T,
101
+ field: FieldType,
102
+ colIndex: number,
103
+ displayIndex: number,
104
+ ): void {
105
+ const { props } = this.getFieldProjection(field.id);
106
+
107
+ const cell: DxGridCellValue = {
108
+ get value() {
109
+ const value = getValue(obj, field.path);
110
+ if (value == null) {
111
+ return '';
112
+ }
80
113
 
81
- const cell: DxGridCellValue = {
82
- get value() {
83
- const value = getValue(obj, field.path);
84
- if (value == null) {
85
- return '';
86
- }
114
+ if (props.type === 'array') {
115
+ return '';
116
+ }
87
117
 
88
- if (props.type === 'array') {
118
+ switch (props.format) {
119
+ case FormatEnum.Boolean:
120
+ case FormatEnum.SingleSelect:
121
+ case FormatEnum.MultiSelect: {
89
122
  return '';
90
123
  }
91
-
92
- switch (props.format) {
93
- case FormatEnum.Boolean:
94
- case FormatEnum.SingleSelect:
95
- case FormatEnum.MultiSelect: {
96
- return '';
124
+ case FormatEnum.Ref: {
125
+ if (!field.referencePath) {
126
+ return ''; // TODO(burdon): Show error.
97
127
  }
98
- case FormatEnum.Ref: {
99
- if (!field.referencePath) {
100
- return ''; // TODO(burdon): Show error.
101
- }
102
128
 
103
- return getValue(value.target, field.referencePath);
104
- }
129
+ return getValue(value.target, field.referencePath);
130
+ }
105
131
 
106
- default: {
107
- return formatForDisplay({ type: props.type, format: props.format, value });
108
- }
132
+ default: {
133
+ return formatForDisplay({ type: props.type, format: props.format, value });
109
134
  }
110
- },
111
- };
135
+ }
136
+ },
137
+ };
112
138
 
113
- const classes = [];
114
- const formatClasses = cellClassesForFieldType({ type: props.type, format: props.format });
115
- if (formatClasses) {
116
- classes.push(formatClasses);
117
- }
118
- const rowSelectionClasses = cellClassesForRowSelection(
119
- this.model.selection.isObjectSelected(obj),
120
- this.model.selection.selectionMode,
121
- );
122
- if (rowSelectionClasses) {
123
- classes.push(rowSelectionClasses);
124
- }
125
- if (classes.length > 0) {
126
- cell.className = mx(classes.flat());
127
- }
139
+ const classes = [];
140
+ const formatClasses = cellClassesForFieldType({ type: props.type, format: props.format });
141
+ if (formatClasses) {
142
+ classes.push(formatClasses);
143
+ }
144
+ const rowSelectionClasses = cellClassesForRowSelection(
145
+ this.model.selection.isObjectSelected(obj),
146
+ this.model.selection.selectionMode,
147
+ );
148
+ if (rowSelectionClasses) {
149
+ classes.push(rowSelectionClasses);
150
+ }
151
+ if (classes.length > 0) {
152
+ cell.className = mx(classes.flat());
153
+ }
128
154
 
129
- if (props.type === TypeEnum.Array) {
130
- const targetArray = getValue(obj, field.path);
131
- if (targetArray && Array.isArray(targetArray)) {
132
- // TODO(ZaymonFC): For arrays of objects, objects should have a 'label' annotation on the field
133
- // that can be used to render tags.
134
- // TODO(ZaymonFC): Move to util.
135
- const getLabel = (value: any) => {
136
- // If the value is falsy, return undefined
137
- // If the object is an array, check for 'name' property
138
- // If the object is a primitive type (string, number, boolean) stringify
139
- // If the object is an array return 'Array'
140
- if (!value) {
141
- return undefined;
142
- }
143
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
144
- return String(value);
145
- }
146
- if (Array.isArray(value)) {
147
- return 'Array';
148
- }
149
- if (typeof value === 'object' && value.name) {
150
- return value.name;
151
- }
152
- if (typeof value === 'object' && value.value) {
153
- return `${value.value}`;
154
- }
155
- return 'Object';
156
- };
157
-
158
- const tags = targetArray
159
- .map(getLabel)
160
- .filter(isNotNullable)
161
- .map((title) => {
162
- return `<span class="dx-tag rounded-xl" data-hue="neutral">${title}</span>`;
163
- })
164
- .join('');
155
+ // Arrays.
156
+ if (props.type === TypeEnum.Array) {
157
+ const targetArray = getValue(obj, field.path);
158
+ if (targetArray && Array.isArray(targetArray)) {
159
+ // TODO(ZaymonFC): For arrays of objects, objects should have a 'label' annotation on the field
160
+ // that can be used to render tags.
161
+ // TODO(ZaymonFC): Move to util.
162
+ const getLabel = (value: any) => {
163
+ // If the value is falsy, return undefined
164
+ // If the object is an array, check for 'name' property
165
+ // If the object is a primitive type (string, number, boolean) stringify
166
+ // If the object is an array return 'Array'
167
+ if (!value) {
168
+ return undefined;
169
+ }
170
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
171
+ return String(value);
172
+ }
173
+ if (Array.isArray(value)) {
174
+ return 'Array';
175
+ }
176
+ if (typeof value === 'object' && value.name) {
177
+ return value.name;
178
+ }
179
+ if (typeof value === 'object' && value.value) {
180
+ return `${value.value}`;
181
+ }
182
+ return 'Object';
183
+ };
184
+
185
+ const tags = targetArray
186
+ .map(getLabel)
187
+ .filter(isNotNullable)
188
+ .map((title) => {
189
+ return `<span class="dx-tag rounded-xl" data-hue="neutral">${title}</span>`;
190
+ })
191
+ .join('');
192
+
193
+ cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
194
+ }
195
+ }
165
196
 
166
- cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
167
- }
197
+ // References.
198
+ if (props.format === FormatEnum.Ref && props.referenceSchema) {
199
+ const targetObj = getValue(obj, field.path)?.target;
200
+ if (targetObj) {
201
+ const dxn = Obj.getDXN(targetObj)?.toString();
202
+ cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-1"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
168
203
  }
204
+ }
169
205
 
170
- if (props.format === FormatEnum.Ref && props.referenceSchema) {
171
- const targetObj = getValue(obj, field.path)?.target;
172
- if (targetObj) {
173
- const dxn = getObjectDXN(targetObj)?.toString();
174
- cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-1"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
206
+ // Booleans.
207
+ if (props.format === FormatEnum.Boolean) {
208
+ const value = getValue(obj, field.path);
209
+ cell.accessoryHtml = tableControls.switch.render({
210
+ colIndex,
211
+ rowIndex: displayIndex,
212
+ checked: value ?? false,
213
+ });
214
+ cell.readonly = 'no-text-select';
215
+ }
216
+
217
+ // Single-Selects.
218
+ if (props.format === FormatEnum.SingleSelect) {
219
+ const value = getValue(obj, field.path);
220
+ const options = this.model.projection.getFieldProjection(field.id).props.options;
221
+ if (options) {
222
+ const option = options.find((o) => o.id === value);
223
+ if (option) {
224
+ cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
175
225
  }
176
226
  }
227
+ }
177
228
 
178
- if (props.format === FormatEnum.Boolean) {
179
- const value = getValue(obj, field.path);
180
- cell.accessoryHtml = tableControls.switch.render({
181
- colIndex,
182
- rowIndex: displayIndex,
183
- checked: value ?? false,
184
- });
185
- cell.readonly = 'no-text-select';
186
- }
229
+ // Multi-Selects.
230
+ if (props.format === FormatEnum.MultiSelect) {
231
+ const values = getValue(obj, field.path) as string[] | undefined;
232
+ const options = this.model.projection.getFieldProjection(field.id).props.options;
233
+ if (options && values && values.length > 0) {
234
+ const tags = values
235
+ .map((value) => {
236
+ const option = options.find((o) => o.id === value);
237
+ if (option) {
238
+ return `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
239
+ }
240
+ return null;
241
+ })
242
+ .filter(Boolean)
243
+ .join('');
187
244
 
188
- if (props.format === FormatEnum.SingleSelect) {
189
- const value = getValue(obj, field.path);
190
- const options = this.model.projection.getFieldProjection(field.id).props.options;
191
- if (options) {
192
- const option = options.find((o) => o.id === value);
193
- if (option) {
194
- cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
195
- }
245
+ if (tags) {
246
+ cell.accessoryHtml = tags;
196
247
  }
197
248
  }
249
+ }
198
250
 
199
- if (props.format === FormatEnum.MultiSelect) {
200
- const values = getValue(obj, field.path) as string[] | undefined;
201
- const options = this.model.projection.getFieldProjection(field.id).props.options;
202
- if (options && values && values.length > 0) {
203
- const tags = values
204
- .map((value) => {
205
- const option = options.find((o) => o.id === value);
206
- if (option) {
207
- return `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
208
- }
209
- return null;
210
- })
211
- .filter(Boolean)
212
- .join(' ');
251
+ const idx = toPlaneCellIndex({ col: colIndex, row: displayIndex });
252
+ cells[idx] = cell;
253
+ }
213
254
 
214
- if (tags) {
215
- cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1">${tags}</div>`;
216
- }
255
+ private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
256
+ const cells: DxGridPlaneCells = {};
257
+ const fields = this.model.view?.fields ?? [];
258
+
259
+ for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
260
+ for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
261
+ const field = fields[col];
262
+ if (!field) {
263
+ continue;
217
264
  }
265
+
266
+ this.createDataCell(cells, this.model.rows.value[row], field, col, row);
218
267
  }
268
+ }
219
269
 
220
- const idx = toPlaneCellIndex({ col: colIndex, row: displayIndex });
221
- cells[idx] = cell;
222
- };
270
+ return cells;
271
+ }
223
272
 
224
- for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
273
+ private getDraftRowCells(range: DxGridPlaneRange): DxGridPlaneCells {
274
+ const cells: DxGridPlaneCells = {};
275
+ const fields = this.model.view?.fields ?? [];
276
+ const draftRows = this.model.draftRows.value;
277
+
278
+ for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
279
+ const draftRow = draftRows[row];
225
280
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
226
281
  const field = fields[col];
227
282
  if (!field) {
228
283
  continue;
229
284
  }
230
285
 
231
- addCell(this.model.rows.value[row], field, col, row);
286
+ this.createDataCell(cells, draftRow.data, field, col, row);
287
+
288
+ if (this.model.hasDraftRowValidationError(row, field.path)) {
289
+ const cellIndex = toPlaneCellIndex({ col, row });
290
+ const cellValue = cells[cellIndex];
291
+ if (cellValue) {
292
+ const existingClasses = cellValue.className || '';
293
+ const draftClasses = 'dx-grid__cell--flagged';
294
+ cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
295
+ }
296
+ }
232
297
  }
233
298
  }
234
299
 
@@ -239,7 +304,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
239
304
  const cells: DxGridPlaneCells = {};
240
305
  const fields = this.model.view?.fields ?? [];
241
306
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
242
- const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
307
+ const { field, props } = this.getFieldProjection(fields[col].id);
243
308
  const sorting = this.model.sorting?.sorting;
244
309
  const direction = sorting?.fieldId === field.id ? sorting.direction : undefined;
245
310
 
@@ -330,6 +395,24 @@ export class TablePresentation<T extends TableRow = TableRow> {
330
395
  },
331
396
  };
332
397
  }
398
+
399
+ private getDraftActionCells(range: DxGridPlaneRange): DxGridPlaneCells {
400
+ const cells: DxGridPlaneCells = {};
401
+ const draftRows = this.model.draftRows.value;
402
+
403
+ for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
404
+ const draftRow = draftRows[row];
405
+ const disabled = !draftRow.valid;
406
+
407
+ cells[toPlaneCellIndex({ col: 0, row })] = {
408
+ value: '',
409
+ readonly: true,
410
+ accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
411
+ };
412
+ }
413
+
414
+ return cells;
415
+ }
333
416
  }
334
417
 
335
418
  export const cellClassesForRowSelection = (selected: boolean, selectionMode: SelectionMode) => {
@@ -0,0 +1,17 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { defineConfig } from '@playwright/test';
6
+
7
+ import { e2ePreset } from '@dxos/test-utils/playwright';
8
+
9
+ export default defineConfig({
10
+ ...e2ePreset(import.meta.dirname),
11
+ // TODO(wittjosiah): Avoid hard-coding ports.
12
+ webServer: {
13
+ command: 'moon run storybook:serve-e2e -- --port=9004',
14
+ port: 9004,
15
+ reuseExistingServer: false,
16
+ },
17
+ });
@@ -8,7 +8,7 @@ import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
8
8
 
9
9
  import { TableManager } from './TableManager';
10
10
 
11
- const storyUrl = storybookUrl('ui-react-ui-table-table--default');
11
+ const storyUrl = storybookUrl('ui-react-ui-table-table--default', 9004);
12
12
 
13
13
  // NOTE(ZaymonFC): This test suite relies on the faker seed being set to 0 in the story.
14
14
  test.describe('Table', () => {
@@ -139,8 +139,9 @@ test.describe('Table', () => {
139
139
  await table.grid.panByWheel(10000, 0);
140
140
 
141
141
  await table.grid.cell(4, 0, 'grid').click();
142
- await page.keyboard.press('A');
143
- await page.keyboard.press('N');
142
+ // TODO(wittjosiah): Surprisingly long delay needed here.
143
+ await page.keyboard.press('A', { delay: 1_000 });
144
+ await page.keyboard.press('n', { delay: 1_000 });
144
145
  await page.getByRole('option', { name: 'Anita Mayer' }).click();
145
146
 
146
147
  // Assert that the value is shown in the cell.
@@ -148,10 +149,11 @@ test.describe('Table', () => {
148
149
 
149
150
  // Create new object.
150
151
  await table.grid.cell(4, 1, 'grid').click();
151
- await page.keyboard.type('t');
152
- await page.keyboard.type('e');
153
- await page.keyboard.type('s');
154
- await page.keyboard.type('t');
152
+ // TODO(wittjosiah): Surprisingly long delay needed here.
153
+ await page.keyboard.type('t', { delay: 1_000 });
154
+ await page.keyboard.type('e', { delay: 1_000 });
155
+ await page.keyboard.type('s', { delay: 1_000 });
156
+ await page.keyboard.type('t', { delay: 1_000 });
155
157
  await page.getByText('Create new object', { exact: false }).click();
156
158
  await page.getByTestId('save-button').click();
157
159
 
@@ -2,9 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export const translationKey = 'table';
5
+ import { type Resource } from '@dxos/react-ui';
6
6
 
7
- export default [
7
+ export const translationKey = 'react-ui-table';
8
+
9
+ export const translations = [
8
10
  {
9
11
  'en-US': {
10
12
  [translationKey]: {
@@ -32,4 +34,4 @@ export default [
32
34
  },
33
35
  },
34
36
  },
35
- ] as const;
37
+ ] as const satisfies Resource[];
@@ -2,13 +2,14 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export type TableButton = 'columnSettings' | 'rowMenu' | 'newColumn' | 'referencedCell' | 'sort';
5
+ export type TableButton = 'columnSettings' | 'rowMenu' | 'newColumn' | 'referencedCell' | 'sort' | 'saveDraftRow';
6
6
 
7
7
  export const BUTTON_IDENTIFIERS: { [K in TableButton]: string } = {
8
8
  columnSettings: 'data-table-column-settings-button',
9
9
  newColumn: 'data-table-new-column-button',
10
10
  referencedCell: 'data-table-ref-cell-button',
11
11
  rowMenu: 'data-table-row-menu-button',
12
+ saveDraftRow: 'data-table-save-draft-row-button',
12
13
  sort: 'data-table-sort-button',
13
14
  } as const;
14
15
 
@@ -16,6 +17,7 @@ type ButtonData =
16
17
  | { type: 'columnSettings'; fieldId: string }
17
18
  | { type: 'newColumn'; disabled?: boolean }
18
19
  | { type: 'rowMenu'; rowIndex: number }
20
+ | { type: 'saveDraftRow'; rowIndex: number; disabled?: boolean }
19
21
  | { type: 'sort'; fieldId: string; direction?: 'asc' | 'desc' };
20
22
 
21
23
  const createButton = ({
@@ -118,9 +120,30 @@ const sortButton = {
118
120
  }),
119
121
  } as const;
120
122
 
123
+ const saveDraftRowButton = {
124
+ attr: BUTTON_IDENTIFIERS.saveDraftRow,
125
+ icon: 'ph--floppy-disk--regular',
126
+ render: ({ rowIndex, disabled }: Omit<Extract<ButtonData, { type: 'saveDraftRow' }>, 'type'>) => {
127
+ return createButton({
128
+ attr: BUTTON_IDENTIFIERS.saveDraftRow,
129
+ icon: saveDraftRowButton.icon,
130
+ disabled,
131
+ data: {
132
+ 'data-row-index': rowIndex.toString(),
133
+ },
134
+ testId: 'table-save-draft-row-button',
135
+ });
136
+ },
137
+ getData: (el: HTMLElement): Extract<ButtonData, { type: 'saveDraftRow' }> => ({
138
+ type: 'saveDraftRow',
139
+ rowIndex: Number(el.getAttribute('data-row-index')!),
140
+ }),
141
+ } as const;
142
+
121
143
  export const tableButtons = {
122
144
  addColumn: addColumnButton,
123
145
  columnSettings: columnSettingsButton,
124
146
  rowMenu: rowMenuButton,
147
+ saveDraftRow: saveDraftRowButton,
125
148
  sort: sortButton,
126
149
  } as const;
@@ -1,50 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
- var chunk_O2VPBNS5_exports = {};
20
- __export(chunk_O2VPBNS5_exports, {
21
- TableSchema: () => TableSchema,
22
- TableType: () => TableType
23
- });
24
- module.exports = __toCommonJS(chunk_O2VPBNS5_exports);
25
- var import_effect = require("effect");
26
- var import_echo = require("@dxos/echo");
27
- var import_echo_schema = require("@dxos/echo-schema");
28
- var import_schema = require("@dxos/schema");
29
- var TableSchema = import_effect.Schema.Struct({
30
- id: import_echo_schema.ObjectId,
31
- name: import_effect.Schema.optional(import_effect.Schema.String),
32
- view: import_effect.Schema.optional((0, import_echo_schema.Ref)(import_schema.ViewType)),
33
- // TODO(burdon): Document why threads is included here?
34
- threads: import_effect.Schema.optional(import_effect.Schema.Array((0, import_echo_schema.Ref)(
35
- import_echo_schema.Expando
36
- /* ThreadType */
37
- )))
38
- }).pipe(import_echo_schema.LabelAnnotation.set([
39
- "name"
40
- ]));
41
- var TableType = TableSchema.pipe(import_echo.Type.Obj({
42
- typename: "dxos.org/type/Table",
43
- version: "0.1.0"
44
- }));
45
- // Annotate the CommonJS export names for ESM import in node:
46
- 0 && (module.exports = {
47
- TableSchema,
48
- TableType
49
- });
50
- //# sourceMappingURL=chunk-O2VPBNS5.cjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types/table.ts"],
4
- "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { Schema } from 'effect';\n\n// import { ThreadType } from '@dxos/plugin-space/types';\nimport { Type } from '@dxos/echo';\nimport { ObjectId, Ref, Expando, LabelAnnotation } from '@dxos/echo-schema';\nimport { ViewType } from '@dxos/schema';\n\nexport const TableSchema = Schema.Struct({\n id: ObjectId,\n name: Schema.optional(Schema.String),\n view: Schema.optional(Ref(ViewType)),\n // TODO(burdon): Document why threads is included here?\n threads: Schema.optional(Schema.Array(Ref(Expando /* ThreadType */))),\n}).pipe(LabelAnnotation.set(['name']));\n\n// TODO(burdon): Move out of react-ui-xxx.\nexport const TableType = TableSchema.pipe(\n Type.Obj({\n typename: 'dxos.org/type/Table',\n version: '0.1.0',\n }),\n);\nexport interface TableType extends Schema.Schema.Type<typeof TableType> {}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;AAIA,oBAAuB;AAGvB,kBAAqB;AACrB,yBAAwD;AACxD,oBAAyB;AAElB,IAAMA,cAAcC,qBAAOC,OAAO;EACvCC,IAAIC;EACJC,MAAMJ,qBAAOK,SAASL,qBAAOM,MAAM;EACnCC,MAAMP,qBAAOK,aAASG,wBAAIC,sBAAAA,CAAAA;;EAE1BC,SAASV,qBAAOK,SAASL,qBAAOW,UAAMH;IAAII;;EAAsB,CAAA,CAAA;AAClE,CAAA,EAAGC,KAAKC,mCAAgBC,IAAI;EAAC;CAAO,CAAA;AAG7B,IAAMC,YAAYjB,YAAYc,KACnCI,iBAAKC,IAAI;EACPC,UAAU;EACVC,SAAS;AACX,CAAA,CAAA;",
6
- "names": ["TableSchema", "Schema", "Struct", "id", "ObjectId", "name", "optional", "String", "view", "Ref", "ViewType", "threads", "Array", "Expando", "pipe", "LabelAnnotation", "set", "TableType", "Type", "Obj", "typename", "version"]
7
- }