@dxos/react-ui-table 0.8.4-main.2e9d522 → 0.8.4-main.3c1ae3b
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-CJQ6T4LL.mjs +77 -0
- package/dist/lib/browser/chunk-CJQ6T4LL.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5XLSHLOG.mjs → chunk-V7A7ZA5E.mjs} +576 -544
- package/dist/lib/browser/chunk-V7A7ZA5E.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +694 -760
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +16 -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-Y66YLSTC.mjs → chunk-P73ALX3N.mjs} +576 -544
- package/dist/lib/node-esm/chunk-P73ALX3N.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs +78 -0
- package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +694 -760
- 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 +16 -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/ColumnActionsMenu.d.ts +2 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -3
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +5 -6
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +37 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +111 -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 +13 -9
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +108 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +14 -9
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +11 -18
- 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 -5
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +3 -2
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +5 -6
- 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 +14 -14
- 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 +9 -6
- 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 +19 -34
- 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 +10 -11
- 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 +57 -51
- package/src/components/Table/ColumnActionsMenu.tsx +5 -2
- package/src/components/Table/ColumnSettings.tsx +3 -4
- package/src/components/Table/DynamicTable.stories.tsx +21 -19
- package/src/components/Table/DynamicTable.tsx +24 -18
- package/src/components/Table/Relations.stories.tsx +233 -0
- package/src/components/Table/RowActionsMenu.tsx +23 -15
- package/src/components/Table/Table.stories.tsx +104 -99
- package/src/components/Table/Table.tsx +338 -295
- package/src/components/TableCellEditor/CellValidationMessage.tsx +2 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +143 -39
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +30 -66
- package/src/components/TableCellEditor/TableCellEditor.tsx +86 -236
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +34 -24
- package/src/components/testing.ts +4 -4
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +7 -5
- package/src/hooks/useProjectionModel.ts +19 -0
- package/src/hooks/useTableModel.ts +13 -15
- package/src/model/modal-controller.ts +1 -29
- package/src/model/selection-model.ts +3 -2
- package/src/model/table-model.test.ts +21 -12
- package/src/model/table-model.ts +64 -58
- package/src/model/table-presentation.test.ts +25 -23
- package/src/model/table-presentation.ts +82 -41
- package/src/model/table-sorting.ts +32 -14
- package/src/playwright/TableManager.ts +5 -5
- package/src/playwright/smoke.spec.ts +132 -38
- package/src/testing/useTestTableModel.ts +32 -24
- package/src/translations.ts +2 -2
- package/src/types/Table.ts +90 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.ts +21 -27
- 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/types/schema.d.ts +0 -8
- package/dist/types/src/types/schema.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/types/schema.ts +0 -16
- package/src/util/table.ts +0 -49
|
@@ -6,18 +6,17 @@ import { effect } from '@preact/signals-core';
|
|
|
6
6
|
import orderBy from 'lodash.orderby';
|
|
7
7
|
import { useEffect, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
10
|
import { type Live } from '@dxos/live-object';
|
|
11
|
-
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
12
11
|
import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
13
|
-
import { type
|
|
12
|
+
import { type ProjectionModel } from '@dxos/schema';
|
|
14
13
|
import { isNonNullable } from '@dxos/util';
|
|
15
14
|
|
|
16
|
-
import {
|
|
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,16 +65,17 @@ 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(() => {
|
|
75
73
|
if (rows) {
|
|
76
|
-
// TODO(ZaymonFC): Remove this workaround once unstable query ordering issue is resolved
|
|
77
74
|
/*
|
|
78
75
|
* Sort all objects by string id field as a temporary workaround for query ordering issues
|
|
79
76
|
* Reference: https://github.com/dxos/dxos/pull/9409
|
|
80
77
|
*/
|
|
78
|
+
// TODO(ZaymonFC): Remove this workaround once unstable query ordering issue is resolved
|
|
81
79
|
const sortedRows = orderBy(rows, [(row) => String(row.id)], ['asc']);
|
|
82
80
|
model?.setRows(sortedRows);
|
|
83
81
|
}
|
|
@@ -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 { ProjectionModel, 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
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
const view = View.make({
|
|
116
|
+
query: Query.select(Filter.type(schema)),
|
|
117
|
+
jsonSchema: schema.jsonSchema,
|
|
118
|
+
});
|
|
119
|
+
const object = Table.make({ view });
|
|
120
|
+
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
121
|
+
return new TableModel({ object, projection, ...props });
|
|
113
122
|
};
|
package/src/model/table-model.ts
CHANGED
|
@@ -2,39 +2,37 @@
|
|
|
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
|
-
import { Obj, Ref } from '@dxos/echo';
|
|
9
|
-
import {
|
|
10
|
-
type FieldSortType,
|
|
11
|
-
FormatEnum,
|
|
12
|
-
getValue,
|
|
13
|
-
setValue,
|
|
14
|
-
type JsonProp,
|
|
15
|
-
getSnapshot,
|
|
16
|
-
getSchema,
|
|
17
|
-
toEffectSchema,
|
|
18
|
-
type JsonSchemaType,
|
|
19
|
-
} from '@dxos/echo-schema';
|
|
8
|
+
import { Format, Obj, Ref } from '@dxos/echo';
|
|
9
|
+
import { type JsonProp, type JsonSchemaType, getValue, setValue, toEffectSchema } from '@dxos/echo/internal';
|
|
20
10
|
import { invariant } from '@dxos/invariant';
|
|
21
11
|
import { ObjectId } from '@dxos/keys';
|
|
22
|
-
import { isLiveObject } from '@dxos/live-object';
|
|
23
|
-
import {
|
|
12
|
+
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
13
|
+
import { type Label } from '@dxos/react-ui';
|
|
24
14
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
25
15
|
import {
|
|
26
16
|
type DxGridAxisMeta,
|
|
27
|
-
type DxGridPlaneRange,
|
|
28
17
|
type DxGridPlanePosition,
|
|
18
|
+
type DxGridPlaneRange,
|
|
29
19
|
type DxGridPosition,
|
|
30
20
|
} from '@dxos/react-ui-grid';
|
|
31
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
type FieldSortType,
|
|
23
|
+
type ProjectionModel,
|
|
24
|
+
type PropertyType,
|
|
25
|
+
type ValidationError,
|
|
26
|
+
type View,
|
|
27
|
+
validateSchema,
|
|
28
|
+
} from '@dxos/schema';
|
|
29
|
+
|
|
30
|
+
import { type Table } from '../types';
|
|
31
|
+
import { touch } from '../util';
|
|
32
|
+
import { extractTagIds } from '../util/tag';
|
|
32
33
|
|
|
33
34
|
import { type SelectionMode, SelectionModel } from './selection-model';
|
|
34
35
|
import { TableSorting } from './table-sorting';
|
|
35
|
-
import { TableView } from '../types';
|
|
36
|
-
import { touch } from '../util';
|
|
37
|
-
import { extractTagIds } from '../util/tag';
|
|
38
36
|
|
|
39
37
|
// Domain types for cell classification
|
|
40
38
|
export type TableCellType = 'standard' | 'draft' | 'header';
|
|
@@ -53,7 +51,7 @@ export type TableRow = Record<JsonProp, any> & { id: string };
|
|
|
53
51
|
|
|
54
52
|
export type TableRowAction = {
|
|
55
53
|
id: string;
|
|
56
|
-
|
|
54
|
+
label: Label;
|
|
57
55
|
};
|
|
58
56
|
|
|
59
57
|
export type TableFeatures = {
|
|
@@ -68,17 +66,18 @@ const defaultFeatures: TableFeatures = {
|
|
|
68
66
|
schemaEditable: false,
|
|
69
67
|
};
|
|
70
68
|
|
|
69
|
+
export type InsertRowResult = 'draft' | 'final';
|
|
70
|
+
|
|
71
71
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
projection?: ProjectionModel;
|
|
72
|
+
object: Table.Table;
|
|
73
|
+
projection: ProjectionModel;
|
|
75
74
|
features?: Partial<TableFeatures>;
|
|
76
75
|
sorting?: FieldSortType[];
|
|
77
76
|
initialSelection?: string[];
|
|
78
77
|
pinnedRows?: { top: number[]; bottom: number[] };
|
|
79
78
|
rowActions?: TableRowAction[];
|
|
80
79
|
onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
|
|
81
|
-
onInsertRow?: (data?: any) =>
|
|
80
|
+
onInsertRow?: (data?: any) => InsertRowResult;
|
|
82
81
|
onDeleteRows?: (index: number, obj: T[]) => void;
|
|
83
82
|
onDeleteColumn?: (fieldId: string) => void;
|
|
84
83
|
onCellUpdate?: (cell: DxGridPosition) => void;
|
|
@@ -87,16 +86,15 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
87
86
|
};
|
|
88
87
|
|
|
89
88
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
90
|
-
private readonly
|
|
89
|
+
private readonly _object: Table.Table;
|
|
91
90
|
private readonly _projection: ProjectionModel;
|
|
92
|
-
private _table?: TableView;
|
|
93
91
|
|
|
94
92
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
95
93
|
start: { row: 0, col: 0 },
|
|
96
94
|
end: { row: 0, col: 0 },
|
|
97
95
|
});
|
|
98
96
|
|
|
99
|
-
private readonly _onInsertRow?: (data?: any) =>
|
|
97
|
+
private readonly _onInsertRow?: (data?: any) => InsertRowResult;
|
|
100
98
|
private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
|
|
101
99
|
private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
|
|
102
100
|
private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
|
|
@@ -114,8 +112,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
114
112
|
private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
|
|
115
113
|
|
|
116
114
|
constructor({
|
|
117
|
-
|
|
118
|
-
schema,
|
|
115
|
+
object,
|
|
119
116
|
projection,
|
|
120
117
|
features = {},
|
|
121
118
|
sorting = [],
|
|
@@ -130,8 +127,9 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
130
127
|
onRowAction,
|
|
131
128
|
}: TableModelProps<T>) {
|
|
132
129
|
super();
|
|
133
|
-
this.
|
|
134
|
-
this._projection = projection
|
|
130
|
+
this._object = object;
|
|
131
|
+
this._projection = projection;
|
|
132
|
+
this._projection.normalizeView();
|
|
135
133
|
|
|
136
134
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
137
135
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -141,11 +139,15 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
141
139
|
'Single selection is not compatible with editable tables.',
|
|
142
140
|
);
|
|
143
141
|
|
|
144
|
-
this._sorting = new TableSorting(
|
|
142
|
+
this._sorting = new TableSorting({
|
|
143
|
+
rows: this._rows,
|
|
144
|
+
getView: () => this.view,
|
|
145
|
+
projection: this._projection,
|
|
146
|
+
});
|
|
145
147
|
|
|
146
148
|
if (sorting.length > 0) {
|
|
147
|
-
const
|
|
148
|
-
this._sorting.setSort(
|
|
149
|
+
const { fieldId, direction } = sorting[0];
|
|
150
|
+
this._sorting.setSort(fieldId, direction);
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
this._selection = new SelectionModel(
|
|
@@ -166,16 +168,17 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
public get id(): string {
|
|
169
|
-
return
|
|
171
|
+
return Obj.getDXN(this._object).toString();
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
public get view():
|
|
173
|
-
|
|
174
|
+
public get view(): View.View {
|
|
175
|
+
const view = this._object.view.target;
|
|
176
|
+
invariant(view, 'Table model not initialized');
|
|
177
|
+
return view;
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
public get table():
|
|
177
|
-
|
|
178
|
-
return this._table;
|
|
180
|
+
public get table(): Table.Table {
|
|
181
|
+
return this._object;
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
public get projection(): ProjectionModel {
|
|
@@ -235,10 +238,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
235
238
|
//
|
|
236
239
|
|
|
237
240
|
protected override async _open(): Promise<void> {
|
|
238
|
-
|
|
239
|
-
invariant(Obj.instanceOf(TableView, presentation));
|
|
240
|
-
this._table = presentation;
|
|
241
|
-
|
|
241
|
+
await this._object.view.load();
|
|
242
242
|
this.initializeColumnMeta();
|
|
243
243
|
this.initializeEffects();
|
|
244
244
|
await this._selection.open(this._ctx);
|
|
@@ -254,7 +254,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
254
254
|
return {
|
|
255
255
|
grid: meta,
|
|
256
256
|
frozenColsStart: { 0: { size: 30, resizeable: false } },
|
|
257
|
-
frozenColsEnd: { 0: { size:
|
|
257
|
+
frozenColsEnd: { 0: { size: 32, resizeable: false } },
|
|
258
258
|
};
|
|
259
259
|
});
|
|
260
260
|
}
|
|
@@ -334,11 +334,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
334
334
|
|
|
335
335
|
public getColumnCount = (): number => this._projection?.fields.length ?? 0;
|
|
336
336
|
|
|
337
|
-
public insertRow = ():
|
|
337
|
+
public insertRow = (): InsertRowResult => {
|
|
338
338
|
const result = this._onInsertRow?.();
|
|
339
|
-
if (result ===
|
|
339
|
+
if (result === 'draft' && this._draftRows.value.length === 0) {
|
|
340
340
|
this.createDraftRow();
|
|
341
341
|
}
|
|
342
|
+
return result ?? 'final';
|
|
342
343
|
};
|
|
343
344
|
|
|
344
345
|
private createDraftRow(): void {
|
|
@@ -393,14 +394,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
393
394
|
return false;
|
|
394
395
|
}
|
|
395
396
|
|
|
396
|
-
const
|
|
397
|
+
const insertRowResult = this._onInsertRow?.(draftRow.data);
|
|
397
398
|
|
|
398
|
-
if (
|
|
399
|
+
if (insertRowResult === 'final') {
|
|
399
400
|
const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
|
|
400
401
|
this._draftRows.value = newDraftRows;
|
|
401
402
|
}
|
|
402
403
|
|
|
403
|
-
return
|
|
404
|
+
return insertRowResult === 'final';
|
|
404
405
|
};
|
|
405
406
|
|
|
406
407
|
public deleteRow = (rowIndex: number): void => {
|
|
@@ -460,7 +461,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
460
461
|
|
|
461
462
|
const { props } = this._projection.getFieldProjection(field.id);
|
|
462
463
|
switch (props.format) {
|
|
463
|
-
case
|
|
464
|
+
case Format.TypeFormat.Ref: {
|
|
464
465
|
if (!field.referencePath) {
|
|
465
466
|
return ''; // TODO(burdon): Show error.
|
|
466
467
|
}
|
|
@@ -469,7 +470,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
469
470
|
}
|
|
470
471
|
|
|
471
472
|
default: {
|
|
472
|
-
return formatForEditing({
|
|
473
|
+
return formatForEditing({
|
|
474
|
+
type: props.type,
|
|
475
|
+
format: props.format,
|
|
476
|
+
value,
|
|
477
|
+
});
|
|
473
478
|
}
|
|
474
479
|
}
|
|
475
480
|
};
|
|
@@ -497,6 +502,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
497
502
|
setValue(snapshot, field.path, transformedValue);
|
|
498
503
|
|
|
499
504
|
const validationErrors = this.validateDraftRowData(snapshot);
|
|
505
|
+
// TODO(thure): These errors sometimes result in a useless message like “is missing” (what is missing?)
|
|
500
506
|
if (validationErrors.length > 0) {
|
|
501
507
|
const error = validationErrors.find((err) => err.path === field.path);
|
|
502
508
|
if (error) {
|
|
@@ -512,7 +518,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
512
518
|
const snapshot = getSnapshot(currentRow);
|
|
513
519
|
setValue(snapshot, field.path, transformedValue);
|
|
514
520
|
|
|
515
|
-
const schema = getSchema(currentRow);
|
|
521
|
+
const schema = Obj.getSchema(currentRow);
|
|
516
522
|
invariant(schema);
|
|
517
523
|
|
|
518
524
|
const validationResult = validateSchema(schema, snapshot);
|
|
@@ -538,7 +544,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
538
544
|
const transformedValue = editorTextToCellValue(props, value);
|
|
539
545
|
|
|
540
546
|
// Special handling for Ref format to preserve existing behavior
|
|
541
|
-
if (props.format ===
|
|
547
|
+
if (props.format === Format.TypeFormat.Ref && !isLiveObject(value)) {
|
|
542
548
|
return;
|
|
543
549
|
}
|
|
544
550
|
|
|
@@ -653,7 +659,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
653
659
|
|
|
654
660
|
const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
655
661
|
switch (props.format) {
|
|
656
|
-
case
|
|
662
|
+
case Format.TypeFormat.Ref: {
|
|
657
663
|
if (isLiveObject(value)) {
|
|
658
664
|
return Ref.make(value);
|
|
659
665
|
} else {
|
|
@@ -661,7 +667,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
661
667
|
}
|
|
662
668
|
}
|
|
663
669
|
|
|
664
|
-
case
|
|
670
|
+
case Format.TypeFormat.SingleSelect: {
|
|
665
671
|
const ids = extractTagIds(value);
|
|
666
672
|
if (ids && ids.length > 0) {
|
|
667
673
|
return ids[0];
|
|
@@ -670,7 +676,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
670
676
|
}
|
|
671
677
|
}
|
|
672
678
|
|
|
673
|
-
case
|
|
679
|
+
case Format.TypeFormat.MultiSelect: {
|
|
674
680
|
const ids = extractTagIds(value);
|
|
675
681
|
return ids || value;
|
|
676
682
|
}
|
|
@@ -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 { ProjectionModel, 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
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
const view = View.make({
|
|
99
|
+
query: Query.select(Filter.type(schema)),
|
|
100
|
+
jsonSchema: schema.jsonSchema,
|
|
101
|
+
});
|
|
102
|
+
const object = Table.make({ view });
|
|
103
|
+
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
104
|
+
return new TableModel({ object, projection, ...props });
|
|
103
105
|
};
|