@dxos/react-ui-table 0.8.3 → 0.8.4-main.1f223c7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/chunk-63UUF4IG.mjs +66 -0
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +7 -0
- package/dist/lib/browser/chunk-BBXDTWCX.mjs +1822 -0
- package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +814 -2152
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +124 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +3 -5
- package/dist/lib/node-esm/chunk-K734DJB7.mjs +67 -0
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs +1823 -0
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +814 -2152
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +125 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +3 -5
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +50 -2
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +44 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +3 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +42 -3
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +8 -3
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +9 -15
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -40
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +14 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +8 -7
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +2 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +59 -21
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +10 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +3 -4
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/useTestTableModel.d.ts +22 -0
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +5 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +28 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util/dynamic-table.d.ts +4 -5
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts +15 -1
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +57 -44
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +15 -11
- package/src/components/Table/CreateRefPanel.tsx +30 -11
- package/src/components/Table/DynamicTable.stories.tsx +8 -5
- package/src/components/Table/DynamicTable.tsx +17 -10
- package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +56 -47
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +126 -159
- package/src/components/Table/Table.tsx +167 -123
- package/src/components/TableCellEditor/CellValidationMessage.tsx +7 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +158 -48
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +31 -47
- package/src/components/TableCellEditor/TableCellEditor.tsx +106 -111
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +7 -24
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddRow.ts +38 -0
- package/src/hooks/useTableModel.ts +21 -17
- package/src/index.ts +1 -2
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +6 -3
- package/src/model/table-model.test.ts +12 -9
- package/src/model/table-model.ts +288 -88
- package/src/model/table-presentation.test.ts +12 -8
- package/src/model/table-presentation.ts +263 -138
- package/src/model/table-sorting.ts +22 -13
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +137 -44
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +124 -0
- package/src/translations.ts +7 -5
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +73 -0
- package/src/util/dynamic-table.ts +24 -21
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +1 -1
- package/src/util/table-buttons.ts +28 -4
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
- package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
- package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
- package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -2739
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/types/index.cjs +0 -31
- package/dist/lib/node/types/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
- package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
- package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/types/table.d.ts +0 -56
- package/dist/types/src/types/table.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -14
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
- package/src/util/table.ts +0 -127
|
@@ -6,16 +6,17 @@ import { computed } from '@preact/signals-core';
|
|
|
6
6
|
import { Schema } from 'effect';
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { Filter, Query } from '@dxos/echo';
|
|
10
10
|
import { TypedObject } from '@dxos/echo-schema';
|
|
11
11
|
import { updateCounter } from '@dxos/echo-schema/testing';
|
|
12
12
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
13
|
+
import { live } from '@dxos/live-object';
|
|
13
14
|
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
14
|
-
import {
|
|
15
|
-
|
|
15
|
+
import { createView } from '@dxos/schema';
|
|
16
|
+
|
|
17
|
+
import { Table } from '../types';
|
|
16
18
|
|
|
17
19
|
import { TableModel, type TableModelProps } from './table-model';
|
|
18
|
-
import { TableType } from '../types';
|
|
19
20
|
|
|
20
21
|
// TODO(burdon): Tests are disabled in project.json since they bring in plugin deps.
|
|
21
22
|
// Restore once factored out into react-ui-table.
|
|
@@ -72,7 +73,6 @@ describe('TableModel', () => {
|
|
|
72
73
|
describe('reactivity', () => {
|
|
73
74
|
it('pure signals should nest', () => {
|
|
74
75
|
const signal$ = live({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
|
|
75
|
-
|
|
76
76
|
const computed$ = computed(() => {
|
|
77
77
|
return signal$.arr.map((row) =>
|
|
78
78
|
computed(() => {
|
|
@@ -107,8 +107,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
107
107
|
|
|
108
108
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
109
109
|
const schema = createEchoSchema(Test);
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
const table = Table.make();
|
|
111
|
+
const view = createView({
|
|
112
|
+
query: Query.select(Filter.type(schema)),
|
|
113
|
+
jsonSchema: schema.jsonSchema,
|
|
114
|
+
presentation: table,
|
|
115
|
+
});
|
|
116
|
+
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
114
117
|
};
|
package/src/model/table-model.ts
CHANGED
|
@@ -2,44 +2,65 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { computed, effect, signal
|
|
5
|
+
import { type ReadonlySignal, computed, effect, signal } from '@preact/signals-core';
|
|
6
6
|
|
|
7
|
-
import { type Space } from '@dxos/client/echo';
|
|
8
7
|
import { Resource } from '@dxos/context';
|
|
8
|
+
import { Obj, Ref } from '@dxos/echo';
|
|
9
9
|
import {
|
|
10
|
-
type FieldSortType,
|
|
11
10
|
FormatEnum,
|
|
12
|
-
getValue,
|
|
13
|
-
setValue,
|
|
14
11
|
type JsonProp,
|
|
15
|
-
|
|
12
|
+
type JsonSchemaType,
|
|
16
13
|
getSchema,
|
|
14
|
+
getValue,
|
|
15
|
+
setValue,
|
|
16
|
+
toEffectSchema,
|
|
17
17
|
} from '@dxos/echo-schema';
|
|
18
18
|
import { invariant } from '@dxos/invariant';
|
|
19
|
-
import {
|
|
19
|
+
import { ObjectId } from '@dxos/keys';
|
|
20
|
+
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
21
|
+
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
22
|
+
import { type Label } from '@dxos/react-ui';
|
|
20
23
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
21
24
|
import {
|
|
22
25
|
type DxGridAxisMeta,
|
|
23
|
-
type DxGridPlaneRange,
|
|
24
26
|
type DxGridPlanePosition,
|
|
27
|
+
type DxGridPlaneRange,
|
|
25
28
|
type DxGridPosition,
|
|
26
29
|
} from '@dxos/react-ui-grid';
|
|
27
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
type DataType,
|
|
32
|
+
type FieldSortType,
|
|
33
|
+
ProjectionModel,
|
|
34
|
+
type PropertyType,
|
|
35
|
+
type ValidationError,
|
|
36
|
+
validateSchema,
|
|
37
|
+
} from '@dxos/schema';
|
|
28
38
|
|
|
29
|
-
import {
|
|
30
|
-
import { TableSorting } from './table-sorting';
|
|
39
|
+
import { Table } from '../types';
|
|
31
40
|
import { touch } from '../util';
|
|
32
41
|
import { extractTagIds } from '../util/tag';
|
|
33
42
|
|
|
43
|
+
import { type SelectionMode, SelectionModel } from './selection-model';
|
|
44
|
+
import { TableSorting } from './table-sorting';
|
|
45
|
+
|
|
46
|
+
// Domain types for cell classification
|
|
47
|
+
export type TableCellType = 'standard' | 'draft' | 'header';
|
|
48
|
+
|
|
34
49
|
// TODO(ZaymonFC): Use a common type?
|
|
35
50
|
export type ValidationResult = { valid: true } | { valid: false; error: string };
|
|
36
51
|
|
|
52
|
+
export type DraftRow<T extends TableRow = TableRow> = {
|
|
53
|
+
data: T;
|
|
54
|
+
valid: boolean;
|
|
55
|
+
validationErrors: ValidationError[];
|
|
56
|
+
};
|
|
57
|
+
|
|
37
58
|
// TODO(burdon): Use schema types.
|
|
38
59
|
export type TableRow = Record<JsonProp, any> & { id: string };
|
|
39
60
|
|
|
40
61
|
export type TableRowAction = {
|
|
41
62
|
id: string;
|
|
42
|
-
|
|
63
|
+
label: Label;
|
|
43
64
|
};
|
|
44
65
|
|
|
45
66
|
export type TableFeatures = {
|
|
@@ -54,16 +75,19 @@ const defaultFeatures: TableFeatures = {
|
|
|
54
75
|
schemaEditable: false,
|
|
55
76
|
};
|
|
56
77
|
|
|
78
|
+
export type InsertRowResult = 'draft' | 'final';
|
|
79
|
+
|
|
57
80
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
projection: ViewProjection;
|
|
81
|
+
view: DataType.View;
|
|
82
|
+
schema: JsonSchemaType;
|
|
83
|
+
projection?: ProjectionModel;
|
|
62
84
|
features?: Partial<TableFeatures>;
|
|
63
85
|
sorting?: FieldSortType[];
|
|
86
|
+
initialSelection?: string[];
|
|
64
87
|
pinnedRows?: { top: number[]; bottom: number[] };
|
|
65
88
|
rowActions?: TableRowAction[];
|
|
66
|
-
|
|
89
|
+
onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
|
|
90
|
+
onInsertRow?: (data?: any) => InsertRowResult;
|
|
67
91
|
onDeleteRows?: (index: number, obj: T[]) => void;
|
|
68
92
|
onDeleteColumn?: (fieldId: string) => void;
|
|
69
93
|
onCellUpdate?: (cell: DxGridPosition) => void;
|
|
@@ -72,17 +96,16 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
72
96
|
};
|
|
73
97
|
|
|
74
98
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
75
|
-
private readonly
|
|
76
|
-
private readonly
|
|
77
|
-
private
|
|
78
|
-
private readonly _projection: ViewProjection;
|
|
99
|
+
private readonly _view: DataType.View;
|
|
100
|
+
private readonly _projection: ProjectionModel;
|
|
101
|
+
private _table?: Table.Table;
|
|
79
102
|
|
|
80
103
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
81
104
|
start: { row: 0, col: 0 },
|
|
82
105
|
end: { row: 0, col: 0 },
|
|
83
106
|
});
|
|
84
107
|
|
|
85
|
-
private readonly _onInsertRow?:
|
|
108
|
+
private readonly _onInsertRow?: (data?: any) => InsertRowResult;
|
|
86
109
|
private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
|
|
87
110
|
private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
|
|
88
111
|
private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
|
|
@@ -92,6 +115,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
92
115
|
private readonly _features: TableFeatures;
|
|
93
116
|
|
|
94
117
|
private readonly _rows = signal<T[]>([]);
|
|
118
|
+
private readonly _draftRows = signal<DraftRow<T>[]>([]);
|
|
95
119
|
private readonly _sorting: TableSorting<T>;
|
|
96
120
|
|
|
97
121
|
private _pinnedRows: NonNullable<TableModelProps<T>['pinnedRows']>;
|
|
@@ -99,12 +123,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
99
123
|
private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
|
|
100
124
|
|
|
101
125
|
constructor({
|
|
102
|
-
id,
|
|
103
|
-
space,
|
|
104
126
|
view,
|
|
127
|
+
schema,
|
|
105
128
|
projection,
|
|
106
129
|
features = {},
|
|
107
130
|
sorting = [],
|
|
131
|
+
initialSelection = [],
|
|
108
132
|
pinnedRows = { top: [], bottom: [] },
|
|
109
133
|
rowActions = [],
|
|
110
134
|
onCellUpdate,
|
|
@@ -115,10 +139,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
115
139
|
onRowAction,
|
|
116
140
|
}: TableModelProps<T>) {
|
|
117
141
|
super();
|
|
118
|
-
this._id = id;
|
|
119
|
-
this._space = space;
|
|
120
142
|
this._view = view;
|
|
121
|
-
this._projection = projection;
|
|
143
|
+
this._projection = projection ?? new ProjectionModel(schema, view.projection);
|
|
122
144
|
|
|
123
145
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
124
146
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -128,13 +150,20 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
128
150
|
'Single selection is not compatible with editable tables.',
|
|
129
151
|
);
|
|
130
152
|
|
|
131
|
-
this._sorting = new TableSorting(this._rows, this._view,
|
|
153
|
+
this._sorting = new TableSorting(this._rows, this._view, this._projection);
|
|
132
154
|
|
|
133
155
|
if (sorting.length > 0) {
|
|
134
156
|
const [sort] = sorting;
|
|
135
157
|
this._sorting.setSort(sort.fieldId, sort.direction);
|
|
136
158
|
}
|
|
137
159
|
|
|
160
|
+
this._selection = new SelectionModel(
|
|
161
|
+
this._sorting.sortedRows,
|
|
162
|
+
this._features.selection.mode ?? 'multiple',
|
|
163
|
+
initialSelection,
|
|
164
|
+
() => this._onRowOrderChange?.(),
|
|
165
|
+
);
|
|
166
|
+
|
|
138
167
|
this._pinnedRows = pinnedRows;
|
|
139
168
|
this._rowActions = rowActions;
|
|
140
169
|
this._onInsertRow = onInsertRow;
|
|
@@ -145,24 +174,25 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
145
174
|
this._onRowAction = onRowAction;
|
|
146
175
|
}
|
|
147
176
|
|
|
148
|
-
public get id() {
|
|
149
|
-
return this.
|
|
177
|
+
public get id(): string {
|
|
178
|
+
return fullyQualifiedId(this._view);
|
|
150
179
|
}
|
|
151
180
|
|
|
152
|
-
public get
|
|
153
|
-
return this.
|
|
181
|
+
public get view(): DataType.View {
|
|
182
|
+
return this._view;
|
|
154
183
|
}
|
|
155
184
|
|
|
156
|
-
public get
|
|
157
|
-
|
|
185
|
+
public get table(): Table.Table {
|
|
186
|
+
invariant(this._table, 'Model not initialized');
|
|
187
|
+
return this._table;
|
|
158
188
|
}
|
|
159
189
|
|
|
160
|
-
public get projection() {
|
|
190
|
+
public get projection(): ProjectionModel {
|
|
161
191
|
return this._projection;
|
|
162
192
|
}
|
|
163
193
|
|
|
164
194
|
public get rows(): ReadonlySignal<T[]> {
|
|
165
|
-
return this.
|
|
195
|
+
return this.sorting.sortedRows;
|
|
166
196
|
}
|
|
167
197
|
|
|
168
198
|
public get features(): TableFeatures {
|
|
@@ -172,7 +202,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
172
202
|
/**
|
|
173
203
|
* @reactive
|
|
174
204
|
*/
|
|
175
|
-
public get sorting(): TableSorting<T>
|
|
205
|
+
public get sorting(): TableSorting<T> {
|
|
176
206
|
return this._sorting;
|
|
177
207
|
}
|
|
178
208
|
|
|
@@ -180,11 +210,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
180
210
|
* Gets the row data at the specified display index.
|
|
181
211
|
*/
|
|
182
212
|
public getRowAt(displayIndex: number): T | undefined {
|
|
183
|
-
if (displayIndex < 0 || displayIndex >= this.
|
|
213
|
+
if (displayIndex < 0 || displayIndex >= this.sorting.sortedRows.value.length) {
|
|
184
214
|
return undefined;
|
|
185
215
|
}
|
|
186
216
|
|
|
187
|
-
return this.
|
|
217
|
+
return this.sorting.sortedRows.value[displayIndex];
|
|
188
218
|
}
|
|
189
219
|
|
|
190
220
|
public get pinnedRows(): NonNullable<TableModelProps<T>['pinnedRows']> {
|
|
@@ -202,7 +232,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
202
232
|
|
|
203
233
|
/** @reactive */
|
|
204
234
|
public get isViewDirty(): boolean {
|
|
205
|
-
return this.
|
|
235
|
+
return this.sorting.isDirty;
|
|
206
236
|
}
|
|
207
237
|
|
|
208
238
|
public get rowActions(): TableRowAction[] {
|
|
@@ -214,32 +244,33 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
214
244
|
//
|
|
215
245
|
|
|
216
246
|
protected override async _open(): Promise<void> {
|
|
247
|
+
const presentation = this._view.presentation.target ?? (await this._view.presentation.load());
|
|
248
|
+
invariant(Obj.instanceOf(Table.Table, presentation));
|
|
249
|
+
this._table = presentation;
|
|
250
|
+
|
|
217
251
|
this.initializeColumnMeta();
|
|
218
252
|
this.initializeEffects();
|
|
219
|
-
this._selection = new SelectionModel(this._sorting.sortedRows, this._features.selection.mode ?? 'multiple', () =>
|
|
220
|
-
this._onRowOrderChange?.(),
|
|
221
|
-
);
|
|
222
253
|
await this._selection.open(this._ctx);
|
|
223
254
|
}
|
|
224
255
|
|
|
225
256
|
private initializeColumnMeta(): void {
|
|
226
257
|
this._columnMeta = computed(() => {
|
|
227
|
-
const fields = this.
|
|
258
|
+
const fields = this._projection?.fields ?? [];
|
|
228
259
|
const meta = Object.fromEntries(
|
|
229
|
-
fields.map((field, index: number) => [index, { size: field
|
|
260
|
+
fields.map((field, index: number) => [index, { size: this.table.sizes[field.path] ?? 256, resizeable: true }]),
|
|
230
261
|
);
|
|
231
262
|
|
|
232
263
|
return {
|
|
233
264
|
grid: meta,
|
|
234
265
|
frozenColsStart: { 0: { size: 30, resizeable: false } },
|
|
235
|
-
frozenColsEnd: { 0: { size:
|
|
266
|
+
frozenColsEnd: { 0: { size: 32, resizeable: false } },
|
|
236
267
|
};
|
|
237
268
|
});
|
|
238
269
|
}
|
|
239
270
|
|
|
240
271
|
private initializeEffects(): void {
|
|
241
272
|
const rowOrderWatcher = effect(() => {
|
|
242
|
-
touch(this.
|
|
273
|
+
touch(this.sorting.sortedRows.value);
|
|
243
274
|
this._onRowOrderChange?.();
|
|
244
275
|
});
|
|
245
276
|
this._ctx.onDispose(rowOrderWatcher);
|
|
@@ -255,8 +286,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
255
286
|
for (let row = start.row; row <= end.row; row++) {
|
|
256
287
|
rowEffects.push(
|
|
257
288
|
effect(() => {
|
|
258
|
-
const obj = this.
|
|
259
|
-
this.
|
|
289
|
+
const obj = this.sorting.sortedRows.value[row];
|
|
290
|
+
this._projection?.fields.forEach((field) => touch(getValue(obj, field.path)));
|
|
260
291
|
this._onCellUpdate?.({ row, col: start.col, plane: 'grid' });
|
|
261
292
|
}),
|
|
262
293
|
);
|
|
@@ -265,6 +296,13 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
265
296
|
return () => rowEffects.forEach((cleanup) => cleanup());
|
|
266
297
|
});
|
|
267
298
|
this._ctx.onDispose(rowEffectManager);
|
|
299
|
+
|
|
300
|
+
const draftRowsWatcher = effect(() => {
|
|
301
|
+
touch(this._draftRows.value);
|
|
302
|
+
// Notify grid that draft rows have changed
|
|
303
|
+
this._onRowOrderChange?.();
|
|
304
|
+
});
|
|
305
|
+
this._ctx.onDispose(draftRowsWatcher);
|
|
268
306
|
}
|
|
269
307
|
|
|
270
308
|
//
|
|
@@ -277,11 +315,102 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
277
315
|
|
|
278
316
|
public getRowCount = (): number => this._rows.value.length;
|
|
279
317
|
|
|
280
|
-
|
|
318
|
+
/**
|
|
319
|
+
* @reactive
|
|
320
|
+
*/
|
|
321
|
+
public getDraftRowCount = (): number => this._draftRows.value.length;
|
|
322
|
+
|
|
323
|
+
// TODO(ZaymonFC): Return .value instead of exposing the signal.
|
|
324
|
+
public get draftRows(): ReadonlySignal<DraftRow<T>[]> {
|
|
325
|
+
return this._draftRows;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Checks if a specific field path has validation errors for a given draft row.
|
|
330
|
+
* @param draftRowIndex - The index of the draft row to check
|
|
331
|
+
* @param fieldPath - The path of the field to check for validation errors
|
|
332
|
+
* @returns true if the field has validation errors, false otherwise
|
|
333
|
+
*/
|
|
334
|
+
public hasDraftRowValidationError(draftRowIndex: number, fieldPath: string): boolean {
|
|
335
|
+
if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const draftRow = this._draftRows.value[draftRowIndex];
|
|
340
|
+
const validationErrors = draftRow.validationErrors || [];
|
|
341
|
+
return validationErrors.some((error) => error.path === fieldPath);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public getColumnCount = (): number => this._projection?.fields.length ?? 0;
|
|
281
345
|
|
|
282
|
-
public insertRow = (
|
|
283
|
-
const
|
|
284
|
-
this.
|
|
346
|
+
public insertRow = (): InsertRowResult => {
|
|
347
|
+
const result = this._onInsertRow?.();
|
|
348
|
+
if (result === 'draft' && this._draftRows.value.length === 0) {
|
|
349
|
+
this.createDraftRow();
|
|
350
|
+
}
|
|
351
|
+
return result ?? 'final';
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
private createDraftRow(): void {
|
|
355
|
+
if (!this._projection) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// NOTE(ZaymonFC): This is initialized with id because it's required in all schemata?
|
|
360
|
+
const draftData = { id: ObjectId.random() } as any as T;
|
|
361
|
+
const validationErrors = this.validateDraftRowData(draftData);
|
|
362
|
+
|
|
363
|
+
const draftRow: DraftRow<T> = {
|
|
364
|
+
data: draftData,
|
|
365
|
+
valid: validationErrors.length === 0,
|
|
366
|
+
validationErrors,
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
this._draftRows.value = [...this._draftRows.value, draftRow];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private validateDraftRow(draftRowIndex: number): void {
|
|
373
|
+
if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const draftRow = this._draftRows.value[draftRowIndex];
|
|
378
|
+
const validationErrors = this.validateDraftRowData(draftRow.data);
|
|
379
|
+
|
|
380
|
+
const updatedDraftRow = {
|
|
381
|
+
...draftRow,
|
|
382
|
+
valid: validationErrors.length === 0,
|
|
383
|
+
validationErrors,
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const newDraftRows = [...this._draftRows.value];
|
|
387
|
+
newDraftRows[draftRowIndex] = updatedDraftRow;
|
|
388
|
+
this._draftRows.value = newDraftRows;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private validateDraftRowData(data: T): ValidationError[] {
|
|
392
|
+
const schema = toEffectSchema(this._projection.baseSchema);
|
|
393
|
+
return validateSchema(schema, data) || [];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public commitDraftRow = (draftRowIndex: number): boolean => {
|
|
397
|
+
if (draftRowIndex < 0 || draftRowIndex >= this._draftRows.value.length) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const draftRow = this._draftRows.value[draftRowIndex];
|
|
402
|
+
if (!draftRow.valid) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const insertRowResult = this._onInsertRow?.(draftRow.data);
|
|
407
|
+
|
|
408
|
+
if (insertRowResult === 'final') {
|
|
409
|
+
const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
|
|
410
|
+
this._draftRows.value = newDraftRows;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return insertRowResult === 'final';
|
|
285
414
|
};
|
|
286
415
|
|
|
287
416
|
public deleteRow = (rowIndex: number): void => {
|
|
@@ -312,15 +441,29 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
312
441
|
}
|
|
313
442
|
};
|
|
314
443
|
|
|
315
|
-
public getCellData = (
|
|
316
|
-
const
|
|
444
|
+
public getCellData = (cell: DxGridPosition): any => {
|
|
445
|
+
const { col, row, plane } = cell;
|
|
446
|
+
const fields = this._projection?.fields ?? [];
|
|
317
447
|
if (col < 0 || col >= fields.length) {
|
|
318
448
|
return undefined;
|
|
319
449
|
}
|
|
320
450
|
|
|
321
451
|
const field = fields[col];
|
|
322
|
-
|
|
323
|
-
|
|
452
|
+
let value: any;
|
|
453
|
+
|
|
454
|
+
if (plane === 'frozenRowsEnd') {
|
|
455
|
+
// Get data from draft row
|
|
456
|
+
if (row >= 0 && row < this._draftRows.value.length) {
|
|
457
|
+
value = getValue(this._draftRows.value[row].data, field.path);
|
|
458
|
+
} else {
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
// Get data from regular row
|
|
463
|
+
const dataIndex = this._sorting.getDataIndex(row);
|
|
464
|
+
value = getValue(this._rows.value[dataIndex], field.path);
|
|
465
|
+
}
|
|
466
|
+
|
|
324
467
|
if (value == null) {
|
|
325
468
|
return '';
|
|
326
469
|
}
|
|
@@ -341,42 +484,62 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
341
484
|
}
|
|
342
485
|
};
|
|
343
486
|
|
|
344
|
-
public validateCellData = async ({ col, row }:
|
|
345
|
-
const
|
|
346
|
-
const fields = this._view?.fields ?? [];
|
|
487
|
+
public validateCellData = async ({ col, row, plane }: DxGridPosition, value: any): Promise<ValidationResult> => {
|
|
488
|
+
const fields = this._projection?.fields ?? [];
|
|
347
489
|
if (col < 0 || col >= fields.length) {
|
|
348
490
|
return { valid: false, error: 'Invalid column index' };
|
|
349
491
|
}
|
|
350
492
|
|
|
351
493
|
const field = fields[col];
|
|
352
494
|
const { props } = this._projection.getFieldProjection(field.id);
|
|
495
|
+
const transformedValue = editorTextToCellValue(props, value);
|
|
353
496
|
|
|
354
|
-
const
|
|
355
|
-
invariant(currentRow, 'Invalid row index');
|
|
497
|
+
const isDraftRow = plane === 'frozenRowsEnd';
|
|
356
498
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
499
|
+
if (isDraftRow) {
|
|
500
|
+
const isInvalidDraftRowIndex = row < 0 || row >= this._draftRows.value.length;
|
|
501
|
+
if (isInvalidDraftRowIndex) {
|
|
502
|
+
return { valid: false, error: 'Invalid draft row index' };
|
|
503
|
+
}
|
|
360
504
|
|
|
361
|
-
|
|
362
|
-
|
|
505
|
+
const draftRow = this._draftRows.value[row];
|
|
506
|
+
const snapshot = { ...draftRow.data };
|
|
507
|
+
setValue(snapshot, field.path, transformedValue);
|
|
363
508
|
|
|
364
|
-
|
|
365
|
-
|
|
509
|
+
const validationErrors = this.validateDraftRowData(snapshot);
|
|
510
|
+
// TODO(thure): These errors sometimes result in a useless message like “is missing” (what is missing?)
|
|
511
|
+
if (validationErrors.length > 0) {
|
|
512
|
+
const error = validationErrors.find((err) => err.path === field.path);
|
|
513
|
+
if (error) {
|
|
514
|
+
return { valid: false, error: error.message };
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return { valid: true };
|
|
518
|
+
} else {
|
|
519
|
+
const rowIdx = this._sorting.getDataIndex(row);
|
|
520
|
+
const currentRow = this._rows.value[rowIdx];
|
|
521
|
+
invariant(currentRow, 'Invalid row index');
|
|
366
522
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const error = validationResult[0];
|
|
370
|
-
invariant(error.path === field.path);
|
|
371
|
-
return { valid: false, error: error.message };
|
|
372
|
-
}
|
|
523
|
+
const snapshot = getSnapshot(currentRow);
|
|
524
|
+
setValue(snapshot, field.path, transformedValue);
|
|
373
525
|
|
|
374
|
-
|
|
526
|
+
const schema = getSchema(currentRow);
|
|
527
|
+
invariant(schema);
|
|
528
|
+
|
|
529
|
+
const validationResult = validateSchema(schema, snapshot);
|
|
530
|
+
if (validationResult && validationResult.length > 0) {
|
|
531
|
+
const error = validationResult[0];
|
|
532
|
+
invariant(error.path === field.path);
|
|
533
|
+
return { valid: false, error: error.message };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return { valid: true };
|
|
537
|
+
}
|
|
375
538
|
};
|
|
376
539
|
|
|
377
|
-
public setCellData = (
|
|
378
|
-
const
|
|
379
|
-
const fields = this.
|
|
540
|
+
public setCellData = (cell: DxGridPosition, value: any): void => {
|
|
541
|
+
const { col, row, plane } = cell;
|
|
542
|
+
const fields = this._projection?.fields ?? [];
|
|
380
543
|
if (col < 0 || col >= fields.length) {
|
|
381
544
|
return;
|
|
382
545
|
}
|
|
@@ -387,12 +550,21 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
387
550
|
|
|
388
551
|
// Special handling for Ref format to preserve existing behavior
|
|
389
552
|
if (props.format === FormatEnum.Ref && !isLiveObject(value)) {
|
|
390
|
-
// TODO(ZaymonFC): This get's called an additional time by the cell editor onBlur, but with the cell editors
|
|
391
|
-
// plain string value. Maybe onBlur should be called with the actual value?
|
|
392
553
|
return;
|
|
393
554
|
}
|
|
394
555
|
|
|
395
|
-
|
|
556
|
+
if (plane === 'frozenRowsEnd') {
|
|
557
|
+
// Update draft row data
|
|
558
|
+
if (row >= 0 && row < this._draftRows.value.length) {
|
|
559
|
+
setValue(this._draftRows.value[row].data, field.path, transformedValue);
|
|
560
|
+
// Re-validate the draft row after the update
|
|
561
|
+
this.validateDraftRow(row);
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
// Update regular row data
|
|
565
|
+
const rowIdx = this._sorting.getDataIndex(row);
|
|
566
|
+
setValue(this._rows.value[rowIdx], field.path, transformedValue);
|
|
567
|
+
}
|
|
396
568
|
};
|
|
397
569
|
|
|
398
570
|
/**
|
|
@@ -402,7 +574,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
402
574
|
*/
|
|
403
575
|
public updateCellData({ col, row }: DxGridPlanePosition, update: (value: any) => any): void {
|
|
404
576
|
const dataRow = this._sorting.getDataIndex(row);
|
|
405
|
-
const fields = this.
|
|
577
|
+
const fields = this._projection?.fields ?? [];
|
|
406
578
|
const field = fields[col];
|
|
407
579
|
|
|
408
580
|
const value = getValue(this._rows.value[dataRow], field.path);
|
|
@@ -410,16 +582,43 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
410
582
|
setValue(this._rows.value[dataRow], field.path, updatedValue);
|
|
411
583
|
}
|
|
412
584
|
|
|
585
|
+
/**
|
|
586
|
+
* Gets the domain-appropriate cell type for a given cell position.
|
|
587
|
+
* This abstracts away the low-level grid plane concepts and provides domain-specific types.
|
|
588
|
+
* @param cell - The cell position to classify
|
|
589
|
+
* @returns The domain-appropriate cell type
|
|
590
|
+
*/
|
|
591
|
+
public getCellType(cell: DxGridPosition): TableCellType {
|
|
592
|
+
switch (cell.plane) {
|
|
593
|
+
case 'frozenRowsEnd':
|
|
594
|
+
return 'draft';
|
|
595
|
+
case 'frozenRowsStart':
|
|
596
|
+
return 'header';
|
|
597
|
+
case 'grid':
|
|
598
|
+
default:
|
|
599
|
+
return 'standard';
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Convenience method to check if a cell is a draft cell.
|
|
605
|
+
* @param cell - The cell position to check
|
|
606
|
+
* @returns true if the cell is a draft cell, false otherwise
|
|
607
|
+
*/
|
|
608
|
+
public isDraftCell(cell: DxGridPosition): boolean {
|
|
609
|
+
return this.getCellType(cell) === 'draft';
|
|
610
|
+
}
|
|
611
|
+
|
|
413
612
|
//
|
|
414
613
|
// Column Operations
|
|
415
614
|
//
|
|
416
615
|
|
|
417
616
|
public deleteColumn(fieldId: string): void {
|
|
418
|
-
if (!this.
|
|
617
|
+
if (!this._projection) {
|
|
419
618
|
return;
|
|
420
619
|
}
|
|
421
620
|
|
|
422
|
-
const field = this.
|
|
621
|
+
const field = this._projection?.fields.find((field) => field.id === fieldId);
|
|
423
622
|
if (field && this._onDeleteColumn) {
|
|
424
623
|
this._onDeleteColumn(field.id);
|
|
425
624
|
}
|
|
@@ -430,12 +629,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
430
629
|
//
|
|
431
630
|
|
|
432
631
|
public setColumnWidth(columnIndex: number, width: number): void {
|
|
433
|
-
const fields = this.
|
|
632
|
+
const fields = this._projection?.fields ?? [];
|
|
434
633
|
if (columnIndex < fields.length) {
|
|
435
634
|
const newWidth = Math.max(0, width);
|
|
436
635
|
const field = fields[columnIndex];
|
|
437
636
|
if (field) {
|
|
438
|
-
field.
|
|
637
|
+
this.table.sizes[field.path] = newWidth;
|
|
439
638
|
}
|
|
440
639
|
}
|
|
441
640
|
}
|
|
@@ -457,6 +656,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
457
656
|
//
|
|
458
657
|
// View operations
|
|
459
658
|
//
|
|
659
|
+
|
|
460
660
|
public saveView(): void {
|
|
461
661
|
this._sorting.save();
|
|
462
662
|
}
|
|
@@ -466,7 +666,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
|
|
|
466
666
|
switch (props.format) {
|
|
467
667
|
case FormatEnum.Ref: {
|
|
468
668
|
if (isLiveObject(value)) {
|
|
469
|
-
return
|
|
669
|
+
return Ref.make(value);
|
|
470
670
|
} else {
|
|
471
671
|
return value;
|
|
472
672
|
}
|