@dxos/react-ui-table 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29
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-FBURZBPH.mjs +70 -0
- package/dist/lib/browser/chunk-FBURZBPH.mjs.map +7 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/browser/chunk-YGC7LZJN.mjs +1695 -0
- package/dist/lib/browser/chunk-YGC7LZJN.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1139 -1193
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +33 -42
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/translations.mjs +36 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +4 -3
- package/dist/lib/node-esm/chunk-DKKMSVKP.mjs +71 -0
- package/dist/lib/node-esm/chunk-DKKMSVKP.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-GKIWDEZ7.mjs +1697 -0
- package/dist/lib/node-esm/chunk-GKIWDEZ7.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1139 -1193
- 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 +33 -42
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/translations.mjs +37 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +4 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +23 -39
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +96 -29
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +20 -18
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +96 -31
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts +17 -0
- package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
- 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 +12 -12
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +7 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +25 -28
- 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/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +23 -16
- 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 +5 -6
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +9 -9
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +5 -8
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +18 -9
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +106 -26
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +3 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +2 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +18 -15
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +21 -23
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +26 -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 +14 -12
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
- package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts +7 -0
- package/dist/types/src/util/query-ast.d.ts.map +1 -0
- 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/signals.d.ts.map +1 -1
- package/dist/types/src/util/sort.d.ts +12 -0
- package/dist/types/src/util/sort.d.ts.map +1 -0
- package/dist/types/src/util/table-buttons.d.ts +20 -20
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts +4 -4
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/src/util/tag.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +67 -76
- package/src/components/Table/ColumnActionsMenu.tsx +17 -12
- package/src/components/Table/ColumnSettings.tsx +8 -9
- package/src/components/Table/DynamicTable.stories.tsx +34 -33
- package/src/components/Table/DynamicTable.tsx +33 -36
- package/src/components/Table/Relations.stories.tsx +196 -48
- package/src/components/Table/RowActionsMenu.tsx +27 -17
- package/src/components/Table/Table.stories.tsx +144 -127
- package/src/components/Table/Table.tsx +43 -403
- package/src/components/Table/TableContent.tsx +421 -0
- package/src/components/Table/TableToolbar.tsx +111 -0
- package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
- package/src/components/TableCellEditor/FormCellEditor.tsx +125 -131
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +32 -55
- package/src/components/TableCellEditor/TableCellEditor.tsx +39 -124
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/index.ts +0 -1
- package/src/components/testing.ts +44 -18
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +8 -10
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +36 -34
- package/src/index.ts +0 -4
- package/src/model/modal-controller.ts +23 -40
- package/src/model/selection-model.ts +75 -44
- package/src/model/table-model.test.ts +93 -56
- package/src/model/table-model.ts +437 -170
- package/src/model/table-presentation.test.ts +67 -56
- package/src/model/table-presentation.ts +45 -41
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +59 -13
- package/src/testing/useTestTableModel.ts +48 -39
- package/src/translations.ts +20 -22
- package/src/types/Table.ts +79 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +43 -31
- package/src/util/index.ts +2 -0
- package/src/util/query-ast.ts +22 -0
- package/src/util/schema.ts +4 -4
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +1 -1
- package/src/util/table-controls.ts +2 -2
- package/dist/lib/browser/chunk-63UUF4IG.mjs +0 -66
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
- package/dist/lib/browser/chunk-GPZSXPSA.mjs +0 -1822
- package/dist/lib/browser/chunk-GPZSXPSA.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-K734DJB7.mjs +0 -67
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XUD6W42C.mjs +0 -1823
- package/dist/lib/node-esm/chunk-XUD6W42C.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/TableToolbar/TableToolbar.d.ts +0 -21
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
- package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
- package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
- package/dist/types/src/model/table-sorting.d.ts +0 -52
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -103
- package/src/components/TableToolbar/TableToolbar.tsx +0 -102
- package/src/components/TableToolbar/index.ts +0 -5
- package/src/model/table-sorting.ts +0 -225
- package/src/types/schema.ts +0 -73
|
@@ -2,41 +2,52 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import { type RefObject, useCallback, useContext, useMemo, useRef } from 'react';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { useClient } from '@dxos/react-client';
|
|
8
|
+
import { type Database, type Type } from '@dxos/echo';
|
|
9
|
+
import { isMutable } from '@dxos/echo/internal';
|
|
10
|
+
import { random } from '@dxos/random';
|
|
11
11
|
import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { useClientStory } from '@dxos/react-client/testing';
|
|
13
|
+
import { useGlobalFilteredObjects } from '@dxos/react-ui-search';
|
|
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';
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
import { useAddRow, useProjectionModel, useTableModel } from '../hooks';
|
|
18
|
+
import { type TableModel, TablePresentation } from '../model';
|
|
19
|
+
import { Table } from '../types';
|
|
20
|
+
|
|
21
|
+
random.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
|
|
22
|
+
|
|
23
|
+
export type TestTableModel<T extends Type.AnyObj = Type.AnyObj> = {
|
|
24
|
+
schema: T | undefined;
|
|
25
|
+
table: Table.Table | undefined;
|
|
26
|
+
projection: ProjectionModel | undefined;
|
|
27
|
+
tableRef: RefObject<TableController | null>;
|
|
28
|
+
model: TableModel | undefined;
|
|
29
|
+
presentation: TablePresentation | undefined;
|
|
30
|
+
db: Database.Database | undefined;
|
|
31
|
+
handleInsertRow: () => void;
|
|
32
|
+
handleSaveView: () => void;
|
|
33
|
+
handleDeleteRows: (rowIndex: number, objects: any[]) => void;
|
|
34
|
+
handleDeleteColumn: (fieldId: string) => void;
|
|
35
|
+
};
|
|
20
36
|
|
|
21
37
|
/**
|
|
22
38
|
* Custom hook to create and manage a test table model for storybook demonstrations.
|
|
23
39
|
* Provides table data, schema, and handlers for table operations.
|
|
24
40
|
*/
|
|
25
|
-
export const useTestTableModel = () => {
|
|
26
|
-
const
|
|
27
|
-
const { space } =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const projection = useMemo(() => {
|
|
36
|
-
if (schema && view?.projection) {
|
|
37
|
-
return new ProjectionModel(toJsonSchema(schema), view.projection);
|
|
38
|
-
}
|
|
39
|
-
}, [schema, view?.projection]);
|
|
41
|
+
export const useTestTableModel = <T extends Type.AnyObj = Type.AnyObj>(): TestTableModel<T> => {
|
|
42
|
+
const registry = useContext(RegistryContext);
|
|
43
|
+
const { space } = useClientStory();
|
|
44
|
+
const db = space?.db;
|
|
45
|
+
|
|
46
|
+
const tables = useQuery(space?.db, Filter.type(Table.Table));
|
|
47
|
+
const table = tables.at(0);
|
|
48
|
+
const typename = table?.view.target?.query ? getTypenameFromQuery(table.view.target.query.ast) : undefined;
|
|
49
|
+
const schema = useSchema<T>(space?.db, typename);
|
|
50
|
+
const projection = useProjectionModel(schema, table, registry);
|
|
40
51
|
|
|
41
52
|
const features = useMemo(
|
|
42
53
|
() => ({
|
|
@@ -47,7 +58,7 @@ export const useTestTableModel = () => {
|
|
|
47
58
|
[schema],
|
|
48
59
|
);
|
|
49
60
|
|
|
50
|
-
const objects = useQuery(
|
|
61
|
+
const objects = useQuery(db, schema ? Filter.type(schema) : Filter.nothing());
|
|
51
62
|
const filteredObjects = useGlobalFilteredObjects(objects);
|
|
52
63
|
|
|
53
64
|
const tableRef = useRef<TableController>(null);
|
|
@@ -59,15 +70,15 @@ export const useTestTableModel = () => {
|
|
|
59
70
|
tableRef.current?.update?.();
|
|
60
71
|
}, []);
|
|
61
72
|
|
|
62
|
-
const addRow = useAddRow({
|
|
73
|
+
const addRow = useAddRow({ db, schema });
|
|
63
74
|
|
|
64
75
|
const handleDeleteRows = useCallback(
|
|
65
76
|
(_: number, objects: any[]) => {
|
|
66
77
|
for (const object of objects) {
|
|
67
|
-
|
|
78
|
+
db?.remove(object);
|
|
68
79
|
}
|
|
69
80
|
},
|
|
70
|
-
[
|
|
81
|
+
[db],
|
|
71
82
|
);
|
|
72
83
|
|
|
73
84
|
const handleDeleteColumn = useCallback(
|
|
@@ -80,14 +91,13 @@ export const useTestTableModel = () => {
|
|
|
80
91
|
);
|
|
81
92
|
|
|
82
93
|
const model = useTableModel({
|
|
83
|
-
|
|
84
|
-
schema: jsonSchema,
|
|
94
|
+
object: table,
|
|
85
95
|
projection,
|
|
86
96
|
features,
|
|
87
97
|
rows: filteredObjects,
|
|
88
98
|
onInsertRow: addRow,
|
|
89
99
|
onDeleteRows: handleDeleteRows,
|
|
90
|
-
|
|
100
|
+
onColumnDelete: handleDeleteColumn,
|
|
91
101
|
onCellUpdate: handleCellUpdate,
|
|
92
102
|
onRowOrderChange: handleRowOrderChange,
|
|
93
103
|
});
|
|
@@ -95,7 +105,7 @@ export const useTestTableModel = () => {
|
|
|
95
105
|
const handleInsertRow = useCallback(() => {
|
|
96
106
|
const insertResult = model?.insertRow();
|
|
97
107
|
tableRef.current?.handleInsertRowResult?.(insertResult);
|
|
98
|
-
}, [model
|
|
108
|
+
}, [model]);
|
|
99
109
|
|
|
100
110
|
const handleSaveView = useCallback(() => {
|
|
101
111
|
model?.saveView();
|
|
@@ -103,19 +113,18 @@ export const useTestTableModel = () => {
|
|
|
103
113
|
|
|
104
114
|
const presentation = useMemo(() => {
|
|
105
115
|
if (model) {
|
|
106
|
-
return new TablePresentation(model);
|
|
116
|
+
return new TablePresentation(registry, model);
|
|
107
117
|
}
|
|
108
|
-
}, [model]);
|
|
118
|
+
}, [registry, model]);
|
|
109
119
|
|
|
110
120
|
return {
|
|
111
121
|
schema,
|
|
112
|
-
|
|
122
|
+
table,
|
|
113
123
|
projection,
|
|
114
124
|
tableRef,
|
|
115
125
|
model,
|
|
116
126
|
presentation,
|
|
117
|
-
|
|
118
|
-
client,
|
|
127
|
+
db,
|
|
119
128
|
handleInsertRow,
|
|
120
129
|
handleSaveView,
|
|
121
130
|
handleDeleteRows,
|
package/src/translations.ts
CHANGED
|
@@ -4,33 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Resource } from '@dxos/react-ui';
|
|
6
6
|
|
|
7
|
-
export const translationKey = 'react-ui-table';
|
|
7
|
+
export const translationKey = '@dxos/react-ui-table';
|
|
8
8
|
|
|
9
9
|
export const translations = [
|
|
10
10
|
{
|
|
11
11
|
'en-US': {
|
|
12
12
|
[translationKey]: {
|
|
13
|
-
'object
|
|
14
|
-
'create
|
|
15
|
-
'table
|
|
16
|
-
'settings
|
|
17
|
-
'table
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'column
|
|
24
|
-
'column
|
|
25
|
-
'column
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'create new object label': 'Create new object “{{text}}”',
|
|
33
|
-
'save view label': 'Save changes to view',
|
|
13
|
+
'object.placeholder': 'New table',
|
|
14
|
+
'create-object.label': 'Create table',
|
|
15
|
+
'table-name.placeholder': 'Table name',
|
|
16
|
+
'settings.title': 'Table settings',
|
|
17
|
+
'table-schema.label': 'Type',
|
|
18
|
+
'continue.label': 'Continue',
|
|
19
|
+
'create-stack-section.label': 'Create table',
|
|
20
|
+
'add-row.label': 'Add row',
|
|
21
|
+
'column-action-sort-descending.menu': 'Sort Descending',
|
|
22
|
+
'column-action-sort-ascending.menu': 'Sort Ascending',
|
|
23
|
+
'column-action-clear-sorting.menu': 'Clear Sorting',
|
|
24
|
+
'column-action-settings.menu': 'Column Settings',
|
|
25
|
+
'column-action-delete.menu': 'Delete Column',
|
|
26
|
+
'delete-row.label': 'Delete row',
|
|
27
|
+
'bulk-delete-row.label': 'Delete selected rows',
|
|
28
|
+
'column-deleted.label': 'Column deleted',
|
|
29
|
+
'new-column-button.label': 'Create column',
|
|
30
|
+
'create-new-object.label': 'Create new object “{{text}}”',
|
|
31
|
+
'save-view.label': 'Save changes to view',
|
|
34
32
|
},
|
|
35
33
|
},
|
|
36
34
|
},
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Match from 'effect/Match';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
8
|
+
|
|
9
|
+
import { Annotation, JsonSchema, Obj, Ref, Type } from '@dxos/echo';
|
|
10
|
+
import { View } from '@dxos/echo';
|
|
11
|
+
import { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';
|
|
12
|
+
import { ViewAnnotation } from '@dxos/schema';
|
|
13
|
+
|
|
14
|
+
export const Table = Schema.Struct({
|
|
15
|
+
name: Schema.String.pipe(Schema.optional),
|
|
16
|
+
|
|
17
|
+
view: Ref.Ref(View.View).pipe(FormInputAnnotation.set(false)),
|
|
18
|
+
|
|
19
|
+
sizes: Schema.Record({
|
|
20
|
+
// TODO(wittjosiah): Should be JsonPath.
|
|
21
|
+
key: Schema.String,
|
|
22
|
+
value: Schema.Number,
|
|
23
|
+
}).pipe(Schema.mutable, FormInputAnnotation.set(false)),
|
|
24
|
+
}).pipe(
|
|
25
|
+
Type.object({
|
|
26
|
+
typename: 'org.dxos.type.table',
|
|
27
|
+
version: '0.1.0',
|
|
28
|
+
}),
|
|
29
|
+
LabelAnnotation.set(['name']),
|
|
30
|
+
ViewAnnotation.set(['view']),
|
|
31
|
+
Annotation.IconAnnotation.set({
|
|
32
|
+
icon: 'ph--table--regular',
|
|
33
|
+
hue: 'green',
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export interface Table extends Schema.Schema.Type<typeof Table> {}
|
|
38
|
+
|
|
39
|
+
type MakeProps = {
|
|
40
|
+
name?: string;
|
|
41
|
+
sizes?: Record<string, number>;
|
|
42
|
+
view: View.View;
|
|
43
|
+
/** Required to auto-size columns. */
|
|
44
|
+
jsonSchema?: JsonSchemaType;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Make a table as a view of a data set.
|
|
49
|
+
*/
|
|
50
|
+
export const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table => {
|
|
51
|
+
const table = Obj.make(Table, { name, view: Ref.make(view), sizes } as Obj.MakeProps<typeof Table>);
|
|
52
|
+
|
|
53
|
+
// Preset sizes.
|
|
54
|
+
if (jsonSchema) {
|
|
55
|
+
const schema = JsonSchema.toEffectSchema(jsonSchema);
|
|
56
|
+
const properties = SchemaAST.getPropertySignatures(schema.ast);
|
|
57
|
+
for (const property of properties) {
|
|
58
|
+
const name = property.name.toString() as JsonPath;
|
|
59
|
+
if (sizes?.[name]) {
|
|
60
|
+
table.sizes[name] = sizes[name];
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Match.type<SchemaAST.AST>().pipe(
|
|
65
|
+
Match.when({ _tag: 'BooleanKeyword' }, () => {
|
|
66
|
+
table.sizes[name] = 100;
|
|
67
|
+
}),
|
|
68
|
+
Match.when({ _tag: 'NumberKeyword' }, () => {
|
|
69
|
+
table.sizes[name] = 100;
|
|
70
|
+
}),
|
|
71
|
+
Match.orElse(() => {
|
|
72
|
+
// Noop.
|
|
73
|
+
}),
|
|
74
|
+
)(property.type);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return table;
|
|
79
|
+
};
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { Format } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { type TablePropertyDefinition, getBaseSchema, makeDynamicTable } from './dynamic-table';
|
|
11
|
+
|
|
12
|
+
describe('makeDynamicTable', () => {
|
|
13
|
+
/**
|
|
14
|
+
* Base case: plain jsonSchema (not from Echo / JsonSchema.toJsonSchema). Does not exercise the path
|
|
15
|
+
* where projection or schema are reactive, so this does not reproduce the Obj.update regression.
|
|
16
|
+
* Confirms makeDynamicTable + setProperties (title) works with plain objects.
|
|
17
|
+
*/
|
|
18
|
+
test('makeDynamicTable with plain jsonSchema and properties with title does not throw', () => {
|
|
19
|
+
const registry = Registry.make();
|
|
20
|
+
const jsonSchema = {
|
|
21
|
+
type: 'object' as const,
|
|
22
|
+
properties: {
|
|
23
|
+
id: { type: 'string' as const },
|
|
24
|
+
name: { type: 'string' as const },
|
|
25
|
+
},
|
|
26
|
+
required: ['id'],
|
|
27
|
+
};
|
|
28
|
+
const properties: TablePropertyDefinition[] = [
|
|
29
|
+
{ name: 'id', format: Format.TypeFormat.String },
|
|
30
|
+
{ name: 'name', format: Format.TypeFormat.String, title: 'Name' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
expect(() => {
|
|
34
|
+
const { projection, object } = makeDynamicTable({
|
|
35
|
+
registry,
|
|
36
|
+
jsonSchema,
|
|
37
|
+
properties,
|
|
38
|
+
});
|
|
39
|
+
expect(projection).toBeDefined();
|
|
40
|
+
expect(object).toBeDefined();
|
|
41
|
+
expect(projection.getFields().length).toBeGreaterThan(0);
|
|
42
|
+
}).not.toThrow();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Regression test for the path where jsonSchema comes from getBaseSchema(typename, properties).
|
|
47
|
+
* That path uses Echo (ViewModel.make, JsonSchema.toJsonSchema); mutations must run inside Obj.update and on a
|
|
48
|
+
* cloned schema. This test ensures that flow does not throw.
|
|
49
|
+
*/
|
|
50
|
+
test('makeDynamicTable with jsonSchema from getBaseSchema(typename, properties) and properties with title does not throw', () => {
|
|
51
|
+
const registry = Registry.make();
|
|
52
|
+
const typename = 'com.example.type.dynamicTableTest';
|
|
53
|
+
const properties: TablePropertyDefinition[] = [
|
|
54
|
+
{ name: 'id', format: Format.TypeFormat.String },
|
|
55
|
+
{ name: 'name', format: Format.TypeFormat.String, title: 'Name' },
|
|
56
|
+
];
|
|
57
|
+
const { jsonSchema } = getBaseSchema({ typename, properties });
|
|
58
|
+
|
|
59
|
+
expect(() => {
|
|
60
|
+
const { projection, object } = makeDynamicTable({
|
|
61
|
+
registry,
|
|
62
|
+
jsonSchema,
|
|
63
|
+
properties,
|
|
64
|
+
});
|
|
65
|
+
expect(projection).toBeDefined();
|
|
66
|
+
expect(object).toBeDefined();
|
|
67
|
+
expect(projection.getFields().length).toBeGreaterThan(0);
|
|
68
|
+
}).not.toThrow();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type
|
|
5
|
+
import { type Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import type * as Types from 'effect/Types';
|
|
6
7
|
|
|
7
|
-
import { Obj } from '@dxos/echo';
|
|
8
|
-
import {
|
|
9
|
-
import type { JsonSchemaType } from '@dxos/echo-schema';
|
|
8
|
+
import { Filter, JsonSchema, Obj, Order, Query, type QueryAST, Ref, Type } from '@dxos/echo';
|
|
9
|
+
import { type View } from '@dxos/echo';
|
|
10
10
|
import {
|
|
11
|
-
type DataType,
|
|
12
11
|
ProjectionModel,
|
|
13
12
|
type SchemaPropertyDefinition,
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
ViewModel,
|
|
14
|
+
createEchoChangeCallback,
|
|
16
15
|
getSchemaFromPropertyDefinitions,
|
|
17
16
|
} from '@dxos/schema';
|
|
18
17
|
|
|
@@ -22,7 +21,7 @@ import { Table } from '../types';
|
|
|
22
21
|
type PropertyDisplayProps = {
|
|
23
22
|
size: number;
|
|
24
23
|
title: string;
|
|
25
|
-
sort:
|
|
24
|
+
sort: QueryAST.OrderDirection;
|
|
26
25
|
};
|
|
27
26
|
|
|
28
27
|
export type TablePropertyDefinition = SchemaPropertyDefinition & Partial<PropertyDisplayProps>;
|
|
@@ -32,21 +31,21 @@ export type TablePropertyDefinition = SchemaPropertyDefinition & Partial<Propert
|
|
|
32
31
|
*/
|
|
33
32
|
// TODO(burdon): Remove variance.
|
|
34
33
|
export const getBaseSchema = ({
|
|
34
|
+
schema,
|
|
35
35
|
typename,
|
|
36
36
|
properties,
|
|
37
37
|
jsonSchema,
|
|
38
|
-
schema,
|
|
39
38
|
}: {
|
|
39
|
+
schema?: Type.AnyEntity;
|
|
40
40
|
typename?: string;
|
|
41
41
|
properties?: TablePropertyDefinition[];
|
|
42
|
-
jsonSchema?:
|
|
43
|
-
|
|
44
|
-
}): { typename: string; jsonSchema: JsonSchemaType } => {
|
|
42
|
+
jsonSchema?: Types.DeepMutable<JsonSchema.JsonSchema>;
|
|
43
|
+
}): { typename: string; jsonSchema: Types.DeepMutable<JsonSchema.JsonSchema> } => {
|
|
45
44
|
if (typename && properties) {
|
|
46
45
|
const schema = getSchemaFromPropertyDefinitions(typename, properties);
|
|
47
|
-
return { typename: schema.typename, jsonSchema: schema
|
|
46
|
+
return { typename: schema.typename, jsonSchema: JsonSchema.toJsonSchema(schema) };
|
|
48
47
|
} else if (schema) {
|
|
49
|
-
return { typename: getTypename(schema)!, jsonSchema: toJsonSchema(schema) };
|
|
48
|
+
return { typename: Type.getTypename(schema)!, jsonSchema: JsonSchema.toJsonSchema(schema) };
|
|
50
49
|
} else if (typename && jsonSchema) {
|
|
51
50
|
return { typename, jsonSchema };
|
|
52
51
|
} else {
|
|
@@ -55,41 +54,48 @@ export const getBaseSchema = ({
|
|
|
55
54
|
};
|
|
56
55
|
|
|
57
56
|
export const makeDynamicTable = ({
|
|
58
|
-
|
|
57
|
+
registry,
|
|
59
58
|
jsonSchema,
|
|
60
59
|
properties,
|
|
61
60
|
}: {
|
|
62
|
-
|
|
63
|
-
jsonSchema:
|
|
61
|
+
registry: Registry.Registry;
|
|
62
|
+
jsonSchema: Types.DeepMutable<JsonSchema.JsonSchema>;
|
|
64
63
|
properties?: TablePropertyDefinition[];
|
|
65
|
-
}): { projection: ProjectionModel;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
typename,
|
|
64
|
+
}): { projection: ProjectionModel; object: Table.Table } => {
|
|
65
|
+
const view = ViewModel.make({
|
|
66
|
+
query: Query.select(Filter.everything()),
|
|
69
67
|
jsonSchema,
|
|
70
|
-
presentation: table,
|
|
71
68
|
...(properties && { fields: properties.map((property) => property.name) }),
|
|
72
69
|
});
|
|
70
|
+
const object = Obj.make(Table.Table, { view: Ref.make(view), sizes: {} });
|
|
73
71
|
|
|
74
|
-
const projection = new ProjectionModel(
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
const projection = new ProjectionModel({
|
|
73
|
+
registry,
|
|
74
|
+
view,
|
|
75
|
+
baseSchema: jsonSchema,
|
|
76
|
+
change: createEchoChangeCallback(view, jsonSchema),
|
|
77
|
+
});
|
|
78
|
+
projection.normalizeView();
|
|
79
|
+
if (properties && projection.getFields()) {
|
|
80
|
+
setProperties(view, projection, object, properties);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
return { projection,
|
|
83
|
+
return { projection, object };
|
|
80
84
|
};
|
|
81
85
|
|
|
82
86
|
const setProperties = (
|
|
83
|
-
view:
|
|
87
|
+
view: View.View,
|
|
84
88
|
projection: ProjectionModel,
|
|
85
89
|
table: Table.Table,
|
|
86
90
|
properties: TablePropertyDefinition[],
|
|
87
91
|
) => {
|
|
88
92
|
for (const property of properties) {
|
|
89
|
-
const field = projection.
|
|
93
|
+
const field = projection.getFields().find((field) => field.path === property.name);
|
|
90
94
|
if (field) {
|
|
91
95
|
if (property.size !== undefined) {
|
|
92
|
-
table
|
|
96
|
+
Obj.update(table, (table) => {
|
|
97
|
+
table.sizes[field.path] = property.size!;
|
|
98
|
+
});
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
if (property.title !== undefined) {
|
|
@@ -101,8 +107,14 @@ const setProperties = (
|
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
if (property.sort) {
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
// Apply sort to query instead of deprecated view.sort
|
|
111
|
+
const currentQuery = Query.fromAst(Obj.getSnapshot(view).query.ast);
|
|
112
|
+
// Use any type parameter since we're working with dynamic field paths
|
|
113
|
+
const newQuery = currentQuery.orderBy(Order.property<any>(field.path as string, property.sort));
|
|
114
|
+
Obj.update(view, (view) => {
|
|
115
|
+
// Type assertion needed because Query AST types have some variance issues.
|
|
116
|
+
view.query.ast = newQuery.ast as typeof view.query.ast;
|
|
117
|
+
});
|
|
106
118
|
}
|
|
107
119
|
}
|
|
108
120
|
}
|
package/src/util/index.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type QueryAST } from '@dxos/echo';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extracts order clauses from a query AST.
|
|
9
|
+
* Handles nested structures (options, order, etc.).
|
|
10
|
+
*/
|
|
11
|
+
export const extractOrder = (queryAst: QueryAST.Query): readonly QueryAST.Order[] | undefined => {
|
|
12
|
+
if (queryAst.type === 'order') {
|
|
13
|
+
return queryAst.order;
|
|
14
|
+
}
|
|
15
|
+
if (queryAst.type === 'options') {
|
|
16
|
+
return extractOrder(queryAst.query);
|
|
17
|
+
}
|
|
18
|
+
if (queryAst.type === 'from') {
|
|
19
|
+
return extractOrder(queryAst.query);
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
};
|
package/src/util/schema.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Creates a narrowed schema from an original schema that only includes
|
|
@@ -20,14 +20,14 @@ export const narrowSchema = <S extends Schema.Schema.AnyNoContext>(
|
|
|
20
20
|
): Schema.Schema<unknown, unknown> | undefined => {
|
|
21
21
|
const ast = (schema as any)?.ast;
|
|
22
22
|
|
|
23
|
-
if (isTypeLiteral(ast)) {
|
|
23
|
+
if (SchemaAST.isTypeLiteral(ast)) {
|
|
24
24
|
// Filter property signatures that match any of the provided paths
|
|
25
25
|
const propertySignatures = ast.propertySignatures.filter((signature) => paths.includes(signature.name.toString()));
|
|
26
26
|
|
|
27
27
|
// If we found at least one matching property
|
|
28
28
|
if (propertySignatures.length > 0) {
|
|
29
29
|
// Create a new TypeLiteral with only the matching properties
|
|
30
|
-
const narrowType = new TypeLiteral(propertySignatures, []);
|
|
30
|
+
const narrowType = new SchemaAST.TypeLiteral(propertySignatures, []);
|
|
31
31
|
return Schema.make(narrowType);
|
|
32
32
|
}
|
|
33
33
|
}
|
package/src/util/sort.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Format, type QueryAST } from '@dxos/echo';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Gets a normalized sort value for a given value and format.
|
|
9
|
+
* This ensures consistent sorting behavior across different data types.
|
|
10
|
+
*/
|
|
11
|
+
export const getSortValue = (value: any, format: Format.TypeFormat): any => {
|
|
12
|
+
// Handle null/undefined
|
|
13
|
+
if (value === undefined || value === null) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Handle dates chronologically if schema indicates date format
|
|
18
|
+
const isDateFormat = format === Format.TypeFormat.DateTime || format === Format.TypeFormat.Date;
|
|
19
|
+
if (isDateFormat) {
|
|
20
|
+
const date = new Date(value);
|
|
21
|
+
if (!isNaN(date.getTime())) {
|
|
22
|
+
return date.getTime();
|
|
23
|
+
}
|
|
24
|
+
// Fallback to string if date parsing fails
|
|
25
|
+
return String(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Handle numbers if schema indicates number format
|
|
29
|
+
if (
|
|
30
|
+
format === Format.TypeFormat.Number ||
|
|
31
|
+
format === Format.TypeFormat.Integer ||
|
|
32
|
+
format === Format.TypeFormat.Currency ||
|
|
33
|
+
format === Format.TypeFormat.Percent ||
|
|
34
|
+
format === Format.TypeFormat.Duration
|
|
35
|
+
) {
|
|
36
|
+
const num = typeof value === 'number' ? value : Number(value);
|
|
37
|
+
if (!isNaN(num)) {
|
|
38
|
+
return num;
|
|
39
|
+
}
|
|
40
|
+
// Fallback to string if number parsing fails
|
|
41
|
+
return String(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Default to string comparison
|
|
45
|
+
if (typeof value === 'string') {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
if (typeof value === 'number') {
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fallback to string representation
|
|
53
|
+
return String(value);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compares two values for sorting based on the field format.
|
|
58
|
+
* Handles dates, numbers, and strings appropriately.
|
|
59
|
+
*/
|
|
60
|
+
export const compareValues = (
|
|
61
|
+
aValue: any,
|
|
62
|
+
bValue: any,
|
|
63
|
+
format: Format.TypeFormat,
|
|
64
|
+
direction: QueryAST.OrderDirection,
|
|
65
|
+
): number => {
|
|
66
|
+
// Handle null/undefined values - nulls sort to the end
|
|
67
|
+
if (aValue === undefined || aValue === null) {
|
|
68
|
+
return bValue === undefined || bValue === null ? 0 : 1;
|
|
69
|
+
}
|
|
70
|
+
if (bValue === undefined || bValue === null) {
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get normalized sort values
|
|
75
|
+
const aSortValue = getSortValue(aValue, format);
|
|
76
|
+
const bSortValue = getSortValue(bValue, format);
|
|
77
|
+
|
|
78
|
+
// Compare based on type
|
|
79
|
+
let comparison = 0;
|
|
80
|
+
if (typeof aSortValue === 'number' && typeof bSortValue === 'number') {
|
|
81
|
+
comparison = aSortValue - bSortValue;
|
|
82
|
+
} else if (typeof aSortValue === 'string' && typeof bSortValue === 'string') {
|
|
83
|
+
comparison = aSortValue.localeCompare(bSortValue);
|
|
84
|
+
} else {
|
|
85
|
+
// Mixed types - convert both to strings
|
|
86
|
+
comparison = String(aSortValue).localeCompare(String(bSortValue));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return direction === 'desc' ? -comparison : comparison;
|
|
90
|
+
};
|
|
@@ -39,7 +39,7 @@ const createButton = ({
|
|
|
39
39
|
.map(([k, v]) => `${k}="${v}"`)
|
|
40
40
|
.join(' ');
|
|
41
41
|
|
|
42
|
-
return `<button ${attr} data-testid="${testId}" class="dx-button
|
|
42
|
+
return `<button ${attr} data-testid="${testId}" class="dx-button w-6 aspect-square px-0.5 min-h-0" ${dataAttrs} ${disabled ? 'disabled' : ''} data-dx-grid-action="accessory"><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const addColumnButton = {
|