@dxos/react-ui-table 0.8.4-main.67995b8 → 0.8.4-main.70d3990
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-5XWSB3JP.mjs +71 -0
- package/dist/lib/browser/chunk-5XWSB3JP.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5XLSHLOG.mjs → chunk-KNC7XYT6.mjs} +573 -541
- package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +625 -698
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +16 -24
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +3 -3
- package/dist/lib/node-esm/{chunk-Y66YLSTC.mjs → chunk-CWSGS3Y2.mjs} +573 -541
- package/dist/lib/node-esm/chunk-CWSGS3Y2.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MKTMOY46.mjs +72 -0
- package/dist/lib/node-esm/chunk-MKTMOY46.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +625 -698
- 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 +16 -24
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +3 -3
- 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 +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +2 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +51 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +108 -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 +8 -4
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +105 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -6
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +12 -17
- 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 +2 -2
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -5
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +3 -2
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +5 -6
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +0 -7
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +14 -14
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +2 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +9 -6
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +1 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +21 -34
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -2
- package/dist/types/src/types/Table.d.ts +28 -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 +9 -9
- 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/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -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 +61 -59
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +1 -1
- package/src/components/Table/DynamicTable.stories.tsx +21 -17
- package/src/components/Table/DynamicTable.tsx +19 -13
- package/src/components/Table/Relations.stories.tsx +237 -0
- package/src/components/Table/RowActionsMenu.tsx +23 -15
- package/src/components/Table/Table.stories.tsx +103 -86
- package/src/components/Table/Table.tsx +140 -115
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +28 -66
- package/src/components/TableCellEditor/TableCellEditor.tsx +75 -225
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +34 -24
- package/src/components/testing.ts +4 -4
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +7 -5
- package/src/hooks/useProjectionModel.ts +19 -0
- package/src/hooks/useTableModel.ts +12 -14
- package/src/model/modal-controller.ts +1 -29
- package/src/model/selection-model.ts +3 -2
- package/src/model/table-model.test.ts +21 -12
- package/src/model/table-model.ts +62 -56
- package/src/model/table-presentation.test.ts +25 -23
- package/src/model/table-presentation.ts +82 -41
- package/src/model/table-sorting.ts +32 -14
- package/src/playwright/TableManager.ts +3 -3
- package/src/playwright/smoke.spec.ts +132 -38
- package/src/testing/useTestTableModel.ts +34 -22
- package/src/translations.ts +2 -2
- package/src/types/Table.ts +89 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.ts +20 -24
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +4 -4
- package/src/util/table-buttons.ts +4 -3
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Y66YLSTC.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/types/schema.d.ts +0 -8
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -11
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -86
- package/src/components/Table/relations.stories.tsx +0 -116
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/types/schema.ts +0 -16
- package/src/util/table.ts +0 -49
package/src/model/table-model.ts
CHANGED
|
@@ -2,39 +2,37 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { computed, effect, signal
|
|
5
|
+
import { type ReadonlySignal, computed, effect, signal } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
|
-
import { Obj, Ref } from '@dxos/echo';
|
|
9
|
-
import {
|
|
10
|
-
type FieldSortType,
|
|
11
|
-
FormatEnum,
|
|
12
|
-
getValue,
|
|
13
|
-
setValue,
|
|
14
|
-
type JsonProp,
|
|
15
|
-
getSnapshot,
|
|
16
|
-
getSchema,
|
|
17
|
-
toEffectSchema,
|
|
18
|
-
type JsonSchemaType,
|
|
19
|
-
} from '@dxos/echo-schema';
|
|
8
|
+
import { Format, Obj, Ref } from '@dxos/echo';
|
|
9
|
+
import { type JsonProp, type JsonSchemaType, getValue, setValue, toEffectSchema } from '@dxos/echo/internal';
|
|
20
10
|
import { invariant } from '@dxos/invariant';
|
|
21
11
|
import { ObjectId } from '@dxos/keys';
|
|
22
|
-
import { isLiveObject } from '@dxos/live-object';
|
|
23
|
-
import {
|
|
12
|
+
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
13
|
+
import { type Label } from '@dxos/react-ui';
|
|
24
14
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
25
15
|
import {
|
|
26
16
|
type DxGridAxisMeta,
|
|
27
|
-
type DxGridPlaneRange,
|
|
28
17
|
type DxGridPlanePosition,
|
|
18
|
+
type DxGridPlaneRange,
|
|
29
19
|
type DxGridPosition,
|
|
30
20
|
} from '@dxos/react-ui-grid';
|
|
31
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
type FieldSortType,
|
|
23
|
+
type ProjectionModel,
|
|
24
|
+
type PropertyType,
|
|
25
|
+
type ValidationError,
|
|
26
|
+
type View,
|
|
27
|
+
validateSchema,
|
|
28
|
+
} from '@dxos/schema';
|
|
29
|
+
|
|
30
|
+
import { type Table } from '../types';
|
|
31
|
+
import { touch } from '../util';
|
|
32
|
+
import { extractTagIds } from '../util/tag';
|
|
32
33
|
|
|
33
34
|
import { type SelectionMode, SelectionModel } from './selection-model';
|
|
34
35
|
import { TableSorting } from './table-sorting';
|
|
35
|
-
import { TableView } from '../types';
|
|
36
|
-
import { touch } from '../util';
|
|
37
|
-
import { extractTagIds } from '../util/tag';
|
|
38
36
|
|
|
39
37
|
// Domain types for cell classification
|
|
40
38
|
export type TableCellType = 'standard' | 'draft' | 'header';
|
|
@@ -53,7 +51,7 @@ export type TableRow = Record<JsonProp, any> & { id: string };
|
|
|
53
51
|
|
|
54
52
|
export type TableRowAction = {
|
|
55
53
|
id: string;
|
|
56
|
-
|
|
54
|
+
label: Label;
|
|
57
55
|
};
|
|
58
56
|
|
|
59
57
|
export type TableFeatures = {
|
|
@@ -68,17 +66,18 @@ const defaultFeatures: TableFeatures = {
|
|
|
68
66
|
schemaEditable: false,
|
|
69
67
|
};
|
|
70
68
|
|
|
69
|
+
export type InsertRowResult = 'draft' | 'final';
|
|
70
|
+
|
|
71
71
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
projection?: ProjectionModel;
|
|
72
|
+
object: Table.Table;
|
|
73
|
+
projection: ProjectionModel;
|
|
75
74
|
features?: Partial<TableFeatures>;
|
|
76
75
|
sorting?: FieldSortType[];
|
|
77
76
|
initialSelection?: string[];
|
|
78
77
|
pinnedRows?: { top: number[]; bottom: number[] };
|
|
79
78
|
rowActions?: TableRowAction[];
|
|
80
79
|
onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
|
|
81
|
-
onInsertRow?: (data?: any) =>
|
|
80
|
+
onInsertRow?: (data?: any) => InsertRowResult;
|
|
82
81
|
onDeleteRows?: (index: number, obj: T[]) => void;
|
|
83
82
|
onDeleteColumn?: (fieldId: string) => void;
|
|
84
83
|
onCellUpdate?: (cell: DxGridPosition) => void;
|
|
@@ -87,16 +86,15 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
87
86
|
};
|
|
88
87
|
|
|
89
88
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
90
|
-
private readonly
|
|
89
|
+
private readonly _object: Table.Table;
|
|
91
90
|
private readonly _projection: ProjectionModel;
|
|
92
|
-
private _table?: TableView;
|
|
93
91
|
|
|
94
92
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
95
93
|
start: { row: 0, col: 0 },
|
|
96
94
|
end: { row: 0, col: 0 },
|
|
97
95
|
});
|
|
98
96
|
|
|
99
|
-
private readonly _onInsertRow?: (data?: any) =>
|
|
97
|
+
private readonly _onInsertRow?: (data?: any) => InsertRowResult;
|
|
100
98
|
private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
|
|
101
99
|
private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
|
|
102
100
|
private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
|
|
@@ -114,8 +112,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
114
112
|
private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
|
|
115
113
|
|
|
116
114
|
constructor({
|
|
117
|
-
|
|
118
|
-
schema,
|
|
115
|
+
object,
|
|
119
116
|
projection,
|
|
120
117
|
features = {},
|
|
121
118
|
sorting = [],
|
|
@@ -130,8 +127,9 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
130
127
|
onRowAction,
|
|
131
128
|
}: TableModelProps<T>) {
|
|
132
129
|
super();
|
|
133
|
-
this.
|
|
134
|
-
this._projection = projection
|
|
130
|
+
this._object = object;
|
|
131
|
+
this._projection = projection;
|
|
132
|
+
this._projection.normalizeView();
|
|
135
133
|
|
|
136
134
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
137
135
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -141,7 +139,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
141
139
|
'Single selection is not compatible with editable tables.',
|
|
142
140
|
);
|
|
143
141
|
|
|
144
|
-
this._sorting = new TableSorting(
|
|
142
|
+
this._sorting = new TableSorting({
|
|
143
|
+
rows: this._rows,
|
|
144
|
+
getView: () => this.view,
|
|
145
|
+
projection: this._projection,
|
|
146
|
+
});
|
|
145
147
|
|
|
146
148
|
if (sorting.length > 0) {
|
|
147
149
|
const [sort] = sorting;
|
|
@@ -166,16 +168,17 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
public get id(): string {
|
|
169
|
-
return
|
|
171
|
+
return Obj.getDXN(this._object).toString();
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
public get view():
|
|
173
|
-
|
|
174
|
+
public get view(): View.View {
|
|
175
|
+
const view = this._object.view.target;
|
|
176
|
+
invariant(view, 'Table model not initialized');
|
|
177
|
+
return view;
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
public get table():
|
|
177
|
-
|
|
178
|
-
return this._table;
|
|
180
|
+
public get table(): Table.Table {
|
|
181
|
+
return this._object;
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
public get projection(): ProjectionModel {
|
|
@@ -235,10 +238,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
235
238
|
//
|
|
236
239
|
|
|
237
240
|
protected override async _open(): Promise<void> {
|
|
238
|
-
|
|
239
|
-
invariant(Obj.instanceOf(TableView, presentation));
|
|
240
|
-
this._table = presentation;
|
|
241
|
-
|
|
241
|
+
await this._object.view.load();
|
|
242
242
|
this.initializeColumnMeta();
|
|
243
243
|
this.initializeEffects();
|
|
244
244
|
await this._selection.open(this._ctx);
|
|
@@ -254,7 +254,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
254
254
|
return {
|
|
255
255
|
grid: meta,
|
|
256
256
|
frozenColsStart: { 0: { size: 30, resizeable: false } },
|
|
257
|
-
frozenColsEnd: { 0: { size:
|
|
257
|
+
frozenColsEnd: { 0: { size: 32, resizeable: false } },
|
|
258
258
|
};
|
|
259
259
|
});
|
|
260
260
|
}
|
|
@@ -334,11 +334,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
334
334
|
|
|
335
335
|
public getColumnCount = (): number => this._projection?.fields.length ?? 0;
|
|
336
336
|
|
|
337
|
-
public insertRow = ():
|
|
337
|
+
public insertRow = (): InsertRowResult => {
|
|
338
338
|
const result = this._onInsertRow?.();
|
|
339
|
-
if (result ===
|
|
339
|
+
if (result === 'draft' && this._draftRows.value.length === 0) {
|
|
340
340
|
this.createDraftRow();
|
|
341
341
|
}
|
|
342
|
+
return result ?? 'final';
|
|
342
343
|
};
|
|
343
344
|
|
|
344
345
|
private createDraftRow(): void {
|
|
@@ -393,14 +394,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
393
394
|
return false;
|
|
394
395
|
}
|
|
395
396
|
|
|
396
|
-
const
|
|
397
|
+
const insertRowResult = this._onInsertRow?.(draftRow.data);
|
|
397
398
|
|
|
398
|
-
if (
|
|
399
|
+
if (insertRowResult === 'final') {
|
|
399
400
|
const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
|
|
400
401
|
this._draftRows.value = newDraftRows;
|
|
401
402
|
}
|
|
402
403
|
|
|
403
|
-
return
|
|
404
|
+
return insertRowResult === 'final';
|
|
404
405
|
};
|
|
405
406
|
|
|
406
407
|
public deleteRow = (rowIndex: number): void => {
|
|
@@ -460,7 +461,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
460
461
|
|
|
461
462
|
const { props } = this._projection.getFieldProjection(field.id);
|
|
462
463
|
switch (props.format) {
|
|
463
|
-
case
|
|
464
|
+
case Format.TypeFormat.Ref: {
|
|
464
465
|
if (!field.referencePath) {
|
|
465
466
|
return ''; // TODO(burdon): Show error.
|
|
466
467
|
}
|
|
@@ -469,7 +470,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
469
470
|
}
|
|
470
471
|
|
|
471
472
|
default: {
|
|
472
|
-
return formatForEditing({
|
|
473
|
+
return formatForEditing({
|
|
474
|
+
type: props.type,
|
|
475
|
+
format: props.format,
|
|
476
|
+
value,
|
|
477
|
+
});
|
|
473
478
|
}
|
|
474
479
|
}
|
|
475
480
|
};
|
|
@@ -497,6 +502,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
497
502
|
setValue(snapshot, field.path, transformedValue);
|
|
498
503
|
|
|
499
504
|
const validationErrors = this.validateDraftRowData(snapshot);
|
|
505
|
+
// TODO(thure): These errors sometimes result in a useless message like “is missing” (what is missing?)
|
|
500
506
|
if (validationErrors.length > 0) {
|
|
501
507
|
const error = validationErrors.find((err) => err.path === field.path);
|
|
502
508
|
if (error) {
|
|
@@ -512,7 +518,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
512
518
|
const snapshot = getSnapshot(currentRow);
|
|
513
519
|
setValue(snapshot, field.path, transformedValue);
|
|
514
520
|
|
|
515
|
-
const schema = getSchema(currentRow);
|
|
521
|
+
const schema = Obj.getSchema(currentRow);
|
|
516
522
|
invariant(schema);
|
|
517
523
|
|
|
518
524
|
const validationResult = validateSchema(schema, snapshot);
|
|
@@ -538,7 +544,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
538
544
|
const transformedValue = editorTextToCellValue(props, value);
|
|
539
545
|
|
|
540
546
|
// Special handling for Ref format to preserve existing behavior
|
|
541
|
-
if (props.format ===
|
|
547
|
+
if (props.format === Format.TypeFormat.Ref && !isLiveObject(value)) {
|
|
542
548
|
return;
|
|
543
549
|
}
|
|
544
550
|
|
|
@@ -653,7 +659,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
653
659
|
|
|
654
660
|
const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
655
661
|
switch (props.format) {
|
|
656
|
-
case
|
|
662
|
+
case Format.TypeFormat.Ref: {
|
|
657
663
|
if (isLiveObject(value)) {
|
|
658
664
|
return Ref.make(value);
|
|
659
665
|
} else {
|
|
@@ -661,7 +667,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
661
667
|
}
|
|
662
668
|
}
|
|
663
669
|
|
|
664
|
-
case
|
|
670
|
+
case Format.TypeFormat.SingleSelect: {
|
|
665
671
|
const ids = extractTagIds(value);
|
|
666
672
|
if (ids && ids.length > 0) {
|
|
667
673
|
return ids[0];
|
|
@@ -670,7 +676,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
670
676
|
}
|
|
671
677
|
}
|
|
672
678
|
|
|
673
|
-
case
|
|
679
|
+
case Format.TypeFormat.MultiSelect: {
|
|
674
680
|
const ids = extractTagIds(value);
|
|
675
681
|
return ids || value;
|
|
676
682
|
}
|
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import { describe, expect, it
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { TypedObject } from '@dxos/echo
|
|
8
|
+
import { Filter, Query } from '@dxos/echo';
|
|
9
|
+
import { TypedObject } from '@dxos/echo/internal';
|
|
10
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
10
11
|
import { live } from '@dxos/live-object';
|
|
11
|
-
import {
|
|
12
|
-
|
|
12
|
+
import { ProjectionModel, View } 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 { TableView } from '../types';
|
|
17
18
|
|
|
18
19
|
describe('TablePresentation', () => {
|
|
19
20
|
describe('row reactivity', () => {
|
|
@@ -26,8 +27,8 @@ describe('TablePresentation', () => {
|
|
|
26
27
|
updateCount = 0;
|
|
27
28
|
({ data } = live({
|
|
28
29
|
data: [
|
|
29
|
-
{
|
|
30
|
-
{
|
|
30
|
+
{ title: 'A', count: 1 },
|
|
31
|
+
{ title: 'B', count: 2 },
|
|
31
32
|
],
|
|
32
33
|
}));
|
|
33
34
|
|
|
@@ -44,8 +45,8 @@ describe('TablePresentation', () => {
|
|
|
44
45
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
45
46
|
|
|
46
47
|
// Trigger a row update.
|
|
47
|
-
data[0].
|
|
48
|
-
expect(updateCount).toBe(
|
|
48
|
+
data[0].title = 'New Value';
|
|
49
|
+
expect(updateCount).toBe(2);
|
|
49
50
|
|
|
50
51
|
// Verify the new value through getCells.
|
|
51
52
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 0 } }, 'grid');
|
|
@@ -56,15 +57,14 @@ describe('TablePresentation', () => {
|
|
|
56
57
|
// Set up visible range to include our test data.
|
|
57
58
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
58
59
|
|
|
59
|
-
data.push({
|
|
60
|
-
expect(updateCount).toBe(
|
|
60
|
+
data.push({ title: 'C', count: 3 });
|
|
61
|
+
expect(updateCount).toBe(2);
|
|
61
62
|
|
|
62
63
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
63
64
|
|
|
64
65
|
// Verify the new row's data.
|
|
65
66
|
expect(cells['0,2']?.value).toBe('C');
|
|
66
67
|
expect(cells['1,2']?.value).toBe('3');
|
|
67
|
-
expect(cells['2,2']?.value).toBe('true');
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
it('should handle combined operations reactively', () => {
|
|
@@ -72,32 +72,34 @@ describe('TablePresentation', () => {
|
|
|
72
72
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
73
73
|
|
|
74
74
|
// Multiple operations.
|
|
75
|
-
data[0].
|
|
76
|
-
data.push({
|
|
75
|
+
data[0].title = 'Updated A';
|
|
76
|
+
data.push({ title: 'C', count: 3, completed: true });
|
|
77
77
|
data.splice(1, 1);
|
|
78
78
|
|
|
79
79
|
// We expect one update per operation affecting visible rows.
|
|
80
|
-
expect(updateCount).toBe(
|
|
80
|
+
expect(updateCount).toBe(4);
|
|
81
81
|
|
|
82
82
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
83
83
|
|
|
84
84
|
// Verify final state.
|
|
85
85
|
expect(cells['0,0']?.value).toBe('Updated A');
|
|
86
86
|
expect(cells['0,1']?.value).toBe('C');
|
|
87
|
-
expect(cells['1,1']?.value).toBe('3');
|
|
88
|
-
expect(cells['2,1']?.value).toBe('true');
|
|
89
87
|
});
|
|
90
88
|
});
|
|
91
89
|
});
|
|
92
90
|
|
|
93
91
|
class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
94
92
|
title: Schema.String,
|
|
95
|
-
|
|
93
|
+
count: Schema.Number,
|
|
96
94
|
}) {}
|
|
97
95
|
|
|
98
96
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
99
97
|
const schema = createEchoSchema(Test);
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
const view = View.make({
|
|
99
|
+
query: Query.select(Filter.type(schema)),
|
|
100
|
+
jsonSchema: schema.jsonSchema,
|
|
101
|
+
});
|
|
102
|
+
const object = Table.make({ view });
|
|
103
|
+
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
104
|
+
return new TableModel({ object, projection, ...props });
|
|
103
105
|
};
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
|
-
import
|
|
6
|
+
import * as Predicate from 'effect/Predicate';
|
|
7
7
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import {
|
|
9
|
+
import { Format, TypeEnum, getValue } from '@dxos/echo/internal';
|
|
10
10
|
import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
|
|
11
11
|
import {
|
|
12
12
|
type DxGridCellValue,
|
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
toPlaneCellIndex,
|
|
17
17
|
} from '@dxos/react-ui-grid';
|
|
18
18
|
import { mx } from '@dxos/react-ui-theme';
|
|
19
|
-
import {
|
|
19
|
+
import { type FieldType, VIEW_FIELD_LIMIT } from '@dxos/schema';
|
|
20
20
|
|
|
21
|
-
import { type SelectionMode } from './selection-model';
|
|
22
|
-
import { type TableRow, type TableModel } from './table-model';
|
|
23
21
|
import { tableButtons, tableControls } from '../util';
|
|
24
22
|
|
|
23
|
+
import { type SelectionMode } from './selection-model';
|
|
24
|
+
import { type TableModel, type TableRow } from './table-model';
|
|
25
|
+
|
|
25
26
|
/**
|
|
26
27
|
* Presentation layer for a table component, handling cell rendering and grid display logic.
|
|
27
28
|
* Manages rendering of table cells, headers, selection columns, and action columns across
|
|
@@ -67,6 +68,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
67
68
|
case 'frozenRowsEnd':
|
|
68
69
|
cells = this.getDraftRowCells(range);
|
|
69
70
|
break;
|
|
71
|
+
case 'fixedEndStart':
|
|
72
|
+
cells = this.getDraftIconCells(range);
|
|
73
|
+
break;
|
|
70
74
|
case 'fixedEndEnd':
|
|
71
75
|
cells = this.getDraftActionCells(range);
|
|
72
76
|
break;
|
|
@@ -116,12 +120,12 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
switch (props.format) {
|
|
119
|
-
case
|
|
120
|
-
case
|
|
121
|
-
case
|
|
123
|
+
case Format.TypeFormat.Boolean:
|
|
124
|
+
case Format.TypeFormat.SingleSelect:
|
|
125
|
+
case Format.TypeFormat.MultiSelect: {
|
|
122
126
|
return '';
|
|
123
127
|
}
|
|
124
|
-
case
|
|
128
|
+
case Format.TypeFormat.Ref: {
|
|
125
129
|
if (!field.referencePath) {
|
|
126
130
|
return ''; // TODO(burdon): Show error.
|
|
127
131
|
}
|
|
@@ -137,20 +141,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
137
141
|
};
|
|
138
142
|
|
|
139
143
|
const classes = [];
|
|
140
|
-
const formatClasses = cellClassesForFieldType(
|
|
144
|
+
const formatClasses = cellClassesForFieldType(props);
|
|
145
|
+
|
|
141
146
|
if (formatClasses) {
|
|
142
147
|
classes.push(formatClasses);
|
|
143
148
|
}
|
|
149
|
+
|
|
144
150
|
const rowSelectionClasses = cellClassesForRowSelection(
|
|
145
151
|
this.model.selection.isObjectSelected(obj),
|
|
146
152
|
this.model.selection.selectionMode,
|
|
147
153
|
);
|
|
154
|
+
|
|
148
155
|
if (rowSelectionClasses) {
|
|
149
156
|
classes.push(rowSelectionClasses);
|
|
150
157
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
158
|
+
|
|
159
|
+
cell.className = mx(classes);
|
|
154
160
|
|
|
155
161
|
// Arrays.
|
|
156
162
|
if (props.type === TypeEnum.Array) {
|
|
@@ -184,7 +190,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
184
190
|
|
|
185
191
|
const tags = targetArray
|
|
186
192
|
.map(getLabel)
|
|
187
|
-
.filter(isNotNullable)
|
|
193
|
+
.filter(Predicate.isNotNullable)
|
|
188
194
|
.map((title) => {
|
|
189
195
|
return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
|
|
190
196
|
})
|
|
@@ -195,16 +201,16 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
// References.
|
|
198
|
-
if (props.format ===
|
|
204
|
+
if (props.format === Format.TypeFormat.Ref && props.referenceSchema) {
|
|
199
205
|
const targetObj = getValue(obj, field.path)?.target;
|
|
200
206
|
if (targetObj) {
|
|
201
207
|
const dxn = Obj.getDXN(targetObj)?.toString();
|
|
202
|
-
cell.accessoryHtml = `<dx-
|
|
208
|
+
cell.accessoryHtml = `<div role="none" class="absolute inline-end-0 inset-block-0 p-[--dx-grid-cell-content-padding-block]"><dx-anchor refId=${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>`;
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
211
|
|
|
206
212
|
// Booleans.
|
|
207
|
-
if (props.format ===
|
|
213
|
+
if (props.format === Format.TypeFormat.Boolean) {
|
|
208
214
|
const value = getValue(obj, field.path);
|
|
209
215
|
cell.accessoryHtml = tableControls.switch.render({
|
|
210
216
|
colIndex,
|
|
@@ -215,7 +221,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
215
221
|
}
|
|
216
222
|
|
|
217
223
|
// Single-Selects.
|
|
218
|
-
if (props.format ===
|
|
224
|
+
if (props.format === Format.TypeFormat.SingleSelect) {
|
|
219
225
|
const value = getValue(obj, field.path);
|
|
220
226
|
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
221
227
|
if (options) {
|
|
@@ -227,7 +233,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
227
233
|
}
|
|
228
234
|
|
|
229
235
|
// Multi-Selects.
|
|
230
|
-
if (props.format ===
|
|
236
|
+
if (props.format === Format.TypeFormat.MultiSelect) {
|
|
231
237
|
const values = getValue(obj, field.path) as string[] | undefined;
|
|
232
238
|
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
233
239
|
if (options && values && values.length > 0) {
|
|
@@ -275,23 +281,34 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
275
281
|
const fields = this.model.projection?.fields ?? [];
|
|
276
282
|
const draftRows = this.model.draftRows.value;
|
|
277
283
|
|
|
278
|
-
|
|
279
|
-
|
|
284
|
+
// Return cells of the CTA row if no draft row is active
|
|
285
|
+
if (draftRows.length === 0) {
|
|
280
286
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
+
}
|
|
285
302
|
|
|
286
|
-
|
|
303
|
+
this.createDataCell(cells, draftRow.data, field, col, row);
|
|
287
304
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
+
}
|
|
295
312
|
}
|
|
296
313
|
}
|
|
297
314
|
}
|
|
@@ -310,14 +327,14 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
310
327
|
|
|
311
328
|
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
312
329
|
// TODO(burdon): Use same logic as form for fallback title.
|
|
313
|
-
value:
|
|
314
|
-
readonly: true,
|
|
330
|
+
value: '',
|
|
315
331
|
resizeHandle: 'col',
|
|
316
332
|
accessoryHtml: `
|
|
333
|
+
<span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
|
|
317
334
|
${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
|
|
318
335
|
${tableButtons.columnSettings.render({ fieldId: field.id })}
|
|
319
336
|
`,
|
|
320
|
-
className: '!bg-toolbarSurface',
|
|
337
|
+
className: '!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch',
|
|
321
338
|
};
|
|
322
339
|
}
|
|
323
340
|
|
|
@@ -400,14 +417,39 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
400
417
|
const cells: DxGridPlaneCells = {};
|
|
401
418
|
const draftRows = this.model.draftRows.value;
|
|
402
419
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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;
|
|
406
446
|
|
|
447
|
+
for (let row = range.start.row; row <= range.end.row; row++) {
|
|
407
448
|
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
408
449
|
value: '',
|
|
409
450
|
readonly: true,
|
|
410
|
-
accessoryHtml:
|
|
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'),
|
|
411
453
|
};
|
|
412
454
|
}
|
|
413
455
|
|
|
@@ -426,7 +468,6 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
|
|
|
426
468
|
|
|
427
469
|
switch (selectionMode) {
|
|
428
470
|
case 'single':
|
|
429
|
-
// TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
|
|
430
471
|
return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
|
|
431
472
|
case 'multiple':
|
|
432
473
|
return ['!bg-gridCellSelected'];
|