@dxos/react-ui-table 0.8.3 → 0.8.4-main.1068cf700f
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-GTGYBRJA.mjs +1748 -0
- package/dist/lib/browser/chunk-GTGYBRJA.mjs.map +7 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs +76 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1156 -2508
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +114 -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-DPNSZHU5.mjs +1750 -0
- package/dist/lib/node-esm/chunk-DPNSZHU5.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs +77 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1156 -2508
- 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 +115 -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/ColumnActionsMenu.d.ts +2 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +37 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +111 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +13 -9
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +108 -4
- package/dist/types/src/components/Table/Table.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 +14 -9
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +12 -18
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
- package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +4 -4
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +26 -56
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -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/useProjectionModel.d.ts +6 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +11 -10
- 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/modal-controller.d.ts +5 -8
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +17 -7
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +149 -32
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +13 -2
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +2 -1
- package/dist/types/src/playwright/TableManager.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 +25 -0
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +5 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +31 -0
- package/dist/types/src/types/Table.d.ts.map +1 -0
- 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/util/dynamic-table.d.ts +15 -14
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
- package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts +7 -0
- package/dist/types/src/util/query-ast.d.ts.map +1 -0
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/src/util/sort.d.ts +12 -0
- package/dist/types/src/util/sort.d.ts.map +1 -0
- 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-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +69 -56
- package/src/components/Table/ColumnActionsMenu.tsx +11 -7
- package/src/components/Table/ColumnSettings.tsx +16 -12
- package/src/components/Table/DynamicTable.stories.tsx +29 -25
- package/src/components/Table/DynamicTable.tsx +34 -22
- package/src/components/Table/Relations.stories.tsx +237 -0
- package/src/components/Table/RowActionsMenu.tsx +26 -17
- package/src/components/Table/Table.stories.tsx +171 -194
- package/src/components/Table/Table.tsx +364 -286
- package/src/components/TableCellEditor/CellValidationMessage.tsx +8 -3
- package/src/components/TableCellEditor/FormCellEditor.tsx +146 -49
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +36 -76
- package/src/components/TableCellEditor/TableCellEditor.tsx +106 -198
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +49 -33
- package/src/components/testing.ts +41 -32
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAddRow.ts +36 -0
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +41 -35
- package/src/index.ts +1 -2
- package/src/model/modal-controller.ts +24 -41
- package/src/model/selection-model.ts +75 -41
- package/src/model/table-model.test.ts +92 -56
- package/src/model/table-model.ts +628 -162
- package/src/model/table-presentation.test.ts +70 -59
- package/src/model/table-presentation.ts +278 -149
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +180 -41
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +133 -0
- package/src/translations.ts +7 -5
- package/src/types/Table.ts +90 -0
- package/src/types/index.ts +2 -2
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +50 -36
- package/src/util/index.ts +2 -1
- package/src/util/query-ast.ts +19 -0
- package/src/util/schema.ts +4 -4
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +28 -4
- package/src/util/table-controls.ts +4 -1
- 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/components/Table/CreateRefPanel.d.ts +0 -9
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
- package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
- package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/model/table-sorting.d.ts +0 -53
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/table.d.ts +0 -56
- package/dist/types/src/types/table.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -14
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -84
- package/src/components/Table/relations.stories.tsx +0 -124
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/model/table-sorting.ts +0 -216
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
- package/src/util/table.ts +0 -127
|
@@ -2,45 +2,57 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { Atom, type Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Predicate from 'effect/Predicate';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
9
|
+
import { Format, TypeEnum, getValue } from '@dxos/echo/internal';
|
|
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
|
-
import {
|
|
18
|
-
import {
|
|
18
|
+
import { type FieldType, VIEW_FIELD_LIMIT } from '@dxos/schema';
|
|
19
|
+
import { mx } from '@dxos/ui-theme';
|
|
19
20
|
|
|
20
|
-
import { type SelectionMode } from './selection-model';
|
|
21
|
-
import { type TableRow, type TableModel } from './table-model';
|
|
22
21
|
import { tableButtons, tableControls } from '../util';
|
|
23
22
|
|
|
23
|
+
import { type SelectionMode } from './selection-model';
|
|
24
|
+
import { type TableModel, type TableRow } from './table-model';
|
|
25
|
+
|
|
24
26
|
/**
|
|
25
27
|
* Presentation layer for a table component, handling cell rendering and grid display logic.
|
|
26
28
|
* Manages rendering of table cells, headers, selection columns, and action columns across
|
|
27
29
|
* different grid planes.
|
|
28
30
|
*/
|
|
29
31
|
export class TablePresentation<T extends TableRow = TableRow> {
|
|
32
|
+
private readonly _registry: Registry.Registry;
|
|
33
|
+
private readonly _visibleRange: Atom.Writable<DxGridPlaneRange>;
|
|
34
|
+
private fieldProjectionCache = new Map<string, ReturnType<typeof this.model.projection.getFieldProjection>>();
|
|
35
|
+
|
|
30
36
|
constructor(
|
|
37
|
+
registry: Registry.Registry,
|
|
31
38
|
private readonly model: TableModel<T>,
|
|
32
|
-
|
|
39
|
+
) {
|
|
40
|
+
this._registry = registry;
|
|
41
|
+
this._visibleRange = Atom.make<DxGridPlaneRange>({
|
|
33
42
|
start: { row: 0, col: 0 },
|
|
34
43
|
end: { row: 0, col: 0 },
|
|
35
|
-
})
|
|
36
|
-
|
|
44
|
+
});
|
|
45
|
+
}
|
|
37
46
|
|
|
38
47
|
public getCells(range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells {
|
|
48
|
+
// Clear cache at the start of each render pass
|
|
49
|
+
this.fieldProjectionCache.clear();
|
|
50
|
+
|
|
39
51
|
let cells: DxGridPlaneCells;
|
|
40
52
|
|
|
41
53
|
switch (plane) {
|
|
42
54
|
case 'grid':
|
|
43
|
-
this._visibleRange
|
|
55
|
+
this._registry.set(this._visibleRange, range);
|
|
44
56
|
cells = this.getMainGridCells(range);
|
|
45
57
|
break;
|
|
46
58
|
case 'frozenRowsStart':
|
|
@@ -58,12 +70,21 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
58
70
|
case 'fixedStartEnd':
|
|
59
71
|
cells = this.getNewColumnCell();
|
|
60
72
|
break;
|
|
73
|
+
case 'frozenRowsEnd':
|
|
74
|
+
cells = this.getDraftRowCells(range);
|
|
75
|
+
break;
|
|
76
|
+
case 'fixedEndStart':
|
|
77
|
+
cells = this.getDraftIconCells(range);
|
|
78
|
+
break;
|
|
79
|
+
case 'fixedEndEnd':
|
|
80
|
+
cells = this.getDraftActionCells(range);
|
|
81
|
+
break;
|
|
61
82
|
default:
|
|
62
83
|
cells = {};
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
if (plane === 'grid' && this.model.features.dataEditable === false) {
|
|
66
|
-
Object.values(cells).forEach((cell) => {
|
|
87
|
+
Object.values<DxGridCellValue>(cells).forEach((cell) => {
|
|
67
88
|
cell.readonly = 'text-select';
|
|
68
89
|
});
|
|
69
90
|
}
|
|
@@ -71,155 +92,180 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
71
92
|
return cells;
|
|
72
93
|
}
|
|
73
94
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Gets a field projection, using cache if available within the current render pass.
|
|
97
|
+
*/
|
|
98
|
+
private getFieldProjection(fieldId: string) {
|
|
99
|
+
let projection = this.fieldProjectionCache.get(fieldId);
|
|
100
|
+
if (!projection) {
|
|
101
|
+
projection = this.model.projection.getFieldProjection(fieldId);
|
|
102
|
+
this.fieldProjectionCache.set(fieldId, projection);
|
|
103
|
+
}
|
|
104
|
+
return projection;
|
|
105
|
+
}
|
|
77
106
|
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
private createDataCell(
|
|
108
|
+
cells: DxGridPlaneCells,
|
|
109
|
+
obj: T,
|
|
110
|
+
field: FieldType,
|
|
111
|
+
colIndex: number,
|
|
112
|
+
displayIndex: number,
|
|
113
|
+
): void {
|
|
114
|
+
const { props } = this.getFieldProjection(field.id);
|
|
115
|
+
|
|
116
|
+
const cell: DxGridCellValue = {
|
|
117
|
+
get value() {
|
|
118
|
+
const value = getValue(obj, field.path);
|
|
119
|
+
if (value == null) {
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
80
122
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (value == null) {
|
|
85
|
-
return '';
|
|
86
|
-
}
|
|
123
|
+
if (props.type === 'array') {
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
87
126
|
|
|
88
|
-
|
|
127
|
+
switch (props.format) {
|
|
128
|
+
case Format.TypeFormat.Boolean:
|
|
129
|
+
case Format.TypeFormat.SingleSelect:
|
|
130
|
+
case Format.TypeFormat.MultiSelect: {
|
|
89
131
|
return '';
|
|
90
132
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
case FormatEnum.SingleSelect:
|
|
95
|
-
case FormatEnum.MultiSelect: {
|
|
96
|
-
return '';
|
|
133
|
+
case Format.TypeFormat.Ref: {
|
|
134
|
+
if (!field.referencePath) {
|
|
135
|
+
return ''; // TODO(burdon): Show error.
|
|
97
136
|
}
|
|
98
|
-
case FormatEnum.Ref: {
|
|
99
|
-
if (!field.referencePath) {
|
|
100
|
-
return ''; // TODO(burdon): Show error.
|
|
101
|
-
}
|
|
102
137
|
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
return getValue(value.target, field.referencePath);
|
|
139
|
+
}
|
|
105
140
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
141
|
+
default: {
|
|
142
|
+
return formatForDisplay({ type: props.type, format: props.format, value });
|
|
109
143
|
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
};
|
|
112
147
|
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
}
|
|
148
|
+
const classes = [];
|
|
149
|
+
const formatClasses = cellClassesForFieldType(props);
|
|
128
150
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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('');
|
|
151
|
+
if (formatClasses) {
|
|
152
|
+
classes.push(formatClasses);
|
|
153
|
+
}
|
|
165
154
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
155
|
+
const rowSelectionClasses = cellClassesForRowSelection(
|
|
156
|
+
this.model.selection.isObjectSelected(obj),
|
|
157
|
+
this.model.selection.selectionMode,
|
|
158
|
+
);
|
|
169
159
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
160
|
+
if (rowSelectionClasses) {
|
|
161
|
+
classes.push(rowSelectionClasses);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
cell.className = mx(classes);
|
|
165
|
+
|
|
166
|
+
// Arrays.
|
|
167
|
+
if (props.type === TypeEnum.Array) {
|
|
168
|
+
const targetArray = getValue(obj, field.path);
|
|
169
|
+
if (targetArray && Array.isArray(targetArray)) {
|
|
170
|
+
// TODO(ZaymonFC): For arrays of objects, objects should have a 'label' annotation on the field
|
|
171
|
+
// that can be used to render tags.
|
|
172
|
+
// TODO(ZaymonFC): Move to util.
|
|
173
|
+
const getLabel = (value: any) => {
|
|
174
|
+
// If the value is falsy, return undefined
|
|
175
|
+
// If the object is an array, check for 'name' property
|
|
176
|
+
// If the object is a primitive type (string, number, boolean) stringify
|
|
177
|
+
// If the object is an array return 'Array'
|
|
178
|
+
if (!value) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
182
|
+
return String(value);
|
|
183
|
+
}
|
|
184
|
+
if (Array.isArray(value)) {
|
|
185
|
+
return 'Array';
|
|
186
|
+
}
|
|
187
|
+
if (typeof value === 'object' && value.name) {
|
|
188
|
+
return value.name;
|
|
189
|
+
}
|
|
190
|
+
if (typeof value === 'object' && value.value) {
|
|
191
|
+
return `${value.value}`;
|
|
192
|
+
}
|
|
193
|
+
return 'Object';
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const tags = targetArray
|
|
197
|
+
.map(getLabel)
|
|
198
|
+
.filter(Predicate.isNotNullable)
|
|
199
|
+
.map((title) => {
|
|
200
|
+
return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
|
|
201
|
+
})
|
|
202
|
+
.join('');
|
|
203
|
+
|
|
204
|
+
cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
|
|
176
205
|
}
|
|
206
|
+
}
|
|
177
207
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
});
|
|
185
|
-
cell.readonly = 'no-text-select';
|
|
208
|
+
// References.
|
|
209
|
+
if (props.format === Format.TypeFormat.Ref && props.referenceSchema) {
|
|
210
|
+
const targetObj = getValue(obj, field.path)?.target;
|
|
211
|
+
if (targetObj) {
|
|
212
|
+
const dxn = Obj.getDXN(targetObj)?.toString();
|
|
213
|
+
cell.accessoryHtml = `<div role="none" class="absolute inline-end-0 inset-block-0 p-[--dx-grid-cell-content-padding-block]"><dx-anchor dxn=${dxn} class="dx-button is-6 aspect-square min-bs-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
|
|
186
214
|
}
|
|
215
|
+
}
|
|
187
216
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
217
|
+
// Booleans.
|
|
218
|
+
if (props.format === Format.TypeFormat.Boolean) {
|
|
219
|
+
const value = getValue(obj, field.path);
|
|
220
|
+
cell.accessoryHtml = tableControls.switch.render({
|
|
221
|
+
colIndex,
|
|
222
|
+
rowIndex: displayIndex,
|
|
223
|
+
checked: value ?? false,
|
|
224
|
+
});
|
|
225
|
+
cell.readonly = 'no-text-select';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Single-Selects.
|
|
229
|
+
if (props.format === Format.TypeFormat.SingleSelect) {
|
|
230
|
+
const value = getValue(obj, field.path);
|
|
231
|
+
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
232
|
+
if (options) {
|
|
233
|
+
const option = options.find((o) => o.id === value);
|
|
234
|
+
if (option) {
|
|
235
|
+
cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
|
|
196
236
|
}
|
|
197
237
|
}
|
|
238
|
+
}
|
|
198
239
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
240
|
+
// Multi-Selects.
|
|
241
|
+
if (props.format === Format.TypeFormat.MultiSelect) {
|
|
242
|
+
const values = getValue(obj, field.path) as string[] | undefined;
|
|
243
|
+
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
244
|
+
if (options && values && values.length > 0) {
|
|
245
|
+
const tags = values
|
|
246
|
+
.map((value) => {
|
|
247
|
+
const option = options.find((o) => o.id === value);
|
|
248
|
+
if (option) {
|
|
249
|
+
return `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
})
|
|
253
|
+
.filter(Boolean)
|
|
254
|
+
.join('');
|
|
213
255
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
256
|
+
if (tags) {
|
|
257
|
+
cell.accessoryHtml = tags;
|
|
217
258
|
}
|
|
218
259
|
}
|
|
260
|
+
}
|
|
219
261
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
262
|
+
const idx = toPlaneCellIndex({ col: colIndex, row: displayIndex });
|
|
263
|
+
cells[idx] = cell;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
267
|
+
const cells: DxGridPlaneCells = {};
|
|
268
|
+
const fields = this.model.projection?.getFields() ?? [];
|
|
223
269
|
|
|
224
270
|
for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
|
|
225
271
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
@@ -228,7 +274,48 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
228
274
|
continue;
|
|
229
275
|
}
|
|
230
276
|
|
|
231
|
-
|
|
277
|
+
this.createDataCell(cells, this.model.getRows()[row], field, col, row);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return cells;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private getDraftRowCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
285
|
+
const cells: DxGridPlaneCells = {};
|
|
286
|
+
const fields = this.model.projection?.getFields() ?? [];
|
|
287
|
+
const draftRows = this.model.getDraftRows();
|
|
288
|
+
|
|
289
|
+
// Return cells of the CTA row if no draft row is active
|
|
290
|
+
if (draftRows.length === 0) {
|
|
291
|
+
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
292
|
+
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
293
|
+
value: '',
|
|
294
|
+
readonly: true,
|
|
295
|
+
className: 'dx-grid__row--cta__cell',
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
300
|
+
const draftRow = draftRows[row];
|
|
301
|
+
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
302
|
+
const cellIndex = toPlaneCellIndex({ col, row });
|
|
303
|
+
const field = fields[col];
|
|
304
|
+
if (!field) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.createDataCell(cells, draftRow.data, field, col, row);
|
|
309
|
+
|
|
310
|
+
if (this.model.hasDraftRowValidationError(row, field.path)) {
|
|
311
|
+
const cellValue = cells[cellIndex];
|
|
312
|
+
if (cellValue) {
|
|
313
|
+
const existingClasses = cellValue.className || '';
|
|
314
|
+
const draftClasses = 'dx-grid__cell--flagged';
|
|
315
|
+
cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
232
319
|
}
|
|
233
320
|
}
|
|
234
321
|
|
|
@@ -237,22 +324,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
237
324
|
|
|
238
325
|
private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
239
326
|
const cells: DxGridPlaneCells = {};
|
|
240
|
-
const fields = this.model.
|
|
327
|
+
const fields = this.model.projection?.getFields() ?? [];
|
|
328
|
+
const currentSort = this.model.getSorting();
|
|
241
329
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
242
330
|
const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
|
|
243
|
-
const
|
|
244
|
-
const direction = sorting?.fieldId === field.id ? sorting.direction : undefined;
|
|
331
|
+
const direction = currentSort?.fieldId === field.id ? currentSort.direction : undefined;
|
|
245
332
|
|
|
246
333
|
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
247
334
|
// TODO(burdon): Use same logic as form for fallback title.
|
|
248
|
-
value:
|
|
249
|
-
readonly: true,
|
|
335
|
+
value: '',
|
|
250
336
|
resizeHandle: 'col',
|
|
251
337
|
accessoryHtml: `
|
|
338
|
+
<span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
|
|
252
339
|
${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
|
|
253
340
|
${tableButtons.columnSettings.render({ fieldId: field.id })}
|
|
254
341
|
`,
|
|
255
|
-
className: '!bg-toolbarSurface',
|
|
342
|
+
className: '!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch',
|
|
256
343
|
};
|
|
257
344
|
}
|
|
258
345
|
|
|
@@ -307,7 +394,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
307
394
|
accessoryHtml: tableControls.checkbox.render({
|
|
308
395
|
rowIndex: 0,
|
|
309
396
|
header: true,
|
|
310
|
-
checked: this.model.selection.
|
|
397
|
+
checked: this.model.selection.allRowsSelected,
|
|
311
398
|
}),
|
|
312
399
|
className: '!bg-toolbarSurface',
|
|
313
400
|
readonly: true,
|
|
@@ -321,7 +408,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
321
408
|
[toPlaneCellIndex({ col: 0, row: 0 })]: {
|
|
322
409
|
accessoryHtml: this.model.features.schemaEditable
|
|
323
410
|
? tableButtons.addColumn.render({
|
|
324
|
-
disabled: (this.model.
|
|
411
|
+
disabled: (this.model.projection?.getFields()?.length ?? 0) >= VIEW_FIELD_LIMIT,
|
|
325
412
|
})
|
|
326
413
|
: undefined,
|
|
327
414
|
className: '!bg-toolbarSurface',
|
|
@@ -330,6 +417,49 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
330
417
|
},
|
|
331
418
|
};
|
|
332
419
|
}
|
|
420
|
+
|
|
421
|
+
private getDraftActionCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
422
|
+
const cells: DxGridPlaneCells = {};
|
|
423
|
+
const draftRows = this.model.getDraftRows();
|
|
424
|
+
|
|
425
|
+
// Return cells of the CTA row if no draft row is active
|
|
426
|
+
if (draftRows.length === 0) {
|
|
427
|
+
cells[toPlaneCellIndex({ col: 0, row: 0 })] = {
|
|
428
|
+
value: '',
|
|
429
|
+
className: 'dx-grid__row--cta__cell',
|
|
430
|
+
readonly: true,
|
|
431
|
+
};
|
|
432
|
+
} else {
|
|
433
|
+
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
434
|
+
const draftRow = draftRows[row];
|
|
435
|
+
const disabled = !draftRow.valid;
|
|
436
|
+
|
|
437
|
+
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
438
|
+
value: '',
|
|
439
|
+
readonly: true,
|
|
440
|
+
accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return cells;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private getDraftIconCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
449
|
+
const cells: DxGridPlaneCells = {};
|
|
450
|
+
const draftRows = this.model.getDraftRows();
|
|
451
|
+
|
|
452
|
+
for (let row = range.start.row; row <= range.end.row; row++) {
|
|
453
|
+
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
454
|
+
value: '',
|
|
455
|
+
readonly: true,
|
|
456
|
+
accessoryHtml: '<dx-icon icon="ph--plus--regular" class="contents"></dx-icon>',
|
|
457
|
+
className: mx('[&>div]:grid [&>div]:place-content-center', draftRows.length < 1 && 'dx-grid__row--cta__cell'),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return cells;
|
|
462
|
+
}
|
|
333
463
|
}
|
|
334
464
|
|
|
335
465
|
export const cellClassesForRowSelection = (selected: boolean, selectionMode: SelectionMode) => {
|
|
@@ -343,8 +473,7 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
|
|
|
343
473
|
|
|
344
474
|
switch (selectionMode) {
|
|
345
475
|
case 'single':
|
|
346
|
-
|
|
347
|
-
return ['!bg-currentRelated hover:bg-hoverSurface', '!cursor-pointer'];
|
|
476
|
+
return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
|
|
348
477
|
case 'multiple':
|
|
349
478
|
return ['!bg-gridCellSelected'];
|
|
350
479
|
}
|
|
@@ -17,6 +17,7 @@ const TABLE_SELECTORS = {
|
|
|
17
17
|
},
|
|
18
18
|
columnDelete: 'column-delete',
|
|
19
19
|
rowDelete: 'row-menu-delete',
|
|
20
|
+
addRow: 'table.toolbar.add-row',
|
|
20
21
|
} as const;
|
|
21
22
|
|
|
22
23
|
export class TableManager {
|
|
@@ -53,12 +54,18 @@ export class TableManager {
|
|
|
53
54
|
await this.page.getByTestId(TABLE_SELECTORS.columnSort[direction]).click();
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
public async
|
|
57
|
+
public async addRow(): Promise<void> {
|
|
58
|
+
await this.page.getByTestId(TABLE_SELECTORS.addRow).click();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public async addColumn({ label, format }: { label: string; format?: string }): Promise<void> {
|
|
57
62
|
await this.page.getByTestId(TABLE_SELECTORS.newColumnButton).click();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
await this.page.
|
|
63
|
+
if (format) {
|
|
64
|
+
await this.page.getByRole('combobox').click();
|
|
65
|
+
await this.page.getByLabel(format).click();
|
|
66
|
+
}
|
|
67
|
+
await this.page.getByPlaceholder('Property label').click();
|
|
68
|
+
await this.page.getByPlaceholder('Property label').fill(label);
|
|
69
|
+
await this.page.getByRole('button', { name: 'Save' }).click();
|
|
63
70
|
}
|
|
64
71
|
}
|
|
@@ -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-react:serve-e2e -- --port=9004',
|
|
14
|
+
port: 9004,
|
|
15
|
+
reuseExistingServer: false,
|
|
16
|
+
},
|
|
17
|
+
});
|