@dxos/react-ui-table 0.8.4-main.5ad4a44 → 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-OZTXLZGM.mjs → chunk-KNC7XYT6.mjs} +521 -542
- package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +75 -59
- 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-S56Z4V4X.mjs → chunk-CWSGS3Y2.mjs} +521 -542
- 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 +75 -59
- 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 -7
- 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.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/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 +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 +7 -9
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +8 -4
- 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 +41 -40
- package/src/components/Table/DynamicTable.stories.tsx +7 -7
- package/src/components/Table/DynamicTable.tsx +12 -4
- package/src/components/Table/Relations.stories.tsx +24 -35
- package/src/components/Table/RowActionsMenu.tsx +21 -13
- package/src/components/Table/Table.stories.tsx +58 -53
- package/src/components/TableCellEditor/FormCellEditor.tsx +11 -11
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +11 -9
- package/src/components/TableCellEditor/TableCellEditor.tsx +4 -4
- package/src/components/TableToolbar/TableToolbar.tsx +32 -22
- 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 +11 -13
- package/src/model/table-model.test.ts +5 -5
- package/src/model/table-model.ts +34 -40
- package/src/model/table-presentation.test.ts +5 -5
- package/src/model/table-presentation.ts +9 -9
- package/src/model/table-sorting.ts +28 -19
- package/src/playwright/TableManager.ts +3 -3
- package/src/playwright/smoke.spec.ts +8 -6
- 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 +12 -16
- package/dist/lib/browser/chunk-4K7JPALT.mjs +0 -66
- package/dist/lib/browser/chunk-4K7JPALT.mjs.map +0 -7
- package/dist/lib/browser/chunk-OZTXLZGM.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-MK6OVUF6.mjs +0 -67
- package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-S56Z4V4X.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
package/src/model/table-model.ts
CHANGED
|
@@ -5,20 +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
|
-
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';
|
|
21
|
-
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
22
13
|
import { type Label } from '@dxos/react-ui';
|
|
23
14
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
24
15
|
import {
|
|
@@ -28,15 +19,15 @@ import {
|
|
|
28
19
|
type DxGridPosition,
|
|
29
20
|
} from '@dxos/react-ui-grid';
|
|
30
21
|
import {
|
|
31
|
-
type DataType,
|
|
32
22
|
type FieldSortType,
|
|
33
|
-
ProjectionModel,
|
|
23
|
+
type ProjectionModel,
|
|
34
24
|
type PropertyType,
|
|
35
25
|
type ValidationError,
|
|
26
|
+
type View,
|
|
36
27
|
validateSchema,
|
|
37
28
|
} from '@dxos/schema';
|
|
38
29
|
|
|
39
|
-
import { Table } from '../types';
|
|
30
|
+
import { type Table } from '../types';
|
|
40
31
|
import { touch } from '../util';
|
|
41
32
|
import { extractTagIds } from '../util/tag';
|
|
42
33
|
|
|
@@ -78,9 +69,8 @@ const defaultFeatures: TableFeatures = {
|
|
|
78
69
|
export type InsertRowResult = 'draft' | 'final';
|
|
79
70
|
|
|
80
71
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
projection?: ProjectionModel;
|
|
72
|
+
object: Table.Table;
|
|
73
|
+
projection: ProjectionModel;
|
|
84
74
|
features?: Partial<TableFeatures>;
|
|
85
75
|
sorting?: FieldSortType[];
|
|
86
76
|
initialSelection?: string[];
|
|
@@ -96,9 +86,8 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
96
86
|
};
|
|
97
87
|
|
|
98
88
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
99
|
-
private readonly
|
|
89
|
+
private readonly _object: Table.Table;
|
|
100
90
|
private readonly _projection: ProjectionModel;
|
|
101
|
-
private _table?: Table.Table;
|
|
102
91
|
|
|
103
92
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
104
93
|
start: { row: 0, col: 0 },
|
|
@@ -123,8 +112,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
123
112
|
private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
|
|
124
113
|
|
|
125
114
|
constructor({
|
|
126
|
-
|
|
127
|
-
schema,
|
|
115
|
+
object,
|
|
128
116
|
projection,
|
|
129
117
|
features = {},
|
|
130
118
|
sorting = [],
|
|
@@ -139,8 +127,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
139
127
|
onRowAction,
|
|
140
128
|
}: TableModelProps<T>) {
|
|
141
129
|
super();
|
|
142
|
-
this.
|
|
143
|
-
this._projection = projection
|
|
130
|
+
this._object = object;
|
|
131
|
+
this._projection = projection;
|
|
144
132
|
this._projection.normalizeView();
|
|
145
133
|
|
|
146
134
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
@@ -151,7 +139,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
151
139
|
'Single selection is not compatible with editable tables.',
|
|
152
140
|
);
|
|
153
141
|
|
|
154
|
-
this._sorting = new TableSorting(
|
|
142
|
+
this._sorting = new TableSorting({
|
|
143
|
+
rows: this._rows,
|
|
144
|
+
getView: () => this.view,
|
|
145
|
+
projection: this._projection,
|
|
146
|
+
});
|
|
155
147
|
|
|
156
148
|
if (sorting.length > 0) {
|
|
157
149
|
const [sort] = sorting;
|
|
@@ -176,16 +168,17 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
176
168
|
}
|
|
177
169
|
|
|
178
170
|
public get id(): string {
|
|
179
|
-
return
|
|
171
|
+
return Obj.getDXN(this._object).toString();
|
|
180
172
|
}
|
|
181
173
|
|
|
182
|
-
public get view():
|
|
183
|
-
|
|
174
|
+
public get view(): View.View {
|
|
175
|
+
const view = this._object.view.target;
|
|
176
|
+
invariant(view, 'Table model not initialized');
|
|
177
|
+
return view;
|
|
184
178
|
}
|
|
185
179
|
|
|
186
180
|
public get table(): Table.Table {
|
|
187
|
-
|
|
188
|
-
return this._table;
|
|
181
|
+
return this._object;
|
|
189
182
|
}
|
|
190
183
|
|
|
191
184
|
public get projection(): ProjectionModel {
|
|
@@ -245,10 +238,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
245
238
|
//
|
|
246
239
|
|
|
247
240
|
protected override async _open(): Promise<void> {
|
|
248
|
-
|
|
249
|
-
invariant(Obj.instanceOf(Table.Table, presentation));
|
|
250
|
-
this._table = presentation;
|
|
251
|
-
|
|
241
|
+
await this._object.view.load();
|
|
252
242
|
this.initializeColumnMeta();
|
|
253
243
|
this.initializeEffects();
|
|
254
244
|
await this._selection.open(this._ctx);
|
|
@@ -471,7 +461,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
471
461
|
|
|
472
462
|
const { props } = this._projection.getFieldProjection(field.id);
|
|
473
463
|
switch (props.format) {
|
|
474
|
-
case
|
|
464
|
+
case Format.TypeFormat.Ref: {
|
|
475
465
|
if (!field.referencePath) {
|
|
476
466
|
return ''; // TODO(burdon): Show error.
|
|
477
467
|
}
|
|
@@ -480,7 +470,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
480
470
|
}
|
|
481
471
|
|
|
482
472
|
default: {
|
|
483
|
-
return formatForEditing({
|
|
473
|
+
return formatForEditing({
|
|
474
|
+
type: props.type,
|
|
475
|
+
format: props.format,
|
|
476
|
+
value,
|
|
477
|
+
});
|
|
484
478
|
}
|
|
485
479
|
}
|
|
486
480
|
};
|
|
@@ -524,7 +518,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
524
518
|
const snapshot = getSnapshot(currentRow);
|
|
525
519
|
setValue(snapshot, field.path, transformedValue);
|
|
526
520
|
|
|
527
|
-
const schema = getSchema(currentRow);
|
|
521
|
+
const schema = Obj.getSchema(currentRow);
|
|
528
522
|
invariant(schema);
|
|
529
523
|
|
|
530
524
|
const validationResult = validateSchema(schema, snapshot);
|
|
@@ -550,7 +544,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
550
544
|
const transformedValue = editorTextToCellValue(props, value);
|
|
551
545
|
|
|
552
546
|
// Special handling for Ref format to preserve existing behavior
|
|
553
|
-
if (props.format ===
|
|
547
|
+
if (props.format === Format.TypeFormat.Ref && !isLiveObject(value)) {
|
|
554
548
|
return;
|
|
555
549
|
}
|
|
556
550
|
|
|
@@ -665,7 +659,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
665
659
|
|
|
666
660
|
const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
667
661
|
switch (props.format) {
|
|
668
|
-
case
|
|
662
|
+
case Format.TypeFormat.Ref: {
|
|
669
663
|
if (isLiveObject(value)) {
|
|
670
664
|
return Ref.make(value);
|
|
671
665
|
} else {
|
|
@@ -673,7 +667,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
673
667
|
}
|
|
674
668
|
}
|
|
675
669
|
|
|
676
|
-
case
|
|
670
|
+
case Format.TypeFormat.SingleSelect: {
|
|
677
671
|
const ids = extractTagIds(value);
|
|
678
672
|
if (ids && ids.length > 0) {
|
|
679
673
|
return ids[0];
|
|
@@ -682,7 +676,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
682
676
|
}
|
|
683
677
|
}
|
|
684
678
|
|
|
685
|
-
case
|
|
679
|
+
case Format.TypeFormat.MultiSelect: {
|
|
686
680
|
const ids = extractTagIds(value);
|
|
687
681
|
return ids || value;
|
|
688
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 {
|
|
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
|
|
99
|
-
const view = createView({
|
|
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,15 +5,15 @@
|
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
import {
|
|
11
|
+
type FieldSortType,
|
|
12
|
+
type FieldType,
|
|
13
|
+
type ProjectionModel,
|
|
14
|
+
type PropertyType,
|
|
15
|
+
type SortDirectionType,
|
|
16
|
+
type View,
|
|
17
17
|
} from '@dxos/schema';
|
|
18
18
|
|
|
19
19
|
import { type TableRow } from './table-model';
|
|
@@ -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();
|
|
@@ -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);
|
|
@@ -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 {
|
|
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