@dxos/react-ui-table 0.8.3 → 0.8.4-main.c1de068
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-5XLSHLOG.mjs +1751 -0
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +7 -0
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +18 -0
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +240 -1639
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +121 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +3 -5
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +20 -0
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs +1752 -0
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +240 -1639
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +122 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +3 -5
- package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +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/FormCellEditor.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -2
- 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 +3 -40
- package/dist/types/src/components/testing.d.ts.map +1 -1
- 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/hooks/useTableModel.d.ts +7 -6
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +2 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +56 -20
- 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/model/table-sorting.d.ts +2 -2
- package/dist/types/src/model/table-sorting.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/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/useTestTableModel.d.ts +41 -0
- package/dist/types/src/testing/useTestTableModel.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/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +8 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util/dynamic-table.d.ts +3 -4
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- 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/src/util/table.d.ts +8 -11
- package/dist/types/src/util/table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +48 -40
- package/src/components/Table/ColumnSettings.tsx +14 -10
- package/src/components/Table/CreateRefPanel.tsx +5 -3
- package/src/components/Table/DynamicTable.stories.tsx +2 -2
- package/src/components/Table/DynamicTable.tsx +13 -7
- package/src/components/Table/Table.stories.tsx +104 -148
- package/src/components/Table/Table.tsx +50 -32
- package/src/components/Table/relations.stories.tsx +27 -35
- package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -28
- package/src/components/TableCellEditor/TableCellEditor.tsx +79 -21
- package/src/components/testing.ts +7 -24
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddRow.ts +36 -0
- package/src/hooks/useTableModel.ts +20 -16
- package/src/index.ts +1 -2
- package/src/model/selection-model.ts +4 -2
- package/src/model/table-model.test.ts +7 -8
- package/src/model/table-model.ts +267 -78
- package/src/model/table-presentation.test.ts +6 -7
- package/src/model/table-presentation.ts +215 -132
- package/src/model/table-sorting.ts +9 -9
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +21 -20
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +122 -0
- package/src/translations.ts +5 -3
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +16 -0
- package/src/util/dynamic-table.ts +19 -17
- package/src/util/table-buttons.ts +24 -1
- package/src/util/table.ts +33 -111
- package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
- package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
- 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/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
- package/dist/types/src/types/table.d.ts +0 -56
- package/dist/types/src/types/table.d.ts.map +0 -1
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
|
@@ -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" 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.projection?.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.projection?.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
|
|
|
@@ -237,7 +302,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
237
302
|
|
|
238
303
|
private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
239
304
|
const cells: DxGridPlaneCells = {};
|
|
240
|
-
const fields = this.model.
|
|
305
|
+
const fields = this.model.projection?.fields ?? [];
|
|
241
306
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
242
307
|
const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
|
|
243
308
|
const sorting = this.model.sorting?.sorting;
|
|
@@ -321,7 +386,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
321
386
|
[toPlaneCellIndex({ col: 0, row: 0 })]: {
|
|
322
387
|
accessoryHtml: this.model.features.schemaEditable
|
|
323
388
|
? tableButtons.addColumn.render({
|
|
324
|
-
disabled: (this.model.
|
|
389
|
+
disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
|
|
325
390
|
})
|
|
326
391
|
: undefined,
|
|
327
392
|
className: '!bg-toolbarSurface',
|
|
@@ -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) => {
|
|
@@ -344,7 +427,7 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
|
|
|
344
427
|
switch (selectionMode) {
|
|
345
428
|
case 'single':
|
|
346
429
|
// TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
|
|
347
|
-
return ['!bg-currentRelated hover:bg-hoverSurface
|
|
430
|
+
return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
|
|
348
431
|
case 'multiple':
|
|
349
432
|
return ['!bg-gridCellSelected'];
|
|
350
433
|
}
|
|
@@ -7,7 +7,7 @@ import orderBy from 'lodash.orderby';
|
|
|
7
7
|
|
|
8
8
|
import { getValue, FormatEnum, TypeEnum, type SortDirectionType, type FieldSortType } from '@dxos/echo-schema';
|
|
9
9
|
import { formatForDisplay } from '@dxos/react-ui-form';
|
|
10
|
-
import type { PropertyType, FieldType,
|
|
10
|
+
import type { PropertyType, FieldType, DataType, ProjectionModel } from '@dxos/schema';
|
|
11
11
|
|
|
12
12
|
import { type TableRow } from './table-model';
|
|
13
13
|
|
|
@@ -49,13 +49,13 @@ export class TableSorting<T extends TableRow> {
|
|
|
49
49
|
|
|
50
50
|
constructor(
|
|
51
51
|
rows: Signal<T[]>,
|
|
52
|
-
private readonly _view:
|
|
53
|
-
private readonly _projection:
|
|
52
|
+
private readonly _view: DataType.View,
|
|
53
|
+
private readonly _projection: ProjectionModel,
|
|
54
54
|
) {
|
|
55
55
|
this._rows = rows;
|
|
56
56
|
this._isDirty = computed(() => {
|
|
57
57
|
const local = this._localSort.value;
|
|
58
|
-
const viewSort = this._view
|
|
58
|
+
const viewSort = this._view.query.sort?.[0];
|
|
59
59
|
if (local?.type === 'cleared') {
|
|
60
60
|
return viewSort !== undefined;
|
|
61
61
|
}
|
|
@@ -83,7 +83,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
83
83
|
return local.sort;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
return this._view
|
|
86
|
+
return this._view.query.sort?.[0];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
@@ -106,7 +106,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
public toggleSort(fieldId: string): void {
|
|
109
|
-
if (!this.
|
|
109
|
+
if (!this.sorting || this.sorting.fieldId !== fieldId) {
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -127,7 +127,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
public save(): void {
|
|
130
|
-
if (this.
|
|
130
|
+
if (this._localSort.value !== undefined) {
|
|
131
131
|
if (this._localSort.value.type === 'active') {
|
|
132
132
|
this._view.query.sort = [this._localSort.value.sort];
|
|
133
133
|
} else {
|
|
@@ -145,11 +145,11 @@ export class TableSorting<T extends TableRow> {
|
|
|
145
145
|
return computed(() => {
|
|
146
146
|
this._displayToDataIndex.clear();
|
|
147
147
|
const sort = this.sorting;
|
|
148
|
-
if (!sort
|
|
148
|
+
if (!sort) {
|
|
149
149
|
return this._rows.value;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
const field = this.
|
|
152
|
+
const field = this._projection.fields.find((f) => f.id === sort.fieldId);
|
|
153
153
|
if (!field) {
|
|
154
154
|
return this._rows.value;
|
|
155
155
|
}
|
|
@@ -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', () => {
|
|
@@ -28,12 +28,12 @@ test.describe('Table', () => {
|
|
|
28
28
|
|
|
29
29
|
await table.grid.ready();
|
|
30
30
|
await table.sortColumn(0, 'descending');
|
|
31
|
-
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('
|
|
32
|
-
await expect(table.grid.cell(0, 9, 'grid')).toContainText('
|
|
31
|
+
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Sit.');
|
|
32
|
+
await expect(table.grid.cell(0, 9, 'grid')).toContainText('Aut.');
|
|
33
33
|
|
|
34
34
|
await table.sortColumn(0, 'ascending');
|
|
35
|
-
await expect(table.grid.cell(0, 0, 'grid')).toContainText('
|
|
36
|
-
await expect(table.grid.cell(0, 9, 'grid')).toHaveText('
|
|
35
|
+
await expect(table.grid.cell(0, 0, 'grid')).toContainText('Aut.');
|
|
36
|
+
await expect(table.grid.cell(0, 9, 'grid')).toHaveText('Sit.');
|
|
37
37
|
await page.close();
|
|
38
38
|
});
|
|
39
39
|
|
|
@@ -65,7 +65,7 @@ test.describe('Table', () => {
|
|
|
65
65
|
|
|
66
66
|
await table.grid.ready();
|
|
67
67
|
await table.deleteRow(0);
|
|
68
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
68
|
+
await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(0);
|
|
69
69
|
await page.close();
|
|
70
70
|
});
|
|
71
71
|
|
|
@@ -80,8 +80,8 @@ test.describe('Table', () => {
|
|
|
80
80
|
// Delete action affects all selected rows.
|
|
81
81
|
await table.deleteRow(0);
|
|
82
82
|
|
|
83
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
84
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
83
|
+
await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(0);
|
|
84
|
+
await expect(page.getByRole('gridcell', { name: 'Beatae.' })).toHaveCount(0);
|
|
85
85
|
await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(0);
|
|
86
86
|
await page.close();
|
|
87
87
|
});
|
|
@@ -112,7 +112,6 @@ test.describe('Table', () => {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
// Rest of add column test remains the same as it's a more complex flow.
|
|
115
|
-
// TODO(ZaymonFC): Restore this after fixing format selection.
|
|
116
115
|
test('add column', async ({ browser, browserName }) => {
|
|
117
116
|
test.skip(browserName === 'webkit');
|
|
118
117
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
@@ -139,19 +138,21 @@ test.describe('Table', () => {
|
|
|
139
138
|
await table.grid.panByWheel(10000, 0);
|
|
140
139
|
|
|
141
140
|
await table.grid.cell(4, 0, 'grid').click();
|
|
142
|
-
|
|
143
|
-
await page.keyboard.press('
|
|
144
|
-
await page.
|
|
141
|
+
// TODO(wittjosiah): Surprisingly long delay needed here.
|
|
142
|
+
await page.keyboard.press('A', { delay: 1_000 });
|
|
143
|
+
await page.keyboard.press('u', { delay: 1_000 });
|
|
144
|
+
await page.getByRole('option', { name: 'Aut.' }).click();
|
|
145
145
|
|
|
146
146
|
// Assert that the value is shown in the cell.
|
|
147
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
147
|
+
await expect(page.getByRole('gridcell', { name: 'Aut.' }).first()).toBeVisible();
|
|
148
148
|
|
|
149
149
|
// Create new object.
|
|
150
150
|
await table.grid.cell(4, 1, 'grid').click();
|
|
151
|
-
|
|
152
|
-
await page.keyboard.type('
|
|
153
|
-
await page.keyboard.type('
|
|
154
|
-
await page.keyboard.type('
|
|
151
|
+
// TODO(wittjosiah): Surprisingly long delay needed here.
|
|
152
|
+
await page.keyboard.type('t', { delay: 1_000 });
|
|
153
|
+
await page.keyboard.type('e', { delay: 1_000 });
|
|
154
|
+
await page.keyboard.type('s', { delay: 1_000 });
|
|
155
|
+
await page.keyboard.type('t', { delay: 1_000 });
|
|
155
156
|
await page.getByText('Create new object', { exact: false }).click();
|
|
156
157
|
await page.getByTestId('save-button').click();
|
|
157
158
|
|
|
@@ -172,13 +173,13 @@ test.describe('Table', () => {
|
|
|
172
173
|
await page.getByTestId('table-switch').nth(7).click();
|
|
173
174
|
|
|
174
175
|
// Test that checks are durable in the data model by sorting.
|
|
175
|
-
await table.sortColumn(
|
|
176
|
+
await table.sortColumn(1, 'descending');
|
|
176
177
|
|
|
177
178
|
// Assert the first two switch checkboxes are checked.
|
|
178
179
|
await expect(page.getByTestId('table-switch').first()).toBeChecked();
|
|
179
180
|
await expect(page.getByTestId('table-switch').nth(1)).toBeChecked();
|
|
180
|
-
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('
|
|
181
|
-
await expect(table.grid.cell(0, 1, 'grid')).toHaveText('
|
|
181
|
+
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Sapiente.');
|
|
182
|
+
await expect(table.grid.cell(0, 1, 'grid')).toHaveText('Beatae.');
|
|
182
183
|
|
|
183
184
|
await page.close();
|
|
184
185
|
});
|