@dxos/react-ui-table 0.8.4-main.f9ba587 → 0.8.4-main.fd6878d
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-L2LSB6KV.mjs +18 -0
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
- package/dist/lib/browser/chunk-ZAOSIG3L.mjs +1814 -0
- package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +658 -2359
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +123 -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-NXZ4CJYX.mjs +20 -0
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs +1815 -0
- package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +658 -2359
- 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 +124 -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 +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/{relations.stories.d.ts → Relations.stories.d.ts} +2 -2
- 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 +1 -0
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/extension.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/useTableModel.d.ts +8 -7
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -1
- 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 +17 -16
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +2 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +4 -4
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- 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 +41 -0
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +2 -2
- 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 +8 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util/dynamic-table.d.ts +3 -4
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -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/src/util/table.d.ts +9 -11
- package/dist/types/src/util/table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -36
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +15 -11
- package/src/components/Table/CreateRefPanel.tsx +6 -4
- package/src/components/Table/DynamicTable.stories.tsx +3 -2
- package/src/components/Table/DynamicTable.tsx +17 -10
- package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +36 -35
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +99 -144
- package/src/components/Table/Table.tsx +87 -71
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -27
- package/src/components/TableCellEditor/TableCellEditor.tsx +42 -27
- package/src/components/TableCellEditor/extension.ts +2 -2
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +7 -24
- package/src/hooks/useTableModel.ts +21 -17
- package/src/index.ts +0 -1
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +6 -3
- package/src/model/table-model.test.ts +8 -8
- package/src/model/table-model.ts +68 -62
- package/src/model/table-presentation.test.ts +8 -8
- package/src/model/table-presentation.ts +35 -13
- package/src/model/table-sorting.ts +11 -11
- package/src/playwright/smoke.spec.ts +105 -14
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +123 -0
- package/src/translations.ts +2 -2
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +16 -0
- package/src/util/dynamic-table.ts +20 -18
- package/src/util/schema.ts +1 -1
- package/src/util/table-buttons.ts +2 -1
- package/src/util/table-controls.ts +4 -1
- package/src/util/table.ts +37 -111
- package/dist/lib/browser/chunk-Q5QQXJUO.mjs +0 -27
- package/dist/lib/browser/chunk-Q5QQXJUO.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs +0 -29
- package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs.map +0 -7
- package/dist/types/src/components/Table/relations.stories.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/src/types/table.ts +0 -27
package/src/model/table-model.ts
CHANGED
|
@@ -2,44 +2,42 @@
|
|
|
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';
|
|
9
|
-
import { Ref } from '@dxos/echo';
|
|
8
|
+
import { Obj, Ref } from '@dxos/echo';
|
|
10
9
|
import {
|
|
11
10
|
type FieldSortType,
|
|
12
11
|
FormatEnum,
|
|
13
|
-
getValue,
|
|
14
|
-
setValue,
|
|
15
12
|
type JsonProp,
|
|
16
|
-
|
|
13
|
+
type JsonSchemaType,
|
|
17
14
|
getSchema,
|
|
15
|
+
getSnapshot,
|
|
16
|
+
getValue,
|
|
17
|
+
setValue,
|
|
18
18
|
toEffectSchema,
|
|
19
19
|
} from '@dxos/echo-schema';
|
|
20
20
|
import { invariant } from '@dxos/invariant';
|
|
21
21
|
import { ObjectId } from '@dxos/keys';
|
|
22
22
|
import { isLiveObject } from '@dxos/live-object';
|
|
23
|
+
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
24
|
+
import { type Label } from '@dxos/react-ui';
|
|
23
25
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
24
26
|
import {
|
|
25
27
|
type DxGridAxisMeta,
|
|
26
|
-
type DxGridPlaneRange,
|
|
27
28
|
type DxGridPlanePosition,
|
|
29
|
+
type DxGridPlaneRange,
|
|
28
30
|
type DxGridPosition,
|
|
29
31
|
} from '@dxos/react-ui-grid';
|
|
30
|
-
import {
|
|
31
|
-
type ViewType,
|
|
32
|
-
type ViewProjection,
|
|
33
|
-
type PropertyType,
|
|
34
|
-
validateSchema,
|
|
35
|
-
type ValidationError,
|
|
36
|
-
} from '@dxos/schema';
|
|
32
|
+
import { type DataType, ProjectionModel, type PropertyType, type ValidationError, validateSchema } from '@dxos/schema';
|
|
37
33
|
|
|
38
|
-
import {
|
|
39
|
-
import { TableSorting } from './table-sorting';
|
|
34
|
+
import { TableView } from '../types';
|
|
40
35
|
import { touch } from '../util';
|
|
41
36
|
import { extractTagIds } from '../util/tag';
|
|
42
37
|
|
|
38
|
+
import { type SelectionMode, SelectionModel } from './selection-model';
|
|
39
|
+
import { TableSorting } from './table-sorting';
|
|
40
|
+
|
|
43
41
|
// Domain types for cell classification
|
|
44
42
|
export type TableCellType = 'standard' | 'draft' | 'header';
|
|
45
43
|
|
|
@@ -57,7 +55,7 @@ export type TableRow = Record<JsonProp, any> & { id: string };
|
|
|
57
55
|
|
|
58
56
|
export type TableRowAction = {
|
|
59
57
|
id: string;
|
|
60
|
-
|
|
58
|
+
label: Label;
|
|
61
59
|
};
|
|
62
60
|
|
|
63
61
|
export type TableFeatures = {
|
|
@@ -73,14 +71,15 @@ const defaultFeatures: TableFeatures = {
|
|
|
73
71
|
};
|
|
74
72
|
|
|
75
73
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
projection: ViewProjection;
|
|
74
|
+
view: DataType.View;
|
|
75
|
+
schema: JsonSchemaType;
|
|
76
|
+
projection?: ProjectionModel;
|
|
80
77
|
features?: Partial<TableFeatures>;
|
|
81
78
|
sorting?: FieldSortType[];
|
|
79
|
+
initialSelection?: string[];
|
|
82
80
|
pinnedRows?: { top: number[]; bottom: number[] };
|
|
83
81
|
rowActions?: TableRowAction[];
|
|
82
|
+
onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
|
|
84
83
|
onInsertRow?: (data?: any) => boolean;
|
|
85
84
|
onDeleteRows?: (index: number, obj: T[]) => void;
|
|
86
85
|
onDeleteColumn?: (fieldId: string) => void;
|
|
@@ -90,10 +89,9 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
90
89
|
};
|
|
91
90
|
|
|
92
91
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
93
|
-
private readonly
|
|
94
|
-
private readonly
|
|
95
|
-
private
|
|
96
|
-
private readonly _projection: ViewProjection;
|
|
92
|
+
private readonly _view: DataType.View;
|
|
93
|
+
private readonly _projection: ProjectionModel;
|
|
94
|
+
private _table?: TableView;
|
|
97
95
|
|
|
98
96
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
99
97
|
start: { row: 0, col: 0 },
|
|
@@ -118,12 +116,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
118
116
|
private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
|
|
119
117
|
|
|
120
118
|
constructor({
|
|
121
|
-
id,
|
|
122
|
-
space,
|
|
123
119
|
view,
|
|
120
|
+
schema,
|
|
124
121
|
projection,
|
|
125
122
|
features = {},
|
|
126
123
|
sorting = [],
|
|
124
|
+
initialSelection = [],
|
|
127
125
|
pinnedRows = { top: [], bottom: [] },
|
|
128
126
|
rowActions = [],
|
|
129
127
|
onCellUpdate,
|
|
@@ -134,10 +132,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
134
132
|
onRowAction,
|
|
135
133
|
}: TableModelProps<T>) {
|
|
136
134
|
super();
|
|
137
|
-
this._id = id;
|
|
138
|
-
this._space = space;
|
|
139
135
|
this._view = view;
|
|
140
|
-
this._projection = projection;
|
|
136
|
+
this._projection = projection ?? new ProjectionModel(schema, view.projection);
|
|
141
137
|
|
|
142
138
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
143
139
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -147,13 +143,20 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
147
143
|
'Single selection is not compatible with editable tables.',
|
|
148
144
|
);
|
|
149
145
|
|
|
150
|
-
this._sorting = new TableSorting(this._rows, this._view,
|
|
146
|
+
this._sorting = new TableSorting(this._rows, this._view, this._projection);
|
|
151
147
|
|
|
152
148
|
if (sorting.length > 0) {
|
|
153
149
|
const [sort] = sorting;
|
|
154
150
|
this._sorting.setSort(sort.fieldId, sort.direction);
|
|
155
151
|
}
|
|
156
152
|
|
|
153
|
+
this._selection = new SelectionModel(
|
|
154
|
+
this._sorting.sortedRows,
|
|
155
|
+
this._features.selection.mode ?? 'multiple',
|
|
156
|
+
initialSelection,
|
|
157
|
+
() => this._onRowOrderChange?.(),
|
|
158
|
+
);
|
|
159
|
+
|
|
157
160
|
this._pinnedRows = pinnedRows;
|
|
158
161
|
this._rowActions = rowActions;
|
|
159
162
|
this._onInsertRow = onInsertRow;
|
|
@@ -164,24 +167,25 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
164
167
|
this._onRowAction = onRowAction;
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
public get id() {
|
|
168
|
-
return this.
|
|
170
|
+
public get id(): string {
|
|
171
|
+
return fullyQualifiedId(this._view);
|
|
169
172
|
}
|
|
170
173
|
|
|
171
|
-
public get
|
|
172
|
-
return this.
|
|
174
|
+
public get view(): DataType.View {
|
|
175
|
+
return this._view;
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
public get
|
|
176
|
-
|
|
178
|
+
public get table(): TableView {
|
|
179
|
+
invariant(this._table, 'Model not initialized');
|
|
180
|
+
return this._table;
|
|
177
181
|
}
|
|
178
182
|
|
|
179
|
-
public get projection() {
|
|
183
|
+
public get projection(): ProjectionModel {
|
|
180
184
|
return this._projection;
|
|
181
185
|
}
|
|
182
186
|
|
|
183
187
|
public get rows(): ReadonlySignal<T[]> {
|
|
184
|
-
return this.
|
|
188
|
+
return this.sorting.sortedRows;
|
|
185
189
|
}
|
|
186
190
|
|
|
187
191
|
public get features(): TableFeatures {
|
|
@@ -191,7 +195,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
191
195
|
/**
|
|
192
196
|
* @reactive
|
|
193
197
|
*/
|
|
194
|
-
public get sorting(): TableSorting<T>
|
|
198
|
+
public get sorting(): TableSorting<T> {
|
|
195
199
|
return this._sorting;
|
|
196
200
|
}
|
|
197
201
|
|
|
@@ -199,11 +203,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
199
203
|
* Gets the row data at the specified display index.
|
|
200
204
|
*/
|
|
201
205
|
public getRowAt(displayIndex: number): T | undefined {
|
|
202
|
-
if (displayIndex < 0 || displayIndex >= this.
|
|
206
|
+
if (displayIndex < 0 || displayIndex >= this.sorting.sortedRows.value.length) {
|
|
203
207
|
return undefined;
|
|
204
208
|
}
|
|
205
209
|
|
|
206
|
-
return this.
|
|
210
|
+
return this.sorting.sortedRows.value[displayIndex];
|
|
207
211
|
}
|
|
208
212
|
|
|
209
213
|
public get pinnedRows(): NonNullable<TableModelProps<T>['pinnedRows']> {
|
|
@@ -221,7 +225,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
221
225
|
|
|
222
226
|
/** @reactive */
|
|
223
227
|
public get isViewDirty(): boolean {
|
|
224
|
-
return this.
|
|
228
|
+
return this.sorting.isDirty;
|
|
225
229
|
}
|
|
226
230
|
|
|
227
231
|
public get rowActions(): TableRowAction[] {
|
|
@@ -233,19 +237,20 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
233
237
|
//
|
|
234
238
|
|
|
235
239
|
protected override async _open(): Promise<void> {
|
|
240
|
+
const presentation = this._view.presentation.target ?? (await this._view.presentation.load());
|
|
241
|
+
invariant(Obj.instanceOf(TableView, presentation));
|
|
242
|
+
this._table = presentation;
|
|
243
|
+
|
|
236
244
|
this.initializeColumnMeta();
|
|
237
245
|
this.initializeEffects();
|
|
238
|
-
this._selection = new SelectionModel(this._sorting.sortedRows, this._features.selection.mode ?? 'multiple', () =>
|
|
239
|
-
this._onRowOrderChange?.(),
|
|
240
|
-
);
|
|
241
246
|
await this._selection.open(this._ctx);
|
|
242
247
|
}
|
|
243
248
|
|
|
244
249
|
private initializeColumnMeta(): void {
|
|
245
250
|
this._columnMeta = computed(() => {
|
|
246
|
-
const fields = this.
|
|
251
|
+
const fields = this._projection?.fields ?? [];
|
|
247
252
|
const meta = Object.fromEntries(
|
|
248
|
-
fields.map((field, index: number) => [index, { size: field
|
|
253
|
+
fields.map((field, index: number) => [index, { size: this.table.sizes[field.path] ?? 256, resizeable: true }]),
|
|
249
254
|
);
|
|
250
255
|
|
|
251
256
|
return {
|
|
@@ -258,7 +263,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
258
263
|
|
|
259
264
|
private initializeEffects(): void {
|
|
260
265
|
const rowOrderWatcher = effect(() => {
|
|
261
|
-
touch(this.
|
|
266
|
+
touch(this.sorting.sortedRows.value);
|
|
262
267
|
this._onRowOrderChange?.();
|
|
263
268
|
});
|
|
264
269
|
this._ctx.onDispose(rowOrderWatcher);
|
|
@@ -274,8 +279,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
274
279
|
for (let row = start.row; row <= end.row; row++) {
|
|
275
280
|
rowEffects.push(
|
|
276
281
|
effect(() => {
|
|
277
|
-
const obj = this.
|
|
278
|
-
this.
|
|
282
|
+
const obj = this.sorting.sortedRows.value[row];
|
|
283
|
+
this._projection?.fields.forEach((field) => touch(getValue(obj, field.path)));
|
|
279
284
|
this._onCellUpdate?.({ row, col: start.col, plane: 'grid' });
|
|
280
285
|
}),
|
|
281
286
|
);
|
|
@@ -329,7 +334,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
329
334
|
return validationErrors.some((error) => error.path === fieldPath);
|
|
330
335
|
}
|
|
331
336
|
|
|
332
|
-
public getColumnCount = (): number => this.
|
|
337
|
+
public getColumnCount = (): number => this._projection?.fields.length ?? 0;
|
|
333
338
|
|
|
334
339
|
public insertRow = (): void => {
|
|
335
340
|
const result = this._onInsertRow?.();
|
|
@@ -339,7 +344,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
339
344
|
};
|
|
340
345
|
|
|
341
346
|
private createDraftRow(): void {
|
|
342
|
-
if (!this.
|
|
347
|
+
if (!this._projection) {
|
|
343
348
|
return;
|
|
344
349
|
}
|
|
345
350
|
|
|
@@ -376,7 +381,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
376
381
|
}
|
|
377
382
|
|
|
378
383
|
private validateDraftRowData(data: T): ValidationError[] {
|
|
379
|
-
const schema = toEffectSchema(this._projection.
|
|
384
|
+
const schema = toEffectSchema(this._projection.baseSchema);
|
|
380
385
|
return validateSchema(schema, data) || [];
|
|
381
386
|
}
|
|
382
387
|
|
|
@@ -430,7 +435,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
430
435
|
|
|
431
436
|
public getCellData = (cell: DxGridPosition): any => {
|
|
432
437
|
const { col, row, plane } = cell;
|
|
433
|
-
const fields = this.
|
|
438
|
+
const fields = this._projection?.fields ?? [];
|
|
434
439
|
if (col < 0 || col >= fields.length) {
|
|
435
440
|
return undefined;
|
|
436
441
|
}
|
|
@@ -472,7 +477,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
472
477
|
};
|
|
473
478
|
|
|
474
479
|
public validateCellData = async ({ col, row, plane }: DxGridPosition, value: any): Promise<ValidationResult> => {
|
|
475
|
-
const fields = this.
|
|
480
|
+
const fields = this._projection?.fields ?? [];
|
|
476
481
|
if (col < 0 || col >= fields.length) {
|
|
477
482
|
return { valid: false, error: 'Invalid column index' };
|
|
478
483
|
}
|
|
@@ -494,6 +499,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
494
499
|
setValue(snapshot, field.path, transformedValue);
|
|
495
500
|
|
|
496
501
|
const validationErrors = this.validateDraftRowData(snapshot);
|
|
502
|
+
// TODO(thure): These errors sometimes result in a useless message like “is missing” (what is missing?)
|
|
497
503
|
if (validationErrors.length > 0) {
|
|
498
504
|
const error = validationErrors.find((err) => err.path === field.path);
|
|
499
505
|
if (error) {
|
|
@@ -525,7 +531,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
525
531
|
|
|
526
532
|
public setCellData = (cell: DxGridPosition, value: any): void => {
|
|
527
533
|
const { col, row, plane } = cell;
|
|
528
|
-
const fields = this.
|
|
534
|
+
const fields = this._projection?.fields ?? [];
|
|
529
535
|
if (col < 0 || col >= fields.length) {
|
|
530
536
|
return;
|
|
531
537
|
}
|
|
@@ -560,7 +566,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
560
566
|
*/
|
|
561
567
|
public updateCellData({ col, row }: DxGridPlanePosition, update: (value: any) => any): void {
|
|
562
568
|
const dataRow = this._sorting.getDataIndex(row);
|
|
563
|
-
const fields = this.
|
|
569
|
+
const fields = this._projection?.fields ?? [];
|
|
564
570
|
const field = fields[col];
|
|
565
571
|
|
|
566
572
|
const value = getValue(this._rows.value[dataRow], field.path);
|
|
@@ -600,11 +606,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
600
606
|
//
|
|
601
607
|
|
|
602
608
|
public deleteColumn(fieldId: string): void {
|
|
603
|
-
if (!this.
|
|
609
|
+
if (!this._projection) {
|
|
604
610
|
return;
|
|
605
611
|
}
|
|
606
612
|
|
|
607
|
-
const field = this.
|
|
613
|
+
const field = this._projection?.fields.find((field) => field.id === fieldId);
|
|
608
614
|
if (field && this._onDeleteColumn) {
|
|
609
615
|
this._onDeleteColumn(field.id);
|
|
610
616
|
}
|
|
@@ -615,12 +621,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
615
621
|
//
|
|
616
622
|
|
|
617
623
|
public setColumnWidth(columnIndex: number, width: number): void {
|
|
618
|
-
const fields = this.
|
|
624
|
+
const fields = this._projection?.fields ?? [];
|
|
619
625
|
if (columnIndex < fields.length) {
|
|
620
626
|
const newWidth = Math.max(0, width);
|
|
621
627
|
const field = fields[columnIndex];
|
|
622
628
|
if (field) {
|
|
623
|
-
field.
|
|
629
|
+
this.table.sizes[field.path] = newWidth;
|
|
624
630
|
}
|
|
625
631
|
}
|
|
626
632
|
}
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
|
-
import { describe, expect, it
|
|
6
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import { Obj
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
9
9
|
import { TypedObject } from '@dxos/echo-schema';
|
|
10
10
|
import { live } from '@dxos/live-object';
|
|
11
11
|
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
12
|
-
import { createView
|
|
12
|
+
import { createView } from '@dxos/schema';
|
|
13
|
+
|
|
14
|
+
import { TableView } from '../types';
|
|
13
15
|
|
|
14
16
|
import { TableModel, type TableModelProps } from './table-model';
|
|
15
17
|
import { TablePresentation } from './table-presentation';
|
|
16
|
-
import { TableType } from '../types';
|
|
17
18
|
|
|
18
19
|
describe('TablePresentation', () => {
|
|
19
20
|
describe('row reactivity', () => {
|
|
@@ -97,8 +98,7 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
97
98
|
|
|
98
99
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
99
100
|
const schema = createEchoSchema(Test);
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
return new TableModel({ id: table.id, view, projection, ...props });
|
|
101
|
+
const table = Obj.make(TableView, { sizes: {} });
|
|
102
|
+
const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
|
|
103
|
+
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
104
104
|
};
|
|
@@ -6,7 +6,7 @@ import { signal } from '@preact/signals-core';
|
|
|
6
6
|
import { isNotNullable } from 'effect/Predicate';
|
|
7
7
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import { FormatEnum,
|
|
9
|
+
import { FormatEnum, TypeEnum, getValue } from '@dxos/echo-schema';
|
|
10
10
|
import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
|
|
11
11
|
import {
|
|
12
12
|
type DxGridCellValue,
|
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
toPlaneCellIndex,
|
|
17
17
|
} from '@dxos/react-ui-grid';
|
|
18
18
|
import { mx } from '@dxos/react-ui-theme';
|
|
19
|
-
import {
|
|
19
|
+
import { type FieldType, VIEW_FIELD_LIMIT } from '@dxos/schema';
|
|
20
20
|
|
|
21
|
-
import { type SelectionMode } from './selection-model';
|
|
22
|
-
import { type TableRow, type TableModel } from './table-model';
|
|
23
21
|
import { tableButtons, tableControls } from '../util';
|
|
24
22
|
|
|
23
|
+
import { type SelectionMode } from './selection-model';
|
|
24
|
+
import { type TableModel, type TableRow } from './table-model';
|
|
25
|
+
|
|
25
26
|
/**
|
|
26
27
|
* Presentation layer for a table component, handling cell rendering and grid display logic.
|
|
27
28
|
* Manages rendering of table cells, headers, selection columns, and action columns across
|
|
@@ -67,6 +68,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
67
68
|
case 'frozenRowsEnd':
|
|
68
69
|
cells = this.getDraftRowCells(range);
|
|
69
70
|
break;
|
|
71
|
+
case 'fixedEndStart':
|
|
72
|
+
cells = this.getDraftSelectCells(range);
|
|
73
|
+
break;
|
|
70
74
|
case 'fixedEndEnd':
|
|
71
75
|
cells = this.getDraftActionCells(range);
|
|
72
76
|
break;
|
|
@@ -186,7 +190,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
186
190
|
.map(getLabel)
|
|
187
191
|
.filter(isNotNullable)
|
|
188
192
|
.map((title) => {
|
|
189
|
-
return `<span class="dx-tag
|
|
193
|
+
return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
|
|
190
194
|
})
|
|
191
195
|
.join('');
|
|
192
196
|
|
|
@@ -199,7 +203,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
199
203
|
const targetObj = getValue(obj, field.path)?.target;
|
|
200
204
|
if (targetObj) {
|
|
201
205
|
const dxn = Obj.getDXN(targetObj)?.toString();
|
|
202
|
-
cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-
|
|
206
|
+
cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-2 block-start-[.2rem]" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
|
|
@@ -254,7 +258,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
254
258
|
|
|
255
259
|
private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
256
260
|
const cells: DxGridPlaneCells = {};
|
|
257
|
-
const fields = this.model.
|
|
261
|
+
const fields = this.model.projection?.fields ?? [];
|
|
258
262
|
|
|
259
263
|
for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
|
|
260
264
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
@@ -272,12 +276,13 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
272
276
|
|
|
273
277
|
private getDraftRowCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
274
278
|
const cells: DxGridPlaneCells = {};
|
|
275
|
-
const fields = this.model.
|
|
279
|
+
const fields = this.model.projection?.fields ?? [];
|
|
276
280
|
const draftRows = this.model.draftRows.value;
|
|
277
281
|
|
|
278
282
|
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
279
283
|
const draftRow = draftRows[row];
|
|
280
284
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
285
|
+
const cellIndex = toPlaneCellIndex({ col, row });
|
|
281
286
|
const field = fields[col];
|
|
282
287
|
if (!field) {
|
|
283
288
|
continue;
|
|
@@ -286,7 +291,6 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
286
291
|
this.createDataCell(cells, draftRow.data, field, col, row);
|
|
287
292
|
|
|
288
293
|
if (this.model.hasDraftRowValidationError(row, field.path)) {
|
|
289
|
-
const cellIndex = toPlaneCellIndex({ col, row });
|
|
290
294
|
const cellValue = cells[cellIndex];
|
|
291
295
|
if (cellValue) {
|
|
292
296
|
const existingClasses = cellValue.className || '';
|
|
@@ -294,6 +298,8 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
294
298
|
cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
|
|
295
299
|
}
|
|
296
300
|
}
|
|
301
|
+
|
|
302
|
+
cells[cellIndex].className += ' !bg-toolbarSurface';
|
|
297
303
|
}
|
|
298
304
|
}
|
|
299
305
|
|
|
@@ -302,9 +308,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
302
308
|
|
|
303
309
|
private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
304
310
|
const cells: DxGridPlaneCells = {};
|
|
305
|
-
const fields = this.model.
|
|
311
|
+
const fields = this.model.projection?.fields ?? [];
|
|
306
312
|
for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
|
|
307
|
-
const { field, props } = this.getFieldProjection(fields[col].id);
|
|
313
|
+
const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
|
|
308
314
|
const sorting = this.model.sorting?.sorting;
|
|
309
315
|
const direction = sorting?.fieldId === field.id ? sorting.direction : undefined;
|
|
310
316
|
|
|
@@ -386,7 +392,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
386
392
|
[toPlaneCellIndex({ col: 0, row: 0 })]: {
|
|
387
393
|
accessoryHtml: this.model.features.schemaEditable
|
|
388
394
|
? tableButtons.addColumn.render({
|
|
389
|
-
disabled: (this.model.
|
|
395
|
+
disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
|
|
390
396
|
})
|
|
391
397
|
: undefined,
|
|
392
398
|
className: '!bg-toolbarSurface',
|
|
@@ -408,6 +414,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
408
414
|
value: '',
|
|
409
415
|
readonly: true,
|
|
410
416
|
accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
|
|
417
|
+
className: '!bg-toolbarSurface',
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return cells;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private getDraftSelectCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
425
|
+
const cells: DxGridPlaneCells = {};
|
|
426
|
+
const draftRows = this.model.draftRows.value;
|
|
427
|
+
|
|
428
|
+
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
429
|
+
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
430
|
+
value: '',
|
|
431
|
+
readonly: true,
|
|
432
|
+
className: '!bg-toolbarSurface',
|
|
411
433
|
};
|
|
412
434
|
}
|
|
413
435
|
|
|
@@ -427,7 +449,7 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
|
|
|
427
449
|
switch (selectionMode) {
|
|
428
450
|
case 'single':
|
|
429
451
|
// TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
|
|
430
|
-
return ['!bg-currentRelated hover:bg-hoverSurface
|
|
452
|
+
return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
|
|
431
453
|
case 'multiple':
|
|
432
454
|
return ['!bg-gridCellSelected'];
|
|
433
455
|
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type ReadonlySignal, type Signal, computed, signal } from '@preact/signals-core';
|
|
6
6
|
import orderBy from 'lodash.orderby';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { type FieldSortType, FormatEnum, type SortDirectionType, TypeEnum, getValue } from '@dxos/echo-schema';
|
|
9
9
|
import { formatForDisplay } from '@dxos/react-ui-form';
|
|
10
|
-
import type {
|
|
10
|
+
import type { DataType, FieldType, ProjectionModel, PropertyType } from '@dxos/schema';
|
|
11
11
|
|
|
12
12
|
import { type TableRow } from './table-model';
|
|
13
13
|
|
|
@@ -49,13 +49,13 @@ export class TableSorting<T extends TableRow> {
|
|
|
49
49
|
|
|
50
50
|
constructor(
|
|
51
51
|
rows: Signal<T[]>,
|
|
52
|
-
private readonly _view:
|
|
53
|
-
private readonly _projection:
|
|
52
|
+
private readonly _view: DataType.View,
|
|
53
|
+
private readonly _projection: ProjectionModel,
|
|
54
54
|
) {
|
|
55
55
|
this._rows = rows;
|
|
56
56
|
this._isDirty = computed(() => {
|
|
57
57
|
const local = this._localSort.value;
|
|
58
|
-
const viewSort = this._view
|
|
58
|
+
const viewSort = this._view.query.sort?.[0];
|
|
59
59
|
if (local?.type === 'cleared') {
|
|
60
60
|
return viewSort !== undefined;
|
|
61
61
|
}
|
|
@@ -83,7 +83,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
83
83
|
return local.sort;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
return this._view
|
|
86
|
+
return this._view.query.sort?.[0];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
@@ -106,7 +106,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
public toggleSort(fieldId: string): void {
|
|
109
|
-
if (!this.
|
|
109
|
+
if (!this.sorting || this.sorting.fieldId !== fieldId) {
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -127,7 +127,7 @@ export class TableSorting<T extends TableRow> {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
public save(): void {
|
|
130
|
-
if (this.
|
|
130
|
+
if (this._localSort.value !== undefined) {
|
|
131
131
|
if (this._localSort.value.type === 'active') {
|
|
132
132
|
this._view.query.sort = [this._localSort.value.sort];
|
|
133
133
|
} else {
|
|
@@ -145,11 +145,11 @@ export class TableSorting<T extends TableRow> {
|
|
|
145
145
|
return computed(() => {
|
|
146
146
|
this._displayToDataIndex.clear();
|
|
147
147
|
const sort = this.sorting;
|
|
148
|
-
if (!sort
|
|
148
|
+
if (!sort) {
|
|
149
149
|
return this._rows.value;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
const field = this.
|
|
152
|
+
const field = this._projection.fields.find((f) => f.id === sort.fieldId);
|
|
153
153
|
if (!field) {
|
|
154
154
|
return this._rows.value;
|
|
155
155
|
}
|