@dxos/react-ui-table 0.8.3 → 0.8.4-main.1da679c
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-63UUF4IG.mjs +66 -0
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +7 -0
- package/dist/lib/browser/chunk-BBXDTWCX.mjs +1822 -0
- package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +816 -2152
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +124 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +3 -5
- package/dist/lib/node-esm/chunk-K734DJB7.mjs +67 -0
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs +1823 -0
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +816 -2152
- 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 +125 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +3 -5
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +50 -2
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +44 -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 +3 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +42 -3
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +8 -3
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +9 -15
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -40
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +14 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +8 -7
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +2 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +59 -21
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +10 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +3 -4
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/useTestTableModel.d.ts +22 -0
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +5 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- 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/types/schema.d.ts +28 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util/dynamic-table.d.ts +4 -5
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts +15 -1
- 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 +57 -44
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +15 -11
- package/src/components/Table/CreateRefPanel.tsx +30 -11
- package/src/components/Table/DynamicTable.stories.tsx +8 -5
- package/src/components/Table/DynamicTable.tsx +17 -10
- package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +56 -47
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +126 -159
- package/src/components/Table/Table.tsx +169 -123
- package/src/components/TableCellEditor/CellValidationMessage.tsx +7 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +158 -48
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +31 -47
- package/src/components/TableCellEditor/TableCellEditor.tsx +106 -111
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +7 -24
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddRow.ts +38 -0
- package/src/hooks/useTableModel.ts +21 -17
- package/src/index.ts +1 -2
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +6 -3
- package/src/model/table-model.test.ts +12 -9
- package/src/model/table-model.ts +288 -88
- package/src/model/table-presentation.test.ts +12 -8
- package/src/model/table-presentation.ts +263 -138
- package/src/model/table-sorting.ts +22 -13
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +137 -44
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +124 -0
- package/src/translations.ts +7 -5
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +73 -0
- package/src/util/dynamic-table.ts +24 -21
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +1 -1
- package/src/util/table-buttons.ts +28 -4
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
- package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
- package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
- package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -2739
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/types/index.cjs +0 -31
- package/dist/lib/node/types/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
- 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/types/table.d.ts +0 -56
- package/dist/types/src/types/table.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -14
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
- package/src/util/table.ts +0 -127
|
@@ -3,46 +3,47 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Schema } from 'effect/Schema';
|
|
6
|
+
import { trim } from 'effect/String';
|
|
6
7
|
import React, {
|
|
7
8
|
type MouseEvent,
|
|
8
9
|
type PropsWithChildren,
|
|
9
10
|
type WheelEvent,
|
|
10
11
|
forwardRef,
|
|
11
12
|
useCallback,
|
|
13
|
+
useEffect,
|
|
12
14
|
useImperativeHandle,
|
|
13
|
-
useState,
|
|
14
15
|
useMemo,
|
|
15
|
-
|
|
16
|
+
useState,
|
|
16
17
|
} from 'react';
|
|
17
18
|
|
|
18
|
-
import { type Client } from '@dxos/client';
|
|
19
|
-
|
|
20
|
-
import { invariant } from '@dxos/invariant';
|
|
21
|
-
import { Filter } from '@dxos/react-client/echo';
|
|
22
|
-
import { useTranslation } from '@dxos/react-ui';
|
|
19
|
+
import { type Client } from '@dxos/react-client';
|
|
20
|
+
// TODO(wittjosiah): Remove dependency on react-client.
|
|
23
21
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
24
22
|
import {
|
|
25
|
-
closestCell,
|
|
26
23
|
type DxGridElement,
|
|
27
|
-
type DxGridPosition,
|
|
28
|
-
type GridContentProps,
|
|
29
|
-
Grid,
|
|
30
24
|
type DxGridPlane,
|
|
31
25
|
type DxGridPlaneRange,
|
|
32
|
-
|
|
26
|
+
type DxGridPosition,
|
|
27
|
+
Grid,
|
|
28
|
+
type GridContentProps,
|
|
29
|
+
closestCell,
|
|
33
30
|
gridSeparatorBlockEnd,
|
|
31
|
+
gridSeparatorInlineEnd,
|
|
34
32
|
} from '@dxos/react-ui-grid';
|
|
33
|
+
import { DxEditRequest } from '@dxos/react-ui-grid';
|
|
35
34
|
import { mx } from '@dxos/react-ui-theme';
|
|
36
|
-
|
|
35
|
+
|
|
36
|
+
import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
37
|
+
import { tableButtons, tableControls } from '../../util';
|
|
38
|
+
import { type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
|
|
37
39
|
|
|
38
40
|
import { ColumnActionsMenu } from './ColumnActionsMenu';
|
|
39
41
|
import { ColumnSettings } from './ColumnSettings';
|
|
40
42
|
import { CreateRefPanel } from './CreateRefPanel';
|
|
41
43
|
import { RowActionsMenu } from './RowActionsMenu';
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
import { createOption, TableValueEditor, type TableCellEditorProps } from '../TableCellEditor';
|
|
44
|
+
|
|
45
|
+
const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
|
|
46
|
+
const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
|
|
46
47
|
|
|
47
48
|
//
|
|
48
49
|
// Table.Root
|
|
@@ -50,15 +51,17 @@ import { createOption, TableValueEditor, type TableCellEditorProps } from '../Ta
|
|
|
50
51
|
|
|
51
52
|
export type TableRootProps = PropsWithChildren<{ role?: string }>;
|
|
52
53
|
|
|
53
|
-
const TableRoot = ({ children, role }: TableRootProps) => {
|
|
54
|
+
const TableRoot = ({ children, role = 'article' }: TableRootProps) => {
|
|
54
55
|
return (
|
|
55
56
|
<div
|
|
56
57
|
role='none'
|
|
57
58
|
className={mx(
|
|
58
|
-
'relative !border-separator',
|
|
59
|
-
role === '
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
'relative !border-separator [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
|
|
60
|
+
role === 'card--popover' && 'popover-card-height',
|
|
61
|
+
role === 'section' && 'attention-surface',
|
|
62
|
+
role === 'card--intrinsic' && '[&_.dx-grid]:bs-[--dx-grid-content-block-size]',
|
|
63
|
+
['card--popover', 'section', 'card--extrinsic'].includes(role) && 'overflow-hidden',
|
|
64
|
+
['article', 'slide'].includes(role) && 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0',
|
|
62
65
|
)}
|
|
63
66
|
>
|
|
64
67
|
{children}
|
|
@@ -72,6 +75,7 @@ const TableRoot = ({ children, role }: TableRootProps) => {
|
|
|
72
75
|
|
|
73
76
|
export type TableController = {
|
|
74
77
|
update?: (cell?: DxGridPosition) => void;
|
|
78
|
+
handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
|
|
75
79
|
};
|
|
76
80
|
|
|
77
81
|
export type TableMainProps = {
|
|
@@ -88,9 +92,15 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
88
92
|
({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
|
|
89
93
|
const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
|
|
90
94
|
const { hasAttention } = useAttention(model?.id ?? 'table');
|
|
91
|
-
const { t } = useTranslation(translationKey);
|
|
92
95
|
const modals = useMemo(() => new ModalController(), []);
|
|
93
96
|
|
|
97
|
+
const draftRowCount = model?.getDraftRowCount() ?? 0;
|
|
98
|
+
|
|
99
|
+
const handleSave = useCallback(() => {
|
|
100
|
+
dxGrid?.updateCells(true);
|
|
101
|
+
dxGrid?.requestUpdate();
|
|
102
|
+
}, [dxGrid]);
|
|
103
|
+
|
|
94
104
|
const frozen = useMemo(() => {
|
|
95
105
|
const noActionColumn =
|
|
96
106
|
model?.features.dataEditable === false &&
|
|
@@ -99,10 +109,11 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
99
109
|
|
|
100
110
|
return {
|
|
101
111
|
frozenRowsStart: 1,
|
|
112
|
+
frozenRowsEnd: Math.max(1, draftRowCount),
|
|
102
113
|
frozenColsStart: model?.features.selection.enabled ? 1 : 0,
|
|
103
114
|
frozenColsEnd: noActionColumn ? 0 : 1,
|
|
104
115
|
};
|
|
105
|
-
}, [model]);
|
|
116
|
+
}, [model, draftRowCount]);
|
|
106
117
|
|
|
107
118
|
const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
|
|
108
119
|
(range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
|
|
@@ -116,37 +127,70 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
116
127
|
dxGrid.getCells = getCells;
|
|
117
128
|
}, [presentation, dxGrid, getCells]);
|
|
118
129
|
|
|
130
|
+
const handleInsertRowResult = useCallback(
|
|
131
|
+
(insertResult?: InsertRowResult) => {
|
|
132
|
+
if (insertResult === 'draft') {
|
|
133
|
+
requestAnimationFrame(() => {
|
|
134
|
+
dxGrid?.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
|
|
135
|
+
dxGrid?.refocus();
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
requestAnimationFrame(() => {
|
|
139
|
+
dxGrid?.setFocus({ plane: 'grid', col: 0, row: model ? model.getRowCount() - 1 : 0 });
|
|
140
|
+
dxGrid?.refocus();
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
[model, dxGrid],
|
|
145
|
+
);
|
|
146
|
+
|
|
119
147
|
/**
|
|
120
148
|
* Provides an external controller that can be called to repaint the table.
|
|
121
149
|
*/
|
|
122
|
-
useImperativeHandle<TableController, TableController>(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return {};
|
|
127
|
-
}
|
|
150
|
+
useImperativeHandle<TableController, TableController>(forwardedRef, () => {
|
|
151
|
+
if (!presentation || !dxGrid) {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
128
154
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
return {
|
|
156
|
+
update: (cell) => {
|
|
157
|
+
if (cell) {
|
|
158
|
+
dxGrid.updateIfWithinBounds(cell, true);
|
|
159
|
+
} else {
|
|
160
|
+
dxGrid.updateCells(true);
|
|
161
|
+
dxGrid.requestUpdate();
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
handleInsertRowResult,
|
|
165
|
+
};
|
|
166
|
+
}, [presentation, dxGrid, model]);
|
|
167
|
+
|
|
168
|
+
const handleSaveDraftRow = useCallback(
|
|
169
|
+
(rowIndex = 0) => {
|
|
170
|
+
if (model && dxGrid) {
|
|
171
|
+
const didCommitSuccessfully = model.commitDraftRow(rowIndex);
|
|
172
|
+
if (didCommitSuccessfully) {
|
|
173
|
+
requestAnimationFrame(() => {
|
|
174
|
+
dxGrid.scrollToEndRow();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
139
178
|
},
|
|
140
|
-
[
|
|
179
|
+
[model, dxGrid],
|
|
141
180
|
);
|
|
142
181
|
|
|
143
182
|
const handleGridClick = useCallback(
|
|
144
183
|
(event: MouseEvent) => {
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
184
|
+
const cell = closestCell(event.target as HTMLElement);
|
|
185
|
+
if (cell) {
|
|
186
|
+
const { row: rowIndex, plane } = cell;
|
|
147
187
|
if (onRowClick) {
|
|
148
|
-
|
|
149
|
-
|
|
188
|
+
if (plane === 'grid') {
|
|
189
|
+
const row = model?.getRowAt(rowIndex);
|
|
190
|
+
row && onRowClick(row);
|
|
191
|
+
} else {
|
|
192
|
+
onRowClick(cell);
|
|
193
|
+
}
|
|
150
194
|
}
|
|
151
195
|
|
|
152
196
|
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
@@ -188,6 +232,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
188
232
|
model?.sorting?.toggleSort(data.fieldId);
|
|
189
233
|
break;
|
|
190
234
|
}
|
|
235
|
+
case 'saveDraftRow': {
|
|
236
|
+
handleSaveDraftRow();
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
191
239
|
}
|
|
192
240
|
return;
|
|
193
241
|
}
|
|
@@ -222,36 +270,23 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
222
270
|
}
|
|
223
271
|
}
|
|
224
272
|
},
|
|
225
|
-
[model, modals],
|
|
273
|
+
[model, modals, dxGrid],
|
|
226
274
|
);
|
|
227
275
|
|
|
228
276
|
const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
|
|
229
|
-
(increment, delta) => {
|
|
230
|
-
if (dxGrid) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
(cell) => {
|
|
239
|
-
if (!model?.features.dataEditable) {
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// TODO(burdon): Insert row only if bottom row isn't completely blank already.
|
|
244
|
-
if (model && cell.row === model.getRowCount() - 1) {
|
|
245
|
-
model.insertRow(cell.row);
|
|
246
|
-
if (dxGrid) {
|
|
247
|
-
requestAnimationFrame(() => {
|
|
248
|
-
dxGrid?.scrollToRow(cell.row + 1);
|
|
249
|
-
dxGrid?.refocus('row', 1);
|
|
250
|
-
});
|
|
277
|
+
(increment, delta, cell) => {
|
|
278
|
+
if (dxGrid && model) {
|
|
279
|
+
if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1) {
|
|
280
|
+
handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
|
|
281
|
+
} else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
|
|
282
|
+
handleSaveDraftRow(cell.row);
|
|
283
|
+
handleInsertRowResult(model.insertRow());
|
|
284
|
+
} else {
|
|
285
|
+
dxGrid.refocus(increment, delta);
|
|
251
286
|
}
|
|
252
287
|
}
|
|
253
288
|
},
|
|
254
|
-
[
|
|
289
|
+
[dxGrid, model],
|
|
255
290
|
);
|
|
256
291
|
|
|
257
292
|
const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
|
|
@@ -265,15 +300,72 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
265
300
|
return;
|
|
266
301
|
}
|
|
267
302
|
|
|
303
|
+
// Handle Meta+C (Copy) and Meta+V (Paste) commands
|
|
304
|
+
if (event.metaKey || event.ctrlKey) {
|
|
305
|
+
switch (event.key) {
|
|
306
|
+
case 'c': {
|
|
307
|
+
// Copy focused cell's text content to clipboard
|
|
308
|
+
try {
|
|
309
|
+
const cellData = model.getCellData(cell);
|
|
310
|
+
const textContent = cellData?.toString() ?? '';
|
|
311
|
+
void navigator.clipboard.writeText(textContent);
|
|
312
|
+
event.preventDefault();
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.warn('Failed to copy cell content:', error);
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
case 'v': {
|
|
319
|
+
// Paste clipboard content to focused cell
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
void navigator.clipboard.readText().then((clipboardText) => {
|
|
322
|
+
try {
|
|
323
|
+
// Attempt to set the cell's content to clipboard content
|
|
324
|
+
model.setCellData(cell, trim(clipboardText));
|
|
325
|
+
handleSave();
|
|
326
|
+
} catch {
|
|
327
|
+
// If validation fails, emit a DxEditRequest event with initialContent from clipboard
|
|
328
|
+
// TODO(thure): Should `dx-grid` expose a method like this?
|
|
329
|
+
const cellElement = (event.target as HTMLElement).closest(
|
|
330
|
+
'[data-dx-grid-action="cell"]',
|
|
331
|
+
) as HTMLElement;
|
|
332
|
+
if (cellElement) {
|
|
333
|
+
const rect = cellElement.getBoundingClientRect();
|
|
334
|
+
const editRequest = new DxEditRequest({
|
|
335
|
+
cellIndex: `${cell.plane},${cell.col},${cell.row}`,
|
|
336
|
+
cellBox: {
|
|
337
|
+
insetInlineStart: rect.left,
|
|
338
|
+
insetBlockStart: rect.top,
|
|
339
|
+
inlineSize: rect.width,
|
|
340
|
+
blockSize: rect.height,
|
|
341
|
+
},
|
|
342
|
+
cellElement,
|
|
343
|
+
initialContent: clipboardText,
|
|
344
|
+
});
|
|
345
|
+
cellElement.dispatchEvent(editRequest);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
268
354
|
switch (event.key) {
|
|
269
355
|
case 'Backspace':
|
|
270
356
|
case 'Delete': {
|
|
271
|
-
|
|
357
|
+
try {
|
|
358
|
+
model.setCellData(cell, undefined);
|
|
359
|
+
event.preventDefault();
|
|
360
|
+
handleSave();
|
|
361
|
+
} catch {
|
|
362
|
+
// Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
|
|
363
|
+
}
|
|
272
364
|
break;
|
|
273
365
|
}
|
|
274
366
|
}
|
|
275
367
|
},
|
|
276
|
-
[model],
|
|
368
|
+
[model, dxGrid, handleSave],
|
|
277
369
|
);
|
|
278
370
|
|
|
279
371
|
const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
|
|
@@ -302,53 +394,6 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
302
394
|
}
|
|
303
395
|
}, [model, dxGrid]);
|
|
304
396
|
|
|
305
|
-
// TODO(burdon): Factor out?
|
|
306
|
-
// TODO(burdon): Generalize to handle other value types (e.g., enums).
|
|
307
|
-
const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
|
|
308
|
-
async ({ field, props }, text) => {
|
|
309
|
-
if (model && props.referenceSchema && field.referencePath) {
|
|
310
|
-
const space = model.space;
|
|
311
|
-
invariant(space);
|
|
312
|
-
|
|
313
|
-
let schema;
|
|
314
|
-
if (client) {
|
|
315
|
-
schema = client.graph.schemaRegistry.getSchema(props.referenceSchema);
|
|
316
|
-
}
|
|
317
|
-
if (!schema) {
|
|
318
|
-
schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (schema) {
|
|
322
|
-
const { objects } = await space.db.query(Filter.type(schema)).run();
|
|
323
|
-
const options = objects
|
|
324
|
-
.map((obj) => {
|
|
325
|
-
const value = getValue(obj, field.referencePath!);
|
|
326
|
-
if (!value || typeof value !== 'string') {
|
|
327
|
-
return undefined;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return {
|
|
331
|
-
label: value,
|
|
332
|
-
data: obj,
|
|
333
|
-
};
|
|
334
|
-
})
|
|
335
|
-
.filter(isNotFalsy);
|
|
336
|
-
|
|
337
|
-
return [
|
|
338
|
-
...options,
|
|
339
|
-
{
|
|
340
|
-
label: t('create new object label', { text }),
|
|
341
|
-
data: createOption(text),
|
|
342
|
-
},
|
|
343
|
-
];
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return [];
|
|
348
|
-
},
|
|
349
|
-
[model, client, t],
|
|
350
|
-
);
|
|
351
|
-
|
|
352
397
|
if (!model || !modals) {
|
|
353
398
|
return <span role='none' className='attention-surface' />;
|
|
354
399
|
}
|
|
@@ -359,21 +404,22 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
359
404
|
model={model}
|
|
360
405
|
modals={modals}
|
|
361
406
|
schema={schema}
|
|
362
|
-
onEnter={handleEnter}
|
|
363
407
|
onFocus={handleFocus}
|
|
364
|
-
|
|
408
|
+
onSave={handleSave}
|
|
409
|
+
client={client}
|
|
365
410
|
/>
|
|
366
411
|
<Grid.Content
|
|
367
412
|
className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
368
413
|
frozen={frozen}
|
|
369
|
-
// getCells={getCells}
|
|
370
414
|
columns={model.columnMeta.value}
|
|
415
|
+
columnDefault={columnDefault}
|
|
416
|
+
rowDefault={rowDefault}
|
|
371
417
|
limitRows={model.getRowCount() ?? 0}
|
|
372
|
-
limitColumns={model.
|
|
418
|
+
limitColumns={model.projection.fields.length}
|
|
373
419
|
overscroll='trap'
|
|
374
420
|
onAxisResize={handleAxisResize}
|
|
375
421
|
onClick={handleGridClick}
|
|
376
|
-
|
|
422
|
+
onKeyDownCapture={handleKeyDown}
|
|
377
423
|
onWheelCapture={handleWheel}
|
|
378
424
|
ref={setDxGrid}
|
|
379
425
|
/>
|
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { type GridScopedProps, useGridContext } from '@dxos/react-ui-grid';
|
|
8
8
|
|
|
9
9
|
export type CellValidationMessageProps = {
|
|
10
10
|
validationError: string | null;
|
|
11
|
+
variant?: 'error' | 'warning';
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export const CellValidationMessage = ({
|
|
14
15
|
validationError,
|
|
16
|
+
variant = 'error',
|
|
15
17
|
__gridScope,
|
|
16
18
|
}: GridScopedProps<CellValidationMessageProps>) => {
|
|
17
19
|
const { editing, editBox: box } = useGridContext('GridSheetCellEditor', __gridScope);
|
|
@@ -20,10 +22,13 @@ export const CellValidationMessage = ({
|
|
|
20
22
|
return null;
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
const bgClass = variant === 'error' ? 'bg-errorSurface' : 'bg-warningSurface';
|
|
26
|
+
const textClass = variant === 'error' ? 'text-errorSurfaceText' : 'text-warningSurfaceText';
|
|
27
|
+
|
|
23
28
|
return (
|
|
24
29
|
<div
|
|
25
30
|
role='none'
|
|
26
|
-
className=
|
|
31
|
+
className={`absolute ${bgClass} ${textClass} rounded-bs-sm text-xs p-1`}
|
|
27
32
|
style={{
|
|
28
33
|
...{ '--dx-gridCellWidth': `${box?.inlineSize ?? 200}px` },
|
|
29
34
|
zIndex: 10,
|