@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.
- package/dist/lib/browser/{chunk-TL7CKQQV.mjs → chunk-Q5QQXJUO.mjs} +2 -2
- package/dist/lib/browser/index.mjs +552 -227
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node-esm/{chunk-GBT6GD4G.mjs → chunk-ZNOU6LHG.mjs} +2 -2
- package/dist/lib/node-esm/index.mjs +552 -227
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/relations.stories.d.ts +1 -1
- package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +2 -2
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +13 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +42 -6
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +8 -0
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +3 -4
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/table.d.ts +4 -4
- package/dist/types/src/util/table-buttons.d.ts +15 -1
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +38 -40
- package/src/components/Table/DynamicTable.stories.tsx +2 -2
- package/src/components/Table/Table.stories.tsx +18 -12
- package/src/components/Table/Table.tsx +17 -4
- package/src/components/Table/relations.stories.tsx +4 -5
- package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -1
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +3 -3
- package/src/components/TableCellEditor/TableCellEditor.tsx +54 -15
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddRow.ts +36 -0
- package/src/index.ts +1 -1
- package/src/model/table-model.ts +220 -34
- package/src/model/table-presentation.ts +213 -130
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +9 -7
- package/src/translations.ts +5 -3
- package/src/util/table-buttons.ts +24 -1
- package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
- package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -2739
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/types/index.cjs +0 -31
- package/dist/lib/node/types/index.cjs.map +0 -7
- package/src/playwright/playwright.config.cts +0 -18
- /package/dist/lib/browser/{chunk-TL7CKQQV.mjs.map → chunk-Q5QQXJUO.mjs.map} +0 -0
- /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 {
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (value == null) {
|
|
85
|
-
return '';
|
|
86
|
-
}
|
|
114
|
+
if (props.type === 'array') {
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
87
117
|
|
|
88
|
-
|
|
118
|
+
switch (props.format) {
|
|
119
|
+
case FormatEnum.Boolean:
|
|
120
|
+
case FormatEnum.SingleSelect:
|
|
121
|
+
case FormatEnum.MultiSelect: {
|
|
89
122
|
return '';
|
|
90
123
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
104
|
-
|
|
129
|
+
return getValue(value.target, field.referencePath);
|
|
130
|
+
}
|
|
105
131
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
132
|
+
default: {
|
|
133
|
+
return formatForDisplay({ type: props.type, format: props.format, value });
|
|
109
134
|
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
};
|
|
112
138
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
215
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
};
|
|
270
|
+
return cells;
|
|
271
|
+
}
|
|
223
272
|
|
|
224
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
143
|
-
await page.keyboard.press('
|
|
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
|
-
|
|
152
|
-
await page.keyboard.type('
|
|
153
|
-
await page.keyboard.type('
|
|
154
|
-
await page.keyboard.type('
|
|
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
|
|
package/src/translations.ts
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { type Resource } from '@dxos/react-ui';
|
|
6
6
|
|
|
7
|
-
export
|
|
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
|
-
}
|