@dxos/react-ui-table 0.8.4-main.28f8d3d → 0.8.4-main.2c6827d
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-2PFAPQL4.mjs +66 -0
- package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +7 -0
- package/dist/lib/browser/{chunk-ZAOSIG3L.mjs → chunk-GS2UNGJK.mjs} +174 -213
- package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +411 -515
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +12 -9
- 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-6POOFFYF.mjs +67 -0
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-ZOFFI3JJ.mjs → chunk-TRR3C5NX.mjs} +174 -213
- package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +411 -515
- 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 +12 -9
- 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 +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 +103 -5
- 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 +8 -5
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +41 -3
- package/dist/types/src/components/Table/Table.stories.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 +8 -13
- 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 -3
- package/dist/types/src/components/testing.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/useTableModel.d.ts +3 -3
- 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 +9 -8
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +1 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +2 -3
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +3 -22
- package/dist/types/src/testing/useTestTableModel.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 +25 -5
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +5 -6
- 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/tsconfig.tsbuildinfo +1 -1
- package/package.json +53 -51
- package/src/components/Table/DynamicTable.stories.tsx +15 -12
- package/src/components/Table/DynamicTable.tsx +4 -7
- package/src/components/Table/Relations.stories.tsx +160 -42
- package/src/components/Table/RowActionsMenu.tsx +21 -13
- package/src/components/Table/Table.stories.tsx +63 -56
- package/src/components/Table/Table.tsx +109 -90
- package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -64
- package/src/components/TableCellEditor/TableCellEditor.tsx +62 -208
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +32 -22
- package/src/components/testing.ts +2 -2
- package/src/hooks/useAddRow.ts +7 -5
- package/src/hooks/useTableModel.ts +5 -5
- package/src/model/modal-controller.ts +0 -28
- package/src/model/table-model.test.ts +19 -11
- package/src/model/table-model.ts +36 -24
- package/src/model/table-presentation.test.ts +22 -21
- package/src/model/table-presentation.ts +58 -39
- package/src/model/table-sorting.ts +16 -7
- package/src/playwright/TableManager.ts +1 -1
- package/src/playwright/smoke.spec.ts +51 -49
- package/src/testing/useTestTableModel.ts +10 -7
- package/src/types/index.ts +1 -1
- package/src/types/schema.ts +63 -6
- package/src/util/dynamic-table.ts +15 -16
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +4 -4
- package/src/util/table-buttons.ts +3 -3
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
- package/dist/lib/browser/chunk-ZAOSIG3L.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-ZOFFI3JJ.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/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -12
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -86
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/util/table.ts +0 -53
|
@@ -3,18 +3,18 @@
|
|
|
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 { TypedObject } from '@dxos/echo
|
|
11
|
-
import { updateCounter } from '@dxos/echo
|
|
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';
|
|
12
13
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
13
14
|
import { live } from '@dxos/live-object';
|
|
14
|
-
import {
|
|
15
|
-
import { createView } from '@dxos/schema';
|
|
15
|
+
import { View } from '@dxos/schema';
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { Table } from '../types';
|
|
18
18
|
|
|
19
19
|
import { TableModel, type TableModelProps } from './table-model';
|
|
20
20
|
|
|
@@ -25,7 +25,7 @@ registerSignalsRuntime();
|
|
|
25
25
|
|
|
26
26
|
describe('TableModel', () => {
|
|
27
27
|
let updateCount = 0;
|
|
28
|
-
let model:
|
|
28
|
+
let model: any;
|
|
29
29
|
|
|
30
30
|
beforeEach(async () => {
|
|
31
31
|
updateCount = 0;
|
|
@@ -33,6 +33,11 @@ describe('TableModel', () => {
|
|
|
33
33
|
onCellUpdate: () => updateCount++,
|
|
34
34
|
});
|
|
35
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
|
+
]);
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
afterEach(async () => {
|
|
@@ -73,7 +78,6 @@ describe('TableModel', () => {
|
|
|
73
78
|
describe('reactivity', () => {
|
|
74
79
|
it('pure signals should nest', () => {
|
|
75
80
|
const signal$ = live({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
|
|
76
|
-
|
|
77
81
|
const computed$ = computed(() => {
|
|
78
82
|
return signal$.arr.map((row) =>
|
|
79
83
|
computed(() => {
|
|
@@ -108,7 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
108
112
|
|
|
109
113
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
110
114
|
const schema = createEchoSchema(Test);
|
|
111
|
-
const table =
|
|
112
|
-
const view =
|
|
115
|
+
const table = Table.make();
|
|
116
|
+
const view = View.make({
|
|
117
|
+
query: Query.select(Filter.type(schema)),
|
|
118
|
+
jsonSchema: schema.jsonSchema,
|
|
119
|
+
presentation: table,
|
|
120
|
+
});
|
|
113
121
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
114
122
|
};
|
package/src/model/table-model.ts
CHANGED
|
@@ -7,20 +7,17 @@ import { type ReadonlySignal, computed, effect, signal } from '@preact/signals-c
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
8
|
import { Obj, Ref } from '@dxos/echo';
|
|
9
9
|
import {
|
|
10
|
-
type FieldSortType,
|
|
11
10
|
FormatEnum,
|
|
12
11
|
type JsonProp,
|
|
13
12
|
type JsonSchemaType,
|
|
14
13
|
getSchema,
|
|
15
|
-
getSnapshot,
|
|
16
14
|
getValue,
|
|
17
15
|
setValue,
|
|
18
16
|
toEffectSchema,
|
|
19
|
-
} from '@dxos/echo
|
|
17
|
+
} from '@dxos/echo/internal';
|
|
20
18
|
import { invariant } from '@dxos/invariant';
|
|
21
19
|
import { ObjectId } from '@dxos/keys';
|
|
22
|
-
import { isLiveObject } from '@dxos/live-object';
|
|
23
|
-
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
20
|
+
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
24
21
|
import { type Label } from '@dxos/react-ui';
|
|
25
22
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
26
23
|
import {
|
|
@@ -29,9 +26,16 @@ import {
|
|
|
29
26
|
type DxGridPlaneRange,
|
|
30
27
|
type DxGridPosition,
|
|
31
28
|
} from '@dxos/react-ui-grid';
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
import {
|
|
30
|
+
type FieldSortType,
|
|
31
|
+
ProjectionModel,
|
|
32
|
+
type PropertyType,
|
|
33
|
+
type ValidationError,
|
|
34
|
+
type View,
|
|
35
|
+
validateSchema,
|
|
36
|
+
} from '@dxos/schema';
|
|
37
|
+
|
|
38
|
+
import { Table } from '../types';
|
|
35
39
|
import { touch } from '../util';
|
|
36
40
|
import { extractTagIds } from '../util/tag';
|
|
37
41
|
|
|
@@ -70,8 +74,10 @@ const defaultFeatures: TableFeatures = {
|
|
|
70
74
|
schemaEditable: false,
|
|
71
75
|
};
|
|
72
76
|
|
|
77
|
+
export type InsertRowResult = 'draft' | 'final';
|
|
78
|
+
|
|
73
79
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
74
|
-
view:
|
|
80
|
+
view: View.View;
|
|
75
81
|
schema: JsonSchemaType;
|
|
76
82
|
projection?: ProjectionModel;
|
|
77
83
|
features?: Partial<TableFeatures>;
|
|
@@ -80,7 +86,7 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
80
86
|
pinnedRows?: { top: number[]; bottom: number[] };
|
|
81
87
|
rowActions?: TableRowAction[];
|
|
82
88
|
onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
|
|
83
|
-
onInsertRow?: (data?: any) =>
|
|
89
|
+
onInsertRow?: (data?: any) => InsertRowResult;
|
|
84
90
|
onDeleteRows?: (index: number, obj: T[]) => void;
|
|
85
91
|
onDeleteColumn?: (fieldId: string) => void;
|
|
86
92
|
onCellUpdate?: (cell: DxGridPosition) => void;
|
|
@@ -89,16 +95,16 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
89
95
|
};
|
|
90
96
|
|
|
91
97
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
92
|
-
private readonly _view:
|
|
98
|
+
private readonly _view: View.View;
|
|
93
99
|
private readonly _projection: ProjectionModel;
|
|
94
|
-
private _table?:
|
|
100
|
+
private _table?: Table.Table;
|
|
95
101
|
|
|
96
102
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
97
103
|
start: { row: 0, col: 0 },
|
|
98
104
|
end: { row: 0, col: 0 },
|
|
99
105
|
});
|
|
100
106
|
|
|
101
|
-
private readonly _onInsertRow?: (data?: any) =>
|
|
107
|
+
private readonly _onInsertRow?: (data?: any) => InsertRowResult;
|
|
102
108
|
private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
|
|
103
109
|
private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
|
|
104
110
|
private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
|
|
@@ -134,6 +140,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
134
140
|
super();
|
|
135
141
|
this._view = view;
|
|
136
142
|
this._projection = projection ?? new ProjectionModel(schema, view.projection);
|
|
143
|
+
this._projection.normalizeView();
|
|
137
144
|
|
|
138
145
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
139
146
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -168,14 +175,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
168
175
|
}
|
|
169
176
|
|
|
170
177
|
public get id(): string {
|
|
171
|
-
return
|
|
178
|
+
return Obj.getDXN(this._view).toString();
|
|
172
179
|
}
|
|
173
180
|
|
|
174
|
-
public get view():
|
|
181
|
+
public get view(): View.View {
|
|
175
182
|
return this._view;
|
|
176
183
|
}
|
|
177
184
|
|
|
178
|
-
public get table():
|
|
185
|
+
public get table(): Table.Table {
|
|
179
186
|
invariant(this._table, 'Model not initialized');
|
|
180
187
|
return this._table;
|
|
181
188
|
}
|
|
@@ -238,7 +245,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
238
245
|
|
|
239
246
|
protected override async _open(): Promise<void> {
|
|
240
247
|
const presentation = this._view.presentation.target ?? (await this._view.presentation.load());
|
|
241
|
-
invariant(Obj.instanceOf(
|
|
248
|
+
invariant(Obj.instanceOf(Table.Table, presentation));
|
|
242
249
|
this._table = presentation;
|
|
243
250
|
|
|
244
251
|
this.initializeColumnMeta();
|
|
@@ -256,7 +263,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
256
263
|
return {
|
|
257
264
|
grid: meta,
|
|
258
265
|
frozenColsStart: { 0: { size: 30, resizeable: false } },
|
|
259
|
-
frozenColsEnd: { 0: { size:
|
|
266
|
+
frozenColsEnd: { 0: { size: 32, resizeable: false } },
|
|
260
267
|
};
|
|
261
268
|
});
|
|
262
269
|
}
|
|
@@ -336,11 +343,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
336
343
|
|
|
337
344
|
public getColumnCount = (): number => this._projection?.fields.length ?? 0;
|
|
338
345
|
|
|
339
|
-
public insertRow = ():
|
|
346
|
+
public insertRow = (): InsertRowResult => {
|
|
340
347
|
const result = this._onInsertRow?.();
|
|
341
|
-
if (result ===
|
|
348
|
+
if (result === 'draft' && this._draftRows.value.length === 0) {
|
|
342
349
|
this.createDraftRow();
|
|
343
350
|
}
|
|
351
|
+
return result ?? 'final';
|
|
344
352
|
};
|
|
345
353
|
|
|
346
354
|
private createDraftRow(): void {
|
|
@@ -395,14 +403,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
395
403
|
return false;
|
|
396
404
|
}
|
|
397
405
|
|
|
398
|
-
const
|
|
406
|
+
const insertRowResult = this._onInsertRow?.(draftRow.data);
|
|
399
407
|
|
|
400
|
-
if (
|
|
408
|
+
if (insertRowResult === 'final') {
|
|
401
409
|
const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
|
|
402
410
|
this._draftRows.value = newDraftRows;
|
|
403
411
|
}
|
|
404
412
|
|
|
405
|
-
return
|
|
413
|
+
return insertRowResult === 'final';
|
|
406
414
|
};
|
|
407
415
|
|
|
408
416
|
public deleteRow = (rowIndex: number): void => {
|
|
@@ -471,7 +479,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
471
479
|
}
|
|
472
480
|
|
|
473
481
|
default: {
|
|
474
|
-
return formatForEditing({
|
|
482
|
+
return formatForEditing({
|
|
483
|
+
type: props.type,
|
|
484
|
+
format: props.format,
|
|
485
|
+
value,
|
|
486
|
+
});
|
|
475
487
|
}
|
|
476
488
|
}
|
|
477
489
|
};
|
|
@@ -2,16 +2,16 @@
|
|
|
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 {
|
|
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
|
-
import { createView } from '@dxos/schema';
|
|
12
|
+
import { View } from '@dxos/schema';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { Table } from '../types';
|
|
15
15
|
|
|
16
16
|
import { TableModel, type TableModelProps } from './table-model';
|
|
17
17
|
import { TablePresentation } from './table-presentation';
|
|
@@ -27,8 +27,8 @@ describe('TablePresentation', () => {
|
|
|
27
27
|
updateCount = 0;
|
|
28
28
|
({ data } = live({
|
|
29
29
|
data: [
|
|
30
|
-
{
|
|
31
|
-
{
|
|
30
|
+
{ title: 'A', count: 1 },
|
|
31
|
+
{ title: 'B', count: 2 },
|
|
32
32
|
],
|
|
33
33
|
}));
|
|
34
34
|
|
|
@@ -45,8 +45,8 @@ describe('TablePresentation', () => {
|
|
|
45
45
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
46
46
|
|
|
47
47
|
// Trigger a row update.
|
|
48
|
-
data[0].
|
|
49
|
-
expect(updateCount).toBe(
|
|
48
|
+
data[0].title = 'New Value';
|
|
49
|
+
expect(updateCount).toBe(2);
|
|
50
50
|
|
|
51
51
|
// Verify the new value through getCells.
|
|
52
52
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 0 } }, 'grid');
|
|
@@ -57,15 +57,14 @@ describe('TablePresentation', () => {
|
|
|
57
57
|
// Set up visible range to include our test data.
|
|
58
58
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
59
59
|
|
|
60
|
-
data.push({
|
|
61
|
-
expect(updateCount).toBe(
|
|
60
|
+
data.push({ title: 'C', count: 3 });
|
|
61
|
+
expect(updateCount).toBe(2);
|
|
62
62
|
|
|
63
63
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
64
64
|
|
|
65
65
|
// Verify the new row's data.
|
|
66
66
|
expect(cells['0,2']?.value).toBe('C');
|
|
67
67
|
expect(cells['1,2']?.value).toBe('3');
|
|
68
|
-
expect(cells['2,2']?.value).toBe('true');
|
|
69
68
|
});
|
|
70
69
|
|
|
71
70
|
it('should handle combined operations reactively', () => {
|
|
@@ -73,32 +72,34 @@ describe('TablePresentation', () => {
|
|
|
73
72
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
74
73
|
|
|
75
74
|
// Multiple operations.
|
|
76
|
-
data[0].
|
|
77
|
-
data.push({
|
|
75
|
+
data[0].title = 'Updated A';
|
|
76
|
+
data.push({ title: 'C', count: 3, completed: true });
|
|
78
77
|
data.splice(1, 1);
|
|
79
78
|
|
|
80
79
|
// We expect one update per operation affecting visible rows.
|
|
81
|
-
expect(updateCount).toBe(
|
|
80
|
+
expect(updateCount).toBe(4);
|
|
82
81
|
|
|
83
82
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
84
83
|
|
|
85
84
|
// Verify final state.
|
|
86
85
|
expect(cells['0,0']?.value).toBe('Updated A');
|
|
87
86
|
expect(cells['0,1']?.value).toBe('C');
|
|
88
|
-
expect(cells['1,1']?.value).toBe('3');
|
|
89
|
-
expect(cells['2,1']?.value).toBe('true');
|
|
90
87
|
});
|
|
91
88
|
});
|
|
92
89
|
});
|
|
93
90
|
|
|
94
91
|
class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
95
92
|
title: Schema.String,
|
|
96
|
-
|
|
93
|
+
count: Schema.Number,
|
|
97
94
|
}) {}
|
|
98
95
|
|
|
99
96
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
100
97
|
const schema = createEchoSchema(Test);
|
|
101
|
-
const table =
|
|
102
|
-
const view =
|
|
98
|
+
const table = Table.make();
|
|
99
|
+
const view = View.make({
|
|
100
|
+
query: Query.select(Filter.type(schema)),
|
|
101
|
+
jsonSchema: schema.jsonSchema,
|
|
102
|
+
presentation: table,
|
|
103
|
+
});
|
|
103
104
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
104
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 { FormatEnum, TypeEnum, getValue } from '@dxos/echo
|
|
9
|
+
import { FormatEnum, TypeEnum, getValue } from '@dxos/echo/internal';
|
|
10
10
|
import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
|
|
11
11
|
import {
|
|
12
12
|
type DxGridCellValue,
|
|
@@ -69,7 +69,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
69
69
|
cells = this.getDraftRowCells(range);
|
|
70
70
|
break;
|
|
71
71
|
case 'fixedEndStart':
|
|
72
|
-
cells = this.
|
|
72
|
+
cells = this.getDraftIconCells(range);
|
|
73
73
|
break;
|
|
74
74
|
case 'fixedEndEnd':
|
|
75
75
|
cells = this.getDraftActionCells(range);
|
|
@@ -141,20 +141,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
141
141
|
};
|
|
142
142
|
|
|
143
143
|
const classes = [];
|
|
144
|
-
const formatClasses = cellClassesForFieldType(
|
|
144
|
+
const formatClasses = cellClassesForFieldType(props);
|
|
145
|
+
|
|
145
146
|
if (formatClasses) {
|
|
146
147
|
classes.push(formatClasses);
|
|
147
148
|
}
|
|
149
|
+
|
|
148
150
|
const rowSelectionClasses = cellClassesForRowSelection(
|
|
149
151
|
this.model.selection.isObjectSelected(obj),
|
|
150
152
|
this.model.selection.selectionMode,
|
|
151
153
|
);
|
|
154
|
+
|
|
152
155
|
if (rowSelectionClasses) {
|
|
153
156
|
classes.push(rowSelectionClasses);
|
|
154
157
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
+
|
|
159
|
+
cell.className = mx(classes);
|
|
158
160
|
|
|
159
161
|
// Arrays.
|
|
160
162
|
if (props.type === TypeEnum.Array) {
|
|
@@ -188,7 +190,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
188
190
|
|
|
189
191
|
const tags = targetArray
|
|
190
192
|
.map(getLabel)
|
|
191
|
-
.filter(isNotNullable)
|
|
193
|
+
.filter(Predicate.isNotNullable)
|
|
192
194
|
.map((title) => {
|
|
193
195
|
return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
|
|
194
196
|
})
|
|
@@ -203,7 +205,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
203
205
|
const targetObj = getValue(obj, field.path)?.target;
|
|
204
206
|
if (targetObj) {
|
|
205
207
|
const dxn = Obj.getDXN(targetObj)?.toString();
|
|
206
|
-
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>`;
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
|
|
@@ -279,27 +281,36 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
279
281
|
const fields = this.model.projection?.fields ?? [];
|
|
280
282
|
const draftRows = this.model.draftRows.value;
|
|
281
283
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
+
// Return cells of the CTA row if no draft row is active
|
|
285
|
+
if (draftRows.length === 0) {
|
|
284
286
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
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
|
+
}
|
|
290
302
|
|
|
291
|
-
|
|
303
|
+
this.createDataCell(cells, draftRow.data, field, col, row);
|
|
292
304
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
+
}
|
|
299
312
|
}
|
|
300
313
|
}
|
|
301
|
-
|
|
302
|
-
cells[cellIndex].className += ' !bg-toolbarSurface';
|
|
303
314
|
}
|
|
304
315
|
}
|
|
305
316
|
|
|
@@ -316,14 +327,14 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
316
327
|
|
|
317
328
|
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
318
329
|
// TODO(burdon): Use same logic as form for fallback title.
|
|
319
|
-
value:
|
|
320
|
-
readonly: true,
|
|
330
|
+
value: '',
|
|
321
331
|
resizeHandle: 'col',
|
|
322
332
|
accessoryHtml: `
|
|
333
|
+
<span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
|
|
323
334
|
${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
|
|
324
335
|
${tableButtons.columnSettings.render({ fieldId: field.id })}
|
|
325
336
|
`,
|
|
326
|
-
className: '!bg-toolbarSurface',
|
|
337
|
+
className: '!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch',
|
|
327
338
|
};
|
|
328
339
|
}
|
|
329
340
|
|
|
@@ -406,30 +417,39 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
406
417
|
const cells: DxGridPlaneCells = {};
|
|
407
418
|
const draftRows = this.model.draftRows.value;
|
|
408
419
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
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 })] = {
|
|
414
423
|
value: '',
|
|
424
|
+
className: 'dx-grid__row--cta__cell',
|
|
415
425
|
readonly: true,
|
|
416
|
-
accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
|
|
417
|
-
className: '!bg-toolbarSurface',
|
|
418
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
|
+
}
|
|
419
438
|
}
|
|
420
439
|
|
|
421
440
|
return cells;
|
|
422
441
|
}
|
|
423
442
|
|
|
424
|
-
private
|
|
443
|
+
private getDraftIconCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
425
444
|
const cells: DxGridPlaneCells = {};
|
|
426
445
|
const draftRows = this.model.draftRows.value;
|
|
427
446
|
|
|
428
|
-
for (let row = range.start.row; row <= range.end.row
|
|
447
|
+
for (let row = range.start.row; row <= range.end.row; row++) {
|
|
429
448
|
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
430
449
|
value: '',
|
|
431
450
|
readonly: true,
|
|
432
|
-
|
|
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'),
|
|
433
453
|
};
|
|
434
454
|
}
|
|
435
455
|
|
|
@@ -448,7 +468,6 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
|
|
|
448
468
|
|
|
449
469
|
switch (selectionMode) {
|
|
450
470
|
case 'single':
|
|
451
|
-
// TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
|
|
452
471
|
return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
|
|
453
472
|
case 'multiple':
|
|
454
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 { FormatEnum, 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.
|
|
@@ -49,13 +58,13 @@ export class TableSorting<T extends TableRow> {
|
|
|
49
58
|
|
|
50
59
|
constructor(
|
|
51
60
|
rows: Signal<T[]>,
|
|
52
|
-
private readonly _view:
|
|
61
|
+
private readonly _view: View.View,
|
|
53
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
|
/**
|
|
@@ -129,9 +138,9 @@ export class TableSorting<T extends TableRow> {
|
|
|
129
138
|
public save(): void {
|
|
130
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
|
}
|
|
@@ -59,6 +59,6 @@ export class TableManager {
|
|
|
59
59
|
await this.page.getByLabel(`format ${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: 'button
|
|
62
|
+
await this.page.getByRole('button', { name: 'save button' }).click();
|
|
63
63
|
}
|
|
64
64
|
}
|