@dxos/react-ui-table 0.8.4-main.5ea62a8 → 0.8.4-main.66e292d
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-RQYXNWWN.mjs → chunk-KNC7XYT6.mjs} +529 -568
- package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +187 -322
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +15 -25
- 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-QNDUGIS7.mjs → chunk-CWSGS3Y2.mjs} +529 -568
- 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 +187 -322
- 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 +15 -25
- 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/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 +5 -5
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +73 -9
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +5 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +72 -10
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -5
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +3 -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 +1 -1
- 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 +4 -5
- 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/table-model.d.ts +8 -10
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +8 -5
- 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 -29
- 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/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +53 -52
- package/src/components/Table/DynamicTable.stories.tsx +14 -13
- package/src/components/Table/DynamicTable.tsx +15 -10
- package/src/components/Table/Relations.stories.tsx +133 -29
- package/src/components/Table/RowActionsMenu.tsx +21 -13
- package/src/components/Table/Table.stories.tsx +85 -79
- package/src/components/Table/Table.tsx +13 -9
- package/src/components/TableCellEditor/FormCellEditor.tsx +51 -41
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +21 -47
- package/src/components/TableCellEditor/TableCellEditor.tsx +20 -115
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +32 -22
- package/src/components/testing.ts +4 -4
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +1 -1
- package/src/hooks/useProjectionModel.ts +19 -0
- package/src/hooks/useTableModel.ts +11 -13
- package/src/model/modal-controller.ts +0 -28
- package/src/model/table-model.test.ts +19 -9
- package/src/model/table-model.ts +42 -41
- package/src/model/table-presentation.test.ts +22 -20
- package/src/model/table-presentation.ts +12 -13
- package/src/model/table-sorting.ts +31 -13
- package/src/playwright/TableManager.ts +3 -3
- package/src/playwright/smoke.spec.ts +8 -6
- package/src/testing/useTestTableModel.ts +33 -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 +17 -21
- package/src/util/schema.ts +4 -4
- package/dist/lib/browser/chunk-63UUF4IG.mjs +0 -66
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
- package/dist/lib/browser/chunk-RQYXNWWN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-K734DJB7.mjs +0 -67
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-QNDUGIS7.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/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -103
- package/src/types/schema.ts +0 -73
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { computed } from '@preact/signals-core';
|
|
6
|
-
import
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { Filter, Query } from '@dxos/echo';
|
|
10
|
+
import { TypedObject } from '@dxos/echo/internal';
|
|
11
|
+
import { updateCounter } from '@dxos/echo/testing';
|
|
12
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
11
13
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
12
14
|
import { live } from '@dxos/live-object';
|
|
13
|
-
import {
|
|
14
|
-
import { createView } from '@dxos/schema';
|
|
15
|
+
import { ProjectionModel, View } from '@dxos/schema';
|
|
15
16
|
|
|
16
17
|
import { Table } from '../types';
|
|
17
18
|
|
|
@@ -24,7 +25,7 @@ registerSignalsRuntime();
|
|
|
24
25
|
|
|
25
26
|
describe('TableModel', () => {
|
|
26
27
|
let updateCount = 0;
|
|
27
|
-
let model:
|
|
28
|
+
let model: any;
|
|
28
29
|
|
|
29
30
|
beforeEach(async () => {
|
|
30
31
|
updateCount = 0;
|
|
@@ -32,6 +33,11 @@ describe('TableModel', () => {
|
|
|
32
33
|
onCellUpdate: () => updateCount++,
|
|
33
34
|
});
|
|
34
35
|
await model.open();
|
|
36
|
+
model.setRows([
|
|
37
|
+
{ id: '1', title: 'Test', completed: false },
|
|
38
|
+
{ id: '2', title: 'Test 2', completed: true },
|
|
39
|
+
{ id: '3', title: 'Test 3', completed: false },
|
|
40
|
+
]);
|
|
35
41
|
});
|
|
36
42
|
|
|
37
43
|
afterEach(async () => {
|
|
@@ -106,7 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
106
112
|
|
|
107
113
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
108
114
|
const schema = createEchoSchema(Test);
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
const view = View.make({
|
|
116
|
+
query: Query.select(Filter.type(schema)),
|
|
117
|
+
jsonSchema: schema.jsonSchema,
|
|
118
|
+
});
|
|
119
|
+
const object = Table.make({ view });
|
|
120
|
+
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
121
|
+
return new TableModel({ object, projection, ...props });
|
|
112
122
|
};
|
package/src/model/table-model.ts
CHANGED
|
@@ -5,21 +5,11 @@
|
|
|
5
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
|
-
type JsonProp,
|
|
13
|
-
type JsonSchemaType,
|
|
14
|
-
getSchema,
|
|
15
|
-
getValue,
|
|
16
|
-
setValue,
|
|
17
|
-
toEffectSchema,
|
|
18
|
-
} 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';
|
|
19
10
|
import { invariant } from '@dxos/invariant';
|
|
20
11
|
import { ObjectId } from '@dxos/keys';
|
|
21
12
|
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
22
|
-
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
23
13
|
import { type Label } from '@dxos/react-ui';
|
|
24
14
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
25
15
|
import {
|
|
@@ -28,9 +18,16 @@ import {
|
|
|
28
18
|
type DxGridPlaneRange,
|
|
29
19
|
type DxGridPosition,
|
|
30
20
|
} from '@dxos/react-ui-grid';
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
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';
|
|
34
31
|
import { touch } from '../util';
|
|
35
32
|
import { extractTagIds } from '../util/tag';
|
|
36
33
|
|
|
@@ -72,9 +69,8 @@ const defaultFeatures: TableFeatures = {
|
|
|
72
69
|
export type InsertRowResult = 'draft' | 'final';
|
|
73
70
|
|
|
74
71
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
projection?: ProjectionModel;
|
|
72
|
+
object: Table.Table;
|
|
73
|
+
projection: ProjectionModel;
|
|
78
74
|
features?: Partial<TableFeatures>;
|
|
79
75
|
sorting?: FieldSortType[];
|
|
80
76
|
initialSelection?: string[];
|
|
@@ -90,9 +86,8 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
90
86
|
};
|
|
91
87
|
|
|
92
88
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
93
|
-
private readonly
|
|
89
|
+
private readonly _object: Table.Table;
|
|
94
90
|
private readonly _projection: ProjectionModel;
|
|
95
|
-
private _table?: Table.Table;
|
|
96
91
|
|
|
97
92
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
98
93
|
start: { row: 0, col: 0 },
|
|
@@ -117,8 +112,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
117
112
|
private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
|
|
118
113
|
|
|
119
114
|
constructor({
|
|
120
|
-
|
|
121
|
-
schema,
|
|
115
|
+
object,
|
|
122
116
|
projection,
|
|
123
117
|
features = {},
|
|
124
118
|
sorting = [],
|
|
@@ -133,8 +127,9 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
133
127
|
onRowAction,
|
|
134
128
|
}: TableModelProps<T>) {
|
|
135
129
|
super();
|
|
136
|
-
this.
|
|
137
|
-
this._projection = projection
|
|
130
|
+
this._object = object;
|
|
131
|
+
this._projection = projection;
|
|
132
|
+
this._projection.normalizeView();
|
|
138
133
|
|
|
139
134
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
140
135
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -144,7 +139,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
144
139
|
'Single selection is not compatible with editable tables.',
|
|
145
140
|
);
|
|
146
141
|
|
|
147
|
-
this._sorting = new TableSorting(
|
|
142
|
+
this._sorting = new TableSorting({
|
|
143
|
+
rows: this._rows,
|
|
144
|
+
getView: () => this.view,
|
|
145
|
+
projection: this._projection,
|
|
146
|
+
});
|
|
148
147
|
|
|
149
148
|
if (sorting.length > 0) {
|
|
150
149
|
const [sort] = sorting;
|
|
@@ -169,16 +168,17 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
169
168
|
}
|
|
170
169
|
|
|
171
170
|
public get id(): string {
|
|
172
|
-
return
|
|
171
|
+
return Obj.getDXN(this._object).toString();
|
|
173
172
|
}
|
|
174
173
|
|
|
175
|
-
public get view():
|
|
176
|
-
|
|
174
|
+
public get view(): View.View {
|
|
175
|
+
const view = this._object.view.target;
|
|
176
|
+
invariant(view, 'Table model not initialized');
|
|
177
|
+
return view;
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
public get table(): Table.Table {
|
|
180
|
-
|
|
181
|
-
return this._table;
|
|
181
|
+
return this._object;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
public get projection(): ProjectionModel {
|
|
@@ -238,10 +238,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
238
238
|
//
|
|
239
239
|
|
|
240
240
|
protected override async _open(): Promise<void> {
|
|
241
|
-
|
|
242
|
-
invariant(Obj.instanceOf(Table.Table, presentation));
|
|
243
|
-
this._table = presentation;
|
|
244
|
-
|
|
241
|
+
await this._object.view.load();
|
|
245
242
|
this.initializeColumnMeta();
|
|
246
243
|
this.initializeEffects();
|
|
247
244
|
await this._selection.open(this._ctx);
|
|
@@ -464,7 +461,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
464
461
|
|
|
465
462
|
const { props } = this._projection.getFieldProjection(field.id);
|
|
466
463
|
switch (props.format) {
|
|
467
|
-
case
|
|
464
|
+
case Format.TypeFormat.Ref: {
|
|
468
465
|
if (!field.referencePath) {
|
|
469
466
|
return ''; // TODO(burdon): Show error.
|
|
470
467
|
}
|
|
@@ -473,7 +470,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
473
470
|
}
|
|
474
471
|
|
|
475
472
|
default: {
|
|
476
|
-
return formatForEditing({
|
|
473
|
+
return formatForEditing({
|
|
474
|
+
type: props.type,
|
|
475
|
+
format: props.format,
|
|
476
|
+
value,
|
|
477
|
+
});
|
|
477
478
|
}
|
|
478
479
|
}
|
|
479
480
|
};
|
|
@@ -517,7 +518,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
517
518
|
const snapshot = getSnapshot(currentRow);
|
|
518
519
|
setValue(snapshot, field.path, transformedValue);
|
|
519
520
|
|
|
520
|
-
const schema = getSchema(currentRow);
|
|
521
|
+
const schema = Obj.getSchema(currentRow);
|
|
521
522
|
invariant(schema);
|
|
522
523
|
|
|
523
524
|
const validationResult = validateSchema(schema, snapshot);
|
|
@@ -543,7 +544,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
543
544
|
const transformedValue = editorTextToCellValue(props, value);
|
|
544
545
|
|
|
545
546
|
// Special handling for Ref format to preserve existing behavior
|
|
546
|
-
if (props.format ===
|
|
547
|
+
if (props.format === Format.TypeFormat.Ref && !isLiveObject(value)) {
|
|
547
548
|
return;
|
|
548
549
|
}
|
|
549
550
|
|
|
@@ -658,7 +659,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
658
659
|
|
|
659
660
|
const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
660
661
|
switch (props.format) {
|
|
661
|
-
case
|
|
662
|
+
case Format.TypeFormat.Ref: {
|
|
662
663
|
if (isLiveObject(value)) {
|
|
663
664
|
return Ref.make(value);
|
|
664
665
|
} else {
|
|
@@ -666,7 +667,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
666
667
|
}
|
|
667
668
|
}
|
|
668
669
|
|
|
669
|
-
case
|
|
670
|
+
case Format.TypeFormat.SingleSelect: {
|
|
670
671
|
const ids = extractTagIds(value);
|
|
671
672
|
if (ids && ids.length > 0) {
|
|
672
673
|
return ids[0];
|
|
@@ -675,7 +676,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
675
676
|
}
|
|
676
677
|
}
|
|
677
678
|
|
|
678
|
-
case
|
|
679
|
+
case Format.TypeFormat.MultiSelect: {
|
|
679
680
|
const ids = extractTagIds(value);
|
|
680
681
|
return ids || value;
|
|
681
682
|
}
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Filter, Query } from '@dxos/echo';
|
|
9
|
+
import { TypedObject } from '@dxos/echo/internal';
|
|
10
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
9
11
|
import { live } from '@dxos/live-object';
|
|
10
|
-
import {
|
|
11
|
-
import { createView } from '@dxos/schema';
|
|
12
|
+
import { ProjectionModel, View } from '@dxos/schema';
|
|
12
13
|
|
|
13
14
|
import { Table } from '../types';
|
|
14
15
|
|
|
@@ -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,
|
|
@@ -120,12 +120,12 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
switch (props.format) {
|
|
123
|
-
case
|
|
124
|
-
case
|
|
125
|
-
case
|
|
123
|
+
case Format.TypeFormat.Boolean:
|
|
124
|
+
case Format.TypeFormat.SingleSelect:
|
|
125
|
+
case Format.TypeFormat.MultiSelect: {
|
|
126
126
|
return '';
|
|
127
127
|
}
|
|
128
|
-
case
|
|
128
|
+
case Format.TypeFormat.Ref: {
|
|
129
129
|
if (!field.referencePath) {
|
|
130
130
|
return ''; // TODO(burdon): Show error.
|
|
131
131
|
}
|
|
@@ -190,7 +190,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
190
190
|
|
|
191
191
|
const tags = targetArray
|
|
192
192
|
.map(getLabel)
|
|
193
|
-
.filter(isNotNullable)
|
|
193
|
+
.filter(Predicate.isNotNullable)
|
|
194
194
|
.map((title) => {
|
|
195
195
|
return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
|
|
196
196
|
})
|
|
@@ -201,16 +201,16 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
// References.
|
|
204
|
-
if (props.format ===
|
|
204
|
+
if (props.format === Format.TypeFormat.Ref && props.referenceSchema) {
|
|
205
205
|
const targetObj = getValue(obj, field.path)?.target;
|
|
206
206
|
if (targetObj) {
|
|
207
207
|
const dxn = Obj.getDXN(targetObj)?.toString();
|
|
208
|
-
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>`;
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
// Booleans.
|
|
213
|
-
if (props.format ===
|
|
213
|
+
if (props.format === Format.TypeFormat.Boolean) {
|
|
214
214
|
const value = getValue(obj, field.path);
|
|
215
215
|
cell.accessoryHtml = tableControls.switch.render({
|
|
216
216
|
colIndex,
|
|
@@ -221,7 +221,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
// Single-Selects.
|
|
224
|
-
if (props.format ===
|
|
224
|
+
if (props.format === Format.TypeFormat.SingleSelect) {
|
|
225
225
|
const value = getValue(obj, field.path);
|
|
226
226
|
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
227
227
|
if (options) {
|
|
@@ -233,7 +233,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
// Multi-Selects.
|
|
236
|
-
if (props.format ===
|
|
236
|
+
if (props.format === Format.TypeFormat.MultiSelect) {
|
|
237
237
|
const values = getValue(obj, field.path) as string[] | undefined;
|
|
238
238
|
const options = this.model.projection.getFieldProjection(field.id).props.options;
|
|
239
239
|
if (options && values && values.length > 0) {
|
|
@@ -468,7 +468,6 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
|
|
|
468
468
|
|
|
469
469
|
switch (selectionMode) {
|
|
470
470
|
case 'single':
|
|
471
|
-
// TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
|
|
472
471
|
return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
|
|
473
472
|
case 'multiple':
|
|
474
473
|
return ['!bg-gridCellSelected'];
|
|
@@ -5,12 +5,21 @@
|
|
|
5
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 { Format, TypeEnum, getValue } from '@dxos/echo/internal';
|
|
9
9
|
import { formatForDisplay } from '@dxos/react-ui-form';
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
type FieldSortType,
|
|
12
|
+
type FieldType,
|
|
13
|
+
type ProjectionModel,
|
|
14
|
+
type PropertyType,
|
|
15
|
+
type SortDirectionType,
|
|
16
|
+
type View,
|
|
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.
|
|
@@ -43,19 +52,27 @@ type LocalSort = { type: 'cleared' } | { type: 'active'; sort: FieldSortType } |
|
|
|
43
52
|
export class TableSorting<T extends TableRow> {
|
|
44
53
|
private readonly _displayToDataIndex = new Map<number, number>();
|
|
45
54
|
private readonly _rows: Signal<T[]>;
|
|
55
|
+
private readonly _getView: () => View.View;
|
|
56
|
+
private readonly _projection: ProjectionModel;
|
|
46
57
|
private readonly _localSort = signal<LocalSort>(undefined);
|
|
47
58
|
private readonly _isDirty: ReadonlySignal<boolean>;
|
|
48
59
|
public readonly sortedRows: ReadonlySignal<T[]>;
|
|
49
60
|
|
|
50
|
-
constructor(
|
|
51
|
-
rows
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
constructor({
|
|
62
|
+
rows,
|
|
63
|
+
projection,
|
|
64
|
+
getView,
|
|
65
|
+
}: {
|
|
66
|
+
rows: Signal<T[]>;
|
|
67
|
+
getView: () => View.View;
|
|
68
|
+
projection: ProjectionModel;
|
|
69
|
+
}) {
|
|
55
70
|
this._rows = rows;
|
|
71
|
+
this._getView = getView;
|
|
72
|
+
this._projection = projection;
|
|
56
73
|
this._isDirty = computed(() => {
|
|
57
74
|
const local = this._localSort.value;
|
|
58
|
-
const viewSort = this.
|
|
75
|
+
const viewSort = this._getView().sort?.[0];
|
|
59
76
|
if (local?.type === 'cleared') {
|
|
60
77
|
return viewSort !== undefined;
|
|
61
78
|
}
|
|
@@ -83,7 +100,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
83
100
|
return local.sort;
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
return this.
|
|
103
|
+
return this._getView().sort?.[0];
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
/**
|
|
@@ -128,10 +145,11 @@ export class TableSorting<T extends TableRow> {
|
|
|
128
145
|
|
|
129
146
|
public save(): void {
|
|
130
147
|
if (this._localSort.value !== undefined) {
|
|
148
|
+
const view = this._getView();
|
|
131
149
|
if (this._localSort.value.type === 'active') {
|
|
132
|
-
|
|
150
|
+
view.sort = [this._localSort.value.sort];
|
|
133
151
|
} else {
|
|
134
|
-
|
|
152
|
+
view.sort = [];
|
|
135
153
|
}
|
|
136
154
|
this._localSort.value = undefined;
|
|
137
155
|
}
|
|
@@ -193,7 +211,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
193
211
|
* For single select fields, the sort value is determined by the index of the option in the
|
|
194
212
|
* options array. This ensures sort order matches the configured option order.
|
|
195
213
|
*/
|
|
196
|
-
if (format ===
|
|
214
|
+
if (format === Format.TypeFormat.SingleSelect) {
|
|
197
215
|
const options = props.options;
|
|
198
216
|
if (!options) {
|
|
199
217
|
return undefined;
|
|
@@ -206,7 +224,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
206
224
|
// TODO(ZaymonFC): Should we format this returned value for display based on the referenced prop format?
|
|
207
225
|
// Right now we're going to be sorting based on the raw values.
|
|
208
226
|
// Maybe we need to recurse this function with the referenced prop format and field?
|
|
209
|
-
if (format ===
|
|
227
|
+
if (format === Format.TypeFormat.Ref && field.referencePath) {
|
|
210
228
|
const refValue = getValue(value.target, field.referencePath);
|
|
211
229
|
return refValue;
|
|
212
230
|
}
|
|
@@ -53,12 +53,12 @@ export class TableManager {
|
|
|
53
53
|
await this.page.getByTestId(TABLE_SELECTORS.columnSort[direction]).click();
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
public async addColumn({ label, format }: { label: string; format:
|
|
56
|
+
public async addColumn({ label, format }: { label: string; format: string }): Promise<void> {
|
|
57
57
|
await this.page.getByTestId(TABLE_SELECTORS.newColumnButton).click();
|
|
58
58
|
await this.page.getByRole('combobox').click();
|
|
59
|
-
await this.page.getByLabel(
|
|
59
|
+
await this.page.getByLabel(format).click();
|
|
60
60
|
await this.page.getByPlaceholder('Property label.').click();
|
|
61
61
|
await this.page.getByPlaceholder('Property label.').fill(label);
|
|
62
|
-
await this.page.getByRole('button', { name: '
|
|
62
|
+
await this.page.getByRole('button', { name: 'Save' }).click();
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -130,7 +130,7 @@ test.describe('Table', () => {
|
|
|
130
130
|
await table.grid.ready();
|
|
131
131
|
const newColumnLabel = 'TEST LABEL';
|
|
132
132
|
|
|
133
|
-
await table.addColumn({ label: newColumnLabel, format: '
|
|
133
|
+
await table.addColumn({ label: newColumnLabel, format: 'Number' });
|
|
134
134
|
|
|
135
135
|
await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
|
|
136
136
|
await page.close();
|
|
@@ -159,7 +159,8 @@ test.describe('Table', () => {
|
|
|
159
159
|
await page.close();
|
|
160
160
|
});
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
// TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
|
|
163
|
+
test.skip('extant relations work as expected', async ({ browser, browserName }) => {
|
|
163
164
|
test.skip(browserName === 'webkit');
|
|
164
165
|
test.skip(browserName === 'firefox');
|
|
165
166
|
const { page } = await setupPage(browser, { url: relationsStoryUrl });
|
|
@@ -187,7 +188,7 @@ test.describe('Table', () => {
|
|
|
187
188
|
// Click on the combobox to open options
|
|
188
189
|
await page.getByRole('combobox').click();
|
|
189
190
|
await page.pause();
|
|
190
|
-
await page.getByPlaceholder('Search
|
|
191
|
+
await page.getByPlaceholder('Search…').focus();
|
|
191
192
|
|
|
192
193
|
// Type the first few letters of an org name.
|
|
193
194
|
const orgName =
|
|
@@ -213,7 +214,8 @@ test.describe('Table', () => {
|
|
|
213
214
|
await page.close();
|
|
214
215
|
});
|
|
215
216
|
|
|
216
|
-
|
|
217
|
+
// TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
|
|
218
|
+
test.skip('new relations work as expected', async ({ browser, browserName }) => {
|
|
217
219
|
test.skip(browserName === 'webkit');
|
|
218
220
|
test.skip(browserName === 'firefox');
|
|
219
221
|
const { page } = await setupPage(browser, { url: relationsStoryUrl });
|
|
@@ -240,7 +242,7 @@ test.describe('Table', () => {
|
|
|
240
242
|
|
|
241
243
|
// Click on the combobox to open options
|
|
242
244
|
await page.getByRole('combobox').click();
|
|
243
|
-
await page.getByPlaceholder('Search
|
|
245
|
+
await page.getByPlaceholder('Search…').focus();
|
|
244
246
|
|
|
245
247
|
// Type the first few letters of an org name.
|
|
246
248
|
const orgName = 'Sally';
|
|
@@ -254,7 +256,7 @@ test.describe('Table', () => {
|
|
|
254
256
|
await page.pause();
|
|
255
257
|
|
|
256
258
|
// Click the save button in the popover
|
|
257
|
-
await page.getByTestId('save-button').click();
|
|
259
|
+
await page.getByTestId('create-referenced-object-form').getByTestId('save-button').click();
|
|
258
260
|
|
|
259
261
|
// Assert that the cell element has the org name
|
|
260
262
|
await expect(targetCell).toHaveText(orgName);
|