@dxos/react-ui-table 0.8.3 → 0.8.4-main.1068cf700f
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 +1156 -2508
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +114 -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-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 +1156 -2508
- 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 +115 -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 +2 -3
- 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/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 +2 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +14 -9
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +12 -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 +26 -56
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +13 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- 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 +11 -10
- 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 +5 -8
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +17 -7
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +149 -32
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +13 -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/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 +25 -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/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 +15 -14
- 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 +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 +69 -56
- package/src/components/Table/ColumnActionsMenu.tsx +11 -7
- package/src/components/Table/ColumnSettings.tsx +16 -12
- package/src/components/Table/DynamicTable.stories.tsx +29 -25
- package/src/components/Table/DynamicTable.tsx +34 -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 +171 -194
- package/src/components/Table/Table.tsx +364 -286
- package/src/components/TableCellEditor/CellValidationMessage.tsx +8 -3
- package/src/components/TableCellEditor/FormCellEditor.tsx +146 -49
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +36 -76
- package/src/components/TableCellEditor/TableCellEditor.tsx +106 -198
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +49 -33
- package/src/components/testing.ts +41 -32
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAddRow.ts +36 -0
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +41 -35
- package/src/index.ts +1 -2
- package/src/model/modal-controller.ts +24 -41
- package/src/model/selection-model.ts +75 -41
- package/src/model/table-model.test.ts +92 -56
- package/src/model/table-model.ts +628 -162
- package/src/model/table-presentation.test.ts +70 -59
- package/src/model/table-presentation.ts +278 -149
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +180 -41
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +133 -0
- package/src/translations.ts +7 -5
- package/src/types/Table.ts +90 -0
- package/src/types/index.ts +2 -2
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +50 -36
- 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 +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/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/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/Table/CreateRefPanel.tsx +0 -84
- package/src/components/Table/relations.stories.tsx +0 -124
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/model/table-sorting.ts +0 -216
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
- package/src/util/table.ts +0 -127
|
@@ -2,47 +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 { invariant } from '@dxos/invariant';
|
|
21
|
-
import { Filter } from '@dxos/react-client/echo';
|
|
22
|
-
import { useTranslation } from '@dxos/react-ui';
|
|
22
|
+
import { type Type } from '@dxos/echo';
|
|
23
|
+
import { log } from '@dxos/log';
|
|
23
24
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
24
25
|
import {
|
|
25
|
-
|
|
26
|
+
type DxGridAxisMeta,
|
|
26
27
|
type DxGridElement,
|
|
27
|
-
type DxGridPosition,
|
|
28
|
-
type GridContentProps,
|
|
29
|
-
Grid,
|
|
30
28
|
type DxGridPlane,
|
|
31
29
|
type DxGridPlaneRange,
|
|
32
|
-
|
|
30
|
+
type DxGridPosition,
|
|
31
|
+
Grid,
|
|
32
|
+
type GridContentProps,
|
|
33
|
+
closestCell,
|
|
33
34
|
gridSeparatorBlockEnd,
|
|
35
|
+
gridSeparatorInlineEnd,
|
|
34
36
|
} from '@dxos/react-ui-grid';
|
|
35
|
-
import {
|
|
36
|
-
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';
|
|
37
43
|
|
|
38
44
|
import { ColumnActionsMenu } from './ColumnActionsMenu';
|
|
39
45
|
import { ColumnSettings } from './ColumnSettings';
|
|
40
|
-
import { CreateRefPanel } from './CreateRefPanel';
|
|
41
46
|
import { RowActionsMenu } from './RowActionsMenu';
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
|
|
48
|
+
const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
|
|
49
|
+
const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
|
|
50
|
+
const emptyColumnMeta = Atom.make<DxGridAxisMeta>({ grid: {} });
|
|
46
51
|
|
|
47
52
|
//
|
|
48
53
|
// Table.Root
|
|
@@ -50,15 +55,17 @@ import { createOption, TableValueEditor, type TableCellEditorProps } from '../Ta
|
|
|
50
55
|
|
|
51
56
|
export type TableRootProps = PropsWithChildren<{ role?: string }>;
|
|
52
57
|
|
|
53
|
-
const TableRoot = ({ children, role }: TableRootProps) => {
|
|
58
|
+
const TableRoot = ({ children, role = 'article' }: TableRootProps) => {
|
|
54
59
|
return (
|
|
55
60
|
<div
|
|
56
61
|
role='none'
|
|
57
62
|
className={mx(
|
|
58
|
-
'relative !border-separator',
|
|
59
|
-
role === '
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
'relative !border-separator [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
|
|
64
|
+
role === 'card--popover' && 'popover-card-height',
|
|
65
|
+
role === 'section' && 'attention-surface',
|
|
66
|
+
role === 'card--intrinsic' && '[&_.dx-grid]:bs-[--dx-grid-content-block-size]',
|
|
67
|
+
['card--popover', 'section', 'card--extrinsic'].includes(role) && 'overflow-hidden',
|
|
68
|
+
['article', 'slide'].includes(role) && 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0',
|
|
62
69
|
)}
|
|
63
70
|
>
|
|
64
71
|
{children}
|
|
@@ -72,319 +79,390 @@ const TableRoot = ({ children, role }: TableRootProps) => {
|
|
|
72
79
|
|
|
73
80
|
export type TableController = {
|
|
74
81
|
update?: (cell?: DxGridPosition) => void;
|
|
82
|
+
handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
|
|
75
83
|
};
|
|
76
84
|
|
|
77
|
-
export type TableMainProps = {
|
|
85
|
+
export type TableMainProps<T extends Type.Entity.Any = Type.Entity.Any> = {
|
|
86
|
+
schema?: T;
|
|
78
87
|
model?: TableModel;
|
|
79
88
|
presentation?: TablePresentation;
|
|
80
|
-
schema?: Schema.AnyNoContext;
|
|
81
|
-
client?: Client;
|
|
82
89
|
// TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
|
|
83
90
|
ignoreAttention?: boolean;
|
|
91
|
+
onCreate?: OnCreateHandler;
|
|
84
92
|
onRowClick?: (row: any) => void;
|
|
93
|
+
testId?: string;
|
|
85
94
|
};
|
|
86
95
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
});
|
|
115
166
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
});
|
|
127
184
|
}
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
[model, dxGrid],
|
|
188
|
+
);
|
|
128
189
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
},
|
|
140
|
-
[presentation, dxGrid],
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const handleGridClick = useCallback(
|
|
144
|
-
(event: MouseEvent) => {
|
|
145
|
-
const rowIndex = safeParseInt((event.target as HTMLElement).ariaRowIndex ?? '');
|
|
146
|
-
if (rowIndex != null) {
|
|
147
|
-
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') {
|
|
148
197
|
const row = model?.getRowAt(rowIndex);
|
|
149
198
|
row && onRowClick(row);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
153
|
-
model.selection.toggleSelectionForRowIndex(rowIndex);
|
|
199
|
+
} else {
|
|
200
|
+
onRowClick(cell);
|
|
154
201
|
}
|
|
155
202
|
}
|
|
156
203
|
|
|
157
|
-
if (
|
|
158
|
-
|
|
204
|
+
if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
|
|
205
|
+
model.selection.toggleSelectionForRowIndex(rowIndex);
|
|
159
206
|
}
|
|
207
|
+
}
|
|
160
208
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
209
|
+
if (!modals) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
164
212
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (!button) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
213
|
+
const buttonSelector = Object.values(tableButtons)
|
|
214
|
+
.map((button) => `button[${button.attr}]`)
|
|
215
|
+
.join(',');
|
|
171
216
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
modals.showRowMenu(data.rowIndex);
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
case 'columnSettings': {
|
|
180
|
-
modals.showColumnMenu(data.fieldId);
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
case 'newColumn': {
|
|
184
|
-
modals.showColumnCreator();
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
case 'sort': {
|
|
188
|
-
model?.sorting?.toggleSort(data.fieldId);
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
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) {
|
|
192
221
|
return;
|
|
193
222
|
}
|
|
194
223
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const control = Object.values(tableControls).find((ctrl) => controlElement.hasAttribute(ctrl.attr));
|
|
202
|
-
if (!control) {
|
|
203
|
-
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;
|
|
204
230
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
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;
|
|
222
246
|
}
|
|
223
247
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
227
250
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
dxGrid.refocus(increment, delta);
|
|
232
|
-
}
|
|
233
|
-
},
|
|
234
|
-
[dxGrid],
|
|
235
|
-
);
|
|
251
|
+
const controlSelector = Object.values(tableControls)
|
|
252
|
+
.map((control) => `input[${control.attr}]`)
|
|
253
|
+
.join(',');
|
|
236
254
|
|
|
237
|
-
|
|
238
|
-
(
|
|
239
|
-
|
|
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) {
|
|
240
259
|
return;
|
|
241
260
|
}
|
|
242
261
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
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;
|
|
251
277
|
}
|
|
252
278
|
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
[model, modals, dxGrid],
|
|
282
|
+
);
|
|
256
283
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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);
|
|
261
294
|
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
[dxGrid, model],
|
|
298
|
+
);
|
|
262
299
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
300
|
+
const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
|
|
301
|
+
(event) => {
|
|
302
|
+
if (!model?.features.dataEditable) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const cell = closestCell(event.target);
|
|
307
|
+
if (!model || !cell) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
267
310
|
|
|
311
|
+
// Handle Meta+C (Copy) and Meta+V (Paste) commands
|
|
312
|
+
if (event.metaKey || event.ctrlKey) {
|
|
268
313
|
switch (event.key) {
|
|
269
|
-
case '
|
|
270
|
-
|
|
271
|
-
|
|
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
|
+
});
|
|
272
357
|
break;
|
|
273
358
|
}
|
|
274
359
|
}
|
|
275
|
-
}
|
|
276
|
-
[model],
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
|
|
280
|
-
(event) => {
|
|
281
|
-
if (event.axis === 'col') {
|
|
282
|
-
const columnIndex = parseInt(event.index, 10);
|
|
283
|
-
model?.setColumnWidth(columnIndex, event.size);
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
[model],
|
|
287
|
-
);
|
|
360
|
+
}
|
|
288
361
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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;
|
|
293
373
|
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
[model, dxGrid, handleSave],
|
|
377
|
+
);
|
|
297
378
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (
|
|
301
|
-
|
|
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);
|
|
302
384
|
}
|
|
303
|
-
},
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
}
|
|
385
|
+
},
|
|
386
|
+
[model],
|
|
387
|
+
);
|
|
320
388
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
389
|
+
const handleWheel = useCallback(
|
|
390
|
+
(event: WheelEvent) => {
|
|
391
|
+
if (!ignoreAttention && !hasAttention) {
|
|
392
|
+
event.stopPropagation();
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
[hasAttention, ignoreAttention],
|
|
396
|
+
);
|
|
329
397
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
}
|
|
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
|
+
}
|
|
346
412
|
|
|
347
|
-
|
|
413
|
+
return {
|
|
414
|
+
update: (cell) => {
|
|
415
|
+
if (cell) {
|
|
416
|
+
dxGrid.updateIfWithinBounds(cell, true);
|
|
417
|
+
} else {
|
|
418
|
+
dxGrid.updateCells(true);
|
|
419
|
+
dxGrid.requestUpdate();
|
|
420
|
+
}
|
|
348
421
|
},
|
|
349
|
-
|
|
350
|
-
|
|
422
|
+
handleInsertRowResult,
|
|
423
|
+
};
|
|
424
|
+
}, [dxGrid, model, presentation]);
|
|
351
425
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
426
|
+
if (!model || !modals) {
|
|
427
|
+
return <span role='none' className='attention-surface' />;
|
|
428
|
+
}
|
|
429
|
+
|
|
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
|
+
};
|
|
355
462
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
model={model}
|
|
360
|
-
modals={modals}
|
|
361
|
-
schema={schema}
|
|
362
|
-
onEnter={handleEnter}
|
|
363
|
-
onFocus={handleFocus}
|
|
364
|
-
onQuery={handleQuery}
|
|
365
|
-
/>
|
|
366
|
-
<Grid.Content
|
|
367
|
-
className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
368
|
-
frozen={frozen}
|
|
369
|
-
// getCells={getCells}
|
|
370
|
-
columns={model.columnMeta.value}
|
|
371
|
-
limitRows={model.getRowCount() ?? 0}
|
|
372
|
-
limitColumns={model.view?.fields?.length ?? 0}
|
|
373
|
-
overscroll='trap'
|
|
374
|
-
onAxisResize={handleAxisResize}
|
|
375
|
-
onClick={handleGridClick}
|
|
376
|
-
onKeyDown={handleKeyDown}
|
|
377
|
-
onWheelCapture={handleWheel}
|
|
378
|
-
ref={setDxGrid}
|
|
379
|
-
/>
|
|
380
|
-
<RowActionsMenu model={model} modals={modals} />
|
|
381
|
-
<ColumnActionsMenu model={model} modals={modals} />
|
|
382
|
-
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
383
|
-
<CreateRefPanel model={model} modals={modals} />
|
|
384
|
-
</Grid.Root>
|
|
385
|
-
);
|
|
386
|
-
},
|
|
387
|
-
);
|
|
463
|
+
const TableMain = forwardRef(TableMainInner) as <T extends Type.Entity.Any = Type.Entity.Any>(
|
|
464
|
+
props: TableMainProps<T> & { ref?: Ref<TableController> },
|
|
465
|
+
) => JSX.Element;
|
|
388
466
|
|
|
389
467
|
export const Table = {
|
|
390
468
|
Root: TableRoot,
|