@dxos/react-ui-table 0.8.4-main.72ec0f3 → 0.8.4-main.7ace549
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-GS2UNGJK.mjs → chunk-KNC7XYT6.mjs} +116 -108
- package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +25 -13
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +14 -27
- 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-TRR3C5NX.mjs → chunk-CWSGS3Y2.mjs} +116 -108
- 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 +25 -13
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +14 -27
- 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.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +2 -2
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +7 -6
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +67 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +2 -2
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +0 -2
- 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/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/table-model.d.ts +6 -8
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +7 -3
- 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 -15
- 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 +7 -6
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +38 -38
- package/src/components/Table/DynamicTable.stories.tsx +7 -7
- package/src/components/Table/DynamicTable.tsx +12 -4
- package/src/components/Table/Relations.stories.tsx +9 -14
- package/src/components/Table/Table.stories.tsx +47 -42
- package/src/components/TableCellEditor/FormCellEditor.tsx +2 -2
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +5 -4
- package/src/components/TableCellEditor/TableCellEditor.tsx +4 -4
- package/src/components/TableToolbar/TableToolbar.tsx +1 -1
- package/src/components/testing.ts +3 -3
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useProjectionModel.ts +19 -0
- package/src/hooks/useTableModel.ts +10 -12
- package/src/model/table-model.test.ts +4 -4
- package/src/model/table-model.ts +27 -36
- package/src/model/table-presentation.test.ts +4 -4
- package/src/model/table-presentation.ts +9 -9
- package/src/model/table-sorting.ts +21 -12
- package/src/playwright/TableManager.ts +3 -3
- package/src/playwright/smoke.spec.ts +1 -1
- package/src/testing/useTestTableModel.ts +32 -24
- 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 +9 -12
- package/dist/lib/browser/chunk-2PFAPQL4.mjs +0 -66
- package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +0 -7
- package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs +0 -67
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +0 -7
- package/dist/types/src/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/types/schema.ts +0 -73
|
@@ -7,17 +7,16 @@ import orderBy from 'lodash.orderby';
|
|
|
7
7
|
import { useEffect, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
9
|
import { Obj } from '@dxos/echo';
|
|
10
|
-
import { type JsonSchemaType } from '@dxos/echo/internal';
|
|
11
10
|
import { type Live } from '@dxos/live-object';
|
|
12
11
|
import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
13
|
-
import { type ProjectionModel
|
|
12
|
+
import { type ProjectionModel } from '@dxos/schema';
|
|
14
13
|
import { isNonNullable } from '@dxos/util';
|
|
15
14
|
|
|
16
15
|
import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
|
|
16
|
+
import { type Table } from '../types';
|
|
17
17
|
|
|
18
18
|
export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
19
|
-
|
|
20
|
-
schema?: JsonSchemaType;
|
|
19
|
+
object?: Table.Table;
|
|
21
20
|
projection?: ProjectionModel;
|
|
22
21
|
rows?: Live<T>[];
|
|
23
22
|
rowActions?: TableRowAction[];
|
|
@@ -29,8 +28,7 @@ export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
|
29
28
|
>;
|
|
30
29
|
|
|
31
30
|
export const useTableModel = <T extends TableRow = TableRow>({
|
|
32
|
-
|
|
33
|
-
schema,
|
|
31
|
+
object,
|
|
34
32
|
projection,
|
|
35
33
|
rows,
|
|
36
34
|
rowActions,
|
|
@@ -39,20 +37,19 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
39
37
|
onRowAction,
|
|
40
38
|
...props
|
|
41
39
|
}: UseTableModelParams<T>): TableModel<T> | undefined => {
|
|
42
|
-
const selected = useSelected(
|
|
43
|
-
const initialSelection = useMemo(() => selected, [
|
|
40
|
+
const selected = useSelected(object && Obj.getDXN(object).toString(), 'multi');
|
|
41
|
+
const initialSelection = useMemo(() => selected, [object]);
|
|
44
42
|
|
|
45
43
|
const [model, setModel] = useState<TableModel<T>>();
|
|
46
44
|
useEffect(() => {
|
|
47
|
-
if (!
|
|
45
|
+
if (!object || !projection) {
|
|
48
46
|
return;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
let model: TableModel<T> | undefined;
|
|
52
50
|
const t = setTimeout(async () => {
|
|
53
51
|
model = new TableModel<T>({
|
|
54
|
-
|
|
55
|
-
schema,
|
|
52
|
+
object,
|
|
56
53
|
projection,
|
|
57
54
|
features,
|
|
58
55
|
rowActions,
|
|
@@ -68,7 +65,8 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
68
65
|
clearTimeout(t);
|
|
69
66
|
void model?.close();
|
|
70
67
|
};
|
|
71
|
-
|
|
68
|
+
// TODO(burdon): Trigger if callbacks change?
|
|
69
|
+
}, [object, projection, features, rowActions, initialSelection]);
|
|
72
70
|
|
|
73
71
|
// Update data.
|
|
74
72
|
useEffect(() => {
|
|
@@ -12,7 +12,7 @@ import { updateCounter } from '@dxos/echo/testing';
|
|
|
12
12
|
import { createEchoSchema } from '@dxos/echo/testing';
|
|
13
13
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
14
14
|
import { live } from '@dxos/live-object';
|
|
15
|
-
import { View } from '@dxos/schema';
|
|
15
|
+
import { ProjectionModel, View } from '@dxos/schema';
|
|
16
16
|
|
|
17
17
|
import { Table } from '../types';
|
|
18
18
|
|
|
@@ -112,11 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
112
112
|
|
|
113
113
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
114
114
|
const schema = createEchoSchema(Test);
|
|
115
|
-
const table = Table.make();
|
|
116
115
|
const view = View.make({
|
|
117
116
|
query: Query.select(Filter.type(schema)),
|
|
118
117
|
jsonSchema: schema.jsonSchema,
|
|
119
|
-
presentation: table,
|
|
120
118
|
});
|
|
121
|
-
|
|
119
|
+
const object = Table.make({ view });
|
|
120
|
+
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
121
|
+
return new TableModel({ object, projection, ...props });
|
|
122
122
|
};
|
package/src/model/table-model.ts
CHANGED
|
@@ -5,16 +5,8 @@
|
|
|
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
|
-
FormatEnum,
|
|
11
|
-
type JsonProp,
|
|
12
|
-
type JsonSchemaType,
|
|
13
|
-
getSchema,
|
|
14
|
-
getValue,
|
|
15
|
-
setValue,
|
|
16
|
-
toEffectSchema,
|
|
17
|
-
} from '@dxos/echo/internal';
|
|
8
|
+
import { Format, Obj, Ref } from '@dxos/echo';
|
|
9
|
+
import { type JsonProp, type JsonSchemaType, getValue, setValue, toEffectSchema } from '@dxos/echo/internal';
|
|
18
10
|
import { invariant } from '@dxos/invariant';
|
|
19
11
|
import { ObjectId } from '@dxos/keys';
|
|
20
12
|
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
@@ -28,14 +20,14 @@ import {
|
|
|
28
20
|
} from '@dxos/react-ui-grid';
|
|
29
21
|
import {
|
|
30
22
|
type FieldSortType,
|
|
31
|
-
ProjectionModel,
|
|
23
|
+
type ProjectionModel,
|
|
32
24
|
type PropertyType,
|
|
33
25
|
type ValidationError,
|
|
34
26
|
type View,
|
|
35
27
|
validateSchema,
|
|
36
28
|
} from '@dxos/schema';
|
|
37
29
|
|
|
38
|
-
import { Table } from '../types';
|
|
30
|
+
import { type Table } from '../types';
|
|
39
31
|
import { touch } from '../util';
|
|
40
32
|
import { extractTagIds } from '../util/tag';
|
|
41
33
|
|
|
@@ -77,9 +69,8 @@ const defaultFeatures: TableFeatures = {
|
|
|
77
69
|
export type InsertRowResult = 'draft' | 'final';
|
|
78
70
|
|
|
79
71
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
projection?: ProjectionModel;
|
|
72
|
+
object: Table.Table;
|
|
73
|
+
projection: ProjectionModel;
|
|
83
74
|
features?: Partial<TableFeatures>;
|
|
84
75
|
sorting?: FieldSortType[];
|
|
85
76
|
initialSelection?: string[];
|
|
@@ -95,9 +86,8 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
95
86
|
};
|
|
96
87
|
|
|
97
88
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
98
|
-
private readonly
|
|
89
|
+
private readonly _object: Table.Table;
|
|
99
90
|
private readonly _projection: ProjectionModel;
|
|
100
|
-
private _table?: Table.Table;
|
|
101
91
|
|
|
102
92
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
103
93
|
start: { row: 0, col: 0 },
|
|
@@ -122,8 +112,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
122
112
|
private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
|
|
123
113
|
|
|
124
114
|
constructor({
|
|
125
|
-
|
|
126
|
-
schema,
|
|
115
|
+
object,
|
|
127
116
|
projection,
|
|
128
117
|
features = {},
|
|
129
118
|
sorting = [],
|
|
@@ -138,8 +127,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
138
127
|
onRowAction,
|
|
139
128
|
}: TableModelProps<T>) {
|
|
140
129
|
super();
|
|
141
|
-
this.
|
|
142
|
-
this._projection = projection
|
|
130
|
+
this._object = object;
|
|
131
|
+
this._projection = projection;
|
|
143
132
|
this._projection.normalizeView();
|
|
144
133
|
|
|
145
134
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
@@ -150,7 +139,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
150
139
|
'Single selection is not compatible with editable tables.',
|
|
151
140
|
);
|
|
152
141
|
|
|
153
|
-
this._sorting = new TableSorting(
|
|
142
|
+
this._sorting = new TableSorting({
|
|
143
|
+
rows: this._rows,
|
|
144
|
+
getView: () => this.view,
|
|
145
|
+
projection: this._projection,
|
|
146
|
+
});
|
|
154
147
|
|
|
155
148
|
if (sorting.length > 0) {
|
|
156
149
|
const [sort] = sorting;
|
|
@@ -175,16 +168,17 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
175
168
|
}
|
|
176
169
|
|
|
177
170
|
public get id(): string {
|
|
178
|
-
return Obj.getDXN(this.
|
|
171
|
+
return Obj.getDXN(this._object).toString();
|
|
179
172
|
}
|
|
180
173
|
|
|
181
174
|
public get view(): View.View {
|
|
182
|
-
|
|
175
|
+
const view = this._object.view.target;
|
|
176
|
+
invariant(view, 'Table model not initialized');
|
|
177
|
+
return view;
|
|
183
178
|
}
|
|
184
179
|
|
|
185
180
|
public get table(): Table.Table {
|
|
186
|
-
|
|
187
|
-
return this._table;
|
|
181
|
+
return this._object;
|
|
188
182
|
}
|
|
189
183
|
|
|
190
184
|
public get projection(): ProjectionModel {
|
|
@@ -244,10 +238,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
244
238
|
//
|
|
245
239
|
|
|
246
240
|
protected override async _open(): Promise<void> {
|
|
247
|
-
|
|
248
|
-
invariant(Obj.instanceOf(Table.Table, presentation));
|
|
249
|
-
this._table = presentation;
|
|
250
|
-
|
|
241
|
+
await this._object.view.load();
|
|
251
242
|
this.initializeColumnMeta();
|
|
252
243
|
this.initializeEffects();
|
|
253
244
|
await this._selection.open(this._ctx);
|
|
@@ -470,7 +461,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
470
461
|
|
|
471
462
|
const { props } = this._projection.getFieldProjection(field.id);
|
|
472
463
|
switch (props.format) {
|
|
473
|
-
case
|
|
464
|
+
case Format.TypeFormat.Ref: {
|
|
474
465
|
if (!field.referencePath) {
|
|
475
466
|
return ''; // TODO(burdon): Show error.
|
|
476
467
|
}
|
|
@@ -527,7 +518,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
527
518
|
const snapshot = getSnapshot(currentRow);
|
|
528
519
|
setValue(snapshot, field.path, transformedValue);
|
|
529
520
|
|
|
530
|
-
const schema = getSchema(currentRow);
|
|
521
|
+
const schema = Obj.getSchema(currentRow);
|
|
531
522
|
invariant(schema);
|
|
532
523
|
|
|
533
524
|
const validationResult = validateSchema(schema, snapshot);
|
|
@@ -553,7 +544,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
553
544
|
const transformedValue = editorTextToCellValue(props, value);
|
|
554
545
|
|
|
555
546
|
// Special handling for Ref format to preserve existing behavior
|
|
556
|
-
if (props.format ===
|
|
547
|
+
if (props.format === Format.TypeFormat.Ref && !isLiveObject(value)) {
|
|
557
548
|
return;
|
|
558
549
|
}
|
|
559
550
|
|
|
@@ -668,7 +659,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
668
659
|
|
|
669
660
|
const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
670
661
|
switch (props.format) {
|
|
671
|
-
case
|
|
662
|
+
case Format.TypeFormat.Ref: {
|
|
672
663
|
if (isLiveObject(value)) {
|
|
673
664
|
return Ref.make(value);
|
|
674
665
|
} else {
|
|
@@ -676,7 +667,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
676
667
|
}
|
|
677
668
|
}
|
|
678
669
|
|
|
679
|
-
case
|
|
670
|
+
case Format.TypeFormat.SingleSelect: {
|
|
680
671
|
const ids = extractTagIds(value);
|
|
681
672
|
if (ids && ids.length > 0) {
|
|
682
673
|
return ids[0];
|
|
@@ -685,7 +676,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
685
676
|
}
|
|
686
677
|
}
|
|
687
678
|
|
|
688
|
-
case
|
|
679
|
+
case Format.TypeFormat.MultiSelect: {
|
|
689
680
|
const ids = extractTagIds(value);
|
|
690
681
|
return ids || value;
|
|
691
682
|
}
|
|
@@ -9,7 +9,7 @@ import { Filter, Query } from '@dxos/echo';
|
|
|
9
9
|
import { TypedObject } from '@dxos/echo/internal';
|
|
10
10
|
import { createEchoSchema } from '@dxos/echo/testing';
|
|
11
11
|
import { live } from '@dxos/live-object';
|
|
12
|
-
import { View } from '@dxos/schema';
|
|
12
|
+
import { ProjectionModel, View } from '@dxos/schema';
|
|
13
13
|
|
|
14
14
|
import { Table } from '../types';
|
|
15
15
|
|
|
@@ -95,11 +95,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
95
95
|
|
|
96
96
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
97
97
|
const schema = createEchoSchema(Test);
|
|
98
|
-
const table = Table.make();
|
|
99
98
|
const view = View.make({
|
|
100
99
|
query: Query.select(Filter.type(schema)),
|
|
101
100
|
jsonSchema: schema.jsonSchema,
|
|
102
|
-
presentation: table,
|
|
103
101
|
});
|
|
104
|
-
|
|
102
|
+
const object = Table.make({ view });
|
|
103
|
+
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
104
|
+
return new TableModel({ object, projection, ...props });
|
|
105
105
|
};
|
|
@@ -6,7 +6,7 @@ import { signal } from '@preact/signals-core';
|
|
|
6
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
|
}
|
|
@@ -201,7 +201,7 @@ 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();
|
|
@@ -210,7 +210,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
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) {
|
|
@@ -5,7 +5,7 @@
|
|
|
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
10
|
import {
|
|
11
11
|
type FieldSortType,
|
|
@@ -52,19 +52,27 @@ type LocalSort = { type: 'cleared' } | { type: 'active'; sort: FieldSortType } |
|
|
|
52
52
|
export class TableSorting<T extends TableRow> {
|
|
53
53
|
private readonly _displayToDataIndex = new Map<number, number>();
|
|
54
54
|
private readonly _rows: Signal<T[]>;
|
|
55
|
+
private readonly _getView: () => View.View;
|
|
56
|
+
private readonly _projection: ProjectionModel;
|
|
55
57
|
private readonly _localSort = signal<LocalSort>(undefined);
|
|
56
58
|
private readonly _isDirty: ReadonlySignal<boolean>;
|
|
57
59
|
public readonly sortedRows: ReadonlySignal<T[]>;
|
|
58
60
|
|
|
59
|
-
constructor(
|
|
60
|
-
rows
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
constructor({
|
|
62
|
+
rows,
|
|
63
|
+
projection,
|
|
64
|
+
getView,
|
|
65
|
+
}: {
|
|
66
|
+
rows: Signal<T[]>;
|
|
67
|
+
getView: () => View.View;
|
|
68
|
+
projection: ProjectionModel;
|
|
69
|
+
}) {
|
|
64
70
|
this._rows = rows;
|
|
71
|
+
this._getView = getView;
|
|
72
|
+
this._projection = projection;
|
|
65
73
|
this._isDirty = computed(() => {
|
|
66
74
|
const local = this._localSort.value;
|
|
67
|
-
const viewSort = this.
|
|
75
|
+
const viewSort = this._getView().sort?.[0];
|
|
68
76
|
if (local?.type === 'cleared') {
|
|
69
77
|
return viewSort !== undefined;
|
|
70
78
|
}
|
|
@@ -92,7 +100,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
92
100
|
return local.sort;
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
return this.
|
|
103
|
+
return this._getView().sort?.[0];
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
/**
|
|
@@ -137,10 +145,11 @@ export class TableSorting<T extends TableRow> {
|
|
|
137
145
|
|
|
138
146
|
public save(): void {
|
|
139
147
|
if (this._localSort.value !== undefined) {
|
|
148
|
+
const view = this._getView();
|
|
140
149
|
if (this._localSort.value.type === 'active') {
|
|
141
|
-
|
|
150
|
+
view.sort = [this._localSort.value.sort];
|
|
142
151
|
} else {
|
|
143
|
-
|
|
152
|
+
view.sort = [];
|
|
144
153
|
}
|
|
145
154
|
this._localSort.value = undefined;
|
|
146
155
|
}
|
|
@@ -202,7 +211,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
202
211
|
* For single select fields, the sort value is determined by the index of the option in the
|
|
203
212
|
* options array. This ensures sort order matches the configured option order.
|
|
204
213
|
*/
|
|
205
|
-
if (format ===
|
|
214
|
+
if (format === Format.TypeFormat.SingleSelect) {
|
|
206
215
|
const options = props.options;
|
|
207
216
|
if (!options) {
|
|
208
217
|
return undefined;
|
|
@@ -215,7 +224,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
215
224
|
// TODO(ZaymonFC): Should we format this returned value for display based on the referenced prop format?
|
|
216
225
|
// Right now we're going to be sorting based on the raw values.
|
|
217
226
|
// Maybe we need to recurse this function with the referenced prop format and field?
|
|
218
|
-
if (format ===
|
|
227
|
+
if (format === Format.TypeFormat.Ref && field.referencePath) {
|
|
219
228
|
const refValue = getValue(value.target, field.referencePath);
|
|
220
229
|
return refValue;
|
|
221
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();
|
|
@@ -2,43 +2,52 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import type * as Schema from 'effect/Schema';
|
|
6
|
+
import { type RefObject, useCallback, useMemo, useRef } from 'react';
|
|
6
7
|
|
|
7
|
-
import { isMutable
|
|
8
|
+
import { isMutable } from '@dxos/echo/internal';
|
|
8
9
|
import { useGlobalFilteredObjects } from '@dxos/plugin-search';
|
|
9
10
|
import { faker } from '@dxos/random';
|
|
10
|
-
import { useClient } from '@dxos/react-client';
|
|
11
|
-
import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
|
|
11
|
+
import { type Client, useClient } from '@dxos/react-client';
|
|
12
|
+
import { Filter, type Space, useQuery, useSchema } from '@dxos/react-client/echo';
|
|
12
13
|
import { useClientProvider } from '@dxos/react-client/testing';
|
|
13
|
-
import { ProjectionModel,
|
|
14
|
+
import { type ProjectionModel, getTypenameFromQuery } from '@dxos/schema';
|
|
14
15
|
|
|
15
16
|
import { type TableController } from '../components';
|
|
16
|
-
import { useAddRow, useTableModel } from '../hooks';
|
|
17
|
-
import { TablePresentation } from '../model';
|
|
17
|
+
import { useAddRow, useProjectionModel, useTableModel } from '../hooks';
|
|
18
|
+
import { type TableModel, TablePresentation } from '../model';
|
|
19
|
+
import { Table } from '../types';
|
|
18
20
|
|
|
19
21
|
faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
|
|
20
22
|
|
|
23
|
+
export type TestTableModel = {
|
|
24
|
+
schema: Schema.Schema.AnyNoContext | undefined;
|
|
25
|
+
table: Table.Table | undefined;
|
|
26
|
+
projection: ProjectionModel | undefined;
|
|
27
|
+
tableRef: RefObject<TableController | null>;
|
|
28
|
+
model: TableModel | undefined;
|
|
29
|
+
presentation: TablePresentation | undefined;
|
|
30
|
+
space: Space | undefined;
|
|
31
|
+
client: Client | undefined;
|
|
32
|
+
handleInsertRow: () => void;
|
|
33
|
+
handleSaveView: () => void;
|
|
34
|
+
handleDeleteRows: (rowIndex: number, objects: any[]) => void;
|
|
35
|
+
handleDeleteColumn: (fieldId: string) => void;
|
|
36
|
+
};
|
|
37
|
+
|
|
21
38
|
/**
|
|
22
39
|
* Custom hook to create and manage a test table model for storybook demonstrations.
|
|
23
40
|
* Provides table data, schema, and handlers for table operations.
|
|
24
41
|
*/
|
|
25
|
-
export const useTestTableModel = () => {
|
|
42
|
+
export const useTestTableModel = (): TestTableModel => {
|
|
26
43
|
const client = useClient();
|
|
27
44
|
const { space } = useClientProvider();
|
|
28
45
|
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
|
|
46
|
+
const tables = useQuery(space, Filter.type(Table.Table));
|
|
47
|
+
const table = tables.at(0);
|
|
48
|
+
const typename = table?.view.target?.query ? getTypenameFromQuery(table.view.target.query.ast) : undefined;
|
|
32
49
|
const schema = useSchema(client, space, typename);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const projection = useMemo(() => {
|
|
36
|
-
if (schema && view?.projection) {
|
|
37
|
-
const projection = new ProjectionModel(toJsonSchema(schema), view.projection);
|
|
38
|
-
projection.normalizeView();
|
|
39
|
-
return projection;
|
|
40
|
-
}
|
|
41
|
-
}, [schema, view?.projection]);
|
|
50
|
+
const projection = useProjectionModel(schema, table);
|
|
42
51
|
|
|
43
52
|
const features = useMemo(
|
|
44
53
|
() => ({
|
|
@@ -82,8 +91,7 @@ export const useTestTableModel = () => {
|
|
|
82
91
|
);
|
|
83
92
|
|
|
84
93
|
const model = useTableModel({
|
|
85
|
-
|
|
86
|
-
schema: jsonSchema,
|
|
94
|
+
object: table,
|
|
87
95
|
projection,
|
|
88
96
|
features,
|
|
89
97
|
rows: filteredObjects,
|
|
@@ -97,7 +105,7 @@ export const useTestTableModel = () => {
|
|
|
97
105
|
const handleInsertRow = useCallback(() => {
|
|
98
106
|
const insertResult = model?.insertRow();
|
|
99
107
|
tableRef.current?.handleInsertRowResult?.(insertResult);
|
|
100
|
-
}, [model
|
|
108
|
+
}, [model]);
|
|
101
109
|
|
|
102
110
|
const handleSaveView = useCallback(() => {
|
|
103
111
|
model?.saveView();
|
|
@@ -111,7 +119,7 @@ export const useTestTableModel = () => {
|
|
|
111
119
|
|
|
112
120
|
return {
|
|
113
121
|
schema,
|
|
114
|
-
|
|
122
|
+
table,
|
|
115
123
|
projection,
|
|
116
124
|
tableRef,
|
|
117
125
|
model,
|
package/src/translations.ts
CHANGED
|
@@ -14,8 +14,8 @@ export const translations = [
|
|
|
14
14
|
'create object label': 'Create table',
|
|
15
15
|
'table name placeholder': 'Table name',
|
|
16
16
|
'settings title': 'Table settings',
|
|
17
|
-
'table schema label': '
|
|
18
|
-
'new schema': 'New
|
|
17
|
+
'table schema label': 'Type',
|
|
18
|
+
'new schema': 'New type',
|
|
19
19
|
'continue label': 'Continue',
|
|
20
20
|
'create stack section label': 'Create table',
|
|
21
21
|
'add row label': 'Add row',
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Match from 'effect/Match';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
|
|
8
|
+
import { Obj, Ref, Type } from '@dxos/echo';
|
|
9
|
+
import { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';
|
|
10
|
+
import { type SimpleType } from '@dxos/effect';
|
|
11
|
+
import { View, ViewAnnotation, getSchemaProperties } from '@dxos/schema';
|
|
12
|
+
|
|
13
|
+
const TableSchema = Schema.Struct({
|
|
14
|
+
name: Schema.String.pipe(Schema.optional),
|
|
15
|
+
|
|
16
|
+
view: Type.Ref(View.View).pipe(FormInputAnnotation.set(false)),
|
|
17
|
+
|
|
18
|
+
sizes: Schema.Record({
|
|
19
|
+
// TODO(wittjosiah): Should be JsonPath.
|
|
20
|
+
key: Schema.String,
|
|
21
|
+
value: Schema.Number,
|
|
22
|
+
}).pipe(Schema.mutable, FormInputAnnotation.set(false)),
|
|
23
|
+
}).pipe(
|
|
24
|
+
Type.Obj({
|
|
25
|
+
typename: 'dxos.org/type/Table',
|
|
26
|
+
version: '0.2.0',
|
|
27
|
+
}),
|
|
28
|
+
LabelAnnotation.set(['name']),
|
|
29
|
+
ViewAnnotation.set(true),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export interface Table extends Schema.Schema.Type<typeof TableSchema> {}
|
|
33
|
+
export interface TableEncoded extends Schema.Schema.Encoded<typeof TableSchema> {}
|
|
34
|
+
export const Table: Schema.Schema<Table, TableEncoded> = TableSchema;
|
|
35
|
+
|
|
36
|
+
type MakeProps = Omit<Partial<Obj.MakeProps<typeof Table>>, 'view'> & {
|
|
37
|
+
view: View.View;
|
|
38
|
+
/** Required to auto-size columns. */
|
|
39
|
+
jsonSchema?: JsonSchemaType;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Make a table as a view of a data set.
|
|
44
|
+
*/
|
|
45
|
+
export const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table => {
|
|
46
|
+
const table = Obj.make(Table, { name, view: Ref.make(view), sizes });
|
|
47
|
+
|
|
48
|
+
// Preset sizes.
|
|
49
|
+
if (jsonSchema) {
|
|
50
|
+
const schema = Type.toEffectSchema(jsonSchema);
|
|
51
|
+
const properties = getSchemaProperties(schema.ast, {});
|
|
52
|
+
for (const property of properties) {
|
|
53
|
+
if (sizes?.[property.name]) {
|
|
54
|
+
table.sizes[property.name] = sizes[property.name];
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Match.type<SimpleType>().pipe(
|
|
59
|
+
Match.when('boolean', () => {
|
|
60
|
+
table.sizes[property.name as JsonPath] = 100;
|
|
61
|
+
}),
|
|
62
|
+
Match.when('number', () => {
|
|
63
|
+
table.sizes[property.name as JsonPath] = 100;
|
|
64
|
+
}),
|
|
65
|
+
Match.orElse(() => {
|
|
66
|
+
// Noop.
|
|
67
|
+
}),
|
|
68
|
+
)(property.type);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return table;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
//
|
|
76
|
+
// V1
|
|
77
|
+
//
|
|
78
|
+
|
|
79
|
+
export const TableV1 = Schema.Struct({
|
|
80
|
+
sizes: Schema.Record({
|
|
81
|
+
key: Schema.String,
|
|
82
|
+
value: Schema.Number,
|
|
83
|
+
}).pipe(Schema.mutable),
|
|
84
|
+
}).pipe(
|
|
85
|
+
Type.Obj({
|
|
86
|
+
typename: 'dxos.org/type/Table',
|
|
87
|
+
version: '0.1.0',
|
|
88
|
+
}),
|
|
89
|
+
);
|
package/src/types/index.ts
CHANGED