@dxos/react-ui-table 0.8.4-main.67995b8 → 0.8.4-main.6fa680abb7
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 +70 -0
- package/dist/lib/browser/chunk-BF54SSNL.mjs.map +7 -0
- package/dist/lib/browser/chunk-PTHMYAAM.mjs +1751 -0
- package/dist/lib/browser/chunk-PTHMYAAM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1185 -1140
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +31 -38
- 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-GI72MQSG.mjs +1753 -0
- package/dist/lib/node-esm/chunk-GI72MQSG.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs +71 -0
- package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1185 -1140
- 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 +31 -38
- 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/ColumnActionsMenu.d.ts +2 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -3
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +37 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +111 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +16 -12
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +108 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +13 -9
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +11 -18
- 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 +5 -5
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +23 -16
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +6 -6
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +10 -10
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +5 -8
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +17 -8
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +112 -30
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +5 -2
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +2 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +18 -34
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +4 -4
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +26 -0
- package/dist/types/src/types/Table.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +14 -12
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
- package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts +7 -0
- package/dist/types/src/util/query-ast.d.ts.map +1 -0
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/src/util/sort.d.ts +12 -0
- package/dist/types/src/util/sort.d.ts.map +1 -0
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +65 -64
- package/src/components/Table/ColumnActionsMenu.tsx +11 -7
- package/src/components/Table/ColumnSettings.tsx +9 -10
- package/src/components/Table/DynamicTable.stories.tsx +28 -24
- package/src/components/Table/DynamicTable.tsx +34 -28
- package/src/components/Table/Relations.stories.tsx +240 -0
- package/src/components/Table/RowActionsMenu.tsx +26 -17
- package/src/components/Table/Table.stories.tsx +148 -118
- package/src/components/Table/Table.tsx +347 -297
- package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -6
- package/src/components/TableCellEditor/FormCellEditor.tsx +134 -40
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +31 -66
- package/src/components/TableCellEditor/TableCellEditor.tsx +88 -238
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +54 -38
- package/src/components/testing.ts +39 -13
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +14 -14
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +36 -34
- package/src/model/modal-controller.ts +24 -41
- package/src/model/selection-model.ts +75 -43
- package/src/model/table-model.test.ts +92 -55
- package/src/model/table-model.ts +453 -175
- package/src/model/table-presentation.test.ts +70 -58
- package/src/model/table-presentation.ts +111 -64
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +171 -33
- package/src/testing/useTestTableModel.ts +46 -35
- package/src/translations.ts +3 -3
- package/src/types/Table.ts +79 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +46 -33
- package/src/util/index.ts +2 -1
- package/src/util/query-ast.ts +22 -0
- package/src/util/schema.ts +4 -4
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +4 -3
- package/src/util/table-controls.ts +6 -3
- package/dist/lib/browser/chunk-5XLSHLOG.mjs +0 -1751
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.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-Y66YLSTC.mjs +0 -1752
- package/dist/lib/node-esm/chunk-Y66YLSTC.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/Table/relations.stories.d.ts +0 -9
- package/dist/types/src/components/Table/relations.stories.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/model/table-sorting.d.ts +0 -53
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -8
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -11
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -86
- package/src/components/Table/relations.stories.tsx +0 -116
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/model/table-sorting.ts +0 -216
- package/src/types/schema.ts +0 -16
- package/src/util/table.ts +0 -49
|
@@ -2,414 +2,464 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import * as String from 'effect/String';
|
|
6
7
|
import React, {
|
|
8
|
+
type JSX,
|
|
7
9
|
type MouseEvent,
|
|
8
10
|
type PropsWithChildren,
|
|
11
|
+
type Ref,
|
|
9
12
|
type WheelEvent,
|
|
10
13
|
forwardRef,
|
|
11
14
|
useCallback,
|
|
15
|
+
useContext,
|
|
16
|
+
useEffect,
|
|
12
17
|
useImperativeHandle,
|
|
13
|
-
useState,
|
|
14
18
|
useMemo,
|
|
15
|
-
|
|
19
|
+
useState,
|
|
16
20
|
} from 'react';
|
|
17
21
|
|
|
18
|
-
import { type
|
|
19
|
-
import {
|
|
20
|
-
import { getValue } from '@dxos/echo-schema';
|
|
21
|
-
import { invariant } from '@dxos/invariant';
|
|
22
|
-
// TODO(wittjosiah): Remove dependency on react-client.
|
|
23
|
-
import { getSpace } from '@dxos/react-client/echo';
|
|
24
|
-
import { useTranslation } from '@dxos/react-ui';
|
|
22
|
+
import { type Type } from '@dxos/echo';
|
|
23
|
+
import { log } from '@dxos/log';
|
|
25
24
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
26
25
|
import {
|
|
27
|
-
|
|
26
|
+
type DxGridAxisMeta,
|
|
28
27
|
type DxGridElement,
|
|
29
|
-
type DxGridPosition,
|
|
30
|
-
type GridContentProps,
|
|
31
|
-
Grid,
|
|
32
28
|
type DxGridPlane,
|
|
33
29
|
type DxGridPlaneRange,
|
|
34
|
-
|
|
30
|
+
type DxGridPosition,
|
|
31
|
+
Grid,
|
|
32
|
+
type GridContentProps,
|
|
33
|
+
closestCell,
|
|
35
34
|
gridSeparatorBlockEnd,
|
|
35
|
+
gridSeparatorInlineEnd,
|
|
36
36
|
} from '@dxos/react-ui-grid';
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
37
|
+
import { DxEditRequest } from '@dxos/react-ui-grid';
|
|
38
|
+
import { composableProps, mx } from '@dxos/ui-theme';
|
|
39
|
+
import { type ComposableProps } from '@dxos/ui-types';
|
|
40
|
+
|
|
41
|
+
import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
42
|
+
import { tableButtons, tableControls } from '../../util';
|
|
43
|
+
import { type OnCreateHandler, type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
|
|
44
|
+
import { TableToolbar } from '../TableToolbar';
|
|
39
45
|
|
|
40
46
|
import { ColumnActionsMenu } from './ColumnActionsMenu';
|
|
41
47
|
import { ColumnSettings } from './ColumnSettings';
|
|
42
|
-
import { CreateRefPanel } from './CreateRefPanel';
|
|
43
48
|
import { RowActionsMenu } from './RowActionsMenu';
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
|
|
50
|
+
const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
|
|
51
|
+
const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
|
|
52
|
+
const emptyColumnMeta = Atom.make<DxGridAxisMeta>({ grid: {} });
|
|
48
53
|
|
|
49
54
|
//
|
|
50
55
|
// Table.Root
|
|
51
56
|
//
|
|
52
57
|
|
|
53
|
-
export type TableRootProps = PropsWithChildren
|
|
58
|
+
export type TableRootProps = PropsWithChildren;
|
|
54
59
|
|
|
55
|
-
const TableRoot = ({ children
|
|
56
|
-
return
|
|
57
|
-
<div
|
|
58
|
-
role='none'
|
|
59
|
-
className={mx(
|
|
60
|
-
'relative !border-separator [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
|
|
61
|
-
role === 'card--popover' && 'popover-card-height',
|
|
62
|
-
role === 'section' && 'attention-surface',
|
|
63
|
-
role === 'card--intrinsic' && '[&_.dx-grid]:bs-[--dx-grid-content-block-size]',
|
|
64
|
-
['card--popover', 'section', 'card--extrinsic'].includes(role) && 'overflow-hidden',
|
|
65
|
-
['article', 'slide'].includes(role) && 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0',
|
|
66
|
-
)}
|
|
67
|
-
>
|
|
68
|
-
{children}
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
60
|
+
const TableRoot = ({ children }: TableRootProps) => {
|
|
61
|
+
return <>{children}</>;
|
|
71
62
|
};
|
|
72
63
|
|
|
73
64
|
//
|
|
74
65
|
// Table.Main
|
|
75
66
|
//
|
|
76
67
|
|
|
68
|
+
const emptyRowsAtom = Atom.make<unknown[]>([]);
|
|
69
|
+
const emptyCellUpdateAtom = Atom.make<number>(0);
|
|
70
|
+
|
|
77
71
|
export type TableController = {
|
|
78
72
|
update?: (cell?: DxGridPosition) => void;
|
|
73
|
+
handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
|
|
79
74
|
};
|
|
80
75
|
|
|
81
|
-
export type TableMainProps = {
|
|
76
|
+
export type TableMainProps<T extends Type.AnyEntity = Type.AnyEntity> = ComposableProps<HTMLDivElement> & {
|
|
77
|
+
schema?: T;
|
|
82
78
|
model?: TableModel;
|
|
83
79
|
presentation?: TablePresentation;
|
|
84
|
-
|
|
85
|
-
client?: Client;
|
|
86
|
-
// TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
|
|
80
|
+
// TODO(burdon): Factor out attention.
|
|
87
81
|
ignoreAttention?: boolean;
|
|
82
|
+
onCreate?: OnCreateHandler;
|
|
88
83
|
onRowClick?: (row: any) => void;
|
|
84
|
+
testId?: string;
|
|
89
85
|
};
|
|
90
86
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
+
});
|
|
122
154
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
}
|
|
132
173
|
}
|
|
174
|
+
},
|
|
175
|
+
[model, dxGrid],
|
|
176
|
+
);
|
|
133
177
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
}, [presentation, dxGrid]);
|
|
145
|
-
|
|
146
|
-
const handleGridClick = useCallback(
|
|
147
|
-
(event: MouseEvent) => {
|
|
148
|
-
const rowIndex = safeParseInt((event.target as HTMLElement).closest('[aria-rowindex]')?.ariaRowIndex ?? '');
|
|
149
|
-
if (rowIndex != null) {
|
|
150
|
-
if (onRowClick) {
|
|
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') {
|
|
151
185
|
const row = model?.getRowAt(rowIndex);
|
|
152
186
|
row && onRowClick(row);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
156
|
-
model.selection.toggleSelectionForRowIndex(rowIndex);
|
|
187
|
+
} else {
|
|
188
|
+
onRowClick(cell);
|
|
157
189
|
}
|
|
158
190
|
}
|
|
159
191
|
|
|
160
|
-
if (
|
|
161
|
-
|
|
192
|
+
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
193
|
+
model.selection.toggleSelectionForRowIndex(rowIndex);
|
|
162
194
|
}
|
|
195
|
+
}
|
|
163
196
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
197
|
+
if (!modals) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
167
200
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!button) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
201
|
+
const buttonSelector = Object.values(tableButtons)
|
|
202
|
+
.map((button) => `button[${button.attr}]`)
|
|
203
|
+
.join(',');
|
|
174
204
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
modals.showRowMenu(data.rowIndex);
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
case 'columnSettings': {
|
|
183
|
-
modals.showColumnMenu(data.fieldId);
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
case 'newColumn': {
|
|
187
|
-
modals.showColumnCreator();
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
case 'sort': {
|
|
191
|
-
model?.sorting?.toggleSort(data.fieldId);
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
case 'saveDraftRow': {
|
|
195
|
-
if (model) {
|
|
196
|
-
const didCommitSuccessfully = model.commitDraftRow(data.rowIndex);
|
|
197
|
-
if (dxGrid && didCommitSuccessfully) {
|
|
198
|
-
requestAnimationFrame(() => {
|
|
199
|
-
dxGrid.scrollToEndRow();
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
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) {
|
|
206
209
|
return;
|
|
207
210
|
}
|
|
208
211
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const control = Object.values(tableControls).find((ctrl) => controlElement.hasAttribute(ctrl.attr));
|
|
216
|
-
if (!control) {
|
|
217
|
-
return;
|
|
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
218
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
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;
|
|
236
234
|
}
|
|
237
235
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
241
238
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
dxGrid.refocus(increment, delta);
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
[dxGrid],
|
|
249
|
-
);
|
|
239
|
+
const controlSelector = Object.values(tableControls)
|
|
240
|
+
.map((control) => `input[${control.attr}]`)
|
|
241
|
+
.join(',');
|
|
250
242
|
|
|
251
|
-
|
|
252
|
-
(
|
|
253
|
-
|
|
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) {
|
|
254
247
|
return;
|
|
255
248
|
}
|
|
256
249
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
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
265
|
}
|
|
266
266
|
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
[model, modals, dxGrid],
|
|
270
|
+
);
|
|
270
271
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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);
|
|
275
282
|
}
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
[dxGrid, model],
|
|
286
|
+
);
|
|
276
287
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
+
}
|
|
281
298
|
|
|
299
|
+
// Handle Meta+C (Copy) and Meta+V (Paste) commands
|
|
300
|
+
if (event.metaKey || event.ctrlKey) {
|
|
282
301
|
switch (event.key) {
|
|
283
|
-
case '
|
|
284
|
-
|
|
302
|
+
case 'c': {
|
|
303
|
+
// Copy focused cell's text content to clipboard
|
|
285
304
|
try {
|
|
286
|
-
model.
|
|
305
|
+
const cellData = model.getCellData(cell);
|
|
306
|
+
const textContent = cellData?.toString() ?? '';
|
|
307
|
+
void navigator.clipboard.writeText(textContent);
|
|
287
308
|
event.preventDefault();
|
|
288
|
-
} catch {
|
|
289
|
-
|
|
309
|
+
} catch (error) {
|
|
310
|
+
log.catch(error);
|
|
290
311
|
}
|
|
291
312
|
break;
|
|
292
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
|
+
}
|
|
293
347
|
}
|
|
294
|
-
}
|
|
295
|
-
[model],
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
|
|
299
|
-
(event) => {
|
|
300
|
-
if (event.axis === 'col') {
|
|
301
|
-
const columnIndex = parseInt(event.index, 10);
|
|
302
|
-
model?.setColumnWidth(columnIndex, event.size);
|
|
303
|
-
}
|
|
304
|
-
},
|
|
305
|
-
[model],
|
|
306
|
-
);
|
|
348
|
+
}
|
|
307
349
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
350
|
+
switch (event.key) {
|
|
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;
|
|
312
361
|
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
[model, dxGrid, handleSave],
|
|
365
|
+
);
|
|
316
366
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
|
|
367
|
+
const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
|
|
368
|
+
(event) => {
|
|
369
|
+
if (event.axis === 'col') {
|
|
370
|
+
const columnIndex = parseInt(event.index, 10);
|
|
371
|
+
model?.setColumnWidth(columnIndex, event.size);
|
|
321
372
|
}
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// TODO(burdon): Generalize to handle other value types (e.g., enums).
|
|
326
|
-
const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
|
|
327
|
-
async ({ field, props }, text) => {
|
|
328
|
-
if (model && props.referenceSchema && field.referencePath) {
|
|
329
|
-
const space = getSpace(model.view);
|
|
330
|
-
invariant(space);
|
|
331
|
-
|
|
332
|
-
let schema;
|
|
333
|
-
if (client) {
|
|
334
|
-
schema = client.graph.schemaRegistry.getSchema(props.referenceSchema);
|
|
335
|
-
}
|
|
336
|
-
if (!schema) {
|
|
337
|
-
schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
|
|
338
|
-
}
|
|
373
|
+
},
|
|
374
|
+
[model],
|
|
375
|
+
);
|
|
339
376
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
377
|
+
const handleWheel = useCallback(
|
|
378
|
+
(event: WheelEvent) => {
|
|
379
|
+
if (!ignoreAttention && !hasAttention) {
|
|
380
|
+
event.stopPropagation();
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
[hasAttention, ignoreAttention],
|
|
384
|
+
);
|
|
348
385
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
}
|
|
386
|
+
const handleNewColumn = useCallback(() => {
|
|
387
|
+
const columns = model?.getColumnCount();
|
|
388
|
+
if (dxGrid && columns) {
|
|
389
|
+
dxGrid.scrollToColumn(columns - 1);
|
|
390
|
+
}
|
|
391
|
+
}, [model, dxGrid]);
|
|
392
|
+
|
|
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
|
+
}
|
|
365
400
|
|
|
366
|
-
|
|
401
|
+
return {
|
|
402
|
+
update: (cell) => {
|
|
403
|
+
if (cell) {
|
|
404
|
+
dxGrid.updateIfWithinBounds(cell, true);
|
|
405
|
+
} else {
|
|
406
|
+
dxGrid.updateCells(true);
|
|
407
|
+
dxGrid.requestUpdate();
|
|
408
|
+
}
|
|
367
409
|
},
|
|
368
|
-
|
|
369
|
-
|
|
410
|
+
handleInsertRowResult,
|
|
411
|
+
};
|
|
412
|
+
}, [dxGrid, model, presentation]);
|
|
370
413
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (!model || !modals) {
|
|
376
|
-
return <span role='none' className='attention-surface' />;
|
|
377
|
-
}
|
|
414
|
+
if (!model || !modals) {
|
|
415
|
+
return <span role='none' className='dx-attention-surface' />;
|
|
416
|
+
}
|
|
378
417
|
|
|
379
|
-
|
|
418
|
+
return (
|
|
419
|
+
<div {...composableProps(props, { role: 'none', className: 'dx-container relative' })}>
|
|
380
420
|
<Grid.Root id={model.id ?? 'table-grid'}>
|
|
381
|
-
<TableValueEditor
|
|
421
|
+
<TableValueEditor<T>
|
|
382
422
|
model={model}
|
|
383
423
|
modals={modals}
|
|
384
424
|
schema={schema}
|
|
385
|
-
onEnter={handleEnter}
|
|
386
425
|
onFocus={handleFocus}
|
|
387
|
-
onQuery={handleQuery}
|
|
388
426
|
onSave={handleSave}
|
|
427
|
+
onCreate={onCreate}
|
|
389
428
|
/>
|
|
390
429
|
<Grid.Content
|
|
391
|
-
className={mx('[--dx-grid-base:var(--
|
|
430
|
+
className={mx('[--dx-grid-base:var(--base-surface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
392
431
|
frozen={frozen}
|
|
393
|
-
columns={
|
|
394
|
-
|
|
395
|
-
|
|
432
|
+
columns={columnMeta}
|
|
433
|
+
columnDefault={columnDefault}
|
|
434
|
+
rowDefault={rowDefault}
|
|
435
|
+
limitRows={rows.length}
|
|
436
|
+
limitColumns={columnCount}
|
|
396
437
|
overscroll='trap'
|
|
397
438
|
onAxisResize={handleAxisResize}
|
|
398
439
|
onClick={handleGridClick}
|
|
399
440
|
onKeyDownCapture={handleKeyDown}
|
|
400
441
|
onWheelCapture={handleWheel}
|
|
442
|
+
{...(testId && { 'data-testid': testId })}
|
|
401
443
|
ref={setDxGrid}
|
|
402
444
|
/>
|
|
403
445
|
<RowActionsMenu model={model} modals={modals} />
|
|
404
446
|
<ColumnActionsMenu model={model} modals={modals} />
|
|
405
447
|
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
406
|
-
<CreateRefPanel model={model} modals={modals} />
|
|
407
448
|
</Grid.Root>
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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;
|
|
456
|
+
|
|
457
|
+
//
|
|
458
|
+
// Table
|
|
459
|
+
//
|
|
411
460
|
|
|
412
461
|
export const Table = {
|
|
413
462
|
Root: TableRoot,
|
|
414
463
|
Main: TableMain,
|
|
464
|
+
Toolbar: TableToolbar,
|
|
415
465
|
};
|