@dxos/react-ui-table 0.8.4-main.2e9d522 → 0.8.4-main.3eb6e50203
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-GTGYBRJA.mjs +1748 -0
- package/dist/lib/browser/chunk-GTGYBRJA.mjs.map +7 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs +76 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1166 -1119
- 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-DPNSZHU5.mjs +1750 -0
- package/dist/lib/node-esm/chunk-DPNSZHU5.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs +77 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1166 -1119
- 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 +13 -9
- 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 +4 -4
- 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 +111 -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 +7 -7
- package/src/components/Table/DynamicTable.stories.tsx +28 -24
- package/src/components/Table/DynamicTable.tsx +28 -22
- package/src/components/Table/Relations.stories.tsx +237 -0
- package/src/components/Table/RowActionsMenu.tsx +26 -17
- package/src/components/Table/Table.stories.tsx +127 -106
- package/src/components/Table/Table.tsx +356 -296
- package/src/components/TableCellEditor/CellValidationMessage.tsx +2 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +128 -40
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +30 -66
- package/src/components/TableCellEditor/TableCellEditor.tsx +88 -238
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +49 -33
- 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 +452 -175
- package/src/model/table-presentation.test.ts +70 -58
- package/src/model/table-presentation.ts +103 -57
- 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 +90 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +45 -33
- package/src/util/index.ts +2 -1
- package/src/util/query-ast.ts +19 -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 +4 -1
- 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,49 +2,52 @@
|
|
|
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 } from '@dxos/ui-theme';
|
|
39
|
+
|
|
40
|
+
import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
41
|
+
import { tableButtons, tableControls } from '../../util';
|
|
42
|
+
import { type OnCreateHandler, type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
|
|
39
43
|
|
|
40
44
|
import { ColumnActionsMenu } from './ColumnActionsMenu';
|
|
41
45
|
import { ColumnSettings } from './ColumnSettings';
|
|
42
|
-
import { CreateRefPanel } from './CreateRefPanel';
|
|
43
46
|
import { RowActionsMenu } from './RowActionsMenu';
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
|
|
48
|
+
const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
|
|
49
|
+
const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
|
|
50
|
+
const emptyColumnMeta = Atom.make<DxGridAxisMeta>({ grid: {} });
|
|
48
51
|
|
|
49
52
|
//
|
|
50
53
|
// Table.Root
|
|
@@ -76,333 +79,390 @@ const TableRoot = ({ children, role = 'article' }: TableRootProps) => {
|
|
|
76
79
|
|
|
77
80
|
export type TableController = {
|
|
78
81
|
update?: (cell?: DxGridPosition) => void;
|
|
82
|
+
handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
|
|
79
83
|
};
|
|
80
84
|
|
|
81
|
-
export type TableMainProps = {
|
|
85
|
+
export type TableMainProps<T extends Type.Entity.Any = Type.Entity.Any> = {
|
|
86
|
+
schema?: T;
|
|
82
87
|
model?: TableModel;
|
|
83
88
|
presentation?: TablePresentation;
|
|
84
|
-
schema?: Schema.AnyNoContext;
|
|
85
|
-
client?: Client;
|
|
86
89
|
// TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
|
|
87
90
|
ignoreAttention?: boolean;
|
|
91
|
+
onCreate?: OnCreateHandler;
|
|
88
92
|
onRowClick?: (row: any) => void;
|
|
93
|
+
testId?: string;
|
|
89
94
|
};
|
|
90
95
|
|
|
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
|
-
|
|
96
|
+
const emptyRowsAtom = Atom.make<unknown[]>([]);
|
|
97
|
+
const emptyCellUpdateAtom = Atom.make<number>(0);
|
|
98
|
+
|
|
99
|
+
const TableMainInner = <T extends Type.Entity.Any = Type.Entity.Any>(
|
|
100
|
+
{ schema, model, presentation, ignoreAttention, onCreate, onRowClick, testId }: TableMainProps<T>,
|
|
101
|
+
forwardedRef: Ref<TableController>,
|
|
102
|
+
) => {
|
|
103
|
+
const registry = useContext(RegistryContext);
|
|
104
|
+
const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
|
|
105
|
+
const { hasAttention } = useAttention(model?.id ?? 'table');
|
|
106
|
+
const modals = useMemo(() => new ModalController(registry), [registry]);
|
|
107
|
+
const columnMeta = useAtomValue(model?.columnMeta ?? emptyColumnMeta);
|
|
108
|
+
// Subscribe to rows atom to trigger re-render when rows change.
|
|
109
|
+
const rows = useAtomValue(model?.rows ?? emptyRowsAtom);
|
|
110
|
+
// Subscribe to cell update counter to trigger re-render when individual cells change.
|
|
111
|
+
const cellUpdateCounter = useAtomValue(model?.cellUpdate ?? emptyCellUpdateAtom);
|
|
112
|
+
// Derive column count from columnMeta (reactive) with fallback.
|
|
113
|
+
const columnCount = Object.keys(columnMeta.grid).length || model?.projection.getFields().length || 0;
|
|
114
|
+
|
|
115
|
+
const draftRowCount = model?.getDraftRowCount() ?? 0;
|
|
116
|
+
|
|
117
|
+
const frozen = useMemo(() => {
|
|
118
|
+
const noActionColumn =
|
|
119
|
+
model?.features.dataEditable === false &&
|
|
120
|
+
model?.features.schemaEditable === false &&
|
|
121
|
+
model.rowActions.length === 0;
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
frozenRowsStart: 1,
|
|
125
|
+
frozenRowsEnd: Math.max(1, draftRowCount),
|
|
126
|
+
frozenColsStart: model?.features.selection.enabled ? 1 : 0,
|
|
127
|
+
frozenColsEnd: noActionColumn ? 0 : 1,
|
|
128
|
+
};
|
|
129
|
+
}, [model, draftRowCount]);
|
|
130
|
+
|
|
131
|
+
const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
|
|
132
|
+
(range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
|
|
133
|
+
[presentation],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!dxGrid || !presentation) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
dxGrid.getCells = getCells;
|
|
142
|
+
}, [dxGrid, presentation, getCells]);
|
|
143
|
+
|
|
144
|
+
// Trigger grid update when rows or cell values change.
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (!dxGrid) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
dxGrid.updateCells(true);
|
|
151
|
+
dxGrid.requestUpdate();
|
|
152
|
+
}, [dxGrid, rows, cellUpdateCounter]);
|
|
153
|
+
|
|
154
|
+
const handleInsertRowResult = useCallback(
|
|
155
|
+
(insertResult?: InsertRowResult) => {
|
|
156
|
+
if (insertResult === 'draft') {
|
|
157
|
+
requestAnimationFrame(() => {
|
|
158
|
+
dxGrid?.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
|
|
159
|
+
dxGrid?.refocus();
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
requestAnimationFrame(() => {
|
|
163
|
+
dxGrid?.setFocus({ plane: 'grid', col: 0, row: model ? model.getRowCount() - 1 : 0 });
|
|
164
|
+
dxGrid?.refocus();
|
|
165
|
+
});
|
|
122
166
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
},
|
|
168
|
+
[model, dxGrid],
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const handleSave = useCallback(() => {
|
|
172
|
+
dxGrid?.updateCells(true);
|
|
173
|
+
dxGrid?.requestUpdate();
|
|
174
|
+
}, [dxGrid]);
|
|
175
|
+
|
|
176
|
+
const handleSaveDraftRow = useCallback(
|
|
177
|
+
(rowIndex = 0) => {
|
|
178
|
+
if (model && dxGrid) {
|
|
179
|
+
const didCommitSuccessfully = model.commitDraftRow(rowIndex);
|
|
180
|
+
if (didCommitSuccessfully) {
|
|
181
|
+
requestAnimationFrame(() => {
|
|
182
|
+
dxGrid.scrollToEndRow();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
132
185
|
}
|
|
186
|
+
},
|
|
187
|
+
[model, dxGrid],
|
|
188
|
+
);
|
|
133
189
|
|
|
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) {
|
|
190
|
+
const handleGridClick = useCallback(
|
|
191
|
+
(event: MouseEvent) => {
|
|
192
|
+
const cell = closestCell(event.target as HTMLElement);
|
|
193
|
+
if (cell) {
|
|
194
|
+
const { row: rowIndex, plane } = cell;
|
|
195
|
+
if (onRowClick) {
|
|
196
|
+
if (plane === 'grid') {
|
|
151
197
|
const row = model?.getRowAt(rowIndex);
|
|
152
198
|
row && onRowClick(row);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
156
|
-
model.selection.toggleSelectionForRowIndex(rowIndex);
|
|
199
|
+
} else {
|
|
200
|
+
onRowClick(cell);
|
|
157
201
|
}
|
|
158
202
|
}
|
|
159
203
|
|
|
160
|
-
if (
|
|
161
|
-
|
|
204
|
+
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
205
|
+
model.selection.toggleSelectionForRowIndex(rowIndex);
|
|
162
206
|
}
|
|
207
|
+
}
|
|
163
208
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
209
|
+
if (!modals) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
167
212
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!button) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
213
|
+
const buttonSelector = Object.values(tableButtons)
|
|
214
|
+
.map((button) => `button[${button.attr}]`)
|
|
215
|
+
.join(',');
|
|
174
216
|
|
|
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
|
-
}
|
|
217
|
+
const tableButtonElement = (event.target as HTMLElement).closest(buttonSelector) as HTMLElement;
|
|
218
|
+
if (tableButtonElement) {
|
|
219
|
+
const button = Object.values(tableButtons).find((btn) => tableButtonElement.hasAttribute(btn.attr));
|
|
220
|
+
if (!button) {
|
|
206
221
|
return;
|
|
207
222
|
}
|
|
208
223
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const control = Object.values(tableControls).find((ctrl) => controlElement.hasAttribute(ctrl.attr));
|
|
216
|
-
if (!control) {
|
|
217
|
-
return;
|
|
224
|
+
modals.setTrigger(tableButtonElement);
|
|
225
|
+
const data = button.getData(tableButtonElement);
|
|
226
|
+
switch (data.type) {
|
|
227
|
+
case 'rowMenu': {
|
|
228
|
+
modals.showRowMenu(data.rowIndex);
|
|
229
|
+
break;
|
|
218
230
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
231
|
+
case 'columnSettings': {
|
|
232
|
+
modals.showColumnMenu(data.fieldId);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case 'newColumn': {
|
|
236
|
+
modals.showColumnCreator();
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case 'sort': {
|
|
240
|
+
model?.toggleSort(data.fieldId);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case 'saveDraftRow': {
|
|
244
|
+
handleSaveDraftRow();
|
|
245
|
+
break;
|
|
236
246
|
}
|
|
237
247
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
241
250
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
dxGrid.refocus(increment, delta);
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
[dxGrid],
|
|
249
|
-
);
|
|
251
|
+
const controlSelector = Object.values(tableControls)
|
|
252
|
+
.map((control) => `input[${control.attr}]`)
|
|
253
|
+
.join(',');
|
|
250
254
|
|
|
251
|
-
|
|
252
|
-
(
|
|
253
|
-
|
|
255
|
+
const controlElement = (event.target as HTMLElement).closest(controlSelector) as HTMLElement;
|
|
256
|
+
if (controlElement) {
|
|
257
|
+
const control = Object.values(tableControls).find((ctrl) => controlElement.hasAttribute(ctrl.attr));
|
|
258
|
+
if (!control) {
|
|
254
259
|
return;
|
|
255
260
|
}
|
|
256
261
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
262
|
+
const data = control.getData(controlElement);
|
|
263
|
+
switch (data.type) {
|
|
264
|
+
case 'checkbox': {
|
|
265
|
+
if (data.header) {
|
|
266
|
+
model?.selection.setSelection(model.selection.allRowsSelected ? 'none' : 'all');
|
|
267
|
+
} else {
|
|
268
|
+
model?.selection.toggleSelectionForRowIndex(data.rowIndex);
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case 'switch': {
|
|
273
|
+
if (model) {
|
|
274
|
+
model.updateCellData({ row: data.rowIndex, col: data.colIndex }, (value) => !value);
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
265
277
|
}
|
|
266
278
|
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
[model, modals, dxGrid],
|
|
282
|
+
);
|
|
270
283
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
284
|
+
const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
|
|
285
|
+
(increment, delta, cell) => {
|
|
286
|
+
if (dxGrid && model) {
|
|
287
|
+
if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1 && increment !== 'col') {
|
|
288
|
+
handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
|
|
289
|
+
} else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
|
|
290
|
+
handleSaveDraftRow(cell.row);
|
|
291
|
+
handleInsertRowResult(model.insertRow());
|
|
292
|
+
} else {
|
|
293
|
+
dxGrid.refocus(increment, delta);
|
|
275
294
|
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
[dxGrid, model],
|
|
298
|
+
);
|
|
276
299
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
300
|
+
const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
|
|
301
|
+
(event) => {
|
|
302
|
+
if (!model?.features.dataEditable) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
281
305
|
|
|
306
|
+
const cell = closestCell(event.target);
|
|
307
|
+
if (!model || !cell) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Handle Meta+C (Copy) and Meta+V (Paste) commands
|
|
312
|
+
if (event.metaKey || event.ctrlKey) {
|
|
282
313
|
switch (event.key) {
|
|
283
|
-
case '
|
|
284
|
-
|
|
285
|
-
|
|
314
|
+
case 'c': {
|
|
315
|
+
// Copy focused cell's text content to clipboard
|
|
316
|
+
try {
|
|
317
|
+
const cellData = model.getCellData(cell);
|
|
318
|
+
const textContent = cellData?.toString() ?? '';
|
|
319
|
+
void navigator.clipboard.writeText(textContent);
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
} catch (error) {
|
|
322
|
+
log.catch(error);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case 'v': {
|
|
327
|
+
// Paste clipboard content to focused cell
|
|
328
|
+
event.preventDefault();
|
|
329
|
+
void navigator.clipboard.readText().then((clipboardText) => {
|
|
330
|
+
try {
|
|
331
|
+
// Attempt to set the cell's content to clipboard content
|
|
332
|
+
model.setCellData(cell, String.trim(clipboardText).replace(/[\n\r]+/, ' '));
|
|
333
|
+
handleSave();
|
|
334
|
+
} catch {
|
|
335
|
+
// If validation fails, emit a DxEditRequest event with initialContent from clipboard
|
|
336
|
+
// TODO(thure): Should `dx-grid` expose a method like this?
|
|
337
|
+
const cellElement = (event.target as HTMLElement).closest(
|
|
338
|
+
'[data-dx-grid-action="cell"]',
|
|
339
|
+
) as HTMLElement;
|
|
340
|
+
if (cellElement) {
|
|
341
|
+
const rect = cellElement.getBoundingClientRect();
|
|
342
|
+
const editRequest = new DxEditRequest({
|
|
343
|
+
cellIndex: `${cell.plane},${cell.col},${cell.row}`,
|
|
344
|
+
cellBox: {
|
|
345
|
+
insetInlineStart: rect.left,
|
|
346
|
+
insetBlockStart: rect.top,
|
|
347
|
+
inlineSize: rect.width,
|
|
348
|
+
blockSize: rect.height,
|
|
349
|
+
},
|
|
350
|
+
cellElement,
|
|
351
|
+
initialContent: clipboardText,
|
|
352
|
+
});
|
|
353
|
+
cellElement.dispatchEvent(editRequest);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
286
357
|
break;
|
|
287
358
|
}
|
|
288
359
|
}
|
|
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
|
-
);
|
|
360
|
+
}
|
|
302
361
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
362
|
+
switch (event.key) {
|
|
363
|
+
case 'Backspace':
|
|
364
|
+
case 'Delete': {
|
|
365
|
+
try {
|
|
366
|
+
model.setCellData(cell, undefined);
|
|
367
|
+
event.preventDefault();
|
|
368
|
+
handleSave();
|
|
369
|
+
} catch {
|
|
370
|
+
// Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
307
373
|
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
[model, dxGrid, handleSave],
|
|
377
|
+
);
|
|
311
378
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
|
|
379
|
+
const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
|
|
380
|
+
(event) => {
|
|
381
|
+
if (event.axis === 'col') {
|
|
382
|
+
const columnIndex = parseInt(event.index, 10);
|
|
383
|
+
model?.setColumnWidth(columnIndex, event.size);
|
|
316
384
|
}
|
|
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
|
-
}
|
|
385
|
+
},
|
|
386
|
+
[model],
|
|
387
|
+
);
|
|
334
388
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
389
|
+
const handleWheel = useCallback(
|
|
390
|
+
(event: WheelEvent) => {
|
|
391
|
+
if (!ignoreAttention && !hasAttention) {
|
|
392
|
+
event.stopPropagation();
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
[hasAttention, ignoreAttention],
|
|
396
|
+
);
|
|
343
397
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
}
|
|
398
|
+
const handleNewColumn = useCallback(() => {
|
|
399
|
+
const columns = model?.getColumnCount();
|
|
400
|
+
if (dxGrid && columns) {
|
|
401
|
+
dxGrid.scrollToColumn(columns - 1);
|
|
402
|
+
}
|
|
403
|
+
}, [model, dxGrid]);
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Provides an external controller that can be called to repaint the table.
|
|
407
|
+
*/
|
|
408
|
+
useImperativeHandle<TableController, TableController>(forwardedRef, () => {
|
|
409
|
+
if (!dxGrid || !presentation) {
|
|
410
|
+
return {};
|
|
411
|
+
}
|
|
360
412
|
|
|
361
|
-
|
|
413
|
+
return {
|
|
414
|
+
update: (cell) => {
|
|
415
|
+
if (cell) {
|
|
416
|
+
dxGrid.updateIfWithinBounds(cell, true);
|
|
417
|
+
} else {
|
|
418
|
+
dxGrid.updateCells(true);
|
|
419
|
+
dxGrid.requestUpdate();
|
|
420
|
+
}
|
|
362
421
|
},
|
|
363
|
-
|
|
364
|
-
|
|
422
|
+
handleInsertRowResult,
|
|
423
|
+
};
|
|
424
|
+
}, [dxGrid, model, presentation]);
|
|
365
425
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
426
|
+
if (!model || !modals) {
|
|
427
|
+
return <span role='none' className='attention-surface' />;
|
|
428
|
+
}
|
|
369
429
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
430
|
+
return (
|
|
431
|
+
<Grid.Root id={model.id ?? 'table-grid'}>
|
|
432
|
+
<TableValueEditor<T>
|
|
433
|
+
model={model}
|
|
434
|
+
modals={modals}
|
|
435
|
+
schema={schema}
|
|
436
|
+
onFocus={handleFocus}
|
|
437
|
+
onSave={handleSave}
|
|
438
|
+
onCreate={onCreate}
|
|
439
|
+
/>
|
|
440
|
+
<Grid.Content
|
|
441
|
+
className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
442
|
+
frozen={frozen}
|
|
443
|
+
columns={columnMeta}
|
|
444
|
+
columnDefault={columnDefault}
|
|
445
|
+
rowDefault={rowDefault}
|
|
446
|
+
limitRows={rows.length}
|
|
447
|
+
limitColumns={columnCount}
|
|
448
|
+
overscroll='trap'
|
|
449
|
+
onAxisResize={handleAxisResize}
|
|
450
|
+
onClick={handleGridClick}
|
|
451
|
+
onKeyDownCapture={handleKeyDown}
|
|
452
|
+
onWheelCapture={handleWheel}
|
|
453
|
+
{...(testId && { 'data-testid': testId })}
|
|
454
|
+
ref={setDxGrid}
|
|
455
|
+
/>
|
|
456
|
+
<RowActionsMenu model={model} modals={modals} />
|
|
457
|
+
<ColumnActionsMenu model={model} modals={modals} />
|
|
458
|
+
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
459
|
+
</Grid.Root>
|
|
460
|
+
);
|
|
461
|
+
};
|
|
373
462
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
model={model}
|
|
378
|
-
modals={modals}
|
|
379
|
-
schema={schema}
|
|
380
|
-
onEnter={handleEnter}
|
|
381
|
-
onFocus={handleFocus}
|
|
382
|
-
onQuery={handleQuery}
|
|
383
|
-
onSave={handleSave}
|
|
384
|
-
/>
|
|
385
|
-
<Grid.Content
|
|
386
|
-
className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
387
|
-
frozen={frozen}
|
|
388
|
-
columns={model.columnMeta.value}
|
|
389
|
-
limitRows={model.getRowCount() ?? 0}
|
|
390
|
-
limitColumns={model.projection.fields.length}
|
|
391
|
-
overscroll='trap'
|
|
392
|
-
onAxisResize={handleAxisResize}
|
|
393
|
-
onClick={handleGridClick}
|
|
394
|
-
onKeyDown={handleKeyDown}
|
|
395
|
-
onWheelCapture={handleWheel}
|
|
396
|
-
ref={setDxGrid}
|
|
397
|
-
/>
|
|
398
|
-
<RowActionsMenu model={model} modals={modals} />
|
|
399
|
-
<ColumnActionsMenu model={model} modals={modals} />
|
|
400
|
-
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
401
|
-
<CreateRefPanel model={model} modals={modals} />
|
|
402
|
-
</Grid.Root>
|
|
403
|
-
);
|
|
404
|
-
},
|
|
405
|
-
);
|
|
463
|
+
const TableMain = forwardRef(TableMainInner) as <T extends Type.Entity.Any = Type.Entity.Any>(
|
|
464
|
+
props: TableMainProps<T> & { ref?: Ref<TableController> },
|
|
465
|
+
) => JSX.Element;
|
|
406
466
|
|
|
407
467
|
export const Table = {
|
|
408
468
|
Root: TableRoot,
|