@dxos/react-ui-table 0.8.4-main.21d9917 → 0.8.4-main.2244d791bb
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-MNZKOFMA.mjs → chunk-GTGYBRJA.mjs} +12 -10
- package/dist/lib/browser/{chunk-MNZKOFMA.mjs.map → chunk-GTGYBRJA.mjs.map} +3 -3
- package/dist/lib/browser/index.mjs +4 -4
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +2 -2
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/node-esm/{chunk-2ATAIIAO.mjs → chunk-DPNSZHU5.mjs} +12 -10
- package/dist/lib/node-esm/{chunk-2ATAIIAO.mjs.map → chunk-DPNSZHU5.mjs.map} +3 -3
- package/dist/lib/node-esm/index.mjs +4 -4
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +2 -2
- package/dist/lib/node-esm/testing/index.mjs.map +2 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts +3 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +1 -1
- package/dist/types/src/model/table-model.d.ts +3 -3
- 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/util/dynamic-table.d.ts +4 -3
- 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/tsconfig.tsbuildinfo +1 -1
- package/package.json +41 -41
- package/src/components/Table/DynamicTable.stories.tsx +5 -4
- package/src/components/Table/DynamicTable.tsx +3 -2
- package/src/components/Table/Relations.stories.tsx +1 -1
- package/src/components/Table/Table.stories.tsx +20 -7
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +1 -1
- package/src/hooks/useProjectionModel.ts +4 -5
- package/src/hooks/useTableModel.ts +1 -1
- package/src/model/table-model.test.ts +1 -1
- package/src/model/table-model.ts +7 -7
- package/src/model/table-presentation.test.ts +1 -1
- package/src/model/table-presentation.ts +1 -1
- package/src/playwright/TableManager.ts +10 -3
- package/src/playwright/smoke.spec.ts +45 -2
- package/src/testing/useTestTableModel.ts +1 -1
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +7 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-table",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.2244d791bb",
|
|
4
4
|
"description": "A table component.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@codemirror/autocomplete": "^6.19.0",
|
|
61
61
|
"@codemirror/state": "^6.5.2",
|
|
62
62
|
"@codemirror/view": "^6.38.4",
|
|
63
|
-
"@effect-atom/atom-react": "^0.
|
|
63
|
+
"@effect-atom/atom-react": "^0.5.0",
|
|
64
64
|
"@fluentui/react-tabster": "9.26.11",
|
|
65
65
|
"@radix-ui/react-context": "1.1.1",
|
|
66
66
|
"@radix-ui/react-primitive": "2.0.2",
|
|
@@ -68,58 +68,58 @@
|
|
|
68
68
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
69
69
|
"date-fns": "^3.3.1",
|
|
70
70
|
"lodash.orderby": "^4.6.0",
|
|
71
|
-
"@dxos/
|
|
72
|
-
"@dxos/debug": "0.8.4-main.
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/echo
|
|
75
|
-
"@dxos/echo": "0.8.4-main.
|
|
76
|
-
"@dxos/effect": "0.8.4-main.
|
|
77
|
-
"@dxos/invariant": "0.8.4-main.
|
|
78
|
-
"@dxos/keys": "0.8.4-main.
|
|
79
|
-
"@dxos/lit-grid": "0.8.4-main.
|
|
80
|
-
"@dxos/
|
|
81
|
-
"@dxos/
|
|
82
|
-
"@dxos/react-client": "0.8.4-main.
|
|
83
|
-
"@dxos/react-ui-attention": "0.8.4-main.
|
|
84
|
-
"@dxos/react-ui-
|
|
85
|
-
"@dxos/react-ui-form": "0.8.4-main.
|
|
86
|
-
"@dxos/react-ui-grid": "0.8.4-main.
|
|
87
|
-
"@dxos/react-ui-
|
|
88
|
-
"@dxos/react-ui-
|
|
89
|
-
"@dxos/
|
|
90
|
-
"@dxos/react-ui-
|
|
91
|
-
"@dxos/
|
|
92
|
-
"@dxos/
|
|
93
|
-
"@dxos/
|
|
94
|
-
"@dxos/ui-types": "0.8.4-main.
|
|
95
|
-
"@dxos/
|
|
71
|
+
"@dxos/async": "0.8.4-main.2244d791bb",
|
|
72
|
+
"@dxos/debug": "0.8.4-main.2244d791bb",
|
|
73
|
+
"@dxos/context": "0.8.4-main.2244d791bb",
|
|
74
|
+
"@dxos/echo": "0.8.4-main.2244d791bb",
|
|
75
|
+
"@dxos/echo-atom": "0.8.4-main.2244d791bb",
|
|
76
|
+
"@dxos/effect": "0.8.4-main.2244d791bb",
|
|
77
|
+
"@dxos/invariant": "0.8.4-main.2244d791bb",
|
|
78
|
+
"@dxos/keys": "0.8.4-main.2244d791bb",
|
|
79
|
+
"@dxos/lit-grid": "0.8.4-main.2244d791bb",
|
|
80
|
+
"@dxos/log": "0.8.4-main.2244d791bb",
|
|
81
|
+
"@dxos/random": "0.8.4-main.2244d791bb",
|
|
82
|
+
"@dxos/react-client": "0.8.4-main.2244d791bb",
|
|
83
|
+
"@dxos/react-ui-attention": "0.8.4-main.2244d791bb",
|
|
84
|
+
"@dxos/react-ui-editor": "0.8.4-main.2244d791bb",
|
|
85
|
+
"@dxos/react-ui-form": "0.8.4-main.2244d791bb",
|
|
86
|
+
"@dxos/react-ui-grid": "0.8.4-main.2244d791bb",
|
|
87
|
+
"@dxos/react-ui-searchlist": "0.8.4-main.2244d791bb",
|
|
88
|
+
"@dxos/react-ui-menu": "0.8.4-main.2244d791bb",
|
|
89
|
+
"@dxos/react-ui-components": "0.8.4-main.2244d791bb",
|
|
90
|
+
"@dxos/react-ui-stack": "0.8.4-main.2244d791bb",
|
|
91
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.2244d791bb",
|
|
92
|
+
"@dxos/types": "0.8.4-main.2244d791bb",
|
|
93
|
+
"@dxos/schema": "0.8.4-main.2244d791bb",
|
|
94
|
+
"@dxos/ui-types": "0.8.4-main.2244d791bb",
|
|
95
|
+
"@dxos/util": "0.8.4-main.2244d791bb"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
|
-
"@effect/platform": "0.
|
|
98
|
+
"@effect/platform": "0.94.4",
|
|
99
99
|
"@types/lodash.orderby": "^4.6.9",
|
|
100
100
|
"@types/react": "~19.2.7",
|
|
101
101
|
"@types/react-dom": "~19.2.3",
|
|
102
|
-
"effect": "3.19.
|
|
102
|
+
"effect": "3.19.16",
|
|
103
103
|
"fast-check": "^3.19.0",
|
|
104
104
|
"react": "~19.2.3",
|
|
105
105
|
"react-dom": "~19.2.3",
|
|
106
106
|
"vite": "7.1.9",
|
|
107
|
-
"@dxos/lit-ui": "0.8.4-main.
|
|
108
|
-
"@dxos/random": "0.8.4-main.
|
|
109
|
-
"@dxos/react-client": "0.8.4-main.
|
|
110
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
111
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
|
112
|
-
"@dxos/
|
|
113
|
-
"@dxos/
|
|
107
|
+
"@dxos/lit-ui": "0.8.4-main.2244d791bb",
|
|
108
|
+
"@dxos/random": "0.8.4-main.2244d791bb",
|
|
109
|
+
"@dxos/react-client": "0.8.4-main.2244d791bb",
|
|
110
|
+
"@dxos/react-ui": "0.8.4-main.2244d791bb",
|
|
111
|
+
"@dxos/storybook-utils": "0.8.4-main.2244d791bb",
|
|
112
|
+
"@dxos/test-utils": "0.8.4-main.2244d791bb",
|
|
113
|
+
"@dxos/ui-theme": "0.8.4-main.2244d791bb"
|
|
114
114
|
},
|
|
115
115
|
"peerDependencies": {
|
|
116
|
-
"@effect-atom/atom-react": "^0.
|
|
117
|
-
"@effect/platform": "0.
|
|
118
|
-
"effect": "3.19.
|
|
116
|
+
"@effect-atom/atom-react": "^0.5.0",
|
|
117
|
+
"@effect/platform": "0.94.4",
|
|
118
|
+
"effect": "3.19.16",
|
|
119
119
|
"react": "~19.2.3",
|
|
120
120
|
"react-dom": "~19.2.3",
|
|
121
|
-
"@dxos/ui
|
|
122
|
-
"@dxos/
|
|
121
|
+
"@dxos/react-ui": "0.8.4-main.2244d791bb",
|
|
122
|
+
"@dxos/ui-theme": "0.8.4-main.2244d791bb"
|
|
123
123
|
},
|
|
124
124
|
"publishConfig": {
|
|
125
125
|
"access": "public"
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import type * as Types from 'effect/Types';
|
|
6
7
|
import React, { useMemo, useState } from 'react';
|
|
7
8
|
|
|
8
|
-
import { Obj } from '@dxos/echo';
|
|
9
|
-
import { Format
|
|
9
|
+
import { type JsonSchema, Obj } from '@dxos/echo';
|
|
10
|
+
import { Format } from '@dxos/echo/internal';
|
|
10
11
|
import { faker } from '@dxos/random';
|
|
11
12
|
import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
|
|
12
13
|
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
@@ -58,7 +59,7 @@ const DynamicTableStory = () => {
|
|
|
58
59
|
const meta = {
|
|
59
60
|
title: 'ui/react-ui-table/DynamicTable',
|
|
60
61
|
component: DynamicTable,
|
|
61
|
-
decorators: [withTheme, withRegistry],
|
|
62
|
+
decorators: [withTheme(), withRegistry],
|
|
62
63
|
parameters: {
|
|
63
64
|
layout: 'fullscreen',
|
|
64
65
|
translations,
|
|
@@ -101,7 +102,7 @@ export const WithClickToSelect: StoryObj = {
|
|
|
101
102
|
|
|
102
103
|
export const WithJsonSchema: StoryObj = {
|
|
103
104
|
render: () => {
|
|
104
|
-
const schema = useMemo<
|
|
105
|
+
const schema = useMemo<Types.DeepMutable<JsonSchema.JsonSchema>>(
|
|
105
106
|
() => ({
|
|
106
107
|
type: 'object',
|
|
107
108
|
properties: {
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import type * as Types from 'effect/Types';
|
|
6
7
|
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
8
|
|
|
8
9
|
import { type Type } from '@dxos/echo';
|
|
9
|
-
import { type
|
|
10
|
+
import { type JsonSchema } from '@dxos/echo';
|
|
10
11
|
import { type ThemedClassName, useDefaultValue } from '@dxos/react-ui';
|
|
11
12
|
import { type ProjectionModel } from '@dxos/schema';
|
|
12
13
|
import { mx } from '@dxos/ui-theme';
|
|
@@ -23,7 +24,7 @@ export type DynamicTableProps<T extends Type.Entity.Any = Type.Entity.Any> = The
|
|
|
23
24
|
name?: string; // TODO(burdon): Remove?
|
|
24
25
|
rows: any[];
|
|
25
26
|
properties?: TablePropertyDefinition[];
|
|
26
|
-
jsonSchema?:
|
|
27
|
+
jsonSchema?: Types.DeepMutable<JsonSchema.JsonSchema>;
|
|
27
28
|
features?: Partial<TableFeatures>;
|
|
28
29
|
rowActions?: TableRowAction[];
|
|
29
30
|
onRowClick?: (row: any) => void;
|
|
@@ -125,7 +125,7 @@ const meta = {
|
|
|
125
125
|
title: 'ui/react-ui-table/Relations',
|
|
126
126
|
render: DefaultStory,
|
|
127
127
|
decorators: [
|
|
128
|
-
withTheme,
|
|
128
|
+
withTheme(),
|
|
129
129
|
withRegistry,
|
|
130
130
|
// TODO(thure): Shouldn't `layout: 'fullscreen'` below make this unnecessary?
|
|
131
131
|
withLayout({ classNames: 'fixed inset-0' }),
|
|
@@ -12,6 +12,7 @@ import { invariant } from '@dxos/invariant';
|
|
|
12
12
|
import { faker } from '@dxos/random';
|
|
13
13
|
import { PublicKey } from '@dxos/react-client';
|
|
14
14
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
15
|
+
import { ScrollArea } from '@dxos/react-ui';
|
|
15
16
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
16
17
|
import { ViewEditor, translations as formTranslations } from '@dxos/react-ui-form';
|
|
17
18
|
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
@@ -103,6 +104,15 @@ const DefaultStory = () => {
|
|
|
103
104
|
const { db, schema, table, tableRef, model, presentation, handleInsertRow, handleSaveView, handleDeleteColumn } =
|
|
104
105
|
useTestTableModel();
|
|
105
106
|
|
|
107
|
+
const handleRowClick = useCallback(
|
|
108
|
+
(row: any) => {
|
|
109
|
+
if (model?.getDraftRowCount() === 0 && ['frozenRowsEnd', 'fixedEndStart', 'fixedEndEnd'].includes(row?.plane)) {
|
|
110
|
+
handleInsertRow();
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
[model, handleInsertRow],
|
|
114
|
+
);
|
|
115
|
+
|
|
106
116
|
if (!schema || !table?.view.target) {
|
|
107
117
|
return <div />;
|
|
108
118
|
}
|
|
@@ -117,16 +127,19 @@ const DefaultStory = () => {
|
|
|
117
127
|
schema={schema}
|
|
118
128
|
model={model}
|
|
119
129
|
presentation={presentation}
|
|
130
|
+
onRowClick={handleRowClick}
|
|
120
131
|
ignoreAttention
|
|
121
132
|
/>
|
|
122
133
|
</TableComponent.Root>
|
|
123
134
|
</div>
|
|
124
|
-
<
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
<ScrollArea.Root orientation='vertical' classNames='border-l border-separator'>
|
|
136
|
+
<ScrollArea.Viewport>
|
|
137
|
+
<StoryViewEditor view={table.view.target} schema={schema} db={db} handleDeleteColumn={handleDeleteColumn} />
|
|
138
|
+
<SyntaxHighlighter language='json' className='text-xs'>
|
|
139
|
+
{JSON.stringify({ view: table.view.target, schema }, null, 2)}
|
|
140
|
+
</SyntaxHighlighter>
|
|
141
|
+
</ScrollArea.Viewport>
|
|
142
|
+
</ScrollArea.Root>
|
|
130
143
|
</div>
|
|
131
144
|
);
|
|
132
145
|
};
|
|
@@ -143,7 +156,7 @@ const meta = {
|
|
|
143
156
|
title: 'ui/react-ui-table/Table',
|
|
144
157
|
render: DefaultStory,
|
|
145
158
|
decorators: [
|
|
146
|
-
withTheme,
|
|
159
|
+
withTheme(),
|
|
147
160
|
withRegistry,
|
|
148
161
|
withClientProvider({
|
|
149
162
|
types: [View.View, Table.Table],
|
|
@@ -7,7 +7,7 @@ import { useState } from 'react';
|
|
|
7
7
|
|
|
8
8
|
import { Type } from '@dxos/echo';
|
|
9
9
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
10
|
-
import { ProjectionModel,
|
|
10
|
+
import { ProjectionModel, createEchoChangeCallback } from '@dxos/schema';
|
|
11
11
|
|
|
12
12
|
import { type Table } from '../types';
|
|
13
13
|
|
|
@@ -25,10 +25,9 @@ export const useProjectionModel = <S extends Type.Entity.Any>(
|
|
|
25
25
|
// For immutable schemas, create a snapshot.
|
|
26
26
|
const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : Type.toJsonSchema(schema);
|
|
27
27
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
: createDirectChangeCallback(view.projection, jsonSchema);
|
|
28
|
+
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
29
|
+
// Pass schema only when mutable to allow schema mutations.
|
|
30
|
+
const change = createEchoChangeCallback(view, Type.isMutable(schema) ? schema : undefined);
|
|
32
31
|
|
|
33
32
|
const projection = new ProjectionModel({ registry, view, baseSchema: jsonSchema, change });
|
|
34
33
|
projection.normalizeView();
|
|
@@ -29,7 +29,7 @@ export type UseTableModelProps<T extends TableRow = TableRow> = {
|
|
|
29
29
|
onRowAction?: (actionId: string, data: T) => void;
|
|
30
30
|
} & Pick<
|
|
31
31
|
TableModelProps<T>,
|
|
32
|
-
'features' | 'onInsertRow' | 'onDeleteRows' | '
|
|
32
|
+
'features' | 'onInsertRow' | 'onDeleteRows' | 'onColumnDelete' | 'onCellUpdate' | 'onRowOrderChange'
|
|
33
33
|
>;
|
|
34
34
|
|
|
35
35
|
export const useTableModel = <T extends TableRow = TableRow>({
|
|
@@ -137,7 +137,7 @@ const createTableModel = (registry: Registry.Registry, props: Partial<TableModel
|
|
|
137
137
|
registry,
|
|
138
138
|
view,
|
|
139
139
|
baseSchema: schema.jsonSchema,
|
|
140
|
-
change: createDirectChangeCallback(view.projection, schema
|
|
140
|
+
change: createDirectChangeCallback(view.projection, Type.toJsonSchema(schema)),
|
|
141
141
|
});
|
|
142
142
|
projection.normalizeView();
|
|
143
143
|
return new TableModel({
|
package/src/model/table-model.ts
CHANGED
|
@@ -122,7 +122,7 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
122
122
|
onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
|
|
123
123
|
onInsertRow?: (data?: any) => InsertRowResult;
|
|
124
124
|
onDeleteRows?: (index: number, obj: T[]) => void;
|
|
125
|
-
|
|
125
|
+
onColumnDelete?: (fieldId: string) => void;
|
|
126
126
|
onCellUpdate?: (cell: DxGridPosition) => void;
|
|
127
127
|
onRowOrderChange?: () => void;
|
|
128
128
|
onRowAction?: (actionId: string, data: T) => void;
|
|
@@ -139,7 +139,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
139
139
|
|
|
140
140
|
private readonly _onInsertRow?: (data?: any) => InsertRowResult;
|
|
141
141
|
private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
|
|
142
|
-
private readonly
|
|
142
|
+
private readonly _onColumnDelete?: TableModelProps<T>['onColumnDelete'];
|
|
143
143
|
private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
|
|
144
144
|
private readonly _onRowOrderChange?: TableModelProps<T>['onRowOrderChange'];
|
|
145
145
|
private readonly _onRowAction?: TableModelProps<T>['onRowAction'];
|
|
@@ -176,7 +176,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
176
176
|
pinnedRows = { top: [], bottom: [] },
|
|
177
177
|
rowActions = [],
|
|
178
178
|
onCellUpdate,
|
|
179
|
-
|
|
179
|
+
onColumnDelete,
|
|
180
180
|
onDeleteRows,
|
|
181
181
|
onInsertRow,
|
|
182
182
|
onRowOrderChange,
|
|
@@ -292,7 +292,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
292
292
|
this._rowActions = rowActions;
|
|
293
293
|
this._onInsertRow = onInsertRow;
|
|
294
294
|
this._onDeleteRows = onDeleteRows;
|
|
295
|
-
this.
|
|
295
|
+
this._onColumnDelete = onColumnDelete;
|
|
296
296
|
this._onCellUpdate = onCellUpdate;
|
|
297
297
|
this._onRowOrderChange = onRowOrderChange;
|
|
298
298
|
this._onRowAction = onRowAction;
|
|
@@ -691,7 +691,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
691
691
|
const currentRow = this._registry.get(this._rows)[row];
|
|
692
692
|
invariant(currentRow, 'Invalid row index');
|
|
693
693
|
|
|
694
|
-
const snapshot = getSnapshot(currentRow);
|
|
694
|
+
const snapshot = { ...getSnapshot(currentRow) };
|
|
695
695
|
setValue(snapshot, field.path, transformedValue);
|
|
696
696
|
|
|
697
697
|
const schema = Obj.getSchema(currentRow);
|
|
@@ -796,8 +796,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
796
796
|
}
|
|
797
797
|
|
|
798
798
|
const field = this._projection?.getFields().find((field) => field.id === fieldId);
|
|
799
|
-
if (field && this.
|
|
800
|
-
this.
|
|
799
|
+
if (field && this._onColumnDelete) {
|
|
800
|
+
this._onColumnDelete(field.id);
|
|
801
801
|
}
|
|
802
802
|
}
|
|
803
803
|
|
|
@@ -108,7 +108,7 @@ const createTableModel = (registry: Registry.Registry, props: Partial<TableModel
|
|
|
108
108
|
registry,
|
|
109
109
|
view,
|
|
110
110
|
baseSchema: schema.jsonSchema,
|
|
111
|
-
change: createDirectChangeCallback(view.projection, schema
|
|
111
|
+
change: createDirectChangeCallback(view.projection, Type.toJsonSchema(schema)),
|
|
112
112
|
});
|
|
113
113
|
projection.normalizeView();
|
|
114
114
|
return new TableModel({ registry, object, projection, ...props });
|
|
@@ -210,7 +210,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
210
210
|
const targetObj = getValue(obj, field.path)?.target;
|
|
211
211
|
if (targetObj) {
|
|
212
212
|
const dxn = Obj.getDXN(targetObj)?.toString();
|
|
213
|
-
cell.accessoryHtml = `<div role="none" class="absolute inline-end-0 inset-block-0 p-[--dx-grid-cell-content-padding-block]"><dx-anchor
|
|
213
|
+
cell.accessoryHtml = `<div role="none" class="absolute inline-end-0 inset-block-0 p-[--dx-grid-cell-content-padding-block]"><dx-anchor dxn=${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>`;
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
@@ -17,6 +17,7 @@ const TABLE_SELECTORS = {
|
|
|
17
17
|
},
|
|
18
18
|
columnDelete: 'column-delete',
|
|
19
19
|
rowDelete: 'row-menu-delete',
|
|
20
|
+
addRow: 'table.toolbar.add-row',
|
|
20
21
|
} as const;
|
|
21
22
|
|
|
22
23
|
export class TableManager {
|
|
@@ -53,10 +54,16 @@ export class TableManager {
|
|
|
53
54
|
await this.page.getByTestId(TABLE_SELECTORS.columnSort[direction]).click();
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
public async
|
|
57
|
+
public async addRow(): Promise<void> {
|
|
58
|
+
await this.page.getByTestId(TABLE_SELECTORS.addRow).click();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public async addColumn({ label, format }: { label: string; format?: string }): Promise<void> {
|
|
57
62
|
await this.page.getByTestId(TABLE_SELECTORS.newColumnButton).click();
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
if (format) {
|
|
64
|
+
await this.page.getByRole('combobox').click();
|
|
65
|
+
await this.page.getByLabel(format).click();
|
|
66
|
+
}
|
|
60
67
|
await this.page.getByPlaceholder('Property label').click();
|
|
61
68
|
await this.page.getByPlaceholder('Property label').fill(label);
|
|
62
69
|
await this.page.getByRole('button', { name: 'Save' }).click();
|
|
@@ -121,8 +121,22 @@ test.describe('Table', () => {
|
|
|
121
121
|
await page.close();
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
test('add column without changing format', async ({ browser, browserName }) => {
|
|
125
|
+
test.skip(browserName === 'webkit');
|
|
126
|
+
test.skip(browserName === 'firefox');
|
|
127
|
+
const { page } = await setupPage(browser, { url: storyUrl });
|
|
128
|
+
const table = new TableManager(page);
|
|
129
|
+
|
|
130
|
+
await table.grid.ready();
|
|
131
|
+
const newColumnLabel = 'DEFAULT FORMAT COL';
|
|
132
|
+
|
|
133
|
+
await table.addColumn({ label: newColumnLabel });
|
|
134
|
+
|
|
135
|
+
await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
|
|
136
|
+
await page.close();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('add column with format', async ({ browser, browserName }) => {
|
|
126
140
|
test.skip(browserName === 'webkit');
|
|
127
141
|
test.skip(browserName === 'firefox');
|
|
128
142
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
@@ -160,6 +174,35 @@ test.describe('Table', () => {
|
|
|
160
174
|
await page.close();
|
|
161
175
|
});
|
|
162
176
|
|
|
177
|
+
test('add row and edit cell', async ({ browser, browserName }) => {
|
|
178
|
+
test.skip(browserName === 'webkit');
|
|
179
|
+
test.skip(browserName === 'firefox');
|
|
180
|
+
const { page } = await setupPage(browser, { url: storyUrl });
|
|
181
|
+
const table = new TableManager(page);
|
|
182
|
+
|
|
183
|
+
await table.grid.ready();
|
|
184
|
+
|
|
185
|
+
// Click the CTA row at the bottom to add a new row.
|
|
186
|
+
await table.grid.cell(0, 0, 'frozenRowsEnd').click();
|
|
187
|
+
|
|
188
|
+
// All schema fields are optional so the row is added directly to the grid (not as a draft).
|
|
189
|
+
const newCell = table.grid.cell(0, 10, 'grid');
|
|
190
|
+
await newCell.waitFor({ state: 'visible' });
|
|
191
|
+
|
|
192
|
+
// Focus should already be on the new row; engage edit mode.
|
|
193
|
+
await page.keyboard.press('Enter');
|
|
194
|
+
await page.getByTestId('grid.cell-editor').waitFor({ state: 'visible' });
|
|
195
|
+
|
|
196
|
+
// Type text and confirm.
|
|
197
|
+
const cellContent = 'New row content';
|
|
198
|
+
await page.keyboard.type(cellContent);
|
|
199
|
+
await page.keyboard.press('Enter');
|
|
200
|
+
|
|
201
|
+
// Assert the cell content is saved.
|
|
202
|
+
await expect(newCell).toHaveText(cellContent);
|
|
203
|
+
await page.close();
|
|
204
|
+
});
|
|
205
|
+
|
|
163
206
|
// TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
|
|
164
207
|
test.skip('extant relations work as expected', async ({ browser, browserName }) => {
|
|
165
208
|
test.skip(browserName === 'webkit');
|
|
@@ -97,7 +97,7 @@ export const useTestTableModel = <T extends Type.Obj.Any = Type.Obj.Any>(): Test
|
|
|
97
97
|
rows: filteredObjects,
|
|
98
98
|
onInsertRow: addRow,
|
|
99
99
|
onDeleteRows: handleDeleteRows,
|
|
100
|
-
|
|
100
|
+
onColumnDelete: handleDeleteColumn,
|
|
101
101
|
onCellUpdate: handleCellUpdate,
|
|
102
102
|
onRowOrderChange: handleRowOrderChange,
|
|
103
103
|
});
|
|
@@ -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 / Type.toJsonSchema). Does not exercise the path
|
|
15
|
+
* where projection or schema are reactive, so this does not reproduce the Obj.change 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 (View.make, Type.toJsonSchema); mutations must run inside Obj.change 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 = 'example.com/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
|
+
});
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import type * as Types from 'effect/Types';
|
|
6
7
|
|
|
7
8
|
import { Filter, type JsonSchema, Obj, Order, Query, type QueryAST, Ref, Type } from '@dxos/echo';
|
|
8
9
|
import {
|
|
9
10
|
ProjectionModel,
|
|
10
11
|
type SchemaPropertyDefinition,
|
|
11
12
|
View,
|
|
12
|
-
|
|
13
|
+
createEchoChangeCallback,
|
|
13
14
|
getSchemaFromPropertyDefinitions,
|
|
14
15
|
} from '@dxos/schema';
|
|
15
16
|
|
|
@@ -37,11 +38,11 @@ export const getBaseSchema = ({
|
|
|
37
38
|
schema?: Type.Entity.Any;
|
|
38
39
|
typename?: string;
|
|
39
40
|
properties?: TablePropertyDefinition[];
|
|
40
|
-
jsonSchema?: JsonSchema.JsonSchema
|
|
41
|
-
}): { typename: string; jsonSchema: JsonSchema.JsonSchema } => {
|
|
41
|
+
jsonSchema?: Types.DeepMutable<JsonSchema.JsonSchema>;
|
|
42
|
+
}): { typename: string; jsonSchema: Types.DeepMutable<JsonSchema.JsonSchema> } => {
|
|
42
43
|
if (typename && properties) {
|
|
43
44
|
const schema = getSchemaFromPropertyDefinitions(typename, properties);
|
|
44
|
-
return { typename: schema.typename, jsonSchema: schema
|
|
45
|
+
return { typename: schema.typename, jsonSchema: Type.toJsonSchema(schema) };
|
|
45
46
|
} else if (schema) {
|
|
46
47
|
return { typename: Type.getTypename(schema)!, jsonSchema: Type.toJsonSchema(schema) };
|
|
47
48
|
} else if (typename && jsonSchema) {
|
|
@@ -57,7 +58,7 @@ export const makeDynamicTable = ({
|
|
|
57
58
|
properties,
|
|
58
59
|
}: {
|
|
59
60
|
registry: Registry.Registry;
|
|
60
|
-
jsonSchema: JsonSchema.JsonSchema
|
|
61
|
+
jsonSchema: Types.DeepMutable<JsonSchema.JsonSchema>;
|
|
61
62
|
properties?: TablePropertyDefinition[];
|
|
62
63
|
}): { projection: ProjectionModel; object: Table.Table } => {
|
|
63
64
|
const view = View.make({
|
|
@@ -67,12 +68,11 @@ export const makeDynamicTable = ({
|
|
|
67
68
|
});
|
|
68
69
|
const object = Obj.make(Table.Table, { view: Ref.make(view), sizes: {} });
|
|
69
70
|
|
|
70
|
-
// Use direct change callback for in-memory objects (non-ECHO backed).
|
|
71
71
|
const projection = new ProjectionModel({
|
|
72
72
|
registry,
|
|
73
73
|
view,
|
|
74
74
|
baseSchema: jsonSchema,
|
|
75
|
-
change:
|
|
75
|
+
change: createEchoChangeCallback(view, jsonSchema),
|
|
76
76
|
});
|
|
77
77
|
projection.normalizeView();
|
|
78
78
|
if (properties && projection.getFields()) {
|