@dxos/react-ui-table 0.8.4-main.c85a9c8dae → 0.8.4-main.d05539e30a
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/LICENSE +102 -5
- package/dist/lib/browser/{chunk-DSR2MRZB.mjs → chunk-4URYOFDL.mjs} +10 -21
- package/dist/lib/browser/chunk-4URYOFDL.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-PTHMYAAM.mjs → chunk-YDXPC7HA.mjs} +16 -72
- package/dist/lib/browser/chunk-YDXPC7HA.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +207 -251
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +8 -6
- 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 +2 -1
- package/dist/lib/node-esm/{chunk-GI72MQSG.mjs → chunk-2AN3QQ3D.mjs} +16 -72
- package/dist/lib/node-esm/chunk-2AN3QQ3D.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-HTZJSULI.mjs → chunk-B2SZYAEC.mjs} +10 -21
- package/dist/lib/node-esm/chunk-B2SZYAEC.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 +207 -251
- 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 +8 -6
- 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 +2 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +1 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +19 -21
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +82 -82
- 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 -21
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +84 -84
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts +18 -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.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +22 -24
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.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.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +4 -5
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -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.map +1 -1
- package/dist/types/src/translations.d.ts +19 -21
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +1 -7
- package/dist/types/src/types/Table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts.map +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.map +1 -1
- 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 +51 -60
- package/src/components/Table/ColumnActionsMenu.tsx +7 -6
- package/src/components/Table/DynamicTable.stories.tsx +15 -15
- package/src/components/Table/DynamicTable.tsx +6 -15
- package/src/components/Table/Relations.stories.tsx +72 -32
- package/src/components/Table/RowActionsMenu.tsx +3 -2
- package/src/components/Table/Table.stories.tsx +28 -31
- package/src/components/Table/Table.tsx +37 -427
- package/src/components/Table/TableContent.tsx +425 -0
- package/src/components/{TableToolbar → Table}/TableToolbar.tsx +34 -41
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +3 -2
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +10 -11
- package/src/components/TableCellEditor/TableCellEditor.tsx +10 -8
- package/src/components/index.ts +0 -1
- package/src/components/testing.ts +10 -10
- package/src/index.ts +0 -4
- package/src/model/table-model.test.ts +1 -2
- package/src/model/table-model.ts +10 -11
- package/src/model/table-presentation.test.ts +1 -2
- package/src/model/table-presentation.ts +3 -5
- package/src/playwright/smoke.spec.ts +3 -3
- package/src/testing/useTestTableModel.ts +5 -5
- package/src/translations.ts +19 -21
- package/src/types/Table.ts +4 -21
- package/src/util/dynamic-table.test.ts +3 -3
- package/src/util/dynamic-table.ts +5 -6
- package/dist/lib/browser/chunk-DSR2MRZB.mjs.map +0 -7
- package/dist/lib/browser/chunk-PTHMYAAM.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-GI72MQSG.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HTZJSULI.mjs.map +0 -7
- 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/src/components/TableToolbar/index.ts +0 -5
|
@@ -6,25 +6,25 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import React, { useCallback } from 'react';
|
|
8
8
|
|
|
9
|
-
import { Annotation, type Database, Format, Obj, type QueryAST, Ref, Type } from '@dxos/echo';
|
|
10
|
-
import { View } from '@dxos/echo';
|
|
9
|
+
import { Annotation, type Database, Format, Obj, type QueryAST, Ref, Type, View } from '@dxos/echo';
|
|
11
10
|
import { type Mutable, PropertyMetaAnnotationId } from '@dxos/echo/internal';
|
|
12
11
|
import { invariant } from '@dxos/invariant';
|
|
13
|
-
import {
|
|
12
|
+
import { random } from '@dxos/random';
|
|
14
13
|
import { PublicKey } from '@dxos/react-client';
|
|
15
14
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
16
15
|
import { Panel, ScrollArea } from '@dxos/react-ui';
|
|
16
|
+
import { ViewEditor } from '@dxos/react-ui-form';
|
|
17
|
+
import { translations as formTranslations } from '@dxos/react-ui-form/translations';
|
|
18
|
+
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
17
19
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
18
|
-
import { ViewEditor, translations as formTranslations } from '@dxos/react-ui-form';
|
|
19
|
-
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
20
20
|
import { ViewModel, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
|
|
21
21
|
import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
|
|
22
22
|
import { withRegistry } from '@dxos/storybook-utils';
|
|
23
23
|
|
|
24
|
+
import { translations } from '#translations';
|
|
25
|
+
|
|
24
26
|
import { useTestTableModel } from '../../testing';
|
|
25
|
-
import { translations } from '../../translations';
|
|
26
27
|
import { Table } from '../../types';
|
|
27
|
-
|
|
28
28
|
import { Table as TableComponent } from './Table';
|
|
29
29
|
|
|
30
30
|
const Example = Schema.Struct({
|
|
@@ -52,7 +52,7 @@ const Example = Schema.Struct({
|
|
|
52
52
|
title: 'Parent',
|
|
53
53
|
}),
|
|
54
54
|
}).pipe(
|
|
55
|
-
Type.object({ typename: `example.
|
|
55
|
+
Type.object({ typename: `com.example.type.${PublicKey.random().truncate()}`, version: '0.1.0' }),
|
|
56
56
|
Annotation.LabelAnnotation.set(['name']),
|
|
57
57
|
);
|
|
58
58
|
interface Example extends Schema.Schema.Type<typeof Example> {}
|
|
@@ -74,8 +74,8 @@ const StoryViewEditor = ({
|
|
|
74
74
|
invariant(Type.isMutable(schema));
|
|
75
75
|
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
76
76
|
invariant(view);
|
|
77
|
-
Obj.
|
|
78
|
-
|
|
77
|
+
Obj.update(view, (view) => {
|
|
78
|
+
view.query.ast = newQuery as Mutable<typeof newQuery>;
|
|
79
79
|
});
|
|
80
80
|
},
|
|
81
81
|
[schema, view],
|
|
@@ -119,7 +119,7 @@ const DefaultStory = () => {
|
|
|
119
119
|
|
|
120
120
|
return (
|
|
121
121
|
<div className='grow grid grid-cols-[1fr_350px]'>
|
|
122
|
-
<TableComponent.Root>
|
|
122
|
+
<TableComponent.Root ref={tableRef}>
|
|
123
123
|
<Panel.Root>
|
|
124
124
|
<Panel.Toolbar asChild>
|
|
125
125
|
<TableComponent.Toolbar
|
|
@@ -129,8 +129,7 @@ const DefaultStory = () => {
|
|
|
129
129
|
/>
|
|
130
130
|
</Panel.Toolbar>
|
|
131
131
|
<Panel.Content asChild>
|
|
132
|
-
<TableComponent.
|
|
133
|
-
ref={tableRef}
|
|
132
|
+
<TableComponent.Content
|
|
134
133
|
schema={schema}
|
|
135
134
|
model={model}
|
|
136
135
|
presentation={presentation}
|
|
@@ -143,16 +142,14 @@ const DefaultStory = () => {
|
|
|
143
142
|
<ScrollArea.Root orientation='vertical' classNames='border-l border-separator'>
|
|
144
143
|
<ScrollArea.Viewport>
|
|
145
144
|
<StoryViewEditor view={table.view.target} schema={schema} db={db} handleDeleteColumn={handleDeleteColumn} />
|
|
146
|
-
<
|
|
147
|
-
{JSON.stringify({ view: table.view.target, schema }, null, 2)}
|
|
148
|
-
</SyntaxHighlighter>
|
|
145
|
+
<JsonHighlighter data={{ view: table.view.target, schema }} classNames='text-xs' />
|
|
149
146
|
</ScrollArea.Viewport>
|
|
150
147
|
</ScrollArea.Root>
|
|
151
148
|
</div>
|
|
152
149
|
);
|
|
153
150
|
};
|
|
154
151
|
|
|
155
|
-
type
|
|
152
|
+
type DefaultStoryProps = { rows?: number };
|
|
156
153
|
|
|
157
154
|
//
|
|
158
155
|
// Story definitions.
|
|
@@ -175,10 +172,10 @@ const meta = {
|
|
|
175
172
|
const [schema] = await space.db.schemaRegistry.register([Example]);
|
|
176
173
|
const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: schema.typename });
|
|
177
174
|
const table = Table.make({ view, jsonSchema });
|
|
178
|
-
Obj.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
...
|
|
175
|
+
Obj.update(view, (view) => {
|
|
176
|
+
view.projection.fields = [
|
|
177
|
+
view.projection.fields.find((field) => field.path === 'name')!,
|
|
178
|
+
...view.projection.fields.filter((field) => field.path !== 'name'),
|
|
182
179
|
];
|
|
183
180
|
});
|
|
184
181
|
|
|
@@ -187,9 +184,9 @@ const meta = {
|
|
|
187
184
|
Array.from({ length: 10 }).map(() => {
|
|
188
185
|
return space.db.add(
|
|
189
186
|
Obj.make(schema, {
|
|
190
|
-
name:
|
|
191
|
-
status:
|
|
192
|
-
description:
|
|
187
|
+
name: random.lorem.sentence(),
|
|
188
|
+
status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
189
|
+
description: random.lorem.paragraph(),
|
|
193
190
|
}),
|
|
194
191
|
);
|
|
195
192
|
});
|
|
@@ -226,7 +223,7 @@ export const StaticSchema: StoryObj = {
|
|
|
226
223
|
const table = Table.make({ view, jsonSchema });
|
|
227
224
|
space.db.add(table);
|
|
228
225
|
|
|
229
|
-
const factory = createObjectFactory(space.db,
|
|
226
|
+
const factory = createObjectFactory(space.db, random as any);
|
|
230
227
|
await factory([
|
|
231
228
|
{ type: TestSchema.Person, count: 10 },
|
|
232
229
|
// { type: TestSchema.Organization, count: 1 },
|
|
@@ -252,7 +249,7 @@ const ContactWithArrayOfEmails = Schema.Struct({
|
|
|
252
249
|
),
|
|
253
250
|
}).pipe(
|
|
254
251
|
Type.object({
|
|
255
|
-
typename: 'dxos.
|
|
252
|
+
typename: 'org.dxos.type.contactWithArrayOfEmails',
|
|
256
253
|
version: '0.1.0',
|
|
257
254
|
}),
|
|
258
255
|
);
|
|
@@ -272,7 +269,7 @@ export const ArrayOfObjects: StoryObj = {
|
|
|
272
269
|
const table = Table.make({ view, jsonSchema });
|
|
273
270
|
space.db.add(table);
|
|
274
271
|
|
|
275
|
-
const factory = createObjectFactory(space.db,
|
|
272
|
+
const factory = createObjectFactory(space.db, random as any);
|
|
276
273
|
await factory([
|
|
277
274
|
// { type: TestSchema.Person, count: 10 },
|
|
278
275
|
// { type: TestSchema.Organization, count: 1 },
|
|
@@ -287,7 +284,7 @@ export const ArrayOfObjects: StoryObj = {
|
|
|
287
284
|
},
|
|
288
285
|
};
|
|
289
286
|
|
|
290
|
-
export const Tags: Meta<
|
|
287
|
+
export const Tags: Meta<DefaultStoryProps> = {
|
|
291
288
|
title: 'ui/react-ui-table/Table',
|
|
292
289
|
render: DefaultStory,
|
|
293
290
|
decorators: [
|
|
@@ -297,7 +294,7 @@ export const Tags: Meta<StoryProps> = {
|
|
|
297
294
|
createSpace: true,
|
|
298
295
|
onCreateSpace: async ({ space }) => {
|
|
299
296
|
// Configure schema.
|
|
300
|
-
const typename = 'example.
|
|
297
|
+
const typename = 'com.example.type.single-select';
|
|
301
298
|
const selectOptions = [
|
|
302
299
|
{ id: 'one', title: 'One', color: 'emerald' },
|
|
303
300
|
{ id: 'two', title: 'Two', color: 'blue' },
|
|
@@ -331,8 +328,8 @@ export const Tags: Meta<StoryProps> = {
|
|
|
331
328
|
Array.from({ length: 10 }).map(() => {
|
|
332
329
|
return space.db.add(
|
|
333
330
|
Obj.make(storedSchema, {
|
|
334
|
-
single:
|
|
335
|
-
multiple:
|
|
331
|
+
single: random.helpers.arrayElement([...selectOptionIds, undefined]),
|
|
332
|
+
multiple: random.helpers.randomSubset(selectOptionIds),
|
|
336
333
|
}),
|
|
337
334
|
);
|
|
338
335
|
});
|
|
@@ -2,458 +2,64 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Atom
|
|
6
|
-
import
|
|
7
|
-
import React, {
|
|
8
|
-
type JSX,
|
|
9
|
-
type MouseEvent,
|
|
10
|
-
type PropsWithChildren,
|
|
11
|
-
type Ref,
|
|
12
|
-
type WheelEvent,
|
|
13
|
-
forwardRef,
|
|
14
|
-
useCallback,
|
|
15
|
-
useContext,
|
|
16
|
-
useEffect,
|
|
17
|
-
useImperativeHandle,
|
|
18
|
-
useMemo,
|
|
19
|
-
useState,
|
|
20
|
-
} from 'react';
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
|
+
import { createContext } from '@radix-ui/react-context';
|
|
7
|
+
import React, { type PropsWithChildren, type RefObject, forwardRef, useImperativeHandle, useRef } from 'react';
|
|
21
8
|
|
|
22
|
-
import { type
|
|
23
|
-
import { log } from '@dxos/log';
|
|
24
|
-
import { useAttention } from '@dxos/react-ui-attention';
|
|
25
|
-
import {
|
|
26
|
-
type DxGridAxisMeta,
|
|
27
|
-
type DxGridElement,
|
|
28
|
-
type DxGridPlane,
|
|
29
|
-
type DxGridPlaneRange,
|
|
30
|
-
type DxGridPosition,
|
|
31
|
-
Grid,
|
|
32
|
-
type GridContentProps,
|
|
33
|
-
closestCell,
|
|
34
|
-
gridSeparatorBlockEnd,
|
|
35
|
-
gridSeparatorInlineEnd,
|
|
36
|
-
} from '@dxos/react-ui-grid';
|
|
37
|
-
import { DxEditRequest } from '@dxos/react-ui-grid';
|
|
38
|
-
import { mx, useComposableProps } from '@dxos/ui-theme';
|
|
39
|
-
import { type ComposableProps } from '@dxos/ui-types';
|
|
9
|
+
import { type DxGridAxisMeta, type DxGridPosition } from '@dxos/react-ui-grid';
|
|
40
10
|
|
|
41
|
-
import { type InsertRowResult
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import { TableToolbar } from '../TableToolbar';
|
|
45
|
-
|
|
46
|
-
import { ColumnActionsMenu } from './ColumnActionsMenu';
|
|
47
|
-
import { ColumnSettings } from './ColumnSettings';
|
|
48
|
-
import { RowActionsMenu } from './RowActionsMenu';
|
|
11
|
+
import { type InsertRowResult } from '../../model';
|
|
12
|
+
import { TableContent } from './TableContent';
|
|
13
|
+
import { TableToolbar } from './TableToolbar';
|
|
49
14
|
|
|
50
15
|
const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
|
|
51
16
|
const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
|
|
52
17
|
const emptyColumnMeta = Atom.make<DxGridAxisMeta>({ grid: {} });
|
|
53
18
|
|
|
54
19
|
//
|
|
55
|
-
//
|
|
20
|
+
// Context
|
|
56
21
|
//
|
|
57
22
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return <>{children}</>;
|
|
23
|
+
type TableContextValue = {
|
|
24
|
+
/** Mutable ref populated by Content so Root can expose the controller. */
|
|
25
|
+
controllerRef: RefObject<TableController>;
|
|
62
26
|
};
|
|
63
27
|
|
|
28
|
+
const [TableContextProvider, useTableContext] = createContext<TableContextValue>('Table');
|
|
29
|
+
|
|
64
30
|
//
|
|
65
|
-
//
|
|
31
|
+
// Controller
|
|
66
32
|
//
|
|
67
33
|
|
|
68
|
-
|
|
69
|
-
const emptyCellUpdateAtom = Atom.make<number>(0);
|
|
70
|
-
|
|
71
|
-
export type TableController = {
|
|
34
|
+
type TableController = {
|
|
72
35
|
update?: (cell?: DxGridPosition) => void;
|
|
73
36
|
handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
|
|
74
37
|
};
|
|
75
38
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
presentation?: TablePresentation;
|
|
80
|
-
// TODO(burdon): Factor out attention.
|
|
81
|
-
ignoreAttention?: boolean;
|
|
82
|
-
onCreate?: OnCreateHandler;
|
|
83
|
-
onRowClick?: (row: any) => void;
|
|
84
|
-
testId?: string;
|
|
85
|
-
}>;
|
|
86
|
-
|
|
87
|
-
const TableMainInner = <T extends Type.AnyEntity = Type.AnyEntity>(
|
|
88
|
-
{ schema, model, presentation, ignoreAttention, onCreate, onRowClick, testId, ...props }: TableMainProps<T>,
|
|
89
|
-
forwardedRef: Ref<TableController>,
|
|
90
|
-
) => {
|
|
91
|
-
const registry = useContext(RegistryContext);
|
|
92
|
-
const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
|
|
93
|
-
const { hasAttention } = useAttention(model?.id ?? 'table');
|
|
94
|
-
const modals = useMemo(() => new ModalController(registry), [registry]);
|
|
95
|
-
const columnMeta = useAtomValue(model?.columnMeta ?? emptyColumnMeta);
|
|
96
|
-
// Subscribe to rows atom to trigger re-render when rows change.
|
|
97
|
-
const rows = useAtomValue(model?.rows ?? emptyRowsAtom);
|
|
98
|
-
// Subscribe to cell update counter to trigger re-render when individual cells change.
|
|
99
|
-
const cellUpdateCounter = useAtomValue(model?.cellUpdate ?? emptyCellUpdateAtom);
|
|
100
|
-
// Derive column count from columnMeta (reactive) with fallback.
|
|
101
|
-
const columnCount = Object.keys(columnMeta.grid).length || model?.projection.getFields().length || 0;
|
|
102
|
-
|
|
103
|
-
const draftRowCount = model?.getDraftRowCount() ?? 0;
|
|
104
|
-
|
|
105
|
-
const frozen = useMemo(() => {
|
|
106
|
-
const noActionColumn =
|
|
107
|
-
model?.features.dataEditable === false &&
|
|
108
|
-
model?.features.schemaEditable === false &&
|
|
109
|
-
model.rowActions.length === 0;
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
frozenRowsStart: 1,
|
|
113
|
-
frozenRowsEnd: Math.max(1, draftRowCount),
|
|
114
|
-
frozenColsStart: model?.features.selection.enabled ? 1 : 0,
|
|
115
|
-
frozenColsEnd: noActionColumn ? 0 : 1,
|
|
116
|
-
};
|
|
117
|
-
}, [model, draftRowCount]);
|
|
118
|
-
|
|
119
|
-
const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
|
|
120
|
-
(range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
|
|
121
|
-
[presentation],
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
if (!dxGrid || !presentation) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
dxGrid.getCells = getCells;
|
|
130
|
-
}, [dxGrid, presentation, getCells]);
|
|
131
|
-
|
|
132
|
-
// Trigger grid update when rows or cell values change.
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
if (!dxGrid) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
dxGrid.updateCells(true);
|
|
139
|
-
dxGrid.requestUpdate();
|
|
140
|
-
}, [dxGrid, rows, cellUpdateCounter]);
|
|
141
|
-
|
|
142
|
-
const handleInsertRowResult = useCallback(
|
|
143
|
-
(insertResult?: InsertRowResult) => {
|
|
144
|
-
if (insertResult === 'draft') {
|
|
145
|
-
requestAnimationFrame(() => {
|
|
146
|
-
dxGrid?.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
|
|
147
|
-
dxGrid?.refocus();
|
|
148
|
-
});
|
|
149
|
-
} else {
|
|
150
|
-
requestAnimationFrame(() => {
|
|
151
|
-
dxGrid?.setFocus({ plane: 'grid', col: 0, row: model ? model.getRowCount() - 1 : 0 });
|
|
152
|
-
dxGrid?.refocus();
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
[model, dxGrid],
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
const handleSave = useCallback(() => {
|
|
160
|
-
dxGrid?.updateCells(true);
|
|
161
|
-
dxGrid?.requestUpdate();
|
|
162
|
-
}, [dxGrid]);
|
|
163
|
-
|
|
164
|
-
const handleSaveDraftRow = useCallback(
|
|
165
|
-
(rowIndex = 0) => {
|
|
166
|
-
if (model && dxGrid) {
|
|
167
|
-
const didCommitSuccessfully = model.commitDraftRow(rowIndex);
|
|
168
|
-
if (didCommitSuccessfully) {
|
|
169
|
-
requestAnimationFrame(() => {
|
|
170
|
-
dxGrid.scrollToEndRow();
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
[model, dxGrid],
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
const handleGridClick = useCallback(
|
|
179
|
-
(event: MouseEvent) => {
|
|
180
|
-
const cell = closestCell(event.target as HTMLElement);
|
|
181
|
-
if (cell) {
|
|
182
|
-
const { row: rowIndex, plane } = cell;
|
|
183
|
-
if (onRowClick) {
|
|
184
|
-
if (plane === 'grid') {
|
|
185
|
-
const row = model?.getRowAt(rowIndex);
|
|
186
|
-
row && onRowClick(row);
|
|
187
|
-
} else {
|
|
188
|
-
onRowClick(cell);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
193
|
-
model.selection.toggleSelectionForRowIndex(rowIndex);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (!modals) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const buttonSelector = Object.values(tableButtons)
|
|
202
|
-
.map((button) => `button[${button.attr}]`)
|
|
203
|
-
.join(',');
|
|
204
|
-
|
|
205
|
-
const tableButtonElement = (event.target as HTMLElement).closest(buttonSelector) as HTMLElement;
|
|
206
|
-
if (tableButtonElement) {
|
|
207
|
-
const button = Object.values(tableButtons).find((btn) => tableButtonElement.hasAttribute(btn.attr));
|
|
208
|
-
if (!button) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
modals.setTrigger(tableButtonElement);
|
|
213
|
-
const data = button.getData(tableButtonElement);
|
|
214
|
-
switch (data.type) {
|
|
215
|
-
case 'rowMenu': {
|
|
216
|
-
modals.showRowMenu(data.rowIndex);
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
case 'columnSettings': {
|
|
220
|
-
modals.showColumnMenu(data.fieldId);
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
case 'newColumn': {
|
|
224
|
-
modals.showColumnCreator();
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
case 'sort': {
|
|
228
|
-
model?.toggleSort(data.fieldId);
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
case 'saveDraftRow': {
|
|
232
|
-
handleSaveDraftRow();
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const controlSelector = Object.values(tableControls)
|
|
240
|
-
.map((control) => `input[${control.attr}]`)
|
|
241
|
-
.join(',');
|
|
242
|
-
|
|
243
|
-
const controlElement = (event.target as HTMLElement).closest(controlSelector) as HTMLElement;
|
|
244
|
-
if (controlElement) {
|
|
245
|
-
const control = Object.values(tableControls).find((ctrl) => controlElement.hasAttribute(ctrl.attr));
|
|
246
|
-
if (!control) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const data = control.getData(controlElement);
|
|
251
|
-
switch (data.type) {
|
|
252
|
-
case 'checkbox': {
|
|
253
|
-
if (data.header) {
|
|
254
|
-
model?.selection.setSelection(model.selection.allRowsSelected ? 'none' : 'all');
|
|
255
|
-
} else {
|
|
256
|
-
model?.selection.toggleSelectionForRowIndex(data.rowIndex);
|
|
257
|
-
}
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
case 'switch': {
|
|
261
|
-
if (model) {
|
|
262
|
-
model.updateCellData({ row: data.rowIndex, col: data.colIndex }, (value) => !value);
|
|
263
|
-
}
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
},
|
|
269
|
-
[model, modals, dxGrid],
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
|
|
273
|
-
(increment, delta, cell) => {
|
|
274
|
-
if (dxGrid && model) {
|
|
275
|
-
if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1 && increment !== 'col') {
|
|
276
|
-
handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
|
|
277
|
-
} else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
|
|
278
|
-
handleSaveDraftRow(cell.row);
|
|
279
|
-
handleInsertRowResult(model.insertRow());
|
|
280
|
-
} else {
|
|
281
|
-
dxGrid.refocus(increment, delta);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
[dxGrid, model],
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
|
|
289
|
-
(event) => {
|
|
290
|
-
if (!model?.features.dataEditable) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const cell = closestCell(event.target);
|
|
295
|
-
if (!model || !cell) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
39
|
+
//
|
|
40
|
+
// Root
|
|
41
|
+
//
|
|
298
42
|
|
|
299
|
-
|
|
300
|
-
if (event.metaKey || event.ctrlKey) {
|
|
301
|
-
switch (event.key) {
|
|
302
|
-
case 'c': {
|
|
303
|
-
// Copy focused cell's text content to clipboard
|
|
304
|
-
try {
|
|
305
|
-
const cellData = model.getCellData(cell);
|
|
306
|
-
const textContent = cellData?.toString() ?? '';
|
|
307
|
-
void navigator.clipboard.writeText(textContent);
|
|
308
|
-
event.preventDefault();
|
|
309
|
-
} catch (error) {
|
|
310
|
-
log.catch(error);
|
|
311
|
-
}
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
case 'v': {
|
|
315
|
-
// Paste clipboard content to focused cell
|
|
316
|
-
event.preventDefault();
|
|
317
|
-
void navigator.clipboard.readText().then((clipboardText) => {
|
|
318
|
-
try {
|
|
319
|
-
// Attempt to set the cell's content to clipboard content
|
|
320
|
-
model.setCellData(cell, String.trim(clipboardText).replace(/[\n\r]+/, ' '));
|
|
321
|
-
handleSave();
|
|
322
|
-
} catch {
|
|
323
|
-
// If validation fails, emit a DxEditRequest event with initialContent from clipboard
|
|
324
|
-
// TODO(thure): Should `dx-grid` expose a method like this?
|
|
325
|
-
const cellElement = (event.target as HTMLElement).closest(
|
|
326
|
-
'[data-dx-grid-action="cell"]',
|
|
327
|
-
) as HTMLElement;
|
|
328
|
-
if (cellElement) {
|
|
329
|
-
const rect = cellElement.getBoundingClientRect();
|
|
330
|
-
const editRequest = new DxEditRequest({
|
|
331
|
-
cellIndex: `${cell.plane},${cell.col},${cell.row}`,
|
|
332
|
-
cellBox: {
|
|
333
|
-
insetInlineStart: rect.left,
|
|
334
|
-
insetBlockStart: rect.top,
|
|
335
|
-
inlineSize: rect.width,
|
|
336
|
-
blockSize: rect.height,
|
|
337
|
-
},
|
|
338
|
-
cellElement,
|
|
339
|
-
initialContent: clipboardText,
|
|
340
|
-
});
|
|
341
|
-
cellElement.dispatchEvent(editRequest);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
break;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
43
|
+
const TABLE_ROOT_NAME = 'Table.Root';
|
|
349
44
|
|
|
350
|
-
|
|
351
|
-
case 'Backspace':
|
|
352
|
-
case 'Delete': {
|
|
353
|
-
try {
|
|
354
|
-
model.setCellData(cell, undefined);
|
|
355
|
-
event.preventDefault();
|
|
356
|
-
handleSave();
|
|
357
|
-
} catch {
|
|
358
|
-
// Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
|
|
359
|
-
}
|
|
360
|
-
break;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
},
|
|
364
|
-
[model, dxGrid, handleSave],
|
|
365
|
-
);
|
|
45
|
+
type TableRootProps = PropsWithChildren;
|
|
366
46
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (event.axis === 'col') {
|
|
370
|
-
const columnIndex = parseInt(event.index, 10);
|
|
371
|
-
model?.setColumnWidth(columnIndex, event.size);
|
|
372
|
-
}
|
|
373
|
-
},
|
|
374
|
-
[model],
|
|
375
|
-
);
|
|
47
|
+
const TableRoot = forwardRef<TableController, TableRootProps>(({ children }, forwardedRef) => {
|
|
48
|
+
const controllerRef = useRef<TableController>({});
|
|
376
49
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
},
|
|
383
|
-
[
|
|
50
|
+
useImperativeHandle(
|
|
51
|
+
forwardedRef,
|
|
52
|
+
() => ({
|
|
53
|
+
update: (cell) => controllerRef.current?.update?.(cell),
|
|
54
|
+
handleInsertRowResult: (result) => controllerRef.current?.handleInsertRowResult?.(result),
|
|
55
|
+
}),
|
|
56
|
+
[],
|
|
384
57
|
);
|
|
385
58
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (dxGrid && columns) {
|
|
389
|
-
dxGrid.scrollToColumn(columns - 1);
|
|
390
|
-
}
|
|
391
|
-
}, [model, dxGrid]);
|
|
59
|
+
return <TableContextProvider controllerRef={controllerRef}>{children}</TableContextProvider>;
|
|
60
|
+
});
|
|
392
61
|
|
|
393
|
-
|
|
394
|
-
* Provides an external controller that can be called to repaint the table.
|
|
395
|
-
*/
|
|
396
|
-
useImperativeHandle<TableController, TableController>(forwardedRef, () => {
|
|
397
|
-
if (!dxGrid || !presentation) {
|
|
398
|
-
return {};
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return {
|
|
402
|
-
update: (cell) => {
|
|
403
|
-
if (cell) {
|
|
404
|
-
dxGrid.updateIfWithinBounds(cell, true);
|
|
405
|
-
} else {
|
|
406
|
-
dxGrid.updateCells(true);
|
|
407
|
-
dxGrid.requestUpdate();
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
handleInsertRowResult,
|
|
411
|
-
};
|
|
412
|
-
}, [dxGrid, model, presentation]);
|
|
413
|
-
|
|
414
|
-
if (!model || !modals) {
|
|
415
|
-
return <span role='none' className='dx-attention-surface' />;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const { className, ...rest } = useComposableProps(props);
|
|
419
|
-
return (
|
|
420
|
-
<div {...rest} role='none' className={mx('dx-container relative', className)}>
|
|
421
|
-
<Grid.Root id={model.id ?? 'table-grid'}>
|
|
422
|
-
<TableValueEditor<T>
|
|
423
|
-
model={model}
|
|
424
|
-
modals={modals}
|
|
425
|
-
schema={schema}
|
|
426
|
-
onFocus={handleFocus}
|
|
427
|
-
onSave={handleSave}
|
|
428
|
-
onCreate={onCreate}
|
|
429
|
-
/>
|
|
430
|
-
<Grid.Content
|
|
431
|
-
className={mx('[--dx-grid-base:var(--base-surface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
432
|
-
frozen={frozen}
|
|
433
|
-
columns={columnMeta}
|
|
434
|
-
columnDefault={columnDefault}
|
|
435
|
-
rowDefault={rowDefault}
|
|
436
|
-
limitRows={rows.length}
|
|
437
|
-
limitColumns={columnCount}
|
|
438
|
-
overscroll='trap'
|
|
439
|
-
onAxisResize={handleAxisResize}
|
|
440
|
-
onClick={handleGridClick}
|
|
441
|
-
onKeyDownCapture={handleKeyDown}
|
|
442
|
-
onWheelCapture={handleWheel}
|
|
443
|
-
{...(testId && { 'data-testid': testId })}
|
|
444
|
-
ref={setDxGrid}
|
|
445
|
-
/>
|
|
446
|
-
<RowActionsMenu model={model} modals={modals} />
|
|
447
|
-
<ColumnActionsMenu model={model} modals={modals} />
|
|
448
|
-
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
449
|
-
</Grid.Root>
|
|
450
|
-
</div>
|
|
451
|
-
);
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
const TableMain = forwardRef(TableMainInner) as <T extends Type.AnyEntity = Type.AnyEntity>(
|
|
455
|
-
props: TableMainProps<T> & { ref?: Ref<TableController> },
|
|
456
|
-
) => JSX.Element;
|
|
62
|
+
TableRoot.displayName = TABLE_ROOT_NAME;
|
|
457
63
|
|
|
458
64
|
//
|
|
459
65
|
// Table
|
|
@@ -461,6 +67,10 @@ const TableMain = forwardRef(TableMainInner) as <T extends Type.AnyEntity = Type
|
|
|
461
67
|
|
|
462
68
|
export const Table = {
|
|
463
69
|
Root: TableRoot,
|
|
464
|
-
Main: TableMain,
|
|
465
70
|
Toolbar: TableToolbar,
|
|
71
|
+
Content: TableContent,
|
|
466
72
|
};
|
|
73
|
+
|
|
74
|
+
export type { TableController, TableRootProps };
|
|
75
|
+
|
|
76
|
+
export { useTableContext };
|