@dxos/react-ui-table 0.8.4-main.d05673bc65 → 0.8.4-main.dfabb4ec29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{chunk-BF54SSNL.mjs → chunk-FBURZBPH.mjs} +7 -7
- package/dist/lib/browser/chunk-FBURZBPH.mjs.map +7 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/browser/{chunk-PTHMYAAM.mjs → chunk-YGC7LZJN.mjs} +14 -70
- package/dist/lib/browser/chunk-YGC7LZJN.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +205 -247
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +6 -5
- 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-SIRDIPHZ.mjs → chunk-DKKMSVKP.mjs} +7 -7
- package/dist/lib/node-esm/chunk-DKKMSVKP.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-GI72MQSG.mjs → chunk-GKIWDEZ7.mjs} +14 -70
- package/dist/lib/node-esm/chunk-GKIWDEZ7.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +205 -247
- 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 +6 -5
- 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.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 +17 -0
- package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.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 +3 -3
- 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/translations.d.ts +19 -21
- package/dist/types/src/translations.d.ts.map +1 -1
- 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 +50 -59
- package/src/components/Table/ColumnActionsMenu.tsx +7 -6
- package/src/components/Table/DynamicTable.stories.tsx +13 -13
- package/src/components/Table/DynamicTable.tsx +5 -13
- package/src/components/Table/Relations.stories.tsx +71 -30
- package/src/components/Table/RowActionsMenu.tsx +3 -2
- package/src/components/Table/Table.stories.tsx +25 -27
- package/src/components/Table/Table.tsx +37 -426
- package/src/components/Table/TableContent.tsx +421 -0
- package/src/components/{TableToolbar → Table}/TableToolbar.tsx +34 -41
- package/src/components/TableCellEditor/CellValidationMessage.tsx +0 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +3 -2
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +9 -9
- package/src/components/TableCellEditor/TableCellEditor.tsx +10 -8
- package/src/components/index.ts +0 -1
- package/src/components/testing.ts +9 -9
- package/src/index.ts +0 -4
- package/src/model/table-model.test.ts +0 -1
- package/src/model/table-model.ts +9 -9
- package/src/model/table-presentation.test.ts +0 -1
- package/src/model/table-presentation.ts +0 -1
- package/src/playwright/smoke.spec.ts +3 -3
- package/src/testing/useTestTableModel.ts +3 -3
- package/src/translations.ts +19 -21
- package/src/types/Table.ts +1 -1
- package/src/util/dynamic-table.test.ts +3 -3
- package/src/util/dynamic-table.ts +4 -4
- package/dist/lib/browser/chunk-BF54SSNL.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-SIRDIPHZ.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
|
@@ -10,21 +10,22 @@ import { Annotation, type Database, Format, Obj, type QueryAST, Ref, Type } from
|
|
|
10
10
|
import { View } from '@dxos/echo';
|
|
11
11
|
import { type Mutable, PropertyMetaAnnotationId } from '@dxos/echo/internal';
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
13
|
-
import {
|
|
13
|
+
import { random } from '@dxos/random';
|
|
14
14
|
import { PublicKey } from '@dxos/react-client';
|
|
15
15
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
16
16
|
import { Panel, ScrollArea } from '@dxos/react-ui';
|
|
17
|
+
import { ViewEditor } from '@dxos/react-ui-form';
|
|
18
|
+
import { translations as formTranslations } from '@dxos/react-ui-form/translations';
|
|
19
|
+
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
17
20
|
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
21
|
import { ViewModel, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
|
|
21
22
|
import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
|
|
22
23
|
import { withRegistry } from '@dxos/storybook-utils';
|
|
23
24
|
|
|
25
|
+
import { translations } from '#translations';
|
|
26
|
+
|
|
24
27
|
import { useTestTableModel } from '../../testing';
|
|
25
|
-
import { translations } from '../../translations';
|
|
26
28
|
import { Table } from '../../types';
|
|
27
|
-
|
|
28
29
|
import { Table as TableComponent } from './Table';
|
|
29
30
|
|
|
30
31
|
const Example = Schema.Struct({
|
|
@@ -74,8 +75,8 @@ const StoryViewEditor = ({
|
|
|
74
75
|
invariant(Type.isMutable(schema));
|
|
75
76
|
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
76
77
|
invariant(view);
|
|
77
|
-
Obj.
|
|
78
|
-
|
|
78
|
+
Obj.update(view, (view) => {
|
|
79
|
+
view.query.ast = newQuery as Mutable<typeof newQuery>;
|
|
79
80
|
});
|
|
80
81
|
},
|
|
81
82
|
[schema, view],
|
|
@@ -119,7 +120,7 @@ const DefaultStory = () => {
|
|
|
119
120
|
|
|
120
121
|
return (
|
|
121
122
|
<div className='grow grid grid-cols-[1fr_350px]'>
|
|
122
|
-
<TableComponent.Root>
|
|
123
|
+
<TableComponent.Root ref={tableRef}>
|
|
123
124
|
<Panel.Root>
|
|
124
125
|
<Panel.Toolbar asChild>
|
|
125
126
|
<TableComponent.Toolbar
|
|
@@ -129,8 +130,7 @@ const DefaultStory = () => {
|
|
|
129
130
|
/>
|
|
130
131
|
</Panel.Toolbar>
|
|
131
132
|
<Panel.Content asChild>
|
|
132
|
-
<TableComponent.
|
|
133
|
-
ref={tableRef}
|
|
133
|
+
<TableComponent.Content
|
|
134
134
|
schema={schema}
|
|
135
135
|
model={model}
|
|
136
136
|
presentation={presentation}
|
|
@@ -143,16 +143,14 @@ const DefaultStory = () => {
|
|
|
143
143
|
<ScrollArea.Root orientation='vertical' classNames='border-l border-separator'>
|
|
144
144
|
<ScrollArea.Viewport>
|
|
145
145
|
<StoryViewEditor view={table.view.target} schema={schema} db={db} handleDeleteColumn={handleDeleteColumn} />
|
|
146
|
-
<
|
|
147
|
-
{JSON.stringify({ view: table.view.target, schema }, null, 2)}
|
|
148
|
-
</SyntaxHighlighter>
|
|
146
|
+
<JsonHighlighter data={{ view: table.view.target, schema }} classNames='text-xs' />
|
|
149
147
|
</ScrollArea.Viewport>
|
|
150
148
|
</ScrollArea.Root>
|
|
151
149
|
</div>
|
|
152
150
|
);
|
|
153
151
|
};
|
|
154
152
|
|
|
155
|
-
type
|
|
153
|
+
type DefaultStoryProps = { rows?: number };
|
|
156
154
|
|
|
157
155
|
//
|
|
158
156
|
// Story definitions.
|
|
@@ -175,10 +173,10 @@ const meta = {
|
|
|
175
173
|
const [schema] = await space.db.schemaRegistry.register([Example]);
|
|
176
174
|
const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: schema.typename });
|
|
177
175
|
const table = Table.make({ view, jsonSchema });
|
|
178
|
-
Obj.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
...
|
|
176
|
+
Obj.update(view, (view) => {
|
|
177
|
+
view.projection.fields = [
|
|
178
|
+
view.projection.fields.find((field) => field.path === 'name')!,
|
|
179
|
+
...view.projection.fields.filter((field) => field.path !== 'name'),
|
|
182
180
|
];
|
|
183
181
|
});
|
|
184
182
|
|
|
@@ -187,9 +185,9 @@ const meta = {
|
|
|
187
185
|
Array.from({ length: 10 }).map(() => {
|
|
188
186
|
return space.db.add(
|
|
189
187
|
Obj.make(schema, {
|
|
190
|
-
name:
|
|
191
|
-
status:
|
|
192
|
-
description:
|
|
188
|
+
name: random.lorem.sentence(),
|
|
189
|
+
status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
190
|
+
description: random.lorem.paragraph(),
|
|
193
191
|
}),
|
|
194
192
|
);
|
|
195
193
|
});
|
|
@@ -226,7 +224,7 @@ export const StaticSchema: StoryObj = {
|
|
|
226
224
|
const table = Table.make({ view, jsonSchema });
|
|
227
225
|
space.db.add(table);
|
|
228
226
|
|
|
229
|
-
const factory = createObjectFactory(space.db,
|
|
227
|
+
const factory = createObjectFactory(space.db, random as any);
|
|
230
228
|
await factory([
|
|
231
229
|
{ type: TestSchema.Person, count: 10 },
|
|
232
230
|
// { type: TestSchema.Organization, count: 1 },
|
|
@@ -252,7 +250,7 @@ const ContactWithArrayOfEmails = Schema.Struct({
|
|
|
252
250
|
),
|
|
253
251
|
}).pipe(
|
|
254
252
|
Type.object({
|
|
255
|
-
typename: 'org.dxos.type.
|
|
253
|
+
typename: 'org.dxos.type.contactWithArrayOfEmails',
|
|
256
254
|
version: '0.1.0',
|
|
257
255
|
}),
|
|
258
256
|
);
|
|
@@ -272,7 +270,7 @@ export const ArrayOfObjects: StoryObj = {
|
|
|
272
270
|
const table = Table.make({ view, jsonSchema });
|
|
273
271
|
space.db.add(table);
|
|
274
272
|
|
|
275
|
-
const factory = createObjectFactory(space.db,
|
|
273
|
+
const factory = createObjectFactory(space.db, random as any);
|
|
276
274
|
await factory([
|
|
277
275
|
// { type: TestSchema.Person, count: 10 },
|
|
278
276
|
// { type: TestSchema.Organization, count: 1 },
|
|
@@ -287,7 +285,7 @@ export const ArrayOfObjects: StoryObj = {
|
|
|
287
285
|
},
|
|
288
286
|
};
|
|
289
287
|
|
|
290
|
-
export const Tags: Meta<
|
|
288
|
+
export const Tags: Meta<DefaultStoryProps> = {
|
|
291
289
|
title: 'ui/react-ui-table/Table',
|
|
292
290
|
render: DefaultStory,
|
|
293
291
|
decorators: [
|
|
@@ -331,8 +329,8 @@ export const Tags: Meta<StoryProps> = {
|
|
|
331
329
|
Array.from({ length: 10 }).map(() => {
|
|
332
330
|
return space.db.add(
|
|
333
331
|
Obj.make(storedSchema, {
|
|
334
|
-
single:
|
|
335
|
-
multiple:
|
|
332
|
+
single: random.helpers.arrayElement([...selectOptionIds, undefined]),
|
|
333
|
+
multiple: random.helpers.randomSubset(selectOptionIds),
|
|
336
334
|
}),
|
|
337
335
|
);
|
|
338
336
|
});
|
|
@@ -2,457 +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 { composableProps, mx } 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
|
-
return (
|
|
419
|
-
<div {...composableProps(props, { role: 'none', className: 'dx-container relative' })}>
|
|
420
|
-
<Grid.Root id={model.id ?? 'table-grid'}>
|
|
421
|
-
<TableValueEditor<T>
|
|
422
|
-
model={model}
|
|
423
|
-
modals={modals}
|
|
424
|
-
schema={schema}
|
|
425
|
-
onFocus={handleFocus}
|
|
426
|
-
onSave={handleSave}
|
|
427
|
-
onCreate={onCreate}
|
|
428
|
-
/>
|
|
429
|
-
<Grid.Content
|
|
430
|
-
className={mx('[--dx-grid-base:var(--base-surface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
431
|
-
frozen={frozen}
|
|
432
|
-
columns={columnMeta}
|
|
433
|
-
columnDefault={columnDefault}
|
|
434
|
-
rowDefault={rowDefault}
|
|
435
|
-
limitRows={rows.length}
|
|
436
|
-
limitColumns={columnCount}
|
|
437
|
-
overscroll='trap'
|
|
438
|
-
onAxisResize={handleAxisResize}
|
|
439
|
-
onClick={handleGridClick}
|
|
440
|
-
onKeyDownCapture={handleKeyDown}
|
|
441
|
-
onWheelCapture={handleWheel}
|
|
442
|
-
{...(testId && { 'data-testid': testId })}
|
|
443
|
-
ref={setDxGrid}
|
|
444
|
-
/>
|
|
445
|
-
<RowActionsMenu model={model} modals={modals} />
|
|
446
|
-
<ColumnActionsMenu model={model} modals={modals} />
|
|
447
|
-
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
448
|
-
</Grid.Root>
|
|
449
|
-
</div>
|
|
450
|
-
);
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
const TableMain = forwardRef(TableMainInner) as <T extends Type.AnyEntity = Type.AnyEntity>(
|
|
454
|
-
props: TableMainProps<T> & { ref?: Ref<TableController> },
|
|
455
|
-
) => JSX.Element;
|
|
62
|
+
TableRoot.displayName = TABLE_ROOT_NAME;
|
|
456
63
|
|
|
457
64
|
//
|
|
458
65
|
// Table
|
|
@@ -460,6 +67,10 @@ const TableMain = forwardRef(TableMainInner) as <T extends Type.AnyEntity = Type
|
|
|
460
67
|
|
|
461
68
|
export const Table = {
|
|
462
69
|
Root: TableRoot,
|
|
463
|
-
Main: TableMain,
|
|
464
70
|
Toolbar: TableToolbar,
|
|
71
|
+
Content: TableContent,
|
|
465
72
|
};
|
|
73
|
+
|
|
74
|
+
export type { TableController, TableRootProps };
|
|
75
|
+
|
|
76
|
+
export { useTableContext };
|