@dxos/react-ui-table 0.8.4-main.c1de068 → 0.8.4-main.c85a9c8dae
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-DSR2MRZB.mjs +80 -0
- package/dist/lib/browser/chunk-DSR2MRZB.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 +1190 -1139
- 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-HTZJSULI.mjs +81 -0
- package/dist/lib/node-esm/chunk-HTZJSULI.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1190 -1139
- 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 +17 -13
- 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 +31 -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 +61 -56
- 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 +33 -27
- package/src/components/Table/Relations.stories.tsx +240 -0
- package/src/components/Table/RowActionsMenu.tsx +26 -17
- package/src/components/Table/Table.stories.tsx +146 -116
- package/src/components/Table/Table.tsx +353 -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 +95 -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,409 +2,465 @@
|
|
|
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 { mx, useComposableProps } 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<{
|
|
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;
|
|
89
|
-
|
|
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
|
+
);
|
|
90
123
|
|
|
91
|
-
|
|
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
|
-
return;
|
|
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
|
-
|
|
285
|
-
|
|
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
|
+
});
|
|
286
345
|
break;
|
|
287
346
|
}
|
|
288
347
|
}
|
|
289
|
-
}
|
|
290
|
-
[model],
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
|
|
294
|
-
(event) => {
|
|
295
|
-
if (event.axis === 'col') {
|
|
296
|
-
const columnIndex = parseInt(event.index, 10);
|
|
297
|
-
model?.setColumnWidth(columnIndex, event.size);
|
|
298
|
-
}
|
|
299
|
-
},
|
|
300
|
-
[model],
|
|
301
|
-
);
|
|
348
|
+
}
|
|
302
349
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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;
|
|
307
361
|
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
[model, dxGrid, handleSave],
|
|
365
|
+
);
|
|
311
366
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
|
|
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);
|
|
316
372
|
}
|
|
317
|
-
},
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// TODO(burdon): Generalize to handle other value types (e.g., enums).
|
|
321
|
-
const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
|
|
322
|
-
async ({ field, props }, text) => {
|
|
323
|
-
if (model && props.referenceSchema && field.referencePath) {
|
|
324
|
-
const space = getSpace(model.view);
|
|
325
|
-
invariant(space);
|
|
326
|
-
|
|
327
|
-
let schema;
|
|
328
|
-
if (client) {
|
|
329
|
-
schema = client.graph.schemaRegistry.getSchema(props.referenceSchema);
|
|
330
|
-
}
|
|
331
|
-
if (!schema) {
|
|
332
|
-
schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
|
|
333
|
-
}
|
|
373
|
+
},
|
|
374
|
+
[model],
|
|
375
|
+
);
|
|
334
376
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
377
|
+
const handleWheel = useCallback(
|
|
378
|
+
(event: WheelEvent) => {
|
|
379
|
+
if (!ignoreAttention && !hasAttention) {
|
|
380
|
+
event.stopPropagation();
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
[hasAttention, ignoreAttention],
|
|
384
|
+
);
|
|
343
385
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
}
|
|
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
|
+
}
|
|
360
400
|
|
|
361
|
-
|
|
401
|
+
return {
|
|
402
|
+
update: (cell) => {
|
|
403
|
+
if (cell) {
|
|
404
|
+
dxGrid.updateIfWithinBounds(cell, true);
|
|
405
|
+
} else {
|
|
406
|
+
dxGrid.updateCells(true);
|
|
407
|
+
dxGrid.requestUpdate();
|
|
408
|
+
}
|
|
362
409
|
},
|
|
363
|
-
|
|
364
|
-
|
|
410
|
+
handleInsertRowResult,
|
|
411
|
+
};
|
|
412
|
+
}, [dxGrid, model, presentation]);
|
|
365
413
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (!model || !modals) {
|
|
371
|
-
return <span role='none' className='attention-surface' />;
|
|
372
|
-
}
|
|
414
|
+
if (!model || !modals) {
|
|
415
|
+
return <span role='none' className='dx-attention-surface' />;
|
|
416
|
+
}
|
|
373
417
|
|
|
374
|
-
|
|
418
|
+
const { className, ...rest } = useComposableProps(props);
|
|
419
|
+
return (
|
|
420
|
+
<div {...rest} role='none' className={mx('dx-container relative', className)}>
|
|
375
421
|
<Grid.Root id={model.id ?? 'table-grid'}>
|
|
376
|
-
<TableValueEditor
|
|
422
|
+
<TableValueEditor<T>
|
|
377
423
|
model={model}
|
|
378
424
|
modals={modals}
|
|
379
425
|
schema={schema}
|
|
380
|
-
onEnter={handleEnter}
|
|
381
426
|
onFocus={handleFocus}
|
|
382
|
-
onQuery={handleQuery}
|
|
383
427
|
onSave={handleSave}
|
|
428
|
+
onCreate={onCreate}
|
|
384
429
|
/>
|
|
385
430
|
<Grid.Content
|
|
386
|
-
className={mx('[--dx-grid-base:var(--
|
|
431
|
+
className={mx('[--dx-grid-base:var(--base-surface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
387
432
|
frozen={frozen}
|
|
388
|
-
columns={
|
|
389
|
-
|
|
390
|
-
|
|
433
|
+
columns={columnMeta}
|
|
434
|
+
columnDefault={columnDefault}
|
|
435
|
+
rowDefault={rowDefault}
|
|
436
|
+
limitRows={rows.length}
|
|
437
|
+
limitColumns={columnCount}
|
|
391
438
|
overscroll='trap'
|
|
392
439
|
onAxisResize={handleAxisResize}
|
|
393
440
|
onClick={handleGridClick}
|
|
394
|
-
|
|
441
|
+
onKeyDownCapture={handleKeyDown}
|
|
395
442
|
onWheelCapture={handleWheel}
|
|
443
|
+
{...(testId && { 'data-testid': testId })}
|
|
396
444
|
ref={setDxGrid}
|
|
397
445
|
/>
|
|
398
446
|
<RowActionsMenu model={model} modals={modals} />
|
|
399
447
|
<ColumnActionsMenu model={model} modals={modals} />
|
|
400
448
|
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
401
|
-
<CreateRefPanel model={model} modals={modals} />
|
|
402
449
|
</Grid.Root>
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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;
|
|
457
|
+
|
|
458
|
+
//
|
|
459
|
+
// Table
|
|
460
|
+
//
|
|
406
461
|
|
|
407
462
|
export const Table = {
|
|
408
463
|
Root: TableRoot,
|
|
409
464
|
Main: TableMain,
|
|
465
|
+
Toolbar: TableToolbar,
|
|
410
466
|
};
|