@dxos/react-ui-table 0.8.4-main.28f8d3d → 0.8.4-main.2c6827d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/chunk-2PFAPQL4.mjs +66 -0
- package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +7 -0
- package/dist/lib/browser/{chunk-ZAOSIG3L.mjs → chunk-GS2UNGJK.mjs} +174 -213
- package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +411 -515
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +12 -9
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +3 -3
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs +67 -0
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-ZOFFI3JJ.mjs → chunk-TRR3C5NX.mjs} +174 -213
- package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +411 -515
- 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 +12 -9
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +3 -3
- package/dist/types/src/components/Table/DynamicTable.d.ts +2 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +51 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +103 -5
- 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 +8 -5
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +41 -3
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -6
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -13
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
- package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -3
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +3 -2
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +3 -3
- 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/table-model.d.ts +9 -8
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +1 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +2 -3
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +3 -22
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +25 -5
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +5 -6
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +53 -51
- package/src/components/Table/DynamicTable.stories.tsx +15 -12
- package/src/components/Table/DynamicTable.tsx +4 -7
- package/src/components/Table/Relations.stories.tsx +160 -42
- package/src/components/Table/RowActionsMenu.tsx +21 -13
- package/src/components/Table/Table.stories.tsx +63 -56
- package/src/components/Table/Table.tsx +109 -90
- package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -64
- package/src/components/TableCellEditor/TableCellEditor.tsx +62 -208
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +32 -22
- package/src/components/testing.ts +2 -2
- package/src/hooks/useAddRow.ts +7 -5
- package/src/hooks/useTableModel.ts +5 -5
- package/src/model/modal-controller.ts +0 -28
- package/src/model/table-model.test.ts +19 -11
- package/src/model/table-model.ts +36 -24
- package/src/model/table-presentation.test.ts +22 -21
- package/src/model/table-presentation.ts +58 -39
- package/src/model/table-sorting.ts +16 -7
- package/src/playwright/TableManager.ts +1 -1
- package/src/playwright/smoke.spec.ts +51 -49
- package/src/testing/useTestTableModel.ts +10 -7
- package/src/types/index.ts +1 -1
- package/src/types/schema.ts +63 -6
- package/src/util/dynamic-table.ts +15 -16
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +4 -4
- package/src/util/table-buttons.ts +3 -3
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
- package/dist/lib/browser/chunk-ZAOSIG3L.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-ZOFFI3JJ.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/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -12
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -86
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/util/table.ts +0 -53
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
9
7
|
import React, { useCallback } from 'react';
|
|
10
8
|
|
|
11
|
-
import { Obj, Type } from '@dxos/echo';
|
|
9
|
+
import { Obj, type QueryAST, Type } from '@dxos/echo';
|
|
12
10
|
import {
|
|
13
11
|
EchoObject,
|
|
14
12
|
FormatAnnotation,
|
|
@@ -16,25 +14,25 @@ import {
|
|
|
16
14
|
GeneratorAnnotation,
|
|
17
15
|
LabelAnnotation,
|
|
18
16
|
PropertyMetaAnnotationId,
|
|
19
|
-
} from '@dxos/echo
|
|
17
|
+
} from '@dxos/echo/internal';
|
|
18
|
+
import { live } from '@dxos/echo/internal';
|
|
20
19
|
import { invariant } from '@dxos/invariant';
|
|
21
20
|
import { faker } from '@dxos/random';
|
|
22
21
|
import { PublicKey } from '@dxos/react-client';
|
|
23
|
-
import { type Space
|
|
22
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
24
23
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
24
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
25
25
|
import { ViewEditor } from '@dxos/react-ui-form';
|
|
26
26
|
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
27
|
-
import {
|
|
27
|
+
import { View, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
|
|
28
28
|
import { Testing, createObjectFactory } from '@dxos/schema/testing';
|
|
29
|
-
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
30
29
|
|
|
31
30
|
import { useTestTableModel } from '../../testing';
|
|
32
31
|
import { translations } from '../../translations';
|
|
33
|
-
import {
|
|
34
|
-
import { createTable } from '../../util';
|
|
32
|
+
import { Table } from '../../types';
|
|
35
33
|
import { TableToolbar } from '../TableToolbar';
|
|
36
34
|
|
|
37
|
-
import { Table } from './Table';
|
|
35
|
+
import { Table as TableComponent } from './Table';
|
|
38
36
|
|
|
39
37
|
const TestSchema = Schema.Struct({
|
|
40
38
|
// TODO(wittjosiah): Should be title. Currently name to work with default label.
|
|
@@ -72,18 +70,18 @@ const StoryViewEditor = ({
|
|
|
72
70
|
space,
|
|
73
71
|
handleDeleteColumn,
|
|
74
72
|
}: {
|
|
75
|
-
view?:
|
|
73
|
+
view?: View.View;
|
|
76
74
|
schema?: Schema.Schema.AnyNoContext;
|
|
77
75
|
space?: Space;
|
|
78
76
|
handleDeleteColumn: (fieldId: string) => void;
|
|
79
77
|
}) => {
|
|
80
|
-
const
|
|
81
|
-
(
|
|
78
|
+
const handleQueryChanged = useCallback(
|
|
79
|
+
(newQuery: QueryAST.Query) => {
|
|
82
80
|
invariant(schema);
|
|
83
81
|
invariant(Type.isMutable(schema));
|
|
84
|
-
schema.updateTypename(
|
|
82
|
+
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
85
83
|
invariant(view);
|
|
86
|
-
view.query.
|
|
84
|
+
view.query.ast = newQuery;
|
|
87
85
|
},
|
|
88
86
|
[schema, view],
|
|
89
87
|
);
|
|
@@ -97,7 +95,7 @@ const StoryViewEditor = ({
|
|
|
97
95
|
registry={space?.db.schemaRegistry}
|
|
98
96
|
schema={schema}
|
|
99
97
|
view={view}
|
|
100
|
-
|
|
98
|
+
onQueryChanged={handleQueryChanged}
|
|
101
99
|
onDelete={handleDeleteColumn}
|
|
102
100
|
/>
|
|
103
101
|
);
|
|
@@ -129,8 +127,8 @@ const DefaultStory = () => {
|
|
|
129
127
|
<div className='grow grid grid-cols-[1fr_350px]'>
|
|
130
128
|
<div className='grid grid-rows-[min-content_1fr] min-bs-0 overflow-hidden'>
|
|
131
129
|
<TableToolbar classNames='border-be border-subduedSeparator' onAdd={handleInsertRow} onSave={handleSaveView} />
|
|
132
|
-
<
|
|
133
|
-
<
|
|
130
|
+
<TableComponent.Root>
|
|
131
|
+
<TableComponent.Main
|
|
134
132
|
ref={tableRef}
|
|
135
133
|
model={model}
|
|
136
134
|
presentation={presentation}
|
|
@@ -138,11 +136,11 @@ const DefaultStory = () => {
|
|
|
138
136
|
client={client}
|
|
139
137
|
ignoreAttention
|
|
140
138
|
/>
|
|
141
|
-
</
|
|
139
|
+
</TableComponent.Root>
|
|
142
140
|
</div>
|
|
143
|
-
<div className='flex flex-col
|
|
141
|
+
<div className='flex flex-col bs-full border-l border-separator overflow-y-auto'>
|
|
144
142
|
<StoryViewEditor view={view} schema={schema} space={space} handleDeleteColumn={handleDeleteColumn} />
|
|
145
|
-
<SyntaxHighlighter language='json' className='
|
|
143
|
+
<SyntaxHighlighter language='json' className='text-xs'>
|
|
146
144
|
{JSON.stringify({ view, schema }, null, 2)}
|
|
147
145
|
</SyntaxHighlighter>
|
|
148
146
|
</div>
|
|
@@ -158,26 +156,23 @@ type StoryProps = { rows?: number };
|
|
|
158
156
|
|
|
159
157
|
// TODO(burdon): Need super simple story.
|
|
160
158
|
|
|
161
|
-
const meta
|
|
159
|
+
const meta = {
|
|
162
160
|
title: 'ui/react-ui-table/Table',
|
|
163
161
|
render: DefaultStory,
|
|
164
|
-
parameters: {
|
|
165
|
-
translations,
|
|
166
|
-
layout: 'fullscreen',
|
|
167
|
-
controls: {
|
|
168
|
-
disable: true,
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
162
|
decorators: [
|
|
172
163
|
withTheme,
|
|
173
|
-
withLayout({ fullscreen: true }),
|
|
174
164
|
withClientProvider({
|
|
175
|
-
types: [
|
|
165
|
+
types: [View.View, Table.Table],
|
|
176
166
|
createIdentity: true,
|
|
177
167
|
createSpace: true,
|
|
178
|
-
|
|
168
|
+
onCreateSpace: async ({ client, space }) => {
|
|
179
169
|
const [schema] = await space.db.schemaRegistry.register([TestSchema]);
|
|
180
|
-
const { view } = await
|
|
170
|
+
const { view } = await Table.makeView({ client, space, typename: schema.typename });
|
|
171
|
+
view.projection.fields = [
|
|
172
|
+
view.projection.fields.find((field: any) => field.path === 'name')!,
|
|
173
|
+
...view.projection.fields.filter((field: any) => field.path !== 'name'),
|
|
174
|
+
];
|
|
175
|
+
|
|
181
176
|
space.db.add(view);
|
|
182
177
|
|
|
183
178
|
Array.from({ length: 10 }).map(() => {
|
|
@@ -192,34 +187,44 @@ const meta: Meta<StoryProps> = {
|
|
|
192
187
|
},
|
|
193
188
|
}),
|
|
194
189
|
],
|
|
195
|
-
|
|
190
|
+
parameters: {
|
|
191
|
+
layout: 'fullscreen',
|
|
192
|
+
controls: {
|
|
193
|
+
disable: true,
|
|
194
|
+
},
|
|
195
|
+
translations,
|
|
196
|
+
},
|
|
197
|
+
} satisfies Meta<typeof TableComponent>;
|
|
196
198
|
|
|
197
199
|
export default meta;
|
|
198
200
|
|
|
199
|
-
|
|
201
|
+
type Story = StoryObj<typeof meta>;
|
|
202
|
+
|
|
203
|
+
export const Default: Story = {};
|
|
200
204
|
|
|
201
205
|
export const StaticSchema: StoryObj = {
|
|
202
206
|
render: DefaultStory,
|
|
203
|
-
parameters: { translations },
|
|
204
207
|
decorators: [
|
|
205
208
|
withClientProvider({
|
|
206
|
-
types: [
|
|
209
|
+
types: [View.View, Table.Table],
|
|
207
210
|
createIdentity: true,
|
|
208
211
|
createSpace: true,
|
|
209
|
-
|
|
210
|
-
const { view } = await
|
|
212
|
+
onCreateSpace: async ({ client, space }) => {
|
|
213
|
+
const { view } = await Table.makeView({ client, space, typename: Testing.Person.typename });
|
|
211
214
|
space.db.add(view);
|
|
212
215
|
|
|
213
216
|
const factory = createObjectFactory(space.db, faker as any);
|
|
214
217
|
await factory([
|
|
215
|
-
{ type: Testing.
|
|
218
|
+
{ type: Testing.Person, count: 10 },
|
|
216
219
|
// { type: Testing.Organization, count: 1 },
|
|
217
220
|
]);
|
|
218
221
|
},
|
|
219
222
|
}),
|
|
220
|
-
withLayout({ fullscreen: true }),
|
|
221
|
-
withTheme,
|
|
222
223
|
],
|
|
224
|
+
parameters: {
|
|
225
|
+
layout: 'fullscreen',
|
|
226
|
+
translations,
|
|
227
|
+
},
|
|
223
228
|
};
|
|
224
229
|
|
|
225
230
|
const ContactWithArrayOfEmails = Schema.Struct({
|
|
@@ -241,39 +246,39 @@ const ContactWithArrayOfEmails = Schema.Struct({
|
|
|
241
246
|
|
|
242
247
|
export const ArrayOfObjects: StoryObj = {
|
|
243
248
|
render: DefaultStory,
|
|
244
|
-
parameters: { translations },
|
|
245
249
|
decorators: [
|
|
246
250
|
withClientProvider({
|
|
247
|
-
types: [
|
|
251
|
+
types: [View.View, Table.Table, Testing.Person, Testing.Organization, ContactWithArrayOfEmails],
|
|
248
252
|
createIdentity: true,
|
|
249
253
|
createSpace: true,
|
|
250
|
-
|
|
251
|
-
const { view } = await
|
|
254
|
+
onCreateSpace: async ({ client, space }) => {
|
|
255
|
+
const { view } = await Table.makeView({ client, space, typename: ContactWithArrayOfEmails.typename });
|
|
252
256
|
space.db.add(view);
|
|
253
257
|
|
|
254
258
|
const factory = createObjectFactory(space.db, faker as any);
|
|
255
259
|
await factory([
|
|
256
|
-
// { type: Testing.
|
|
260
|
+
// { type: Testing.Person, count: 10 },
|
|
257
261
|
// { type: Testing.Organization, count: 1 },
|
|
258
262
|
{ type: ContactWithArrayOfEmails, count: 10 },
|
|
259
263
|
]);
|
|
260
264
|
},
|
|
261
265
|
}),
|
|
262
|
-
withLayout({ fullscreen: true }),
|
|
263
|
-
withTheme,
|
|
264
266
|
],
|
|
267
|
+
parameters: {
|
|
268
|
+
layout: 'fullscreen',
|
|
269
|
+
translations,
|
|
270
|
+
},
|
|
265
271
|
};
|
|
266
272
|
|
|
267
273
|
export const Tags: Meta<StoryProps> = {
|
|
268
274
|
title: 'ui/react-ui-table/Table',
|
|
269
275
|
render: DefaultStory,
|
|
270
|
-
parameters: { translations },
|
|
271
276
|
decorators: [
|
|
272
277
|
withClientProvider({
|
|
273
|
-
types: [
|
|
278
|
+
types: [View.View, Table.Table],
|
|
274
279
|
createIdentity: true,
|
|
275
280
|
createSpace: true,
|
|
276
|
-
|
|
281
|
+
onCreateSpace: async ({ client, space }) => {
|
|
277
282
|
// Configure schema.
|
|
278
283
|
const typename = 'example.com/SingleSelect';
|
|
279
284
|
const selectOptions = [
|
|
@@ -301,7 +306,7 @@ export const Tags: Meta<StoryProps> = {
|
|
|
301
306
|
const [storedSchema] = await space.db.schemaRegistry.register([schema]);
|
|
302
307
|
|
|
303
308
|
// Initialize table.
|
|
304
|
-
const { view } = await
|
|
309
|
+
const { view } = await Table.makeView({ client, space, typename });
|
|
305
310
|
space.db.add(view);
|
|
306
311
|
|
|
307
312
|
// Populate.
|
|
@@ -315,7 +320,9 @@ export const Tags: Meta<StoryProps> = {
|
|
|
315
320
|
});
|
|
316
321
|
},
|
|
317
322
|
}),
|
|
318
|
-
withLayout({ fullscreen: true }),
|
|
319
|
-
withTheme,
|
|
320
323
|
],
|
|
324
|
+
parameters: {
|
|
325
|
+
layout: 'fullscreen',
|
|
326
|
+
translations,
|
|
327
|
+
},
|
|
321
328
|
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import type * as Schema from 'effect/Schema';
|
|
6
|
+
import * as String from 'effect/String';
|
|
6
7
|
import React, {
|
|
7
8
|
type MouseEvent,
|
|
8
9
|
type PropsWithChildren,
|
|
@@ -15,13 +16,8 @@ import React, {
|
|
|
15
16
|
useState,
|
|
16
17
|
} from 'react';
|
|
17
18
|
|
|
18
|
-
import { type Client } from '@dxos/client';
|
|
19
|
-
import { Filter } from '@dxos/echo';
|
|
20
|
-
import { getValue } from '@dxos/echo-schema';
|
|
21
|
-
import { invariant } from '@dxos/invariant';
|
|
22
19
|
// TODO(wittjosiah): Remove dependency on react-client.
|
|
23
|
-
import {
|
|
24
|
-
import { useTranslation } from '@dxos/react-ui';
|
|
20
|
+
import { type Client } from '@dxos/react-client';
|
|
25
21
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
26
22
|
import {
|
|
27
23
|
type DxGridElement,
|
|
@@ -34,19 +30,20 @@ import {
|
|
|
34
30
|
gridSeparatorBlockEnd,
|
|
35
31
|
gridSeparatorInlineEnd,
|
|
36
32
|
} from '@dxos/react-ui-grid';
|
|
33
|
+
import { DxEditRequest } from '@dxos/react-ui-grid';
|
|
37
34
|
import { mx } from '@dxos/react-ui-theme';
|
|
38
|
-
import { isNotFalsy, safeParseInt } from '@dxos/util';
|
|
39
35
|
|
|
40
|
-
import { ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
41
|
-
import { translationKey } from '../../translations';
|
|
36
|
+
import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
42
37
|
import { tableButtons, tableControls } from '../../util';
|
|
43
|
-
import { type TableCellEditorProps, TableValueEditor
|
|
38
|
+
import { type OnCreateHandler, type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
|
|
44
39
|
|
|
45
40
|
import { ColumnActionsMenu } from './ColumnActionsMenu';
|
|
46
41
|
import { ColumnSettings } from './ColumnSettings';
|
|
47
|
-
import { CreateRefPanel } from './CreateRefPanel';
|
|
48
42
|
import { RowActionsMenu } from './RowActionsMenu';
|
|
49
43
|
|
|
44
|
+
const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
|
|
45
|
+
const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
|
|
46
|
+
|
|
50
47
|
//
|
|
51
48
|
// Table.Root
|
|
52
49
|
//
|
|
@@ -77,28 +74,34 @@ const TableRoot = ({ children, role = 'article' }: TableRootProps) => {
|
|
|
77
74
|
|
|
78
75
|
export type TableController = {
|
|
79
76
|
update?: (cell?: DxGridPosition) => void;
|
|
80
|
-
|
|
77
|
+
handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
|
|
81
78
|
};
|
|
82
79
|
|
|
83
80
|
export type TableMainProps = {
|
|
84
81
|
model?: TableModel;
|
|
85
82
|
presentation?: TablePresentation;
|
|
86
|
-
schema?: Schema.AnyNoContext;
|
|
83
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
87
84
|
client?: Client;
|
|
88
85
|
// TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
|
|
89
86
|
ignoreAttention?: boolean;
|
|
87
|
+
onCreate?: OnCreateHandler;
|
|
90
88
|
onRowClick?: (row: any) => void;
|
|
89
|
+
testId?: string;
|
|
91
90
|
};
|
|
92
91
|
|
|
93
92
|
const TableMain = forwardRef<TableController, TableMainProps>(
|
|
94
|
-
({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
|
|
93
|
+
({ model, presentation, ignoreAttention, schema, client, onCreate, onRowClick, testId }, forwardedRef) => {
|
|
95
94
|
const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
|
|
96
95
|
const { hasAttention } = useAttention(model?.id ?? 'table');
|
|
97
|
-
const { t } = useTranslation(translationKey);
|
|
98
96
|
const modals = useMemo(() => new ModalController(), []);
|
|
99
97
|
|
|
100
98
|
const draftRowCount = model?.getDraftRowCount() ?? 0;
|
|
101
99
|
|
|
100
|
+
const handleSave = useCallback(() => {
|
|
101
|
+
dxGrid?.updateCells(true);
|
|
102
|
+
dxGrid?.requestUpdate();
|
|
103
|
+
}, [dxGrid]);
|
|
104
|
+
|
|
102
105
|
const frozen = useMemo(() => {
|
|
103
106
|
const noActionColumn =
|
|
104
107
|
model?.features.dataEditable === false &&
|
|
@@ -107,7 +110,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
107
110
|
|
|
108
111
|
return {
|
|
109
112
|
frozenRowsStart: 1,
|
|
110
|
-
frozenRowsEnd: draftRowCount,
|
|
113
|
+
frozenRowsEnd: Math.max(1, draftRowCount),
|
|
111
114
|
frozenColsStart: model?.features.selection.enabled ? 1 : 0,
|
|
112
115
|
frozenColsEnd: noActionColumn ? 0 : 1,
|
|
113
116
|
};
|
|
@@ -125,6 +128,23 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
125
128
|
dxGrid.getCells = getCells;
|
|
126
129
|
}, [presentation, dxGrid, getCells]);
|
|
127
130
|
|
|
131
|
+
const handleInsertRowResult = useCallback(
|
|
132
|
+
(insertResult?: InsertRowResult) => {
|
|
133
|
+
if (insertResult === 'draft') {
|
|
134
|
+
requestAnimationFrame(() => {
|
|
135
|
+
dxGrid?.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
|
|
136
|
+
dxGrid?.refocus();
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
requestAnimationFrame(() => {
|
|
140
|
+
dxGrid?.setFocus({ plane: 'grid', col: 0, row: model ? model.getRowCount() - 1 : 0 });
|
|
141
|
+
dxGrid?.refocus();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
[model, dxGrid],
|
|
146
|
+
);
|
|
147
|
+
|
|
128
148
|
/**
|
|
129
149
|
* Provides an external controller that can be called to repaint the table.
|
|
130
150
|
*/
|
|
@@ -142,14 +162,9 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
142
162
|
dxGrid.requestUpdate();
|
|
143
163
|
}
|
|
144
164
|
},
|
|
145
|
-
|
|
146
|
-
requestAnimationFrame(() => {
|
|
147
|
-
dxGrid.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
|
|
148
|
-
dxGrid.refocus();
|
|
149
|
-
});
|
|
150
|
-
},
|
|
165
|
+
handleInsertRowResult,
|
|
151
166
|
};
|
|
152
|
-
}, [presentation, dxGrid]);
|
|
167
|
+
}, [presentation, dxGrid, model]);
|
|
153
168
|
|
|
154
169
|
const handleSaveDraftRow = useCallback(
|
|
155
170
|
(rowIndex = 0) => {
|
|
@@ -167,11 +182,16 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
167
182
|
|
|
168
183
|
const handleGridClick = useCallback(
|
|
169
184
|
(event: MouseEvent) => {
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
185
|
+
const cell = closestCell(event.target as HTMLElement);
|
|
186
|
+
if (cell) {
|
|
187
|
+
const { row: rowIndex, plane } = cell;
|
|
172
188
|
if (onRowClick) {
|
|
173
|
-
|
|
174
|
-
|
|
189
|
+
if (plane === 'grid') {
|
|
190
|
+
const row = model?.getRowAt(rowIndex);
|
|
191
|
+
row && onRowClick(row);
|
|
192
|
+
} else {
|
|
193
|
+
onRowClick(cell);
|
|
194
|
+
}
|
|
175
195
|
}
|
|
176
196
|
|
|
177
197
|
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
@@ -257,15 +277,11 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
257
277
|
const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
|
|
258
278
|
(increment, delta, cell) => {
|
|
259
279
|
if (dxGrid && model) {
|
|
260
|
-
if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1) {
|
|
261
|
-
|
|
262
|
-
model.insertRow();
|
|
263
|
-
}
|
|
264
|
-
dxGrid.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
|
|
280
|
+
if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1 && increment !== 'col') {
|
|
281
|
+
handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
|
|
265
282
|
} else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
|
|
266
283
|
handleSaveDraftRow(cell.row);
|
|
267
|
-
model.insertRow();
|
|
268
|
-
dxGrid.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
|
|
284
|
+
handleInsertRowResult(model.insertRow());
|
|
269
285
|
} else {
|
|
270
286
|
dxGrid.refocus(increment, delta);
|
|
271
287
|
}
|
|
@@ -285,12 +301,64 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
285
301
|
return;
|
|
286
302
|
}
|
|
287
303
|
|
|
304
|
+
// Handle Meta+C (Copy) and Meta+V (Paste) commands
|
|
305
|
+
if (event.metaKey || event.ctrlKey) {
|
|
306
|
+
switch (event.key) {
|
|
307
|
+
case 'c': {
|
|
308
|
+
// Copy focused cell's text content to clipboard
|
|
309
|
+
try {
|
|
310
|
+
const cellData = model.getCellData(cell);
|
|
311
|
+
const textContent = cellData?.toString() ?? '';
|
|
312
|
+
void navigator.clipboard.writeText(textContent);
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.warn('Failed to copy cell content:', error);
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case 'v': {
|
|
320
|
+
// Paste clipboard content to focused cell
|
|
321
|
+
event.preventDefault();
|
|
322
|
+
void navigator.clipboard.readText().then((clipboardText) => {
|
|
323
|
+
try {
|
|
324
|
+
// Attempt to set the cell's content to clipboard content
|
|
325
|
+
model.setCellData(cell, String.trim(clipboardText).replace(/[\n\r]+/, ' '));
|
|
326
|
+
handleSave();
|
|
327
|
+
} catch {
|
|
328
|
+
// If validation fails, emit a DxEditRequest event with initialContent from clipboard
|
|
329
|
+
// TODO(thure): Should `dx-grid` expose a method like this?
|
|
330
|
+
const cellElement = (event.target as HTMLElement).closest(
|
|
331
|
+
'[data-dx-grid-action="cell"]',
|
|
332
|
+
) as HTMLElement;
|
|
333
|
+
if (cellElement) {
|
|
334
|
+
const rect = cellElement.getBoundingClientRect();
|
|
335
|
+
const editRequest = new DxEditRequest({
|
|
336
|
+
cellIndex: `${cell.plane},${cell.col},${cell.row}`,
|
|
337
|
+
cellBox: {
|
|
338
|
+
insetInlineStart: rect.left,
|
|
339
|
+
insetBlockStart: rect.top,
|
|
340
|
+
inlineSize: rect.width,
|
|
341
|
+
blockSize: rect.height,
|
|
342
|
+
},
|
|
343
|
+
cellElement,
|
|
344
|
+
initialContent: clipboardText,
|
|
345
|
+
});
|
|
346
|
+
cellElement.dispatchEvent(editRequest);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
288
355
|
switch (event.key) {
|
|
289
356
|
case 'Backspace':
|
|
290
357
|
case 'Delete': {
|
|
291
358
|
try {
|
|
292
359
|
model.setCellData(cell, undefined);
|
|
293
360
|
event.preventDefault();
|
|
361
|
+
handleSave();
|
|
294
362
|
} catch {
|
|
295
363
|
// Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
|
|
296
364
|
}
|
|
@@ -298,7 +366,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
298
366
|
}
|
|
299
367
|
}
|
|
300
368
|
},
|
|
301
|
-
[model],
|
|
369
|
+
[model, dxGrid, handleSave],
|
|
302
370
|
);
|
|
303
371
|
|
|
304
372
|
const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
|
|
@@ -327,58 +395,6 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
327
395
|
}
|
|
328
396
|
}, [model, dxGrid]);
|
|
329
397
|
|
|
330
|
-
// TODO(burdon): Factor out?
|
|
331
|
-
// TODO(burdon): Generalize to handle other value types (e.g., enums).
|
|
332
|
-
const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
|
|
333
|
-
async ({ field, props }, text) => {
|
|
334
|
-
if (model && props.referenceSchema && field.referencePath) {
|
|
335
|
-
const space = getSpace(model.view);
|
|
336
|
-
invariant(space);
|
|
337
|
-
|
|
338
|
-
let schema;
|
|
339
|
-
if (client) {
|
|
340
|
-
schema = client.graph.schemaRegistry.getSchema(props.referenceSchema);
|
|
341
|
-
}
|
|
342
|
-
if (!schema) {
|
|
343
|
-
schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (schema) {
|
|
347
|
-
const { objects } = await space.db.query(Filter.type(schema)).run();
|
|
348
|
-
const options = objects
|
|
349
|
-
.map((obj) => {
|
|
350
|
-
const value = getValue(obj, field.referencePath!);
|
|
351
|
-
if (!value || typeof value !== 'string') {
|
|
352
|
-
return undefined;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return {
|
|
356
|
-
label: value,
|
|
357
|
-
data: obj,
|
|
358
|
-
};
|
|
359
|
-
})
|
|
360
|
-
.filter(isNotFalsy);
|
|
361
|
-
|
|
362
|
-
return [
|
|
363
|
-
...options,
|
|
364
|
-
{
|
|
365
|
-
label: t('create new object label', { text }),
|
|
366
|
-
data: createOption(text),
|
|
367
|
-
},
|
|
368
|
-
];
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return [];
|
|
373
|
-
},
|
|
374
|
-
[model, client, t],
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
const handleSave = useCallback(() => {
|
|
378
|
-
dxGrid?.updateCells(true);
|
|
379
|
-
dxGrid?.requestUpdate();
|
|
380
|
-
}, [dxGrid]);
|
|
381
|
-
|
|
382
398
|
if (!model || !modals) {
|
|
383
399
|
return <span role='none' className='attention-surface' />;
|
|
384
400
|
}
|
|
@@ -390,13 +406,16 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
390
406
|
modals={modals}
|
|
391
407
|
schema={schema}
|
|
392
408
|
onFocus={handleFocus}
|
|
393
|
-
onQuery={handleQuery}
|
|
394
409
|
onSave={handleSave}
|
|
410
|
+
onCreate={onCreate}
|
|
411
|
+
client={client}
|
|
395
412
|
/>
|
|
396
413
|
<Grid.Content
|
|
397
414
|
className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
398
415
|
frozen={frozen}
|
|
399
416
|
columns={model.columnMeta.value}
|
|
417
|
+
columnDefault={columnDefault}
|
|
418
|
+
rowDefault={rowDefault}
|
|
400
419
|
limitRows={model.getRowCount() ?? 0}
|
|
401
420
|
limitColumns={model.projection.fields.length}
|
|
402
421
|
overscroll='trap'
|
|
@@ -404,12 +423,12 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
404
423
|
onClick={handleGridClick}
|
|
405
424
|
onKeyDownCapture={handleKeyDown}
|
|
406
425
|
onWheelCapture={handleWheel}
|
|
426
|
+
{...(testId && { 'data-testid': testId })}
|
|
407
427
|
ref={setDxGrid}
|
|
408
428
|
/>
|
|
409
429
|
<RowActionsMenu model={model} modals={modals} />
|
|
410
430
|
<ColumnActionsMenu model={model} modals={modals} />
|
|
411
431
|
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
412
|
-
<CreateRefPanel model={model} modals={modals} />
|
|
413
432
|
</Grid.Root>
|
|
414
433
|
);
|
|
415
434
|
},
|