@dxos/react-ui-table 0.8.4-main.67995b8 → 0.8.4-main.72ec0f3
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-5XLSHLOG.mjs → chunk-GS2UNGJK.mjs} +493 -469
- package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +610 -695
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +14 -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-Y66YLSTC.mjs → chunk-TRR3C5NX.mjs} +493 -469
- package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +610 -695
- 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 +14 -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/ColumnActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +2 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +51 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +107 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +8 -4
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +42 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -6
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +12 -17
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
- package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -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 +4 -4
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +0 -7
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +11 -9
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +2 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +3 -4
- 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/translations.d.ts +2 -2
- 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/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +61 -59
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +1 -1
- package/src/components/Table/DynamicTable.stories.tsx +18 -14
- package/src/components/Table/DynamicTable.tsx +8 -10
- package/src/components/Table/Relations.stories.tsx +242 -0
- package/src/components/Table/RowActionsMenu.tsx +23 -15
- package/src/components/Table/Table.stories.tsx +72 -60
- package/src/components/Table/Table.tsx +140 -115
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +26 -65
- package/src/components/TableCellEditor/TableCellEditor.tsx +74 -224
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +34 -24
- package/src/components/testing.ts +2 -2
- package/src/hooks/useAddRow.ts +7 -5
- package/src/hooks/useTableModel.ts +6 -6
- package/src/model/modal-controller.ts +1 -29
- package/src/model/selection-model.ts +3 -2
- package/src/model/table-model.test.ts +20 -11
- package/src/model/table-model.ts +46 -31
- package/src/model/table-presentation.test.ts +24 -22
- package/src/model/table-presentation.ts +74 -33
- package/src/model/table-sorting.ts +17 -8
- package/src/playwright/TableManager.ts +1 -1
- package/src/playwright/smoke.spec.ts +131 -37
- package/src/testing/useTestTableModel.ts +12 -8
- package/src/translations.ts +2 -2
- package/src/types/index.ts +1 -1
- package/src/types/schema.ts +63 -6
- package/src/util/dynamic-table.ts +16 -17
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +4 -4
- package/src/util/table-buttons.ts +4 -3
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +0 -7
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
- package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
- package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -11
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -86
- package/src/components/Table/relations.stories.tsx +0 -116
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/util/table.ts +0 -49
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
|
-
import { type
|
|
7
|
-
|
|
8
|
-
import { type Live } from '@dxos/live-object';
|
|
9
|
-
import { log } from '@dxos/log';
|
|
6
|
+
import { type MutableRefObject, type RefObject } from 'react';
|
|
10
7
|
|
|
11
8
|
export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: string };
|
|
12
9
|
|
|
@@ -14,12 +11,6 @@ export type ModalState =
|
|
|
14
11
|
| { type: 'row'; rowIndex: number }
|
|
15
12
|
| { type: 'column'; fieldId: string }
|
|
16
13
|
| { type: 'refPanel'; targetId: string; typename: string }
|
|
17
|
-
| {
|
|
18
|
-
type: 'createRefPanel';
|
|
19
|
-
typename: string;
|
|
20
|
-
initialValues?: Record<string, string>;
|
|
21
|
-
onCreate?: (obj: Live<any>) => void;
|
|
22
|
-
}
|
|
23
14
|
| { type: 'columnSettings'; mode: ColumnSettingsMode }
|
|
24
15
|
| { type: 'closed' };
|
|
25
16
|
|
|
@@ -68,25 +59,6 @@ export class ModalController {
|
|
|
68
59
|
}
|
|
69
60
|
};
|
|
70
61
|
|
|
71
|
-
public openCreateRef = (
|
|
72
|
-
typename: string,
|
|
73
|
-
anchorCell: Element | null,
|
|
74
|
-
initialValues?: Record<string, string>,
|
|
75
|
-
onCreate?: (obj: Live<any>) => void,
|
|
76
|
-
) => {
|
|
77
|
-
if (anchorCell) {
|
|
78
|
-
this._triggerRef.current = anchorCell as HTMLElement;
|
|
79
|
-
this._state.value = {
|
|
80
|
-
type: 'createRefPanel',
|
|
81
|
-
typename,
|
|
82
|
-
initialValues,
|
|
83
|
-
onCreate,
|
|
84
|
-
};
|
|
85
|
-
} else {
|
|
86
|
-
log.warn('anchor cell not found while creating new ref');
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
62
|
public close = () => {
|
|
91
63
|
this._state.value = { type: 'closed' };
|
|
92
64
|
};
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type ReadonlySignal, type Signal, computed, effect, signal } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
8
|
|
|
9
|
-
import { type TableRow } from './table-model';
|
|
10
9
|
import { touch } from '../util';
|
|
11
10
|
|
|
11
|
+
import { type TableRow } from './table-model';
|
|
12
|
+
|
|
12
13
|
export type SelectionMode = 'single' | 'multiple';
|
|
13
14
|
|
|
14
15
|
export class SelectionModel<T extends TableRow> extends Resource {
|
|
@@ -3,19 +3,20 @@
|
|
|
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
|
-
|
|
15
|
+
import { View } from '@dxos/schema';
|
|
16
|
+
|
|
17
|
+
import { Table } from '../types';
|
|
16
18
|
|
|
17
19
|
import { TableModel, type TableModelProps } from './table-model';
|
|
18
|
-
import { TableView } from '../types';
|
|
19
20
|
|
|
20
21
|
// TODO(burdon): Tests are disabled in project.json since they bring in plugin deps.
|
|
21
22
|
// Restore once factored out into react-ui-table.
|
|
@@ -24,7 +25,7 @@ registerSignalsRuntime();
|
|
|
24
25
|
|
|
25
26
|
describe('TableModel', () => {
|
|
26
27
|
let updateCount = 0;
|
|
27
|
-
let model:
|
|
28
|
+
let model: any;
|
|
28
29
|
|
|
29
30
|
beforeEach(async () => {
|
|
30
31
|
updateCount = 0;
|
|
@@ -32,6 +33,11 @@ describe('TableModel', () => {
|
|
|
32
33
|
onCellUpdate: () => updateCount++,
|
|
33
34
|
});
|
|
34
35
|
await model.open();
|
|
36
|
+
model.setRows([
|
|
37
|
+
{ id: '1', title: 'Test', completed: false },
|
|
38
|
+
{ id: '2', title: 'Test 2', completed: true },
|
|
39
|
+
{ id: '3', title: 'Test 3', completed: false },
|
|
40
|
+
]);
|
|
35
41
|
});
|
|
36
42
|
|
|
37
43
|
afterEach(async () => {
|
|
@@ -72,7 +78,6 @@ describe('TableModel', () => {
|
|
|
72
78
|
describe('reactivity', () => {
|
|
73
79
|
it('pure signals should nest', () => {
|
|
74
80
|
const signal$ = live({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
|
|
75
|
-
|
|
76
81
|
const computed$ = computed(() => {
|
|
77
82
|
return signal$.arr.map((row) =>
|
|
78
83
|
computed(() => {
|
|
@@ -107,7 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
107
112
|
|
|
108
113
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
109
114
|
const schema = createEchoSchema(Test);
|
|
110
|
-
const table =
|
|
111
|
-
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
|
+
});
|
|
112
121
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
113
122
|
};
|
package/src/model/table-model.ts
CHANGED
|
@@ -2,39 +2,45 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { computed, effect, signal
|
|
5
|
+
import { type ReadonlySignal, computed, effect, signal } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
8
|
import { Obj, Ref } from '@dxos/echo';
|
|
9
9
|
import {
|
|
10
|
-
type FieldSortType,
|
|
11
10
|
FormatEnum,
|
|
12
|
-
getValue,
|
|
13
|
-
setValue,
|
|
14
11
|
type JsonProp,
|
|
15
|
-
|
|
12
|
+
type JsonSchemaType,
|
|
16
13
|
getSchema,
|
|
14
|
+
getValue,
|
|
15
|
+
setValue,
|
|
17
16
|
toEffectSchema,
|
|
18
|
-
|
|
19
|
-
} from '@dxos/echo-schema';
|
|
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 {
|
|
20
|
+
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
21
|
+
import { type Label } from '@dxos/react-ui';
|
|
24
22
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
25
23
|
import {
|
|
26
24
|
type DxGridAxisMeta,
|
|
27
|
-
type DxGridPlaneRange,
|
|
28
25
|
type DxGridPlanePosition,
|
|
26
|
+
type DxGridPlaneRange,
|
|
29
27
|
type DxGridPosition,
|
|
30
28
|
} from '@dxos/react-ui-grid';
|
|
31
|
-
import {
|
|
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';
|
|
39
|
+
import { touch } from '../util';
|
|
40
|
+
import { extractTagIds } from '../util/tag';
|
|
32
41
|
|
|
33
42
|
import { type SelectionMode, SelectionModel } from './selection-model';
|
|
34
43
|
import { TableSorting } from './table-sorting';
|
|
35
|
-
import { TableView } from '../types';
|
|
36
|
-
import { touch } from '../util';
|
|
37
|
-
import { extractTagIds } from '../util/tag';
|
|
38
44
|
|
|
39
45
|
// Domain types for cell classification
|
|
40
46
|
export type TableCellType = 'standard' | 'draft' | 'header';
|
|
@@ -53,7 +59,7 @@ export type TableRow = Record<JsonProp, any> & { id: string };
|
|
|
53
59
|
|
|
54
60
|
export type TableRowAction = {
|
|
55
61
|
id: string;
|
|
56
|
-
|
|
62
|
+
label: Label;
|
|
57
63
|
};
|
|
58
64
|
|
|
59
65
|
export type TableFeatures = {
|
|
@@ -68,8 +74,10 @@ const defaultFeatures: TableFeatures = {
|
|
|
68
74
|
schemaEditable: false,
|
|
69
75
|
};
|
|
70
76
|
|
|
77
|
+
export type InsertRowResult = 'draft' | 'final';
|
|
78
|
+
|
|
71
79
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
72
|
-
view:
|
|
80
|
+
view: View.View;
|
|
73
81
|
schema: JsonSchemaType;
|
|
74
82
|
projection?: ProjectionModel;
|
|
75
83
|
features?: Partial<TableFeatures>;
|
|
@@ -78,7 +86,7 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
78
86
|
pinnedRows?: { top: number[]; bottom: number[] };
|
|
79
87
|
rowActions?: TableRowAction[];
|
|
80
88
|
onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
|
|
81
|
-
onInsertRow?: (data?: any) =>
|
|
89
|
+
onInsertRow?: (data?: any) => InsertRowResult;
|
|
82
90
|
onDeleteRows?: (index: number, obj: T[]) => void;
|
|
83
91
|
onDeleteColumn?: (fieldId: string) => void;
|
|
84
92
|
onCellUpdate?: (cell: DxGridPosition) => void;
|
|
@@ -87,16 +95,16 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
87
95
|
};
|
|
88
96
|
|
|
89
97
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
90
|
-
private readonly _view:
|
|
98
|
+
private readonly _view: View.View;
|
|
91
99
|
private readonly _projection: ProjectionModel;
|
|
92
|
-
private _table?:
|
|
100
|
+
private _table?: Table.Table;
|
|
93
101
|
|
|
94
102
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
95
103
|
start: { row: 0, col: 0 },
|
|
96
104
|
end: { row: 0, col: 0 },
|
|
97
105
|
});
|
|
98
106
|
|
|
99
|
-
private readonly _onInsertRow?: (data?: any) =>
|
|
107
|
+
private readonly _onInsertRow?: (data?: any) => InsertRowResult;
|
|
100
108
|
private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
|
|
101
109
|
private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
|
|
102
110
|
private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
|
|
@@ -132,6 +140,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
132
140
|
super();
|
|
133
141
|
this._view = view;
|
|
134
142
|
this._projection = projection ?? new ProjectionModel(schema, view.projection);
|
|
143
|
+
this._projection.normalizeView();
|
|
135
144
|
|
|
136
145
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
137
146
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -166,14 +175,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
166
175
|
}
|
|
167
176
|
|
|
168
177
|
public get id(): string {
|
|
169
|
-
return
|
|
178
|
+
return Obj.getDXN(this._view).toString();
|
|
170
179
|
}
|
|
171
180
|
|
|
172
|
-
public get view():
|
|
181
|
+
public get view(): View.View {
|
|
173
182
|
return this._view;
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
public get table():
|
|
185
|
+
public get table(): Table.Table {
|
|
177
186
|
invariant(this._table, 'Model not initialized');
|
|
178
187
|
return this._table;
|
|
179
188
|
}
|
|
@@ -236,7 +245,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
236
245
|
|
|
237
246
|
protected override async _open(): Promise<void> {
|
|
238
247
|
const presentation = this._view.presentation.target ?? (await this._view.presentation.load());
|
|
239
|
-
invariant(Obj.instanceOf(
|
|
248
|
+
invariant(Obj.instanceOf(Table.Table, presentation));
|
|
240
249
|
this._table = presentation;
|
|
241
250
|
|
|
242
251
|
this.initializeColumnMeta();
|
|
@@ -254,7 +263,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
254
263
|
return {
|
|
255
264
|
grid: meta,
|
|
256
265
|
frozenColsStart: { 0: { size: 30, resizeable: false } },
|
|
257
|
-
frozenColsEnd: { 0: { size:
|
|
266
|
+
frozenColsEnd: { 0: { size: 32, resizeable: false } },
|
|
258
267
|
};
|
|
259
268
|
});
|
|
260
269
|
}
|
|
@@ -334,11 +343,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
334
343
|
|
|
335
344
|
public getColumnCount = (): number => this._projection?.fields.length ?? 0;
|
|
336
345
|
|
|
337
|
-
public insertRow = ():
|
|
346
|
+
public insertRow = (): InsertRowResult => {
|
|
338
347
|
const result = this._onInsertRow?.();
|
|
339
|
-
if (result ===
|
|
348
|
+
if (result === 'draft' && this._draftRows.value.length === 0) {
|
|
340
349
|
this.createDraftRow();
|
|
341
350
|
}
|
|
351
|
+
return result ?? 'final';
|
|
342
352
|
};
|
|
343
353
|
|
|
344
354
|
private createDraftRow(): void {
|
|
@@ -393,14 +403,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
393
403
|
return false;
|
|
394
404
|
}
|
|
395
405
|
|
|
396
|
-
const
|
|
406
|
+
const insertRowResult = this._onInsertRow?.(draftRow.data);
|
|
397
407
|
|
|
398
|
-
if (
|
|
408
|
+
if (insertRowResult === 'final') {
|
|
399
409
|
const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
|
|
400
410
|
this._draftRows.value = newDraftRows;
|
|
401
411
|
}
|
|
402
412
|
|
|
403
|
-
return
|
|
413
|
+
return insertRowResult === 'final';
|
|
404
414
|
};
|
|
405
415
|
|
|
406
416
|
public deleteRow = (rowIndex: number): void => {
|
|
@@ -469,7 +479,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
469
479
|
}
|
|
470
480
|
|
|
471
481
|
default: {
|
|
472
|
-
return formatForEditing({
|
|
482
|
+
return formatForEditing({
|
|
483
|
+
type: props.type,
|
|
484
|
+
format: props.format,
|
|
485
|
+
value,
|
|
486
|
+
});
|
|
473
487
|
}
|
|
474
488
|
}
|
|
475
489
|
};
|
|
@@ -497,6 +511,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
497
511
|
setValue(snapshot, field.path, transformedValue);
|
|
498
512
|
|
|
499
513
|
const validationErrors = this.validateDraftRowData(snapshot);
|
|
514
|
+
// TODO(thure): These errors sometimes result in a useless message like “is missing” (what is missing?)
|
|
500
515
|
if (validationErrors.length > 0) {
|
|
501
516
|
const error = validationErrors.find((err) => err.path === field.path);
|
|
502
517
|
if (error) {
|
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import { describe, expect, it
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { TypedObject } from '@dxos/echo
|
|
8
|
+
import { Filter, Query } from '@dxos/echo';
|
|
9
|
+
import { TypedObject } from '@dxos/echo/internal';
|
|
10
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
10
11
|
import { live } from '@dxos/live-object';
|
|
11
|
-
import {
|
|
12
|
-
|
|
12
|
+
import { View } from '@dxos/schema';
|
|
13
|
+
|
|
14
|
+
import { Table } from '../types';
|
|
13
15
|
|
|
14
16
|
import { TableModel, type TableModelProps } from './table-model';
|
|
15
17
|
import { TablePresentation } from './table-presentation';
|
|
16
|
-
import { TableView } from '../types';
|
|
17
18
|
|
|
18
19
|
describe('TablePresentation', () => {
|
|
19
20
|
describe('row reactivity', () => {
|
|
@@ -26,8 +27,8 @@ describe('TablePresentation', () => {
|
|
|
26
27
|
updateCount = 0;
|
|
27
28
|
({ data } = live({
|
|
28
29
|
data: [
|
|
29
|
-
{
|
|
30
|
-
{
|
|
30
|
+
{ title: 'A', count: 1 },
|
|
31
|
+
{ title: 'B', count: 2 },
|
|
31
32
|
],
|
|
32
33
|
}));
|
|
33
34
|
|
|
@@ -44,8 +45,8 @@ describe('TablePresentation', () => {
|
|
|
44
45
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
45
46
|
|
|
46
47
|
// Trigger a row update.
|
|
47
|
-
data[0].
|
|
48
|
-
expect(updateCount).toBe(
|
|
48
|
+
data[0].title = 'New Value';
|
|
49
|
+
expect(updateCount).toBe(2);
|
|
49
50
|
|
|
50
51
|
// Verify the new value through getCells.
|
|
51
52
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 0 } }, 'grid');
|
|
@@ -56,15 +57,14 @@ describe('TablePresentation', () => {
|
|
|
56
57
|
// Set up visible range to include our test data.
|
|
57
58
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
58
59
|
|
|
59
|
-
data.push({
|
|
60
|
-
expect(updateCount).toBe(
|
|
60
|
+
data.push({ title: 'C', count: 3 });
|
|
61
|
+
expect(updateCount).toBe(2);
|
|
61
62
|
|
|
62
63
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
63
64
|
|
|
64
65
|
// Verify the new row's data.
|
|
65
66
|
expect(cells['0,2']?.value).toBe('C');
|
|
66
67
|
expect(cells['1,2']?.value).toBe('3');
|
|
67
|
-
expect(cells['2,2']?.value).toBe('true');
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
it('should handle combined operations reactively', () => {
|
|
@@ -72,32 +72,34 @@ describe('TablePresentation', () => {
|
|
|
72
72
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
73
73
|
|
|
74
74
|
// Multiple operations.
|
|
75
|
-
data[0].
|
|
76
|
-
data.push({
|
|
75
|
+
data[0].title = 'Updated A';
|
|
76
|
+
data.push({ title: 'C', count: 3, completed: true });
|
|
77
77
|
data.splice(1, 1);
|
|
78
78
|
|
|
79
79
|
// We expect one update per operation affecting visible rows.
|
|
80
|
-
expect(updateCount).toBe(
|
|
80
|
+
expect(updateCount).toBe(4);
|
|
81
81
|
|
|
82
82
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
83
83
|
|
|
84
84
|
// Verify final state.
|
|
85
85
|
expect(cells['0,0']?.value).toBe('Updated A');
|
|
86
86
|
expect(cells['0,1']?.value).toBe('C');
|
|
87
|
-
expect(cells['1,1']?.value).toBe('3');
|
|
88
|
-
expect(cells['2,1']?.value).toBe('true');
|
|
89
87
|
});
|
|
90
88
|
});
|
|
91
89
|
});
|
|
92
90
|
|
|
93
91
|
class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
94
92
|
title: Schema.String,
|
|
95
|
-
|
|
93
|
+
count: Schema.Number,
|
|
96
94
|
}) {}
|
|
97
95
|
|
|
98
96
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
99
97
|
const schema = createEchoSchema(Test);
|
|
100
|
-
const table =
|
|
101
|
-
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
|
+
});
|
|
102
104
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
103
105
|
};
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
|
-
import
|
|
6
|
+
import * as Predicate from 'effect/Predicate';
|
|
7
7
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import { FormatEnum,
|
|
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,
|
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
toPlaneCellIndex,
|
|
17
17
|
} from '@dxos/react-ui-grid';
|
|
18
18
|
import { mx } from '@dxos/react-ui-theme';
|
|
19
|
-
import {
|
|
19
|
+
import { type FieldType, VIEW_FIELD_LIMIT } from '@dxos/schema';
|
|
20
20
|
|
|
21
|
-
import { type SelectionMode } from './selection-model';
|
|
22
|
-
import { type TableRow, type TableModel } from './table-model';
|
|
23
21
|
import { tableButtons, tableControls } from '../util';
|
|
24
22
|
|
|
23
|
+
import { type SelectionMode } from './selection-model';
|
|
24
|
+
import { type TableModel, type TableRow } from './table-model';
|
|
25
|
+
|
|
25
26
|
/**
|
|
26
27
|
* Presentation layer for a table component, handling cell rendering and grid display logic.
|
|
27
28
|
* Manages rendering of table cells, headers, selection columns, and action columns across
|
|
@@ -67,6 +68,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
67
68
|
case 'frozenRowsEnd':
|
|
68
69
|
cells = this.getDraftRowCells(range);
|
|
69
70
|
break;
|
|
71
|
+
case 'fixedEndStart':
|
|
72
|
+
cells = this.getDraftIconCells(range);
|
|
73
|
+
break;
|
|
70
74
|
case 'fixedEndEnd':
|
|
71
75
|
cells = this.getDraftActionCells(range);
|
|
72
76
|
break;
|
|
@@ -137,20 +141,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
137
141
|
};
|
|
138
142
|
|
|
139
143
|
const classes = [];
|
|
140
|
-
const formatClasses = cellClassesForFieldType(
|
|
144
|
+
const formatClasses = cellClassesForFieldType(props);
|
|
145
|
+
|
|
141
146
|
if (formatClasses) {
|
|
142
147
|
classes.push(formatClasses);
|
|
143
148
|
}
|
|
149
|
+
|
|
144
150
|
const rowSelectionClasses = cellClassesForRowSelection(
|
|
145
151
|
this.model.selection.isObjectSelected(obj),
|
|
146
152
|
this.model.selection.selectionMode,
|
|
147
153
|
);
|
|
154
|
+
|
|
148
155
|
if (rowSelectionClasses) {
|
|
149
156
|
classes.push(rowSelectionClasses);
|
|
150
157
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
158
|
+
|
|
159
|
+
cell.className = mx(classes);
|
|
154
160
|
|
|
155
161
|
// Arrays.
|
|
156
162
|
if (props.type === TypeEnum.Array) {
|
|
@@ -184,7 +190,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
184
190
|
|
|
185
191
|
const tags = targetArray
|
|
186
192
|
.map(getLabel)
|
|
187
|
-
.filter(isNotNullable)
|
|
193
|
+
.filter(Predicate.isNotNullable)
|
|
188
194
|
.map((title) => {
|
|
189
195
|
return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
|
|
190
196
|
})
|
|
@@ -199,7 +205,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
199
205
|
const targetObj = getValue(obj, field.path)?.target;
|
|
200
206
|
if (targetObj) {
|
|
201
207
|
const dxn = Obj.getDXN(targetObj)?.toString();
|
|
202
|
-
cell.accessoryHtml = `<dx-
|
|
208
|
+
cell.accessoryHtml = `<div role="none" class="absolute inline-end-0 inset-block-0 p-[--dx-grid-cell-content-padding-block]"><dx-anchor refId=${dxn} class="dx-button is-6 aspect-square min-bs-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
211
|
|
|
@@ -275,23 +281,34 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
275
281
|
const fields = this.model.projection?.fields ?? [];
|
|
276
282
|
const draftRows = this.model.draftRows.value;
|
|
277
283
|
|
|
278
|
-
|
|
279
|
-
|
|
284
|
+
// Return cells of the CTA row if no draft row is active
|
|
285
|
+
if (draftRows.length === 0) {
|
|
280
286
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
287
|
+
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
288
|
+
value: '',
|
|
289
|
+
readonly: true,
|
|
290
|
+
className: 'dx-grid__row--cta__cell',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
295
|
+
const draftRow = draftRows[row];
|
|
296
|
+
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
297
|
+
const cellIndex = toPlaneCellIndex({ col, row });
|
|
298
|
+
const field = fields[col];
|
|
299
|
+
if (!field) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
285
302
|
|
|
286
|
-
|
|
303
|
+
this.createDataCell(cells, draftRow.data, field, col, row);
|
|
287
304
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
305
|
+
if (this.model.hasDraftRowValidationError(row, field.path)) {
|
|
306
|
+
const cellValue = cells[cellIndex];
|
|
307
|
+
if (cellValue) {
|
|
308
|
+
const existingClasses = cellValue.className || '';
|
|
309
|
+
const draftClasses = 'dx-grid__cell--flagged';
|
|
310
|
+
cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
|
|
311
|
+
}
|
|
295
312
|
}
|
|
296
313
|
}
|
|
297
314
|
}
|
|
@@ -310,14 +327,14 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
310
327
|
|
|
311
328
|
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
312
329
|
// TODO(burdon): Use same logic as form for fallback title.
|
|
313
|
-
value:
|
|
314
|
-
readonly: true,
|
|
330
|
+
value: '',
|
|
315
331
|
resizeHandle: 'col',
|
|
316
332
|
accessoryHtml: `
|
|
333
|
+
<span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
|
|
317
334
|
${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
|
|
318
335
|
${tableButtons.columnSettings.render({ fieldId: field.id })}
|
|
319
336
|
`,
|
|
320
|
-
className: '!bg-toolbarSurface',
|
|
337
|
+
className: '!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch',
|
|
321
338
|
};
|
|
322
339
|
}
|
|
323
340
|
|
|
@@ -400,14 +417,39 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
400
417
|
const cells: DxGridPlaneCells = {};
|
|
401
418
|
const draftRows = this.model.draftRows.value;
|
|
402
419
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
420
|
+
// Return cells of the CTA row if no draft row is active
|
|
421
|
+
if (draftRows.length === 0) {
|
|
422
|
+
cells[toPlaneCellIndex({ col: 0, row: 0 })] = {
|
|
423
|
+
value: '',
|
|
424
|
+
className: 'dx-grid__row--cta__cell',
|
|
425
|
+
readonly: true,
|
|
426
|
+
};
|
|
427
|
+
} else {
|
|
428
|
+
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
429
|
+
const draftRow = draftRows[row];
|
|
430
|
+
const disabled = !draftRow.valid;
|
|
431
|
+
|
|
432
|
+
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
433
|
+
value: '',
|
|
434
|
+
readonly: true,
|
|
435
|
+
accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return cells;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private getDraftIconCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
444
|
+
const cells: DxGridPlaneCells = {};
|
|
445
|
+
const draftRows = this.model.draftRows.value;
|
|
406
446
|
|
|
447
|
+
for (let row = range.start.row; row <= range.end.row; row++) {
|
|
407
448
|
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
408
449
|
value: '',
|
|
409
450
|
readonly: true,
|
|
410
|
-
accessoryHtml:
|
|
451
|
+
accessoryHtml: '<dx-icon icon="ph--plus--regular" class="contents"></dx-icon>',
|
|
452
|
+
className: mx('[&>div]:grid [&>div]:place-content-center', draftRows.length < 1 && 'dx-grid__row--cta__cell'),
|
|
411
453
|
};
|
|
412
454
|
}
|
|
413
455
|
|
|
@@ -426,7 +468,6 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
|
|
|
426
468
|
|
|
427
469
|
switch (selectionMode) {
|
|
428
470
|
case 'single':
|
|
429
|
-
// TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
|
|
430
471
|
return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
|
|
431
472
|
case 'multiple':
|
|
432
473
|
return ['!bg-gridCellSelected'];
|