@dxos/react-ui-table 0.8.4-main.c4373fc → 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 +1160 -1024
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +30 -42
- 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 +1160 -1024
- 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 +30 -42
- 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 +1 -2
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
- 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 +4 -18
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +12 -9
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +12 -12
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +71 -5
- 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/FormCellEditor.d.ts +10 -12
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +4 -4
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.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 +22 -15
- 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 +5 -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 +9 -9
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +5 -1
- 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 +105 -25
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +3 -1
- 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 -15
- 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 -11
- 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 -0
- 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/sort.d.ts +12 -0
- package/dist/types/src/util/sort.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +54 -54
- package/src/components/Table/ColumnActionsMenu.tsx +10 -6
- package/src/components/Table/ColumnSettings.tsx +8 -9
- package/src/components/Table/DynamicTable.stories.tsx +16 -16
- package/src/components/Table/DynamicTable.tsx +29 -21
- package/src/components/Table/Relations.stories.tsx +52 -60
- package/src/components/Table/RowActionsMenu.tsx +24 -15
- package/src/components/Table/Table.stories.tsx +114 -96
- package/src/components/Table/Table.tsx +326 -300
- package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -5
- package/src/components/TableCellEditor/FormCellEditor.tsx +117 -134
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +18 -13
- package/src/components/TableCellEditor/TableCellEditor.tsx +31 -31
- package/src/components/TableToolbar/TableToolbar.tsx +53 -37
- package/src/components/testing.ts +38 -12
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +8 -10
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +36 -34
- package/src/model/modal-controller.ts +23 -12
- package/src/model/selection-model.ts +75 -44
- package/src/model/table-model.test.ts +82 -54
- package/src/model/table-model.ts +436 -170
- package/src/model/table-presentation.test.ts +59 -49
- package/src/model/table-presentation.ts +43 -37
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +56 -10
- package/src/testing/useTestTableModel.ts +45 -38
- 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 +41 -28
- package/src/util/index.ts +2 -0
- package/src/util/query-ast.ts +22 -0
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +1 -1
- package/src/util/table-controls.ts +2 -2
- package/dist/lib/browser/chunk-4K7JPALT.mjs +0 -66
- package/dist/lib/browser/chunk-4K7JPALT.mjs.map +0 -7
- package/dist/lib/browser/chunk-OZTXLZGM.mjs +0 -1804
- package/dist/lib/browser/chunk-OZTXLZGM.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-MK6OVUF6.mjs +0 -67
- package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-S56Z4V4X.mjs +0 -1805
- package/dist/lib/node-esm/chunk-S56Z4V4X.mjs.map +0 -7
- package/dist/types/src/model/table-sorting.d.ts +0 -52
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/model/table-sorting.ts +0 -225
- package/src/types/schema.ts +0 -73
|
@@ -2,24 +2,28 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
|
|
6
6
|
import * as String from 'effect/String';
|
|
7
7
|
import React, {
|
|
8
|
+
type JSX,
|
|
8
9
|
type MouseEvent,
|
|
9
10
|
type PropsWithChildren,
|
|
11
|
+
type Ref,
|
|
10
12
|
type WheelEvent,
|
|
11
13
|
forwardRef,
|
|
12
14
|
useCallback,
|
|
15
|
+
useContext,
|
|
13
16
|
useEffect,
|
|
14
17
|
useImperativeHandle,
|
|
15
18
|
useMemo,
|
|
16
19
|
useState,
|
|
17
20
|
} from 'react';
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
import {
|
|
22
|
+
import { type Type } from '@dxos/echo';
|
|
23
|
+
import { log } from '@dxos/log';
|
|
21
24
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
22
25
|
import {
|
|
26
|
+
type DxGridAxisMeta,
|
|
23
27
|
type DxGridElement,
|
|
24
28
|
type DxGridPlane,
|
|
25
29
|
type DxGridPlaneRange,
|
|
@@ -31,11 +35,13 @@ import {
|
|
|
31
35
|
gridSeparatorInlineEnd,
|
|
32
36
|
} from '@dxos/react-ui-grid';
|
|
33
37
|
import { DxEditRequest } from '@dxos/react-ui-grid';
|
|
34
|
-
import { mx } from '@dxos/
|
|
38
|
+
import { mx, useComposableProps } from '@dxos/ui-theme';
|
|
39
|
+
import { type ComposableProps } from '@dxos/ui-types';
|
|
35
40
|
|
|
36
41
|
import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
37
42
|
import { tableButtons, tableControls } from '../../util';
|
|
38
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';
|
|
@@ -43,381 +49,392 @@ import { RowActionsMenu } from './RowActionsMenu';
|
|
|
43
49
|
|
|
44
50
|
const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
|
|
45
51
|
const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
|
|
52
|
+
const emptyColumnMeta = Atom.make<DxGridAxisMeta>({ grid: {} });
|
|
46
53
|
|
|
47
54
|
//
|
|
48
55
|
// Table.Root
|
|
49
56
|
//
|
|
50
57
|
|
|
51
|
-
export type TableRootProps = PropsWithChildren
|
|
58
|
+
export type TableRootProps = PropsWithChildren;
|
|
52
59
|
|
|
53
|
-
const TableRoot = ({ children
|
|
54
|
-
return
|
|
55
|
-
<div
|
|
56
|
-
role='none'
|
|
57
|
-
className={mx(
|
|
58
|
-
'relative !border-separator [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
|
|
59
|
-
role === 'card--popover' && 'popover-card-height',
|
|
60
|
-
role === 'section' && 'attention-surface',
|
|
61
|
-
role === 'card--intrinsic' && '[&_.dx-grid]:bs-[--dx-grid-content-block-size]',
|
|
62
|
-
['card--popover', 'section', 'card--extrinsic'].includes(role) && 'overflow-hidden',
|
|
63
|
-
['article', 'slide'].includes(role) && 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0',
|
|
64
|
-
)}
|
|
65
|
-
>
|
|
66
|
-
{children}
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
60
|
+
const TableRoot = ({ children }: TableRootProps) => {
|
|
61
|
+
return <>{children}</>;
|
|
69
62
|
};
|
|
70
63
|
|
|
71
64
|
//
|
|
72
65
|
// Table.Main
|
|
73
66
|
//
|
|
74
67
|
|
|
68
|
+
const emptyRowsAtom = Atom.make<unknown[]>([]);
|
|
69
|
+
const emptyCellUpdateAtom = Atom.make<number>(0);
|
|
70
|
+
|
|
75
71
|
export type TableController = {
|
|
76
72
|
update?: (cell?: DxGridPosition) => void;
|
|
77
73
|
handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
|
|
78
74
|
};
|
|
79
75
|
|
|
80
|
-
export type TableMainProps = {
|
|
76
|
+
export type TableMainProps<T extends Type.AnyEntity = Type.AnyEntity> = ComposableProps<{
|
|
77
|
+
schema?: T;
|
|
81
78
|
model?: TableModel;
|
|
82
79
|
presentation?: TablePresentation;
|
|
83
|
-
|
|
84
|
-
client?: Client;
|
|
85
|
-
// 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.
|
|
86
81
|
ignoreAttention?: boolean;
|
|
87
82
|
onCreate?: OnCreateHandler;
|
|
88
83
|
onRowClick?: (row: any) => void;
|
|
89
84
|
testId?: string;
|
|
90
|
-
}
|
|
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
|
+
);
|
|
91
123
|
|
|
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
|
-
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
if (!presentation || !dxGrid) {
|
|
126
|
-
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
|
+
});
|
|
127
154
|
}
|
|
128
|
-
|
|
129
|
-
|
|
155
|
+
},
|
|
156
|
+
[model, dxGrid],
|
|
157
|
+
);
|
|
130
158
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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) {
|
|
139
169
|
requestAnimationFrame(() => {
|
|
140
|
-
dxGrid
|
|
141
|
-
dxGrid?.refocus();
|
|
170
|
+
dxGrid.scrollToEndRow();
|
|
142
171
|
});
|
|
143
172
|
}
|
|
144
|
-
},
|
|
145
|
-
[model, dxGrid],
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Provides an external controller that can be called to repaint the table.
|
|
150
|
-
*/
|
|
151
|
-
useImperativeHandle<TableController, TableController>(forwardedRef, () => {
|
|
152
|
-
if (!presentation || !dxGrid) {
|
|
153
|
-
return {};
|
|
154
173
|
}
|
|
174
|
+
},
|
|
175
|
+
[model, dxGrid],
|
|
176
|
+
);
|
|
155
177
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
178
|
+
const handleGridClick = useCallback(
|
|
179
|
+
(event: MouseEvent) => {
|
|
180
|
+
const cell = closestCell(event.target as HTMLElement);
|
|
181
|
+
if (cell) {
|
|
182
|
+
const { row: rowIndex, plane } = cell;
|
|
183
|
+
if (onRowClick) {
|
|
184
|
+
if (plane === 'grid') {
|
|
185
|
+
const row = model?.getRowAt(rowIndex);
|
|
186
|
+
row && onRowClick(row);
|
|
160
187
|
} else {
|
|
161
|
-
|
|
162
|
-
dxGrid.requestUpdate();
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
handleInsertRowResult,
|
|
166
|
-
};
|
|
167
|
-
}, [presentation, dxGrid, model]);
|
|
168
|
-
|
|
169
|
-
const handleSaveDraftRow = useCallback(
|
|
170
|
-
(rowIndex = 0) => {
|
|
171
|
-
if (model && dxGrid) {
|
|
172
|
-
const didCommitSuccessfully = model.commitDraftRow(rowIndex);
|
|
173
|
-
if (didCommitSuccessfully) {
|
|
174
|
-
requestAnimationFrame(() => {
|
|
175
|
-
dxGrid.scrollToEndRow();
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
[model, dxGrid],
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
const handleGridClick = useCallback(
|
|
184
|
-
(event: MouseEvent) => {
|
|
185
|
-
const cell = closestCell(event.target as HTMLElement);
|
|
186
|
-
if (cell) {
|
|
187
|
-
const { row: rowIndex, plane } = cell;
|
|
188
|
-
if (onRowClick) {
|
|
189
|
-
if (plane === 'grid') {
|
|
190
|
-
const row = model?.getRowAt(rowIndex);
|
|
191
|
-
row && onRowClick(row);
|
|
192
|
-
} else {
|
|
193
|
-
onRowClick(cell);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
198
|
-
model.selection.toggleSelectionForRowIndex(rowIndex);
|
|
188
|
+
onRowClick(cell);
|
|
199
189
|
}
|
|
200
190
|
}
|
|
201
191
|
|
|
202
|
-
if (
|
|
203
|
-
|
|
192
|
+
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
193
|
+
model.selection.toggleSelectionForRowIndex(rowIndex);
|
|
204
194
|
}
|
|
195
|
+
}
|
|
205
196
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
197
|
+
if (!modals) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
209
200
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (!button) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
201
|
+
const buttonSelector = Object.values(tableButtons)
|
|
202
|
+
.map((button) => `button[${button.attr}]`)
|
|
203
|
+
.join(',');
|
|
216
204
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
modals.showRowMenu(data.rowIndex);
|
|
222
|
-
break;
|
|
223
|
-
}
|
|
224
|
-
case 'columnSettings': {
|
|
225
|
-
modals.showColumnMenu(data.fieldId);
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
228
|
-
case 'newColumn': {
|
|
229
|
-
modals.showColumnCreator();
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
case 'sort': {
|
|
233
|
-
model?.sorting?.toggleSort(data.fieldId);
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
case 'saveDraftRow': {
|
|
237
|
-
handleSaveDraftRow();
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
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) {
|
|
241
209
|
return;
|
|
242
210
|
}
|
|
243
211
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const control = Object.values(tableControls).find((ctrl) => controlElement.hasAttribute(ctrl.attr));
|
|
251
|
-
if (!control) {
|
|
252
|
-
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;
|
|
253
218
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
case 'checkbox': {
|
|
258
|
-
if (data.header) {
|
|
259
|
-
model?.selection.setSelection(model.selection.allRowsSeleted.value ? 'none' : 'all');
|
|
260
|
-
} else {
|
|
261
|
-
model?.selection.toggleSelectionForRowIndex(data.rowIndex);
|
|
262
|
-
}
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
case 'switch': {
|
|
266
|
-
if (model) {
|
|
267
|
-
model.updateCellData({ row: data.rowIndex, col: data.colIndex }, (value) => !value);
|
|
268
|
-
}
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
219
|
+
case 'columnSettings': {
|
|
220
|
+
modals.showColumnMenu(data.fieldId);
|
|
221
|
+
break;
|
|
271
222
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
handleSaveDraftRow(cell.row);
|
|
284
|
-
handleInsertRowResult(model.insertRow());
|
|
285
|
-
} else {
|
|
286
|
-
dxGrid.refocus(increment, delta);
|
|
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;
|
|
287
234
|
}
|
|
288
235
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
292
238
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
239
|
+
const controlSelector = Object.values(tableControls)
|
|
240
|
+
.map((control) => `input[${control.attr}]`)
|
|
241
|
+
.join(',');
|
|
298
242
|
|
|
299
|
-
|
|
300
|
-
|
|
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) {
|
|
301
247
|
return;
|
|
302
248
|
}
|
|
303
249
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const textContent = cellData?.toString() ?? '';
|
|
312
|
-
void navigator.clipboard.writeText(textContent);
|
|
313
|
-
event.preventDefault();
|
|
314
|
-
} catch (error) {
|
|
315
|
-
console.warn('Failed to copy cell content:', error);
|
|
316
|
-
}
|
|
317
|
-
break;
|
|
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);
|
|
318
257
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
// Attempt to set the cell's content to clipboard content
|
|
325
|
-
model.setCellData(cell, String.trim(clipboardText).replace(/[\n\r]+/, ' '));
|
|
326
|
-
handleSave();
|
|
327
|
-
} catch {
|
|
328
|
-
// If validation fails, emit a DxEditRequest event with initialContent from clipboard
|
|
329
|
-
// TODO(thure): Should `dx-grid` expose a method like this?
|
|
330
|
-
const cellElement = (event.target as HTMLElement).closest(
|
|
331
|
-
'[data-dx-grid-action="cell"]',
|
|
332
|
-
) as HTMLElement;
|
|
333
|
-
if (cellElement) {
|
|
334
|
-
const rect = cellElement.getBoundingClientRect();
|
|
335
|
-
const editRequest = new DxEditRequest({
|
|
336
|
-
cellIndex: `${cell.plane},${cell.col},${cell.row}`,
|
|
337
|
-
cellBox: {
|
|
338
|
-
insetInlineStart: rect.left,
|
|
339
|
-
insetBlockStart: rect.top,
|
|
340
|
-
inlineSize: rect.width,
|
|
341
|
-
blockSize: rect.height,
|
|
342
|
-
},
|
|
343
|
-
cellElement,
|
|
344
|
-
initialContent: clipboardText,
|
|
345
|
-
});
|
|
346
|
-
cellElement.dispatchEvent(editRequest);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
break;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case 'switch': {
|
|
261
|
+
if (model) {
|
|
262
|
+
model.updateCellData({ row: data.rowIndex, col: data.colIndex }, (value) => !value);
|
|
351
263
|
}
|
|
264
|
+
break;
|
|
352
265
|
}
|
|
353
266
|
}
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
[model, modals, dxGrid],
|
|
270
|
+
);
|
|
354
271
|
|
|
272
|
+
const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
|
|
273
|
+
(increment, delta, cell) => {
|
|
274
|
+
if (dxGrid && model) {
|
|
275
|
+
if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1 && increment !== 'col') {
|
|
276
|
+
handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
|
|
277
|
+
} else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
|
|
278
|
+
handleSaveDraftRow(cell.row);
|
|
279
|
+
handleInsertRowResult(model.insertRow());
|
|
280
|
+
} else {
|
|
281
|
+
dxGrid.refocus(increment, delta);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
[dxGrid, model],
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
|
|
289
|
+
(event) => {
|
|
290
|
+
if (!model?.features.dataEditable) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const cell = closestCell(event.target);
|
|
295
|
+
if (!model || !cell) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Handle Meta+C (Copy) and Meta+V (Paste) commands
|
|
300
|
+
if (event.metaKey || event.ctrlKey) {
|
|
355
301
|
switch (event.key) {
|
|
356
|
-
case '
|
|
357
|
-
|
|
302
|
+
case 'c': {
|
|
303
|
+
// Copy focused cell's text content to clipboard
|
|
358
304
|
try {
|
|
359
|
-
model.
|
|
305
|
+
const cellData = model.getCellData(cell);
|
|
306
|
+
const textContent = cellData?.toString() ?? '';
|
|
307
|
+
void navigator.clipboard.writeText(textContent);
|
|
360
308
|
event.preventDefault();
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
|
|
309
|
+
} catch (error) {
|
|
310
|
+
log.catch(error);
|
|
364
311
|
}
|
|
365
312
|
break;
|
|
366
313
|
}
|
|
314
|
+
case 'v': {
|
|
315
|
+
// Paste clipboard content to focused cell
|
|
316
|
+
event.preventDefault();
|
|
317
|
+
void navigator.clipboard.readText().then((clipboardText) => {
|
|
318
|
+
try {
|
|
319
|
+
// Attempt to set the cell's content to clipboard content
|
|
320
|
+
model.setCellData(cell, String.trim(clipboardText).replace(/[\n\r]+/, ' '));
|
|
321
|
+
handleSave();
|
|
322
|
+
} catch {
|
|
323
|
+
// If validation fails, emit a DxEditRequest event with initialContent from clipboard
|
|
324
|
+
// TODO(thure): Should `dx-grid` expose a method like this?
|
|
325
|
+
const cellElement = (event.target as HTMLElement).closest(
|
|
326
|
+
'[data-dx-grid-action="cell"]',
|
|
327
|
+
) as HTMLElement;
|
|
328
|
+
if (cellElement) {
|
|
329
|
+
const rect = cellElement.getBoundingClientRect();
|
|
330
|
+
const editRequest = new DxEditRequest({
|
|
331
|
+
cellIndex: `${cell.plane},${cell.col},${cell.row}`,
|
|
332
|
+
cellBox: {
|
|
333
|
+
insetInlineStart: rect.left,
|
|
334
|
+
insetBlockStart: rect.top,
|
|
335
|
+
inlineSize: rect.width,
|
|
336
|
+
blockSize: rect.height,
|
|
337
|
+
},
|
|
338
|
+
cellElement,
|
|
339
|
+
initialContent: clipboardText,
|
|
340
|
+
});
|
|
341
|
+
cellElement.dispatchEvent(editRequest);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
367
347
|
}
|
|
368
|
-
}
|
|
369
|
-
[model, dxGrid, handleSave],
|
|
370
|
-
);
|
|
371
|
-
|
|
372
|
-
const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
|
|
373
|
-
(event) => {
|
|
374
|
-
if (event.axis === 'col') {
|
|
375
|
-
const columnIndex = parseInt(event.index, 10);
|
|
376
|
-
model?.setColumnWidth(columnIndex, event.size);
|
|
377
|
-
}
|
|
378
|
-
},
|
|
379
|
-
[model],
|
|
380
|
-
);
|
|
348
|
+
}
|
|
381
349
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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;
|
|
386
361
|
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
[model, dxGrid, handleSave],
|
|
365
|
+
);
|
|
390
366
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if (
|
|
394
|
-
|
|
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);
|
|
395
372
|
}
|
|
396
|
-
},
|
|
373
|
+
},
|
|
374
|
+
[model],
|
|
375
|
+
);
|
|
397
376
|
|
|
398
|
-
|
|
399
|
-
|
|
377
|
+
const handleWheel = useCallback(
|
|
378
|
+
(event: WheelEvent) => {
|
|
379
|
+
if (!ignoreAttention && !hasAttention) {
|
|
380
|
+
event.stopPropagation();
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
[hasAttention, ignoreAttention],
|
|
384
|
+
);
|
|
385
|
+
|
|
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 {};
|
|
400
399
|
}
|
|
401
400
|
|
|
402
|
-
return
|
|
401
|
+
return {
|
|
402
|
+
update: (cell) => {
|
|
403
|
+
if (cell) {
|
|
404
|
+
dxGrid.updateIfWithinBounds(cell, true);
|
|
405
|
+
} else {
|
|
406
|
+
dxGrid.updateCells(true);
|
|
407
|
+
dxGrid.requestUpdate();
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
handleInsertRowResult,
|
|
411
|
+
};
|
|
412
|
+
}, [dxGrid, model, presentation]);
|
|
413
|
+
|
|
414
|
+
if (!model || !modals) {
|
|
415
|
+
return <span role='none' className='dx-attention-surface' />;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const { className, ...rest } = useComposableProps(props);
|
|
419
|
+
return (
|
|
420
|
+
<div {...rest} role='none' className={mx('dx-container relative', className)}>
|
|
403
421
|
<Grid.Root id={model.id ?? 'table-grid'}>
|
|
404
|
-
<TableValueEditor
|
|
422
|
+
<TableValueEditor<T>
|
|
405
423
|
model={model}
|
|
406
424
|
modals={modals}
|
|
407
425
|
schema={schema}
|
|
408
426
|
onFocus={handleFocus}
|
|
409
427
|
onSave={handleSave}
|
|
410
428
|
onCreate={onCreate}
|
|
411
|
-
client={client}
|
|
412
429
|
/>
|
|
413
430
|
<Grid.Content
|
|
414
|
-
className={mx('[--dx-grid-base:var(--
|
|
431
|
+
className={mx('[--dx-grid-base:var(--base-surface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
415
432
|
frozen={frozen}
|
|
416
|
-
columns={
|
|
433
|
+
columns={columnMeta}
|
|
417
434
|
columnDefault={columnDefault}
|
|
418
435
|
rowDefault={rowDefault}
|
|
419
|
-
limitRows={
|
|
420
|
-
limitColumns={
|
|
436
|
+
limitRows={rows.length}
|
|
437
|
+
limitColumns={columnCount}
|
|
421
438
|
overscroll='trap'
|
|
422
439
|
onAxisResize={handleAxisResize}
|
|
423
440
|
onClick={handleGridClick}
|
|
@@ -430,11 +447,20 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
430
447
|
<ColumnActionsMenu model={model} modals={modals} />
|
|
431
448
|
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
432
449
|
</Grid.Root>
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
+
//
|
|
436
461
|
|
|
437
462
|
export const Table = {
|
|
438
463
|
Root: TableRoot,
|
|
439
464
|
Main: TableMain,
|
|
465
|
+
Toolbar: TableToolbar,
|
|
440
466
|
};
|