@dxos/react-ui-table 0.8.3 → 0.8.4-main.1da679c
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-63UUF4IG.mjs +66 -0
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +7 -0
- package/dist/lib/browser/chunk-BBXDTWCX.mjs +1822 -0
- package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +816 -2152
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +124 -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-K734DJB7.mjs +67 -0
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs +1823 -0
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +816 -2152
- 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 +125 -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 +1 -1
- 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/CreateRefPanel.d.ts +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 +50 -2
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +44 -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 +3 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +42 -3
- 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 +8 -3
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +9 -15
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.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 +14 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +8 -7
- 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.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 +59 -21
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +10 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +3 -4
- 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 +22 -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/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +28 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util/dynamic-table.d.ts +4 -5
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.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-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +57 -44
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +15 -11
- package/src/components/Table/CreateRefPanel.tsx +30 -11
- package/src/components/Table/DynamicTable.stories.tsx +8 -5
- package/src/components/Table/DynamicTable.tsx +17 -10
- package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +56 -47
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +126 -159
- package/src/components/Table/Table.tsx +169 -123
- package/src/components/TableCellEditor/CellValidationMessage.tsx +7 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +158 -48
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +31 -47
- package/src/components/TableCellEditor/TableCellEditor.tsx +106 -111
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +7 -24
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddRow.ts +38 -0
- package/src/hooks/useTableModel.ts +21 -17
- package/src/index.ts +1 -2
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +6 -3
- package/src/model/table-model.test.ts +12 -9
- package/src/model/table-model.ts +288 -88
- package/src/model/table-presentation.test.ts +12 -8
- package/src/model/table-presentation.ts +263 -138
- package/src/model/table-sorting.ts +22 -13
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +137 -44
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +124 -0
- package/src/translations.ts +7 -5
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +73 -0
- package/src/util/dynamic-table.ts +24 -21
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +1 -1
- 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/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/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/TableCellEditor/extension.ts +0 -61
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
- package/src/util/table.ts +0 -127
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
|
-
import { describe, expect, it
|
|
6
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Filter, Query } from '@dxos/echo';
|
|
9
9
|
import { TypedObject } from '@dxos/echo-schema';
|
|
10
10
|
import { live } from '@dxos/live-object';
|
|
11
11
|
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
12
|
-
import { createView
|
|
12
|
+
import { createView } from '@dxos/schema';
|
|
13
|
+
|
|
14
|
+
import { Table } from '../types';
|
|
13
15
|
|
|
14
16
|
import { TableModel, type TableModelProps } from './table-model';
|
|
15
17
|
import { TablePresentation } from './table-presentation';
|
|
16
|
-
import { TableType } from '../types';
|
|
17
18
|
|
|
18
19
|
describe('TablePresentation', () => {
|
|
19
20
|
describe('row reactivity', () => {
|
|
@@ -97,8 +98,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
97
98
|
|
|
98
99
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
99
100
|
const schema = createEchoSchema(Test);
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
const table = Table.make();
|
|
102
|
+
const view = createView({
|
|
103
|
+
query: Query.select(Filter.type(schema)),
|
|
104
|
+
jsonSchema: schema.jsonSchema,
|
|
105
|
+
presentation: table,
|
|
106
|
+
});
|
|
107
|
+
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
104
108
|
};
|
|
@@ -5,28 +5,32 @@
|
|
|
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, TypeEnum, getValue } 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';
|
|
18
|
-
import {
|
|
19
|
+
import { type FieldType, VIEW_FIELD_LIMIT } from '@dxos/schema';
|
|
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 fieldProjectionCache = new Map<string, ReturnType<typeof this.model.projection.getFieldProjection>>();
|
|
33
|
+
|
|
30
34
|
constructor(
|
|
31
35
|
private readonly model: TableModel<T>,
|
|
32
36
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
@@ -36,6 +40,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
36
40
|
) {}
|
|
37
41
|
|
|
38
42
|
public getCells(range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells {
|
|
43
|
+
// Clear cache at the start of each render pass
|
|
44
|
+
this.fieldProjectionCache.clear();
|
|
45
|
+
|
|
39
46
|
let cells: DxGridPlaneCells;
|
|
40
47
|
|
|
41
48
|
switch (plane) {
|
|
@@ -58,12 +65,21 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
58
65
|
case 'fixedStartEnd':
|
|
59
66
|
cells = this.getNewColumnCell();
|
|
60
67
|
break;
|
|
68
|
+
case 'frozenRowsEnd':
|
|
69
|
+
cells = this.getDraftRowCells(range);
|
|
70
|
+
break;
|
|
71
|
+
case 'fixedEndStart':
|
|
72
|
+
cells = this.getDraftIconCells(range);
|
|
73
|
+
break;
|
|
74
|
+
case 'fixedEndEnd':
|
|
75
|
+
cells = this.getDraftActionCells(range);
|
|
76
|
+
break;
|
|
61
77
|
default:
|
|
62
78
|
cells = {};
|
|
63
79
|
}
|
|
64
80
|
|
|
65
81
|
if (plane === 'grid' && this.model.features.dataEditable === false) {
|
|
66
|
-
Object.values(cells).forEach((cell) => {
|
|
82
|
+
Object.values<DxGridCellValue>(cells).forEach((cell) => {
|
|
67
83
|
cell.readonly = 'text-select';
|
|
68
84
|
});
|
|
69
85
|
}
|
|
@@ -71,155 +87,180 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
71
87
|
return cells;
|
|
72
88
|
}
|
|
73
89
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Gets a field projection, using cache if available within the current render pass.
|
|
92
|
+
*/
|
|
93
|
+
private getFieldProjection(fieldId: string) {
|
|
94
|
+
let projection = this.fieldProjectionCache.get(fieldId);
|
|
95
|
+
if (!projection) {
|
|
96
|
+
projection = this.model.projection.getFieldProjection(fieldId);
|
|
97
|
+
this.fieldProjectionCache.set(fieldId, projection);
|
|
98
|
+
}
|
|
99
|
+
return projection;
|
|
100
|
+
}
|
|
77
101
|
|
|
78
|
-
|
|
79
|
-
|
|
102
|
+
private createDataCell(
|
|
103
|
+
cells: DxGridPlaneCells,
|
|
104
|
+
obj: T,
|
|
105
|
+
field: FieldType,
|
|
106
|
+
colIndex: number,
|
|
107
|
+
displayIndex: number,
|
|
108
|
+
): void {
|
|
109
|
+
const { props } = this.getFieldProjection(field.id);
|
|
110
|
+
|
|
111
|
+
const cell: DxGridCellValue = {
|
|
112
|
+
get value() {
|
|
113
|
+
const value = getValue(obj, field.path);
|
|
114
|
+
if (value == null) {
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
80
117
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (value == null) {
|
|
85
|
-
return '';
|
|
86
|
-
}
|
|
118
|
+
if (props.type === 'array') {
|
|
119
|
+
return '';
|
|
120
|
+
}
|
|
87
121
|
|
|
88
|
-
|
|
122
|
+
switch (props.format) {
|
|
123
|
+
case FormatEnum.Boolean:
|
|
124
|
+
case FormatEnum.SingleSelect:
|
|
125
|
+
case FormatEnum.MultiSelect: {
|
|
89
126
|
return '';
|
|
90
127
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
case FormatEnum.SingleSelect:
|
|
95
|
-
case FormatEnum.MultiSelect: {
|
|
96
|
-
return '';
|
|
128
|
+
case FormatEnum.Ref: {
|
|
129
|
+
if (!field.referencePath) {
|
|
130
|
+
return ''; // TODO(burdon): Show error.
|
|
97
131
|
}
|
|
98
|
-
case FormatEnum.Ref: {
|
|
99
|
-
if (!field.referencePath) {
|
|
100
|
-
return ''; // TODO(burdon): Show error.
|
|
101
|
-
}
|
|
102
132
|
|
|
103
|
-
|
|
104
|
-
|
|
133
|
+
return getValue(value.target, field.referencePath);
|
|
134
|
+
}
|
|
105
135
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
136
|
+
default: {
|
|
137
|
+
return formatForDisplay({ type: props.type, format: props.format, value });
|
|
109
138
|
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
};
|
|
112
142
|
|
|
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
|
-
}
|
|
143
|
+
const classes = [];
|
|
144
|
+
const formatClasses = cellClassesForFieldType(props);
|
|
128
145
|
|
|
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('');
|
|
146
|
+
if (formatClasses) {
|
|
147
|
+
classes.push(formatClasses);
|
|
148
|
+
}
|
|
165
149
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
150
|
+
const rowSelectionClasses = cellClassesForRowSelection(
|
|
151
|
+
this.model.selection.isObjectSelected(obj),
|
|
152
|
+
this.model.selection.selectionMode,
|
|
153
|
+
);
|
|
169
154
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
155
|
+
if (rowSelectionClasses) {
|
|
156
|
+
classes.push(rowSelectionClasses);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
cell.className = mx(classes);
|
|
160
|
+
|
|
161
|
+
// Arrays.
|
|
162
|
+
if (props.type === TypeEnum.Array) {
|
|
163
|
+
const targetArray = getValue(obj, field.path);
|
|
164
|
+
if (targetArray && Array.isArray(targetArray)) {
|
|
165
|
+
// TODO(ZaymonFC): For arrays of objects, objects should have a 'label' annotation on the field
|
|
166
|
+
// that can be used to render tags.
|
|
167
|
+
// TODO(ZaymonFC): Move to util.
|
|
168
|
+
const getLabel = (value: any) => {
|
|
169
|
+
// If the value is falsy, return undefined
|
|
170
|
+
// If the object is an array, check for 'name' property
|
|
171
|
+
// If the object is a primitive type (string, number, boolean) stringify
|
|
172
|
+
// If the object is an array return 'Array'
|
|
173
|
+
if (!value) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
177
|
+
return String(value);
|
|
178
|
+
}
|
|
179
|
+
if (Array.isArray(value)) {
|
|
180
|
+
return 'Array';
|
|
181
|
+
}
|
|
182
|
+
if (typeof value === 'object' && value.name) {
|
|
183
|
+
return value.name;
|
|
184
|
+
}
|
|
185
|
+
if (typeof value === 'object' && value.value) {
|
|
186
|
+
return `${value.value}`;
|
|
187
|
+
}
|
|
188
|
+
return 'Object';
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const tags = targetArray
|
|
192
|
+
.map(getLabel)
|
|
193
|
+
.filter(isNotNullable)
|
|
194
|
+
.map((title) => {
|
|
195
|
+
return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
|
|
196
|
+
})
|
|
197
|
+
.join('');
|
|
198
|
+
|
|
199
|
+
cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
|
|
176
200
|
}
|
|
201
|
+
}
|
|
177
202
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
});
|
|
185
|
-
cell.readonly = 'no-text-select';
|
|
203
|
+
// References.
|
|
204
|
+
if (props.format === FormatEnum.Ref && props.referenceSchema) {
|
|
205
|
+
const targetObj = getValue(obj, field.path)?.target;
|
|
206
|
+
if (targetObj) {
|
|
207
|
+
const dxn = Obj.getDXN(targetObj)?.toString();
|
|
208
|
+
cell.accessoryHtml = `<dx-anchor refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-2 block-start-[.2rem]" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor>`;
|
|
186
209
|
}
|
|
210
|
+
}
|
|
187
211
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
212
|
+
// Booleans.
|
|
213
|
+
if (props.format === FormatEnum.Boolean) {
|
|
214
|
+
const value = getValue(obj, field.path);
|
|
215
|
+
cell.accessoryHtml = tableControls.switch.render({
|
|
216
|
+
colIndex,
|
|
217
|
+
rowIndex: displayIndex,
|
|
218
|
+
checked: value ?? false,
|
|
219
|
+
});
|
|
220
|
+
cell.readonly = 'no-text-select';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Single-Selects.
|
|
224
|
+
if (props.format === FormatEnum.SingleSelect) {
|
|
225
|
+
const value = getValue(obj, field.path);
|
|
226
|
+
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
227
|
+
if (options) {
|
|
228
|
+
const option = options.find((o) => o.id === value);
|
|
229
|
+
if (option) {
|
|
230
|
+
cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
|
|
196
231
|
}
|
|
197
232
|
}
|
|
233
|
+
}
|
|
198
234
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
235
|
+
// Multi-Selects.
|
|
236
|
+
if (props.format === FormatEnum.MultiSelect) {
|
|
237
|
+
const values = getValue(obj, field.path) as string[] | undefined;
|
|
238
|
+
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
239
|
+
if (options && values && values.length > 0) {
|
|
240
|
+
const tags = values
|
|
241
|
+
.map((value) => {
|
|
242
|
+
const option = options.find((o) => o.id === value);
|
|
243
|
+
if (option) {
|
|
244
|
+
return `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
})
|
|
248
|
+
.filter(Boolean)
|
|
249
|
+
.join('');
|
|
213
250
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
251
|
+
if (tags) {
|
|
252
|
+
cell.accessoryHtml = tags;
|
|
217
253
|
}
|
|
218
254
|
}
|
|
255
|
+
}
|
|
219
256
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
257
|
+
const idx = toPlaneCellIndex({ col: colIndex, row: displayIndex });
|
|
258
|
+
cells[idx] = cell;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
262
|
+
const cells: DxGridPlaneCells = {};
|
|
263
|
+
const fields = this.model.projection?.fields ?? [];
|
|
223
264
|
|
|
224
265
|
for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
|
|
225
266
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
@@ -228,7 +269,48 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
228
269
|
continue;
|
|
229
270
|
}
|
|
230
271
|
|
|
231
|
-
|
|
272
|
+
this.createDataCell(cells, this.model.rows.value[row], field, col, row);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return cells;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private getDraftRowCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
280
|
+
const cells: DxGridPlaneCells = {};
|
|
281
|
+
const fields = this.model.projection?.fields ?? [];
|
|
282
|
+
const draftRows = this.model.draftRows.value;
|
|
283
|
+
|
|
284
|
+
// Return cells of the CTA row if no draft row is active
|
|
285
|
+
if (draftRows.length === 0) {
|
|
286
|
+
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
287
|
+
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
288
|
+
value: '',
|
|
289
|
+
readonly: true,
|
|
290
|
+
className: 'dx-grid__row--cta__cell',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
295
|
+
const draftRow = draftRows[row];
|
|
296
|
+
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
297
|
+
const cellIndex = toPlaneCellIndex({ col, row });
|
|
298
|
+
const field = fields[col];
|
|
299
|
+
if (!field) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this.createDataCell(cells, draftRow.data, field, col, row);
|
|
304
|
+
|
|
305
|
+
if (this.model.hasDraftRowValidationError(row, field.path)) {
|
|
306
|
+
const cellValue = cells[cellIndex];
|
|
307
|
+
if (cellValue) {
|
|
308
|
+
const existingClasses = cellValue.className || '';
|
|
309
|
+
const draftClasses = 'dx-grid__cell--flagged';
|
|
310
|
+
cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
232
314
|
}
|
|
233
315
|
}
|
|
234
316
|
|
|
@@ -237,7 +319,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
237
319
|
|
|
238
320
|
private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
239
321
|
const cells: DxGridPlaneCells = {};
|
|
240
|
-
const fields = this.model.
|
|
322
|
+
const fields = this.model.projection?.fields ?? [];
|
|
241
323
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
242
324
|
const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
|
|
243
325
|
const sorting = this.model.sorting?.sorting;
|
|
@@ -245,14 +327,14 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
245
327
|
|
|
246
328
|
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
247
329
|
// TODO(burdon): Use same logic as form for fallback title.
|
|
248
|
-
value:
|
|
249
|
-
readonly: true,
|
|
330
|
+
value: '',
|
|
250
331
|
resizeHandle: 'col',
|
|
251
332
|
accessoryHtml: `
|
|
333
|
+
<span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
|
|
252
334
|
${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
|
|
253
335
|
${tableButtons.columnSettings.render({ fieldId: field.id })}
|
|
254
336
|
`,
|
|
255
|
-
className: '!bg-toolbarSurface',
|
|
337
|
+
className: '!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch',
|
|
256
338
|
};
|
|
257
339
|
}
|
|
258
340
|
|
|
@@ -321,7 +403,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
321
403
|
[toPlaneCellIndex({ col: 0, row: 0 })]: {
|
|
322
404
|
accessoryHtml: this.model.features.schemaEditable
|
|
323
405
|
? tableButtons.addColumn.render({
|
|
324
|
-
disabled: (this.model.
|
|
406
|
+
disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
|
|
325
407
|
})
|
|
326
408
|
: undefined,
|
|
327
409
|
className: '!bg-toolbarSurface',
|
|
@@ -330,6 +412,49 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
330
412
|
},
|
|
331
413
|
};
|
|
332
414
|
}
|
|
415
|
+
|
|
416
|
+
private getDraftActionCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
417
|
+
const cells: DxGridPlaneCells = {};
|
|
418
|
+
const draftRows = this.model.draftRows.value;
|
|
419
|
+
|
|
420
|
+
// Return cells of the CTA row if no draft row is active
|
|
421
|
+
if (draftRows.length === 0) {
|
|
422
|
+
cells[toPlaneCellIndex({ col: 0, row: 0 })] = {
|
|
423
|
+
value: '',
|
|
424
|
+
className: 'dx-grid__row--cta__cell',
|
|
425
|
+
readonly: true,
|
|
426
|
+
};
|
|
427
|
+
} else {
|
|
428
|
+
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
429
|
+
const draftRow = draftRows[row];
|
|
430
|
+
const disabled = !draftRow.valid;
|
|
431
|
+
|
|
432
|
+
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
433
|
+
value: '',
|
|
434
|
+
readonly: true,
|
|
435
|
+
accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return cells;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private getDraftIconCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
444
|
+
const cells: DxGridPlaneCells = {};
|
|
445
|
+
const draftRows = this.model.draftRows.value;
|
|
446
|
+
|
|
447
|
+
for (let row = range.start.row; row <= range.end.row; row++) {
|
|
448
|
+
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
449
|
+
value: '',
|
|
450
|
+
readonly: true,
|
|
451
|
+
accessoryHtml: '<dx-icon icon="ph--plus--regular" class="contents"></dx-icon>',
|
|
452
|
+
className: mx('[&>div]:grid [&>div]:place-content-center', draftRows.length < 1 && 'dx-grid__row--cta__cell'),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return cells;
|
|
457
|
+
}
|
|
333
458
|
}
|
|
334
459
|
|
|
335
460
|
export const cellClassesForRowSelection = (selected: boolean, selectionMode: SelectionMode) => {
|
|
@@ -344,7 +469,7 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
|
|
|
344
469
|
switch (selectionMode) {
|
|
345
470
|
case 'single':
|
|
346
471
|
// TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
|
|
347
|
-
return ['!bg-currentRelated hover:bg-hoverSurface
|
|
472
|
+
return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
|
|
348
473
|
case 'multiple':
|
|
349
474
|
return ['!bg-gridCellSelected'];
|
|
350
475
|
}
|
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type ReadonlySignal, type Signal, computed, signal } from '@preact/signals-core';
|
|
6
6
|
import orderBy from 'lodash.orderby';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { FormatEnum, TypeEnum, getValue } from '@dxos/echo-schema';
|
|
9
9
|
import { formatForDisplay } from '@dxos/react-ui-form';
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
DataType,
|
|
12
|
+
FieldSortType,
|
|
13
|
+
FieldType,
|
|
14
|
+
ProjectionModel,
|
|
15
|
+
PropertyType,
|
|
16
|
+
SortDirectionType,
|
|
17
|
+
} from '@dxos/schema';
|
|
11
18
|
|
|
12
19
|
import { type TableRow } from './table-model';
|
|
13
20
|
|
|
21
|
+
// TODO(wittjosiah): Remove. Use ordering in queries instead.
|
|
22
|
+
|
|
14
23
|
/**
|
|
15
24
|
* Represents the local sort state.
|
|
16
25
|
* - undefined: No local sort set, fallback to view sort.
|
|
@@ -49,13 +58,13 @@ export class TableSorting<T extends TableRow> {
|
|
|
49
58
|
|
|
50
59
|
constructor(
|
|
51
60
|
rows: Signal<T[]>,
|
|
52
|
-
private readonly _view:
|
|
53
|
-
private readonly _projection:
|
|
61
|
+
private readonly _view: DataType.View,
|
|
62
|
+
private readonly _projection: ProjectionModel,
|
|
54
63
|
) {
|
|
55
64
|
this._rows = rows;
|
|
56
65
|
this._isDirty = computed(() => {
|
|
57
66
|
const local = this._localSort.value;
|
|
58
|
-
const viewSort = this._view
|
|
67
|
+
const viewSort = this._view.sort?.[0];
|
|
59
68
|
if (local?.type === 'cleared') {
|
|
60
69
|
return viewSort !== undefined;
|
|
61
70
|
}
|
|
@@ -83,7 +92,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
83
92
|
return local.sort;
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
return this._view
|
|
95
|
+
return this._view.sort?.[0];
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
/**
|
|
@@ -106,7 +115,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
public toggleSort(fieldId: string): void {
|
|
109
|
-
if (!this.
|
|
118
|
+
if (!this.sorting || this.sorting.fieldId !== fieldId) {
|
|
110
119
|
return;
|
|
111
120
|
}
|
|
112
121
|
|
|
@@ -127,11 +136,11 @@ export class TableSorting<T extends TableRow> {
|
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
public save(): void {
|
|
130
|
-
if (this.
|
|
139
|
+
if (this._localSort.value !== undefined) {
|
|
131
140
|
if (this._localSort.value.type === 'active') {
|
|
132
|
-
this._view.
|
|
141
|
+
this._view.sort = [this._localSort.value.sort];
|
|
133
142
|
} else {
|
|
134
|
-
this._view.
|
|
143
|
+
this._view.sort = [];
|
|
135
144
|
}
|
|
136
145
|
this._localSort.value = undefined;
|
|
137
146
|
}
|
|
@@ -145,11 +154,11 @@ export class TableSorting<T extends TableRow> {
|
|
|
145
154
|
return computed(() => {
|
|
146
155
|
this._displayToDataIndex.clear();
|
|
147
156
|
const sort = this.sorting;
|
|
148
|
-
if (!sort
|
|
157
|
+
if (!sort) {
|
|
149
158
|
return this._rows.value;
|
|
150
159
|
}
|
|
151
160
|
|
|
152
|
-
const field = this.
|
|
161
|
+
const field = this._projection.fields.find((f) => f.id === sort.fieldId);
|
|
153
162
|
if (!field) {
|
|
154
163
|
return this._rows.value;
|
|
155
164
|
}
|
|
@@ -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
|
+
});
|