@dxos/react-ui-table 0.8.4-main.5ea62a8 → 0.8.4-main.60689f5b1c
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/LICENSE +102 -5
- package/dist/lib/browser/chunk-4URYOFDL.mjs +69 -0
- package/dist/lib/browser/chunk-4URYOFDL.mjs.map +7 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/browser/chunk-YDXPC7HA.mjs +1695 -0
- package/dist/lib/browser/chunk-YDXPC7HA.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1139 -1198
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +35 -42
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/translations.mjs +36 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +4 -3
- package/dist/lib/node-esm/chunk-2AN3QQ3D.mjs +1697 -0
- package/dist/lib/node-esm/chunk-2AN3QQ3D.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-B2SZYAEC.mjs +70 -0
- package/dist/lib/node-esm/chunk-B2SZYAEC.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1139 -1198
- 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 +35 -42
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/translations.mjs +37 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +4 -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 +6 -7
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +23 -39
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +96 -29
- 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 +20 -18
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +96 -31
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts +18 -0
- package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -12
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +7 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +25 -28
- 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/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +23 -16
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +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/index.d.ts +0 -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 +18 -9
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +105 -26
- 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/playwright/playwright.config.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +18 -29
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +21 -23
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +25 -0
- package/dist/types/src/types/Table.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +14 -12
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
- package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -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/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/src/util/signals.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 +20 -20
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts +4 -4
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/src/util/tag.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +69 -78
- package/src/components/Table/ColumnActionsMenu.tsx +17 -12
- package/src/components/Table/ColumnSettings.tsx +8 -9
- package/src/components/Table/DynamicTable.stories.tsx +35 -34
- package/src/components/Table/DynamicTable.tsx +32 -36
- package/src/components/Table/Relations.stories.tsx +195 -48
- package/src/components/Table/RowActionsMenu.tsx +27 -17
- package/src/components/Table/Table.stories.tsx +145 -129
- package/src/components/Table/Table.tsx +43 -403
- package/src/components/Table/TableContent.tsx +425 -0
- package/src/components/Table/TableToolbar.tsx +111 -0
- package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
- package/src/components/TableCellEditor/FormCellEditor.tsx +125 -131
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +32 -56
- package/src/components/TableCellEditor/TableCellEditor.tsx +47 -140
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/index.ts +0 -1
- package/src/components/testing.ts +44 -18
- 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/index.ts +0 -4
- package/src/model/modal-controller.ts +23 -40
- package/src/model/selection-model.ts +75 -44
- package/src/model/table-model.test.ts +93 -56
- package/src/model/table-model.ts +435 -163
- package/src/model/table-presentation.test.ts +67 -56
- package/src/model/table-presentation.ts +45 -42
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +59 -13
- package/src/testing/useTestTableModel.ts +49 -39
- package/src/translations.ts +20 -22
- package/src/types/Table.ts +78 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +42 -30
- package/src/util/index.ts +2 -0
- package/src/util/query-ast.ts +22 -0
- package/src/util/schema.ts +4 -4
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +1 -1
- package/src/util/table-controls.ts +2 -2
- package/dist/lib/browser/chunk-63UUF4IG.mjs +0 -66
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
- package/dist/lib/browser/chunk-RQYXNWWN.mjs +0 -1822
- package/dist/lib/browser/chunk-RQYXNWWN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-K734DJB7.mjs +0 -67
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-QNDUGIS7.mjs +0 -1823
- package/dist/lib/node-esm/chunk-QNDUGIS7.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/TableToolbar/TableToolbar.d.ts +0 -21
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
- package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
- package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
- package/dist/types/src/model/table-sorting.d.ts +0 -53
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -103
- package/src/components/TableToolbar/TableToolbar.tsx +0 -102
- package/src/components/TableToolbar/index.ts +0 -5
- package/src/model/table-sorting.ts +0 -216
- package/src/types/schema.ts +0 -73
package/src/model/table-model.ts
CHANGED
|
@@ -2,24 +2,15 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom, type Registry } from '@effect-atom/atom-react';
|
|
6
6
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
|
-
import { Obj, Ref } from '@dxos/echo';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type JsonProp,
|
|
13
|
-
type JsonSchemaType,
|
|
14
|
-
getSchema,
|
|
15
|
-
getValue,
|
|
16
|
-
setValue,
|
|
17
|
-
toEffectSchema,
|
|
18
|
-
} from '@dxos/echo-schema';
|
|
8
|
+
import { type Database, Format, Obj, Order, Query, type QueryAST, Ref, type View } from '@dxos/echo';
|
|
9
|
+
import { type JsonProp, type JsonSchemaType, type Mutable, toEffectSchema } from '@dxos/echo/internal';
|
|
10
|
+
import { getSnapshot } from '@dxos/echo/internal';
|
|
11
|
+
import { getValue, setValue } from '@dxos/effect';
|
|
19
12
|
import { invariant } from '@dxos/invariant';
|
|
20
13
|
import { ObjectId } from '@dxos/keys';
|
|
21
|
-
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
22
|
-
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
23
14
|
import { type Label } from '@dxos/react-ui';
|
|
24
15
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
25
16
|
import {
|
|
@@ -28,14 +19,57 @@ import {
|
|
|
28
19
|
type DxGridPlaneRange,
|
|
29
20
|
type DxGridPosition,
|
|
30
21
|
} from '@dxos/react-ui-grid';
|
|
31
|
-
import { type
|
|
22
|
+
import { type ProjectionModel, type PropertyType, type ValidationError, validateSchema } from '@dxos/schema';
|
|
32
23
|
|
|
33
|
-
import { Table } from '../types';
|
|
34
|
-
import {
|
|
24
|
+
import { type Table } from '../types';
|
|
25
|
+
import { extractOrder } from '../util';
|
|
26
|
+
import { compareValues } from '../util/sort';
|
|
35
27
|
import { extractTagIds } from '../util/tag';
|
|
36
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Field sort configuration.
|
|
31
|
+
*/
|
|
32
|
+
export type FieldSortType = {
|
|
33
|
+
fieldId: string;
|
|
34
|
+
direction: QueryAST.OrderDirection;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
37
|
import { type SelectionMode, SelectionModel } from './selection-model';
|
|
38
|
-
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Callback type for wrapping mutations in Obj.update().
|
|
41
|
+
* Contains separate callbacks for table object and row mutations.
|
|
42
|
+
*/
|
|
43
|
+
export type TableChangeCallback<T extends TableRow> = {
|
|
44
|
+
/** Callback to wrap table object mutations. */
|
|
45
|
+
table: (mutate: (mutableTable: Table.Table) => void) => void;
|
|
46
|
+
/** Callback to wrap row mutations. */
|
|
47
|
+
row: (row: T, mutate: (mutableRow: T) => void) => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates a change callback for ECHO-backed table and rows.
|
|
52
|
+
* Use this when the table and rows are stored in the ECHO database.
|
|
53
|
+
*/
|
|
54
|
+
export const createEchoChangeCallback = <T extends TableRow>(table: Table.Table): TableChangeCallback<T> => ({
|
|
55
|
+
table: (mutate) => Obj.update(table, (table) => mutate(table as unknown as Table.Table)),
|
|
56
|
+
row: (row, mutate) => {
|
|
57
|
+
if (Obj.isObject(row)) {
|
|
58
|
+
Obj.update(row, (row) => mutate(row as T));
|
|
59
|
+
} else {
|
|
60
|
+
mutate(row);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates a change callback that directly mutates objects without wrapping.
|
|
67
|
+
* Use this for plain JavaScript objects (tests, non-ECHO scenarios).
|
|
68
|
+
*/
|
|
69
|
+
export const createDirectChangeCallback = <T extends TableRow>(table: Table.Table): TableChangeCallback<T> => ({
|
|
70
|
+
table: (mutate) => mutate(table),
|
|
71
|
+
row: (row, mutate) => mutate(row),
|
|
72
|
+
});
|
|
39
73
|
|
|
40
74
|
// Domain types for cell classification
|
|
41
75
|
export type TableCellType = 'standard' | 'draft' | 'header';
|
|
@@ -72,69 +106,89 @@ const defaultFeatures: TableFeatures = {
|
|
|
72
106
|
export type InsertRowResult = 'draft' | 'final';
|
|
73
107
|
|
|
74
108
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
projection
|
|
109
|
+
registry: Registry.Registry;
|
|
110
|
+
object: Table.Table;
|
|
111
|
+
projection: ProjectionModel;
|
|
112
|
+
db?: Database.Database;
|
|
113
|
+
/**
|
|
114
|
+
* Callbacks to wrap mutations in Obj.update().
|
|
115
|
+
* Use createEchoChangeCallback() for ECHO-backed objects or createDirectChangeCallback() for plain objects.
|
|
116
|
+
*/
|
|
117
|
+
change?: TableChangeCallback<T>;
|
|
78
118
|
features?: Partial<TableFeatures>;
|
|
79
|
-
sorting?: FieldSortType[];
|
|
80
119
|
initialSelection?: string[];
|
|
81
120
|
pinnedRows?: { top: number[]; bottom: number[] };
|
|
82
121
|
rowActions?: TableRowAction[];
|
|
83
122
|
onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
|
|
84
123
|
onInsertRow?: (data?: any) => InsertRowResult;
|
|
85
124
|
onDeleteRows?: (index: number, obj: T[]) => void;
|
|
86
|
-
|
|
125
|
+
onColumnDelete?: (fieldId: string) => void;
|
|
87
126
|
onCellUpdate?: (cell: DxGridPosition) => void;
|
|
88
127
|
onRowOrderChange?: () => void;
|
|
89
128
|
onRowAction?: (actionId: string, data: T) => void;
|
|
90
129
|
};
|
|
91
130
|
|
|
92
131
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
93
|
-
private readonly
|
|
132
|
+
private readonly _registry: Registry.Registry;
|
|
133
|
+
private readonly _object: Table.Table;
|
|
94
134
|
private readonly _projection: ProjectionModel;
|
|
95
|
-
private
|
|
135
|
+
private readonly _db?: Database.Database;
|
|
136
|
+
private readonly _change: TableChangeCallback<T>;
|
|
96
137
|
|
|
97
|
-
private readonly _visibleRange
|
|
98
|
-
start: { row: 0, col: 0 },
|
|
99
|
-
end: { row: 0, col: 0 },
|
|
100
|
-
});
|
|
138
|
+
private readonly _visibleRange: Atom.Writable<DxGridPlaneRange>;
|
|
101
139
|
|
|
102
140
|
private readonly _onInsertRow?: (data?: any) => InsertRowResult;
|
|
103
141
|
private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
|
|
104
|
-
private readonly
|
|
142
|
+
private readonly _onColumnDelete?: TableModelProps<T>['onColumnDelete'];
|
|
105
143
|
private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
|
|
106
144
|
private readonly _onRowOrderChange?: TableModelProps<T>['onRowOrderChange'];
|
|
107
145
|
private readonly _onRowAction?: TableModelProps<T>['onRowAction'];
|
|
108
146
|
private readonly _rowActions: TableRowAction[];
|
|
109
147
|
private readonly _features: TableFeatures;
|
|
110
148
|
|
|
111
|
-
private readonly _rows
|
|
112
|
-
private readonly _draftRows
|
|
113
|
-
|
|
149
|
+
private readonly _rows: Atom.Writable<T[]>;
|
|
150
|
+
private readonly _draftRows: Atom.Writable<DraftRow<T>[]>;
|
|
151
|
+
// In-memory sort state - changes are local until saved.
|
|
152
|
+
private readonly _inMemorySort: Atom.Writable<FieldSortType | undefined>;
|
|
153
|
+
// Derived atom for persisted sort from view.query.ast.
|
|
154
|
+
private readonly _persistedSort: Atom.Atom<FieldSortType | undefined>;
|
|
155
|
+
// Derived atom for current sort (in-memory takes precedence over persisted).
|
|
156
|
+
private readonly _sorting: Atom.Atom<FieldSortType | undefined>;
|
|
157
|
+
// Derived atom for sorted rows (used by rows getter and SelectionModel).
|
|
158
|
+
private readonly _sortedRows: Atom.Atom<T[]>;
|
|
159
|
+
// Derived atom for viewDirty state.
|
|
160
|
+
private readonly _viewDirty: Atom.Atom<boolean>;
|
|
114
161
|
|
|
115
162
|
private _pinnedRows: NonNullable<TableModelProps<T>['pinnedRows']>;
|
|
116
163
|
private _selection!: SelectionModel<T>;
|
|
117
|
-
private _columnMeta?:
|
|
164
|
+
private _columnMeta?: Atom.Atom<DxGridAxisMeta>;
|
|
165
|
+
// Counter atom that increments when cells are updated - allows UI to react to cell changes.
|
|
166
|
+
private readonly _cellUpdateCounter: Atom.Writable<number>;
|
|
118
167
|
|
|
119
168
|
constructor({
|
|
120
|
-
|
|
121
|
-
|
|
169
|
+
registry,
|
|
170
|
+
object,
|
|
122
171
|
projection,
|
|
172
|
+
db,
|
|
173
|
+
change,
|
|
123
174
|
features = {},
|
|
124
|
-
sorting = [],
|
|
125
175
|
initialSelection = [],
|
|
126
176
|
pinnedRows = { top: [], bottom: [] },
|
|
127
177
|
rowActions = [],
|
|
128
178
|
onCellUpdate,
|
|
129
|
-
|
|
179
|
+
onColumnDelete,
|
|
130
180
|
onDeleteRows,
|
|
131
181
|
onInsertRow,
|
|
132
182
|
onRowOrderChange,
|
|
133
183
|
onRowAction,
|
|
134
184
|
}: TableModelProps<T>) {
|
|
135
185
|
super();
|
|
136
|
-
this.
|
|
137
|
-
this.
|
|
186
|
+
this._registry = registry;
|
|
187
|
+
this._object = object;
|
|
188
|
+
this._projection = projection;
|
|
189
|
+
this._projection.normalizeView();
|
|
190
|
+
this._db = db;
|
|
191
|
+
this._change = change ?? createEchoChangeCallback<T>(object);
|
|
138
192
|
|
|
139
193
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
140
194
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -144,49 +198,154 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
144
198
|
'Single selection is not compatible with editable tables.',
|
|
145
199
|
);
|
|
146
200
|
|
|
147
|
-
|
|
201
|
+
// Initialize writable atoms.
|
|
202
|
+
this._visibleRange = Atom.make<DxGridPlaneRange>({
|
|
203
|
+
start: { row: 0, col: 0 },
|
|
204
|
+
end: { row: 0, col: 0 },
|
|
205
|
+
});
|
|
206
|
+
this._rows = Atom.make<T[]>([]);
|
|
207
|
+
this._draftRows = Atom.make<DraftRow<T>[]>([]);
|
|
208
|
+
this._inMemorySort = Atom.make<FieldSortType | undefined>(undefined);
|
|
209
|
+
this._cellUpdateCounter = Atom.make<number>(0);
|
|
210
|
+
|
|
211
|
+
// Create derived atom for persisted sort from view.query.ast.
|
|
212
|
+
this._persistedSort = Atom.make((_get) => {
|
|
213
|
+
// Note: This depends on the view being loaded and the projection being set.
|
|
214
|
+
// The view is loaded in _open().
|
|
215
|
+
const viewSnapshot = Obj.getSnapshot(this.view);
|
|
216
|
+
const ast = viewSnapshot.query.ast;
|
|
217
|
+
const orders = extractOrder(ast);
|
|
218
|
+
|
|
219
|
+
if (orders && orders.length > 0) {
|
|
220
|
+
const firstOrder = orders[0];
|
|
221
|
+
if (firstOrder.kind === 'property') {
|
|
222
|
+
// Find field by property path.
|
|
223
|
+
const field = this._projection.getFields().find((f) => f.path === firstOrder.property);
|
|
224
|
+
if (field) {
|
|
225
|
+
return {
|
|
226
|
+
fieldId: field.id,
|
|
227
|
+
direction: firstOrder.direction,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
148
232
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
233
|
+
return undefined;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Create derived atom for current sort (in-memory takes precedence over persisted).
|
|
237
|
+
this._sorting = Atom.make((get) => {
|
|
238
|
+
const inMemorySort = get(this._inMemorySort);
|
|
239
|
+
return inMemorySort ?? get(this._persistedSort);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Create derived atom for sorted rows (used by rows getter and SelectionModel).
|
|
243
|
+
this._sortedRows = Atom.make((get) => {
|
|
244
|
+
const rows = get(this._rows);
|
|
245
|
+
const sort = get(this._sorting);
|
|
246
|
+
|
|
247
|
+
if (!sort || rows.length === 0) {
|
|
248
|
+
return rows;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const field = this._projection.getFields().find((f) => f.id === sort.fieldId);
|
|
252
|
+
if (!field) {
|
|
253
|
+
return rows;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Get field projection to check format.
|
|
257
|
+
const fieldProjection = this._projection.getFieldProjection(field.id);
|
|
258
|
+
|
|
259
|
+
const sorted = [...rows].sort((a, b) => {
|
|
260
|
+
const aValue = getValue(a, field.path);
|
|
261
|
+
const bValue = getValue(b, field.path);
|
|
262
|
+
return compareValues(aValue, bValue, fieldProjection.props.format, sort.direction);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return sorted;
|
|
266
|
+
});
|
|
153
267
|
|
|
154
268
|
this._selection = new SelectionModel(
|
|
155
|
-
this.
|
|
269
|
+
this._registry,
|
|
270
|
+
this._sortedRows,
|
|
156
271
|
this._features.selection.mode ?? 'multiple',
|
|
157
272
|
initialSelection,
|
|
158
273
|
() => this._onRowOrderChange?.(),
|
|
159
274
|
);
|
|
160
275
|
|
|
276
|
+
// Create derived atom for viewDirty.
|
|
277
|
+
this._viewDirty = Atom.make((get) => {
|
|
278
|
+
const inMemorySort = get(this._inMemorySort);
|
|
279
|
+
if (!inMemorySort) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const persisted = get(this._persistedSort);
|
|
284
|
+
if (!persisted) {
|
|
285
|
+
return true; // In-memory sort but no persisted sort.
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return inMemorySort.fieldId !== persisted.fieldId || inMemorySort.direction !== persisted.direction;
|
|
289
|
+
});
|
|
290
|
+
|
|
161
291
|
this._pinnedRows = pinnedRows;
|
|
162
292
|
this._rowActions = rowActions;
|
|
163
293
|
this._onInsertRow = onInsertRow;
|
|
164
294
|
this._onDeleteRows = onDeleteRows;
|
|
165
|
-
this.
|
|
295
|
+
this._onColumnDelete = onColumnDelete;
|
|
166
296
|
this._onCellUpdate = onCellUpdate;
|
|
167
297
|
this._onRowOrderChange = onRowOrderChange;
|
|
168
298
|
this._onRowAction = onRowAction;
|
|
169
299
|
}
|
|
170
300
|
|
|
171
301
|
public get id(): string {
|
|
172
|
-
return
|
|
302
|
+
return Obj.getDXN(this._object).toString();
|
|
173
303
|
}
|
|
174
304
|
|
|
175
|
-
public get view():
|
|
176
|
-
|
|
305
|
+
public get view(): View.View {
|
|
306
|
+
const view = this._object.view.target;
|
|
307
|
+
invariant(view, 'Table model not initialized');
|
|
308
|
+
return view;
|
|
177
309
|
}
|
|
178
310
|
|
|
179
311
|
public get table(): Table.Table {
|
|
180
|
-
|
|
181
|
-
return this._table;
|
|
312
|
+
return this._object;
|
|
182
313
|
}
|
|
183
314
|
|
|
184
315
|
public get projection(): ProjectionModel {
|
|
185
316
|
return this._projection;
|
|
186
317
|
}
|
|
187
318
|
|
|
188
|
-
public get
|
|
189
|
-
return this.
|
|
319
|
+
public get db(): Database.Database | undefined {
|
|
320
|
+
return this._db;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Atom for reactive access to rows with in-memory sorting applied.
|
|
325
|
+
*/
|
|
326
|
+
public get rows(): Atom.Atom<T[]> {
|
|
327
|
+
return this._sortedRows;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Gets rows with in-memory sorting applied.
|
|
332
|
+
* In-memory sort is local to this instance until saved.
|
|
333
|
+
*/
|
|
334
|
+
public getRows(): T[] {
|
|
335
|
+
return this._registry.get(this._sortedRows);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Atom for reactive access to cell changes. */
|
|
339
|
+
public get cellUpdate(): Atom.Atom<number> {
|
|
340
|
+
return this._cellUpdateCounter;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Change a row using the configured change callback.
|
|
345
|
+
* Use this instead of directly mutating to ensure consistent mutation handling.
|
|
346
|
+
*/
|
|
347
|
+
public changeRow(row: T, mutate: (mutableRow: T) => void): void {
|
|
348
|
+
this._change.row(row, mutate);
|
|
190
349
|
}
|
|
191
350
|
|
|
192
351
|
public get features(): TableFeatures {
|
|
@@ -194,28 +353,30 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
194
353
|
}
|
|
195
354
|
|
|
196
355
|
/**
|
|
197
|
-
*
|
|
356
|
+
* Gets the current sort state.
|
|
357
|
+
* Priority: in-memory sort (local changes) > persisted sort (from query AST).
|
|
198
358
|
*/
|
|
199
|
-
public
|
|
200
|
-
return this._sorting;
|
|
359
|
+
public getSorting(): FieldSortType | undefined {
|
|
360
|
+
return this._registry.get(this._sorting);
|
|
201
361
|
}
|
|
202
362
|
|
|
203
363
|
/**
|
|
204
364
|
* Gets the row data at the specified display index.
|
|
205
365
|
*/
|
|
206
366
|
public getRowAt(displayIndex: number): T | undefined {
|
|
207
|
-
|
|
367
|
+
const sortedRows = this._registry.get(this._sortedRows);
|
|
368
|
+
if (displayIndex < 0 || displayIndex >= sortedRows.length) {
|
|
208
369
|
return undefined;
|
|
209
370
|
}
|
|
210
371
|
|
|
211
|
-
return
|
|
372
|
+
return sortedRows[displayIndex];
|
|
212
373
|
}
|
|
213
374
|
|
|
214
375
|
public get pinnedRows(): NonNullable<TableModelProps<T>['pinnedRows']> {
|
|
215
376
|
return this._pinnedRows;
|
|
216
377
|
}
|
|
217
378
|
|
|
218
|
-
public get columnMeta():
|
|
379
|
+
public get columnMeta(): Atom.Atom<DxGridAxisMeta> {
|
|
219
380
|
invariant(this._columnMeta);
|
|
220
381
|
return this._columnMeta;
|
|
221
382
|
}
|
|
@@ -224,11 +385,6 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
224
385
|
return this._selection;
|
|
225
386
|
}
|
|
226
387
|
|
|
227
|
-
/** @reactive */
|
|
228
|
-
public get isViewDirty(): boolean {
|
|
229
|
-
return this.sorting.isDirty;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
388
|
public get rowActions(): TableRowAction[] {
|
|
233
389
|
return this._rowActions;
|
|
234
390
|
}
|
|
@@ -238,18 +394,19 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
238
394
|
//
|
|
239
395
|
|
|
240
396
|
protected override async _open(): Promise<void> {
|
|
241
|
-
|
|
242
|
-
invariant(Obj.instanceOf(Table.Table, presentation));
|
|
243
|
-
this._table = presentation;
|
|
244
|
-
|
|
397
|
+
await this._object.view.load();
|
|
245
398
|
this.initializeColumnMeta();
|
|
246
399
|
this.initializeEffects();
|
|
247
400
|
await this._selection.open(this._ctx);
|
|
248
401
|
}
|
|
249
402
|
|
|
250
403
|
private initializeColumnMeta(): void {
|
|
251
|
-
this.
|
|
252
|
-
|
|
404
|
+
invariant(this._object.view.target, 'View must be loaded before initializing column meta');
|
|
405
|
+
|
|
406
|
+
// Derive columnMeta from the projection's fields atom - updates when fields change.
|
|
407
|
+
// The projection.fields atom handles subscriptions to view changes internally.
|
|
408
|
+
this._columnMeta = Atom.make((get) => {
|
|
409
|
+
const fields = get(this._projection.fields);
|
|
253
410
|
const meta = Object.fromEntries(
|
|
254
411
|
fields.map((field, index: number) => [index, { size: this.table.sizes[field.path] ?? 256, resizeable: true }]),
|
|
255
412
|
);
|
|
@@ -263,40 +420,51 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
263
420
|
}
|
|
264
421
|
|
|
265
422
|
private initializeEffects(): void {
|
|
266
|
-
|
|
267
|
-
|
|
423
|
+
// Subscribe to rows changes to notify row order changed.
|
|
424
|
+
const rowsUnsubscribe = this._registry.subscribe(this._rows, () => {
|
|
268
425
|
this._onRowOrderChange?.();
|
|
269
426
|
});
|
|
270
|
-
this._ctx.onDispose(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
rowEffects.push(
|
|
282
|
-
effect(() => {
|
|
283
|
-
const obj = this.sorting.sortedRows.value[row];
|
|
284
|
-
this._projection?.fields.forEach((field) => touch(getValue(obj, field.path)));
|
|
285
|
-
this._onCellUpdate?.({ row, col: start.col, plane: 'grid' });
|
|
286
|
-
}),
|
|
287
|
-
);
|
|
427
|
+
this._ctx.onDispose(rowsUnsubscribe);
|
|
428
|
+
|
|
429
|
+
// Track row subscriptions for cleanup.
|
|
430
|
+
const rowSubscriptions = new Map<string, () => void>();
|
|
431
|
+
|
|
432
|
+
// Subscribe to visible range changes to set up row subscriptions.
|
|
433
|
+
const visibleRangeUnsubscribe = this._registry.subscribe(this._visibleRange, () => {
|
|
434
|
+
// Unsubscribe from old row subscriptions.
|
|
435
|
+
for (const [id, unsub] of rowSubscriptions) {
|
|
436
|
+
unsub();
|
|
437
|
+
rowSubscriptions.delete(id);
|
|
288
438
|
}
|
|
289
439
|
|
|
290
|
-
|
|
440
|
+
const { start, end } = this._registry.get(this._visibleRange);
|
|
441
|
+
const sortedRows = this._registry.get(this._sortedRows);
|
|
442
|
+
|
|
443
|
+
// Subscribe to each visible row's changes.
|
|
444
|
+
for (let row = start.row; row <= end.row && row < sortedRows.length; row++) {
|
|
445
|
+
const obj = sortedRows[row];
|
|
446
|
+
if (Obj.isObject(obj) && !rowSubscriptions.has(obj.id)) {
|
|
447
|
+
const unsub = Obj.subscribe(obj, () => {
|
|
448
|
+
// Increment cell update counter to notify UI of changes.
|
|
449
|
+
this._registry.set(this._cellUpdateCounter, this._registry.get(this._cellUpdateCounter) + 1);
|
|
450
|
+
this._onCellUpdate?.({ row, col: start.col, plane: 'grid' });
|
|
451
|
+
});
|
|
452
|
+
rowSubscriptions.set(obj.id, unsub);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
this._ctx.onDispose(() => {
|
|
457
|
+
visibleRangeUnsubscribe();
|
|
458
|
+
for (const unsub of rowSubscriptions.values()) {
|
|
459
|
+
unsub();
|
|
460
|
+
}
|
|
291
461
|
});
|
|
292
|
-
this._ctx.onDispose(rowEffectManager);
|
|
293
462
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// Notify grid that draft rows have changed
|
|
463
|
+
// Subscribe to draft rows changes to notify grid.
|
|
464
|
+
const draftRowsUnsubscribe = this._registry.subscribe(this._draftRows, () => {
|
|
297
465
|
this._onRowOrderChange?.();
|
|
298
466
|
});
|
|
299
|
-
this._ctx.onDispose(
|
|
467
|
+
this._ctx.onDispose(draftRowsUnsubscribe);
|
|
300
468
|
}
|
|
301
469
|
|
|
302
470
|
//
|
|
@@ -304,21 +472,25 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
304
472
|
//
|
|
305
473
|
|
|
306
474
|
public setRows = (obj: T[]): void => {
|
|
307
|
-
this._rows
|
|
475
|
+
this._registry.set(this._rows, obj);
|
|
308
476
|
};
|
|
309
477
|
|
|
310
|
-
public getRowCount = (): number => this.
|
|
478
|
+
public getRowCount = (): number => this._registry.get(this._sortedRows).length;
|
|
311
479
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
public getDraftRowCount = (): number => this._draftRows.value.length;
|
|
480
|
+
public getDraftRowCount(): number {
|
|
481
|
+
return this._registry.get(this._draftRows).length;
|
|
482
|
+
}
|
|
316
483
|
|
|
317
|
-
|
|
318
|
-
public get draftRows():
|
|
484
|
+
/** Atom for reactive access to draft rows. */
|
|
485
|
+
public get draftRows(): Atom.Atom<DraftRow<T>[]> {
|
|
319
486
|
return this._draftRows;
|
|
320
487
|
}
|
|
321
488
|
|
|
489
|
+
/** Gets the current draft rows. */
|
|
490
|
+
public getDraftRows(): DraftRow<T>[] {
|
|
491
|
+
return this._registry.get(this._draftRows);
|
|
492
|
+
}
|
|
493
|
+
|
|
322
494
|
/**
|
|
323
495
|
* Checks if a specific field path has validation errors for a given draft row.
|
|
324
496
|
* @param draftRowIndex - The index of the draft row to check
|
|
@@ -326,20 +498,20 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
326
498
|
* @returns true if the field has validation errors, false otherwise
|
|
327
499
|
*/
|
|
328
500
|
public hasDraftRowValidationError(draftRowIndex: number, fieldPath: string): boolean {
|
|
329
|
-
if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.
|
|
501
|
+
if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
|
|
330
502
|
return false;
|
|
331
503
|
}
|
|
332
504
|
|
|
333
|
-
const draftRow = this._draftRows
|
|
505
|
+
const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
|
|
334
506
|
const validationErrors = draftRow.validationErrors || [];
|
|
335
507
|
return validationErrors.some((error) => error.path === fieldPath);
|
|
336
508
|
}
|
|
337
509
|
|
|
338
|
-
public getColumnCount = (): number => this._projection?.
|
|
510
|
+
public getColumnCount = (): number => this._projection?.getFields().length ?? 0;
|
|
339
511
|
|
|
340
512
|
public insertRow = (): InsertRowResult => {
|
|
341
513
|
const result = this._onInsertRow?.();
|
|
342
|
-
if (result === 'draft' && this._draftRows.
|
|
514
|
+
if (result === 'draft' && this._registry.get(this._draftRows).length === 0) {
|
|
343
515
|
this.createDraftRow();
|
|
344
516
|
}
|
|
345
517
|
return result ?? 'final';
|
|
@@ -360,15 +532,15 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
360
532
|
validationErrors,
|
|
361
533
|
};
|
|
362
534
|
|
|
363
|
-
this._draftRows
|
|
535
|
+
this._registry.set(this._draftRows, [...this._registry.get(this._draftRows), draftRow]);
|
|
364
536
|
}
|
|
365
537
|
|
|
366
538
|
private validateDraftRow(draftRowIndex: number): void {
|
|
367
|
-
if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.
|
|
539
|
+
if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
|
|
368
540
|
return;
|
|
369
541
|
}
|
|
370
542
|
|
|
371
|
-
const draftRow = this._draftRows
|
|
543
|
+
const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
|
|
372
544
|
const validationErrors = this.validateDraftRowData(draftRow.data);
|
|
373
545
|
|
|
374
546
|
const updatedDraftRow = {
|
|
@@ -377,9 +549,9 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
377
549
|
validationErrors,
|
|
378
550
|
};
|
|
379
551
|
|
|
380
|
-
const newDraftRows = [...this._draftRows
|
|
552
|
+
const newDraftRows = [...this._registry.get(this._draftRows)];
|
|
381
553
|
newDraftRows[draftRowIndex] = updatedDraftRow;
|
|
382
|
-
this._draftRows
|
|
554
|
+
this._registry.set(this._draftRows, newDraftRows);
|
|
383
555
|
}
|
|
384
556
|
|
|
385
557
|
private validateDraftRowData(data: T): ValidationError[] {
|
|
@@ -388,11 +560,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
388
560
|
}
|
|
389
561
|
|
|
390
562
|
public commitDraftRow = (draftRowIndex: number): boolean => {
|
|
391
|
-
if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.
|
|
563
|
+
if (draftRowIndex < 0 || draftRowIndex >= this._registry.get(this._draftRows).length) {
|
|
392
564
|
return false;
|
|
393
565
|
}
|
|
394
566
|
|
|
395
|
-
const draftRow = this._draftRows
|
|
567
|
+
const draftRow = this._registry.get(this._draftRows)[draftRowIndex];
|
|
396
568
|
if (!draftRow.valid) {
|
|
397
569
|
return false;
|
|
398
570
|
}
|
|
@@ -400,26 +572,28 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
400
572
|
const insertRowResult = this._onInsertRow?.(draftRow.data);
|
|
401
573
|
|
|
402
574
|
if (insertRowResult === 'final') {
|
|
403
|
-
const newDraftRows = this._draftRows.
|
|
404
|
-
this._draftRows
|
|
575
|
+
const newDraftRows = this._registry.get(this._draftRows).filter((_, index) => index !== draftRowIndex);
|
|
576
|
+
this._registry.set(this._draftRows, newDraftRows);
|
|
405
577
|
}
|
|
406
578
|
|
|
407
579
|
return insertRowResult === 'final';
|
|
408
580
|
};
|
|
409
581
|
|
|
410
582
|
public deleteRow = (rowIndex: number): void => {
|
|
411
|
-
const
|
|
412
|
-
const obj =
|
|
583
|
+
const sortedRows = this._registry.get(this._sortedRows);
|
|
584
|
+
const obj = sortedRows[rowIndex];
|
|
413
585
|
const objectsToDelete = [];
|
|
414
586
|
|
|
415
|
-
if (this.features.selection.enabled && this._selection.hasSelection
|
|
587
|
+
if (this.features.selection.enabled && this._selection.hasSelection) {
|
|
416
588
|
const selectedRows = this._selection.getSelectedRows();
|
|
417
589
|
objectsToDelete.push(...selectedRows);
|
|
418
|
-
} else {
|
|
590
|
+
} else if (obj) {
|
|
419
591
|
objectsToDelete.push(obj);
|
|
420
592
|
}
|
|
421
593
|
|
|
422
|
-
|
|
594
|
+
if (objectsToDelete.length > 0) {
|
|
595
|
+
this._onDeleteRows?.(rowIndex, objectsToDelete);
|
|
596
|
+
}
|
|
423
597
|
};
|
|
424
598
|
|
|
425
599
|
public handleRowAction = (actionId: string, rowIndex: number): void => {
|
|
@@ -427,8 +601,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
427
601
|
return;
|
|
428
602
|
}
|
|
429
603
|
|
|
430
|
-
const
|
|
431
|
-
const data =
|
|
604
|
+
const sortedRows = this._registry.get(this._sortedRows);
|
|
605
|
+
const data = sortedRows[rowIndex];
|
|
432
606
|
|
|
433
607
|
if (data) {
|
|
434
608
|
this._onRowAction(actionId, data);
|
|
@@ -437,7 +611,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
437
611
|
|
|
438
612
|
public getCellData = (cell: DxGridPosition): any => {
|
|
439
613
|
const { col, row, plane } = cell;
|
|
440
|
-
const fields = this._projection?.
|
|
614
|
+
const fields = this._projection?.getFields() ?? [];
|
|
441
615
|
if (col < 0 || col >= fields.length) {
|
|
442
616
|
return undefined;
|
|
443
617
|
}
|
|
@@ -447,15 +621,15 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
447
621
|
|
|
448
622
|
if (plane === 'frozenRowsEnd') {
|
|
449
623
|
// Get data from draft row
|
|
450
|
-
if (row >= 0 && row < this._draftRows.
|
|
451
|
-
value = getValue(this._draftRows
|
|
624
|
+
if (row >= 0 && row < this._registry.get(this._draftRows).length) {
|
|
625
|
+
value = getValue(this._registry.get(this._draftRows)[row].data, field.path);
|
|
452
626
|
} else {
|
|
453
627
|
return undefined;
|
|
454
628
|
}
|
|
455
629
|
} else {
|
|
456
|
-
// Get data from regular row
|
|
457
|
-
const
|
|
458
|
-
value = getValue(
|
|
630
|
+
// Get data from regular row (use sorted rows for display index)
|
|
631
|
+
const sortedRows = this._registry.get(this._sortedRows);
|
|
632
|
+
value = getValue(sortedRows[row], field.path);
|
|
459
633
|
}
|
|
460
634
|
|
|
461
635
|
if (value == null) {
|
|
@@ -464,7 +638,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
464
638
|
|
|
465
639
|
const { props } = this._projection.getFieldProjection(field.id);
|
|
466
640
|
switch (props.format) {
|
|
467
|
-
case
|
|
641
|
+
case Format.TypeFormat.Ref: {
|
|
468
642
|
if (!field.referencePath) {
|
|
469
643
|
return ''; // TODO(burdon): Show error.
|
|
470
644
|
}
|
|
@@ -473,13 +647,17 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
473
647
|
}
|
|
474
648
|
|
|
475
649
|
default: {
|
|
476
|
-
return formatForEditing({
|
|
650
|
+
return formatForEditing({
|
|
651
|
+
type: props.type,
|
|
652
|
+
format: props.format,
|
|
653
|
+
value,
|
|
654
|
+
});
|
|
477
655
|
}
|
|
478
656
|
}
|
|
479
657
|
};
|
|
480
658
|
|
|
481
659
|
public validateCellData = async ({ col, row, plane }: DxGridPosition, value: any): Promise<ValidationResult> => {
|
|
482
|
-
const fields = this._projection?.
|
|
660
|
+
const fields = this._projection?.getFields() ?? [];
|
|
483
661
|
if (col < 0 || col >= fields.length) {
|
|
484
662
|
return { valid: false, error: 'Invalid column index' };
|
|
485
663
|
}
|
|
@@ -491,12 +669,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
491
669
|
const isDraftRow = plane === 'frozenRowsEnd';
|
|
492
670
|
|
|
493
671
|
if (isDraftRow) {
|
|
494
|
-
const isInvalidDraftRowIndex = row < 0 || row >= this._draftRows.
|
|
672
|
+
const isInvalidDraftRowIndex = row < 0 || row >= this._registry.get(this._draftRows).length;
|
|
495
673
|
if (isInvalidDraftRowIndex) {
|
|
496
674
|
return { valid: false, error: 'Invalid draft row index' };
|
|
497
675
|
}
|
|
498
676
|
|
|
499
|
-
const draftRow = this._draftRows
|
|
677
|
+
const draftRow = this._registry.get(this._draftRows)[row];
|
|
500
678
|
const snapshot = { ...draftRow.data };
|
|
501
679
|
setValue(snapshot, field.path, transformedValue);
|
|
502
680
|
|
|
@@ -510,14 +688,13 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
510
688
|
}
|
|
511
689
|
return { valid: true };
|
|
512
690
|
} else {
|
|
513
|
-
const
|
|
514
|
-
const currentRow = this._rows.value[rowIdx];
|
|
691
|
+
const currentRow = this._registry.get(this._rows)[row];
|
|
515
692
|
invariant(currentRow, 'Invalid row index');
|
|
516
693
|
|
|
517
|
-
const snapshot = getSnapshot(currentRow);
|
|
694
|
+
const snapshot = { ...getSnapshot(currentRow) };
|
|
518
695
|
setValue(snapshot, field.path, transformedValue);
|
|
519
696
|
|
|
520
|
-
const schema = getSchema(currentRow);
|
|
697
|
+
const schema = Obj.getSchema(currentRow as unknown as Obj.Unknown);
|
|
521
698
|
invariant(schema);
|
|
522
699
|
|
|
523
700
|
const validationResult = validateSchema(schema, snapshot);
|
|
@@ -533,7 +710,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
533
710
|
|
|
534
711
|
public setCellData = (cell: DxGridPosition, value: any): void => {
|
|
535
712
|
const { col, row, plane } = cell;
|
|
536
|
-
const fields = this._projection?.
|
|
713
|
+
const fields = this._projection?.getFields() ?? [];
|
|
537
714
|
if (col < 0 || col >= fields.length) {
|
|
538
715
|
return;
|
|
539
716
|
}
|
|
@@ -543,21 +720,24 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
543
720
|
const transformedValue = editorTextToCellValue(props, value);
|
|
544
721
|
|
|
545
722
|
// Special handling for Ref format to preserve existing behavior
|
|
546
|
-
if (props.format ===
|
|
723
|
+
if (props.format === Format.TypeFormat.Ref && !Obj.isObject(value)) {
|
|
547
724
|
return;
|
|
548
725
|
}
|
|
549
726
|
|
|
550
727
|
if (plane === 'frozenRowsEnd') {
|
|
551
|
-
// Update draft row data
|
|
552
|
-
if (row >= 0 && row < this._draftRows.
|
|
553
|
-
setValue(this._draftRows
|
|
728
|
+
// Update draft row data (draft rows are plain objects, not ECHO objects).
|
|
729
|
+
if (row >= 0 && row < this._registry.get(this._draftRows).length) {
|
|
730
|
+
setValue(this._registry.get(this._draftRows)[row].data, field.path, transformedValue);
|
|
554
731
|
// Re-validate the draft row after the update
|
|
555
732
|
this.validateDraftRow(row);
|
|
556
733
|
}
|
|
557
734
|
} else {
|
|
558
|
-
// Update regular row data
|
|
559
|
-
const
|
|
560
|
-
|
|
735
|
+
// Update regular row data (use sorted rows for display index)
|
|
736
|
+
const sortedRows = this._registry.get(this._sortedRows);
|
|
737
|
+
const rowData = sortedRows[row];
|
|
738
|
+
this._change.row(rowData, (mutableRow) => {
|
|
739
|
+
setValue(mutableRow, field.path, transformedValue);
|
|
740
|
+
});
|
|
561
741
|
}
|
|
562
742
|
};
|
|
563
743
|
|
|
@@ -567,13 +747,16 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
567
747
|
* @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
|
|
568
748
|
*/
|
|
569
749
|
public updateCellData({ col, row }: DxGridPlanePosition, update: (value: any) => any): void {
|
|
570
|
-
const
|
|
571
|
-
const fields = this._projection?.fields ?? [];
|
|
750
|
+
const fields = this._projection?.getFields() ?? [];
|
|
572
751
|
const field = fields[col];
|
|
752
|
+
const sortedRows = this._registry.get(this._sortedRows);
|
|
753
|
+
const rowData = sortedRows[row];
|
|
573
754
|
|
|
574
|
-
const value = getValue(
|
|
755
|
+
const value = getValue(rowData, field.path);
|
|
575
756
|
const updatedValue = update(value);
|
|
576
|
-
|
|
757
|
+
this._change.row(rowData, (mutableRow) => {
|
|
758
|
+
setValue(mutableRow, field.path, updatedValue);
|
|
759
|
+
});
|
|
577
760
|
}
|
|
578
761
|
|
|
579
762
|
/**
|
|
@@ -612,9 +795,9 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
612
795
|
return;
|
|
613
796
|
}
|
|
614
797
|
|
|
615
|
-
const field = this._projection?.
|
|
616
|
-
if (field && this.
|
|
617
|
-
this.
|
|
798
|
+
const field = this._projection?.getFields().find((field) => field.id === fieldId);
|
|
799
|
+
if (field && this._onColumnDelete) {
|
|
800
|
+
this._onColumnDelete(field.id);
|
|
618
801
|
}
|
|
619
802
|
}
|
|
620
803
|
|
|
@@ -623,12 +806,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
623
806
|
//
|
|
624
807
|
|
|
625
808
|
public setColumnWidth(columnIndex: number, width: number): void {
|
|
626
|
-
const fields = this._projection?.
|
|
809
|
+
const fields = this._projection?.getFields() ?? [];
|
|
627
810
|
if (columnIndex < fields.length) {
|
|
628
811
|
const newWidth = Math.max(0, width);
|
|
629
812
|
const field = fields[columnIndex];
|
|
630
813
|
if (field) {
|
|
631
|
-
this.table
|
|
814
|
+
this._change.table((mutableTable) => {
|
|
815
|
+
mutableTable.sizes[field.path] = newWidth;
|
|
816
|
+
});
|
|
632
817
|
}
|
|
633
818
|
}
|
|
634
819
|
}
|
|
@@ -648,25 +833,112 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
648
833
|
}
|
|
649
834
|
|
|
650
835
|
//
|
|
651
|
-
//
|
|
836
|
+
// Sort operations
|
|
652
837
|
//
|
|
653
838
|
|
|
839
|
+
/**
|
|
840
|
+
* Sets the sort order for a field in-memory (local to this instance).
|
|
841
|
+
* Use saveView() to persist the sort to the view query.
|
|
842
|
+
*/
|
|
843
|
+
public setSort(fieldId: string, direction: QueryAST.OrderDirection): void {
|
|
844
|
+
const field = this._projection.getFields().find((f) => f.id === fieldId);
|
|
845
|
+
if (!field) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Update in-memory sort (local changes).
|
|
850
|
+
this._registry.set(this._inMemorySort, {
|
|
851
|
+
fieldId,
|
|
852
|
+
direction,
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Toggles the sort direction for a field in-memory.
|
|
858
|
+
*/
|
|
859
|
+
public toggleSort(fieldId: string): void {
|
|
860
|
+
const currentSort = this.getSorting();
|
|
861
|
+
if (!currentSort || currentSort.fieldId !== fieldId) {
|
|
862
|
+
this.setSort(fieldId, 'asc');
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const newDirection = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
|
867
|
+
this.setSort(fieldId, newDirection);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Clears the in-memory sort order.
|
|
872
|
+
*/
|
|
873
|
+
public clearSort(): void {
|
|
874
|
+
this._registry.set(this._inMemorySort, undefined);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Saves the current in-memory sort to the view query AST.
|
|
879
|
+
* This persists the sort so it becomes the default for all viewers.
|
|
880
|
+
*/
|
|
654
881
|
public saveView(): void {
|
|
655
|
-
this.
|
|
882
|
+
const view = this.view;
|
|
883
|
+
const inMemorySort = this._registry.get(this._inMemorySort);
|
|
884
|
+
|
|
885
|
+
// Snapshot view before accessing query.ast
|
|
886
|
+
const viewSnapshot = Obj.getSnapshot(view);
|
|
887
|
+
const currentQuery = Query.fromAst(viewSnapshot.query.ast);
|
|
888
|
+
const baseQuery = this._getBaseQuery(currentQuery);
|
|
889
|
+
|
|
890
|
+
if (inMemorySort) {
|
|
891
|
+
// Find the field to get its path
|
|
892
|
+
const field = this._projection.getFields().find((f) => f.id === inMemorySort.fieldId);
|
|
893
|
+
if (field) {
|
|
894
|
+
// Persist sort to view.query.ast
|
|
895
|
+
const newQuery = baseQuery.orderBy(Order.property<any>(field.path as string, inMemorySort.direction));
|
|
896
|
+
Obj.update(view, (view) => {
|
|
897
|
+
view.query.ast = newQuery.ast as Mutable<typeof newQuery.ast>;
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
} else {
|
|
901
|
+
// Clear sort from view.query.ast
|
|
902
|
+
Obj.update(view, (view) => {
|
|
903
|
+
view.query.ast = baseQuery.ast as Mutable<typeof baseQuery.ast>;
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Clear in-memory sort since it's now persisted.
|
|
908
|
+
this._registry.set(this._inMemorySort, undefined);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Extracts the base query without order clauses.
|
|
913
|
+
*/
|
|
914
|
+
private _getBaseQuery(query: Query.Any): Query.Any {
|
|
915
|
+
const ast = query.ast;
|
|
916
|
+
// If the query has an order clause, extract the inner query
|
|
917
|
+
if (ast.type === 'order') {
|
|
918
|
+
return Query.fromAst(ast.query);
|
|
919
|
+
}
|
|
920
|
+
return query;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Checks if the view has unsaved changes (in-memory sort differs from persisted sort).
|
|
925
|
+
*/
|
|
926
|
+
public getViewDirty(): boolean {
|
|
927
|
+
return this._registry.get(this._viewDirty);
|
|
656
928
|
}
|
|
657
929
|
}
|
|
658
930
|
|
|
659
931
|
const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
660
932
|
switch (props.format) {
|
|
661
|
-
case
|
|
662
|
-
if (
|
|
933
|
+
case Format.TypeFormat.Ref: {
|
|
934
|
+
if (Obj.isObject(value)) {
|
|
663
935
|
return Ref.make(value);
|
|
664
936
|
} else {
|
|
665
937
|
return value;
|
|
666
938
|
}
|
|
667
939
|
}
|
|
668
940
|
|
|
669
|
-
case
|
|
941
|
+
case Format.TypeFormat.SingleSelect: {
|
|
670
942
|
const ids = extractTagIds(value);
|
|
671
943
|
if (ids && ids.length > 0) {
|
|
672
944
|
return ids[0];
|
|
@@ -675,7 +947,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
675
947
|
}
|
|
676
948
|
}
|
|
677
949
|
|
|
678
|
-
case
|
|
950
|
+
case Format.TypeFormat.MultiSelect: {
|
|
679
951
|
const ids = extractTagIds(value);
|
|
680
952
|
return ids || value;
|
|
681
953
|
}
|