@dxos/react-ui-table 0.7.4-staging.f7e8224 → 0.7.5-main.499c70c

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.
Files changed (90) hide show
  1. package/dist/lib/browser/{chunk-IYCSJDMB.mjs → chunk-AT7O4RAD.mjs} +4 -4
  2. package/dist/lib/browser/{chunk-IYCSJDMB.mjs.map → chunk-AT7O4RAD.mjs.map} +2 -2
  3. package/dist/lib/browser/index.mjs +1398 -1216
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types/index.mjs +1 -1
  7. package/dist/lib/node/{chunk-MHBMVCFZ.cjs → chunk-YSBXNBDJ.cjs} +6 -6
  8. package/dist/lib/node/{chunk-MHBMVCFZ.cjs.map → chunk-YSBXNBDJ.cjs.map} +2 -2
  9. package/dist/lib/node/index.cjs +1130 -934
  10. package/dist/lib/node/index.cjs.map +4 -4
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/types/index.cjs +3 -3
  13. package/dist/lib/node/types/index.cjs.map +1 -1
  14. package/dist/lib/node-esm/{chunk-BMWGPXPL.mjs → chunk-6OH2NHUX.mjs} +4 -4
  15. package/dist/lib/node-esm/{chunk-BMWGPXPL.mjs.map → chunk-6OH2NHUX.mjs.map} +2 -2
  16. package/dist/lib/node-esm/index.mjs +1398 -1216
  17. package/dist/lib/node-esm/index.mjs.map +4 -4
  18. package/dist/lib/node-esm/meta.json +1 -1
  19. package/dist/lib/node-esm/types/index.mjs +1 -1
  20. package/dist/types/src/_deprecated/hooks/useColumnSorting.d.ts +1 -1
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +4 -3
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/CreateRefPanel.d.ts +3 -2
  26. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/RefPanel.d.ts +4 -3
  28. package/dist/types/src/components/Table/RefPanel.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/RowActionsMenu.d.ts +3 -2
  30. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/Table.d.ts +2 -1
  32. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -2
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/testing.d.ts +55 -71
  38. package/dist/types/src/components/testing.d.ts.map +1 -1
  39. package/dist/types/src/index.d.ts +1 -0
  40. package/dist/types/src/index.d.ts.map +1 -1
  41. package/dist/types/src/model/index.d.ts +1 -0
  42. package/dist/types/src/model/index.d.ts.map +1 -1
  43. package/dist/types/src/model/modal-controller.d.ts +6 -2
  44. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  45. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  46. package/dist/types/src/model/table-model.d.ts +8 -12
  47. package/dist/types/src/model/table-model.d.ts.map +1 -1
  48. package/dist/types/src/model/table-presentation.d.ts +22 -0
  49. package/dist/types/src/model/table-presentation.d.ts.map +1 -0
  50. package/dist/types/src/model/table-presentation.test.d.ts +2 -0
  51. package/dist/types/src/model/table-presentation.test.d.ts.map +1 -0
  52. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  53. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  54. package/dist/types/src/types/table.d.ts +118 -154
  55. package/dist/types/src/types/table.d.ts.map +1 -1
  56. package/dist/types/src/util/index.d.ts +1 -0
  57. package/dist/types/src/util/index.d.ts.map +1 -1
  58. package/dist/types/src/util/table-buttons.d.ts +50 -20
  59. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  60. package/dist/types/src/util/table-controls.d.ts +32 -9
  61. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  62. package/dist/types/src/util/table.d.ts +2 -2
  63. package/dist/types/src/util/table.d.ts.map +1 -1
  64. package/dist/types/tsconfig.tsbuildinfo +1 -0
  65. package/package.json +29 -29
  66. package/src/components/Table/ColumnActionsMenu.tsx +8 -8
  67. package/src/components/Table/ColumnSettings.tsx +11 -11
  68. package/src/components/Table/CreateRefPanel.tsx +10 -10
  69. package/src/components/Table/RefPanel.tsx +9 -9
  70. package/src/components/Table/RowActionsMenu.tsx +6 -7
  71. package/src/components/Table/Table.stories.tsx +40 -23
  72. package/src/components/Table/Table.tsx +235 -148
  73. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +13 -12
  74. package/src/components/TableCellEditor/TableCellEditor.tsx +6 -4
  75. package/src/components/testing.ts +10 -8
  76. package/src/index.ts +2 -0
  77. package/src/model/index.ts +1 -0
  78. package/src/model/modal-controller.ts +15 -48
  79. package/src/model/selection-model.ts +12 -1
  80. package/src/model/table-model.test.ts +4 -71
  81. package/src/model/table-model.ts +59 -232
  82. package/src/model/table-presentation.test.ts +102 -0
  83. package/src/model/table-presentation.ts +214 -0
  84. package/src/playwright/TableManager.ts +5 -1
  85. package/src/playwright/smoke.spec.ts +64 -1
  86. package/src/types/table.ts +3 -3
  87. package/src/util/index.ts +1 -0
  88. package/src/util/table-buttons.ts +97 -48
  89. package/src/util/table-controls.ts +71 -7
  90. package/src/util/table.ts +21 -13
@@ -3,13 +3,11 @@
3
3
  //
4
4
 
5
5
  import { signal } from '@preact/signals-core';
6
- import { type RefObject, type MouseEvent, type MutableRefObject } from 'react';
6
+ import { type RefObject, type MutableRefObject } from 'react';
7
7
 
8
8
  import { type ReactiveObject } from '@dxos/live-object';
9
9
  import { log } from '@dxos/log';
10
10
 
11
- import { tableButtons } from '../util';
12
-
13
11
  export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: string };
14
12
 
15
13
  export type ModalState =
@@ -37,55 +35,24 @@ export class ModalController {
37
35
  return this._triggerRef as RefObject<HTMLButtonElement>;
38
36
  }
39
37
 
40
- public handleClick = (event: MouseEvent): boolean => {
41
- const target = event.target as HTMLElement;
42
-
43
- // TODO(thure): why not just get the value of the closest recognized attribute and `switch` on that?
44
- // Repeated querying is more error-prone and less legible, I’d think.
45
-
46
- const rowButton = target.closest(`button[${tableButtons.rowMenu.attr}]`);
47
- if (rowButton) {
48
- this._triggerRef.current = rowButton as HTMLElement;
49
- this._state.value = {
50
- type: 'row',
51
- rowIndex: Number(rowButton.getAttribute(tableButtons.rowMenu.attr)),
52
- };
53
- return true;
54
- }
38
+ public setTrigger = (trigger: HTMLElement) => {
39
+ this._triggerRef.current = trigger;
40
+ };
55
41
 
56
- const columnButton = target.closest(`button[${tableButtons.columnSettings.attr}]`);
57
- if (columnButton) {
58
- const fieldId = columnButton.getAttribute(tableButtons.columnSettings.attr)!;
59
- this._triggerRef.current = columnButton as HTMLElement;
60
- this._state.value = {
61
- type: 'column',
62
- fieldId,
63
- };
64
- return true;
65
- }
42
+ public showRowMenu = (rowIndex: number) => {
43
+ this._state.value = { type: 'row', rowIndex };
44
+ };
66
45
 
67
- const addColumnButton = target.closest(`button[${tableButtons.addColumn.attr}]`);
68
- if (addColumnButton) {
69
- this._triggerRef.current = addColumnButton as HTMLElement;
70
- this._state.value = {
71
- type: 'columnSettings',
72
- mode: { type: 'create' },
73
- };
74
- return true;
75
- }
46
+ public showColumnMenu = (fieldId: string) => {
47
+ this._state.value = { type: 'column', fieldId };
48
+ };
76
49
 
77
- const refButton = target.closest(`button[${tableButtons.referencedCell.attr}]`);
78
- if (refButton) {
79
- this._triggerRef.current = refButton as HTMLElement;
80
- const [schemaId, targetId] = refButton.getAttribute(tableButtons.referencedCell.attr)!.split('#');
81
- this._state.value = {
82
- type: 'refPanel',
83
- targetId,
84
- typename: schemaId,
85
- };
86
- }
50
+ public showColumnCreator = () => {
51
+ this._state.value = { type: 'columnSettings', mode: { type: 'create' } };
52
+ };
87
53
 
88
- return false;
54
+ public showReferencePanel = (targetId: string, schemaId: string) => {
55
+ this._state.value = { type: 'refPanel', targetId, typename: schemaId };
89
56
  };
90
57
 
91
58
  public openColumnSettings = () => {
@@ -17,6 +17,7 @@ export class SelectionModel<T extends BaseTableRow> extends Resource {
17
17
  if (!rows) {
18
18
  return new Set<string>();
19
19
  }
20
+
20
21
  const validIds = new Set(rows.map((row) => row.id));
21
22
  return new Set([...this._selection.value].filter((id) => validIds.has(id)));
22
23
  });
@@ -28,6 +29,7 @@ export class SelectionModel<T extends BaseTableRow> extends Resource {
28
29
  if (rows.length === 0) {
29
30
  return false;
30
31
  }
32
+
31
33
  return rows.every((row) => this._selection.value.has(row.id));
32
34
  });
33
35
 
@@ -94,6 +96,15 @@ export class SelectionModel<T extends BaseTableRow> extends Resource {
94
96
  };
95
97
 
96
98
  public setSelection = (mode: 'all' | 'none'): void => {
97
- this._selection.value = mode === 'all' ? new Set(this._rows.value.map((row) => row.id)) : new Set();
99
+ switch (mode) {
100
+ case 'all': {
101
+ this._selection.value = new Set(this._rows.value.map((row) => row.id));
102
+ break;
103
+ }
104
+ case 'none': {
105
+ this._selection.value = new Set();
106
+ break;
107
+ }
108
+ }
98
109
  };
99
110
  }
@@ -8,8 +8,8 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
8
  import { S, TypedObject } from '@dxos/echo-schema';
9
9
  import { updateCounter } from '@dxos/echo-schema/testing';
10
10
  import { registerSignalsRuntime } from '@dxos/echo-signals';
11
- import { createMutableSchema } from '@dxos/live-object/testing';
12
- import { create } from '@dxos/react-client/echo';
11
+ import { createEchoSchema } from '@dxos/live-object/testing';
12
+ import { create, makeRef } from '@dxos/react-client/echo';
13
13
  import { createView, ViewProjection } from '@dxos/schema';
14
14
 
15
15
  import { TableModel, type TableModelProps } from './table-model';
@@ -100,73 +100,6 @@ describe('TableModel', () => {
100
100
  expect(outerCounter.count).toBe(1);
101
101
  expect(innerCounter.count).toBe(1);
102
102
  });
103
-
104
- describe('row reactivity', () => {
105
- let data: any[];
106
-
107
- beforeEach(async () => {
108
- // TODO(burdon): Use generator.
109
- ({ data } = create({
110
- data: [
111
- { col1: 'A', col2: 1, col3: true },
112
- { col1: 'B', col2: 2, col3: false },
113
- ],
114
- }));
115
-
116
- const model = createTableModel();
117
- model.setRows(data);
118
- await model.open();
119
- });
120
-
121
- it('should update with row-level reactivity', () => {
122
- // Set up visible range to include our test data
123
- model.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
124
-
125
- // Trigger a row update
126
- data[0].col1 = 'New Value';
127
- expect(updateCount).toBe(1);
128
-
129
- // Verify the new value through getCells
130
- const cells = model.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 0 } }, 'grid');
131
- expect(cells['0,0']?.value).toBe('New Value');
132
- });
133
-
134
- it('should update reactively when adding a new row', () => {
135
- // Set up visible range to include our test data
136
- model.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
137
-
138
- data.push({ col1: 'C', col2: 3, col3: true });
139
- expect(updateCount).toBe(1);
140
-
141
- const cells = model.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
142
-
143
- // Verify the new row's data
144
- expect(cells['0,2']?.value).toBe('C');
145
- expect(cells['1,2']?.value).toBe('3');
146
- expect(cells['2,2']?.value).toBe('true');
147
- });
148
-
149
- it('should handle combined operations reactively', () => {
150
- // Set up visible range
151
- model.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
152
-
153
- // Multiple operations
154
- data[0].col1 = 'Updated A';
155
- data.push({ col1: 'C', col2: 3, col3: true });
156
- data.splice(1, 1);
157
-
158
- // We expect one update per operation affecting visible rows
159
- expect(updateCount).toBe(3);
160
-
161
- const cells = model.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
162
-
163
- // Verify final state
164
- expect(cells['0,0']?.value).toBe('Updated A');
165
- expect(cells['0,1']?.value).toBe('C');
166
- expect(cells['1,1']?.value).toBe('3');
167
- expect(cells['2,1']?.value).toBe('true');
168
- });
169
- });
170
103
  });
171
104
  });
172
105
 
@@ -176,9 +109,9 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
176
109
  }) {}
177
110
 
178
111
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
179
- const schema = createMutableSchema(Test);
112
+ const schema = createEchoSchema(Test);
180
113
  const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: schema.jsonSchema });
181
114
  const projection = new ViewProjection(schema, view);
182
- const table = create(TableType, { view });
115
+ const table = create(TableType, { view: makeRef(view) });
183
116
  return new TableModel({ table, projection, ...props });
184
117
  };
@@ -8,32 +8,20 @@ import orderBy from 'lodash.orderby';
8
8
  import { Resource } from '@dxos/context';
9
9
  import { getValue, setValue, FormatEnum, type JsonProp } from '@dxos/echo-schema';
10
10
  import { invariant } from '@dxos/invariant';
11
+ import { isReactiveObject, makeRef } from '@dxos/live-object';
11
12
  import { PublicKey } from '@dxos/react-client';
12
- import {
13
- cellClassesForFieldType,
14
- cellClassesForRowSelection,
15
- formatForDisplay,
16
- formatForEditing,
17
- parseValue,
18
- } from '@dxos/react-ui-form';
13
+ import { formatForDisplay, formatForEditing, parseValue } from '@dxos/react-ui-form';
19
14
  import {
20
15
  type DxGridAxisMeta,
21
- type DxGridCellValue,
22
- type DxGridPlane,
23
- type DxGridPlaneCells,
24
16
  type DxGridPlaneRange,
25
17
  type DxGridPlanePosition,
26
- toPlaneCellIndex,
27
18
  type DxGridPosition,
28
19
  } from '@dxos/react-ui-grid';
29
- import { mx } from '@dxos/react-ui-theme';
30
- import { VIEW_FIELD_LIMIT, type ViewProjection, type FieldType } from '@dxos/schema';
20
+ import { type ViewProjection } from '@dxos/schema';
31
21
 
32
- import { ModalController } from './modal-controller';
33
22
  import { SelectionModel } from './selection-model';
34
23
  import { type TableType } from '../types';
35
- import { tableButtons, touch } from '../util';
36
- import { tableControls } from '../util/table-controls';
24
+ import { touch } from '../util';
37
25
 
38
26
  export type SortDirection = 'asc' | 'desc';
39
27
  export type SortConfig = { fieldId: string; direction: SortDirection };
@@ -84,7 +72,6 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
84
72
 
85
73
  private _selection!: SelectionModel<T>;
86
74
  private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
87
- private readonly _modalController = new ModalController();
88
75
 
89
76
  constructor({
90
77
  table,
@@ -118,6 +105,10 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
118
105
  return this._projection;
119
106
  }
120
107
 
108
+ public get rows(): ReadonlySignal<T[]> {
109
+ return this._sortedRows;
110
+ }
111
+
121
112
  public get pinnedRows(): NonNullable<TableModelProps<T>['pinnedRows']> {
122
113
  return this._pinnedRows;
123
114
  }
@@ -131,10 +122,6 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
131
122
  return this._sorting.value;
132
123
  }
133
124
 
134
- public get modalController() {
135
- return this._modalController;
136
- }
137
-
138
125
  public get selection() {
139
126
  return this._selection;
140
127
  }
@@ -153,7 +140,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
153
140
 
154
141
  private initializeColumnMeta(): void {
155
142
  this._columnMeta = computed(() => {
156
- const fields = this._table.view?.fields ?? [];
143
+ const fields = this._table.view?.target?.fields ?? [];
157
144
  const meta = Object.fromEntries(
158
145
  fields.map((field, index: number) => [index, { size: field?.size ?? 256, resizeable: true }]),
159
146
  );
@@ -174,15 +161,35 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
174
161
  return this._rows.value;
175
162
  }
176
163
 
177
- const field = this._table.view?.fields.find((field) => field.id === sort.fieldId);
164
+ const field = this._table.view?.target?.fields.find((field) => field.id === sort.fieldId);
178
165
  if (!field) {
179
166
  return this._rows.value;
180
167
  }
181
168
 
169
+ const { props } = this._projection.getFieldProjection(field.id);
170
+
182
171
  const dataWithIndices = this._rows.value.map((item, index) => {
183
- const value = getValue(item, field.path);
184
- return { item, index, value, isEmpty: value == null || value === '' };
172
+ const rawValue = getValue(item, field.path);
173
+ let sortValue = rawValue;
174
+
175
+ if (props.type.startsWith('boolean')) {
176
+ sortValue = !!rawValue; // Coerce null/undefined/false to false for booleans
177
+ } else if (rawValue != null) {
178
+ if (props.format === FormatEnum.Ref && field.referencePath) {
179
+ sortValue = getValue(rawValue, field.referencePath);
180
+ } else {
181
+ sortValue = formatForDisplay({ type: props.type, format: props.format, value: rawValue });
182
+ }
183
+ }
184
+
185
+ return {
186
+ item,
187
+ index,
188
+ value: sortValue,
189
+ isEmpty: !props.type.startsWith('boolean') && (sortValue == null || sortValue === ''),
190
+ };
185
191
  });
192
+
186
193
  const sorted = orderBy(dataWithIndices, ['isEmpty', 'value'], ['asc', sort.direction]);
187
194
 
188
195
  for (let displayIndex = 0; displayIndex < sorted.length; displayIndex++) {
@@ -215,7 +222,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
215
222
  rowEffects.push(
216
223
  effect(() => {
217
224
  const obj = this._sortedRows.value[row];
218
- this?._table?.view?.fields.forEach((field) => touch(getValue(obj, field.path)));
225
+ this?._table?.view?.target?.fields.forEach((field) => touch(getValue(obj, field.path)));
219
226
  this._onCellUpdate?.({ row, col: start.col, plane: 'grid' });
220
227
  }),
221
228
  );
@@ -226,181 +233,6 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
226
233
  this._ctx.onDispose(rowEffectManager);
227
234
  }
228
235
 
229
- //
230
- // Get Cells
231
- //
232
-
233
- public getCells = (range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells => {
234
- switch (plane) {
235
- case 'grid': {
236
- this._visibleRange.value = range;
237
- return this.getMainGridCells(range);
238
- }
239
- case 'frozenRowsStart': {
240
- return this.getHeaderCells(range);
241
- }
242
- case 'frozenColsStart': {
243
- return this.getSelectionColumnCells(range);
244
- }
245
- case 'frozenColsEnd': {
246
- return this.getActionColumnCells(range);
247
- }
248
- case 'fixedStartStart': {
249
- return this.getSelectAllCell();
250
- }
251
- case 'fixedStartEnd': {
252
- return this.getNewColumnCell();
253
- }
254
- default: {
255
- return {};
256
- }
257
- }
258
- };
259
-
260
- private getMainGridCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
261
- const cells: DxGridPlaneCells = {};
262
- const fields = this._table.view?.fields ?? [];
263
-
264
- const addCell = (obj: T, field: FieldType, colIndex: number, displayIndex: number): void => {
265
- const { props } = this._projection.getFieldProjection(field.id);
266
-
267
- const cell: DxGridCellValue = {
268
- get value() {
269
- const value = getValue(obj, field.path);
270
- if (value == null) {
271
- return '';
272
- }
273
-
274
- switch (props.format) {
275
- case FormatEnum.Ref: {
276
- if (!field.referencePath) {
277
- return ''; // TODO(burdon): Show error.
278
- }
279
-
280
- return getValue(value, field.referencePath);
281
- }
282
-
283
- default: {
284
- return formatForDisplay({ type: props.type, format: props.format, value });
285
- }
286
- }
287
- },
288
- };
289
-
290
- const classes = [];
291
- const formatClasses = cellClassesForFieldType({ type: props.type, format: props.format });
292
- if (formatClasses) {
293
- classes.push(formatClasses);
294
- }
295
- const rowSelectionClasses = cellClassesForRowSelection(this._selection.isObjectSelected(obj));
296
- if (rowSelectionClasses) {
297
- classes.push(rowSelectionClasses);
298
- }
299
- if (classes.length > 0) {
300
- cell.className = mx(classes.flat());
301
- }
302
-
303
- if (cell.value && props.format === FormatEnum.Ref && props.referenceSchema) {
304
- const targetObj = getValue(obj, field.path);
305
- cell.accessoryHtml = tableButtons.referencedCell.render({
306
- targetId: targetObj.id,
307
- schemaId: props.referenceSchema,
308
- });
309
- }
310
-
311
- const idx = toPlaneCellIndex({ col: colIndex, row: displayIndex });
312
- cells[idx] = cell;
313
- };
314
-
315
- for (let row = range.start.row; row <= range.end.row && row < this._sortedRows.value.length; row++) {
316
- for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
317
- const field = fields[col];
318
- if (!field) {
319
- continue;
320
- }
321
-
322
- addCell(this._sortedRows.value[row], field, col, row);
323
- }
324
- }
325
-
326
- return cells;
327
- };
328
-
329
- private getHeaderCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
330
- const cells: DxGridPlaneCells = {};
331
- const fields = this.table.view?.fields ?? [];
332
- for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
333
- const { field, props } = this._projection.getFieldProjection(fields[col].id);
334
- cells[toPlaneCellIndex({ col, row: 0 })] = {
335
- // TODO(burdon): Use same logic as form for fallback title.
336
- value: props.title ?? field.path,
337
- readonly: true,
338
- resizeHandle: 'col',
339
- accessoryHtml: tableButtons.columnSettings.render({ fieldId: field.id }),
340
- };
341
- }
342
-
343
- return cells;
344
- };
345
-
346
- private getSelectionColumnCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
347
- const cells: DxGridPlaneCells = {};
348
- for (let row = range.start.row; row <= range.end.row && row < this._rows.value.length; row++) {
349
- const isSelected = this._selection.isRowIndexSelected(row);
350
- const classes = cellClassesForRowSelection(isSelected);
351
- cells[toPlaneCellIndex({ col: 0, row })] = {
352
- value: '',
353
- readonly: true,
354
- className: classes ? mx(classes) : undefined,
355
- accessoryHtml: tableControls.checkbox.render({ rowIndex: row, checked: isSelected }),
356
- };
357
- }
358
-
359
- return cells;
360
- };
361
-
362
- private getActionColumnCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
363
- const cells: DxGridPlaneCells = {};
364
- for (let row = range.start.row; row <= range.end.row && row < this._rows.value.length; row++) {
365
- const isSelected = this._selection.isRowIndexSelected(row);
366
- const classes = cellClassesForRowSelection(isSelected);
367
- cells[toPlaneCellIndex({ col: 0, row })] = {
368
- value: '',
369
- readonly: true,
370
- className: classes ? mx(classes) : undefined,
371
- accessoryHtml: tableButtons.rowMenu.render({ rowIndex: row }),
372
- };
373
- }
374
-
375
- return cells;
376
- };
377
-
378
- private getSelectAllCell = (): DxGridPlaneCells => {
379
- return {
380
- [toPlaneCellIndex({ col: 0, row: 0 })]: {
381
- value: '',
382
- accessoryHtml: tableControls.checkbox.render({
383
- rowIndex: 0,
384
- header: true,
385
- checked: this._selection.allRowsSeleted.value,
386
- }),
387
- readonly: true,
388
- },
389
- };
390
- };
391
-
392
- private getNewColumnCell = (): DxGridPlaneCells => {
393
- return {
394
- [toPlaneCellIndex({ col: 0, row: 0 })]: {
395
- value: '',
396
- accessoryHtml: tableButtons.addColumn.render({
397
- disabled: (this._table.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
398
- }),
399
- readonly: true,
400
- },
401
- };
402
- };
403
-
404
236
  //
405
237
  // Data
406
238
  //
@@ -411,7 +243,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
411
243
 
412
244
  public getRowCount = (): number => this._rows.value.length;
413
245
 
414
- public getColumnCount = (): number => this.table.view?.fields.length ?? 0;
246
+ public getColumnCount = (): number => this.table.view?.target?.fields.length ?? 0;
415
247
 
416
248
  public insertRow = (rowIndex?: number): void => {
417
249
  const row = rowIndex !== undefined ? this._displayToDataIndex.get(rowIndex) ?? rowIndex : this._rows.value.length;
@@ -434,7 +266,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
434
266
  };
435
267
 
436
268
  public getCellData = ({ col, row }: DxGridPlanePosition): any => {
437
- const fields = this.table.view?.fields ?? [];
269
+ const fields = this.table.view?.target?.fields ?? [];
438
270
  if (col < 0 || col >= fields.length) {
439
271
  return undefined;
440
272
  }
@@ -453,7 +285,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
453
285
  return ''; // TODO(burdon): Show error.
454
286
  }
455
287
 
456
- return getValue(value, field.referencePath);
288
+ return getValue(value.target, field.referencePath);
457
289
  }
458
290
 
459
291
  default: {
@@ -464,7 +296,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
464
296
 
465
297
  public setCellData = ({ col, row }: DxGridPlanePosition, value: any): void => {
466
298
  const rowIdx = this._displayToDataIndex.get(row) ?? row;
467
- const fields = this.table.view?.fields ?? [];
299
+ const fields = this.table.view?.target?.fields ?? [];
468
300
  if (col < 0 || col >= fields.length) {
469
301
  return;
470
302
  }
@@ -473,7 +305,11 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
473
305
  const { props } = this._projection.getFieldProjection(field.id);
474
306
  switch (props.format) {
475
307
  case FormatEnum.Ref: {
476
- setValue(this._rows.value[rowIdx], field.path, value);
308
+ // TODO(ZaymonFC): This get's called an additional time by the cell editor onBlur, but with the cell editors
309
+ // plain string value. Maybe onBlur should be called with the actual value?
310
+ if (isReactiveObject(value)) {
311
+ setValue(this._rows.value[rowIdx], field.path, makeRef(value));
312
+ }
477
313
  break;
478
314
  }
479
315
 
@@ -492,6 +328,21 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
492
328
  }
493
329
  };
494
330
 
331
+ /**
332
+ * Updates a cell's value using a transform function.
333
+ * @param {DxGridPlanePosition} position - The position of the cell to update.
334
+ * @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
335
+ */
336
+ public updateCellData({ col, row }: DxGridPlanePosition, update: (value: any) => any): void {
337
+ const dataRow = this._displayToDataIndex.get(row) ?? row;
338
+ const fields = this.table.view?.target?.fields ?? [];
339
+ const field = fields[col];
340
+
341
+ const value = getValue(this._rows.value[dataRow], field.path);
342
+ const updatedValue = update(value);
343
+ setValue(this._rows.value[dataRow], field.path, updatedValue);
344
+ }
345
+
495
346
  //
496
347
  // Column Operations
497
348
  //
@@ -501,7 +352,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
501
352
  return;
502
353
  }
503
354
 
504
- const field = this.table.view.fields.find((field) => field.id === fieldId);
355
+ const field = this.table.view.target?.fields.find((field) => field.id === fieldId);
505
356
  if (field && this._onDeleteColumn) {
506
357
  if (this._sorting.value?.fieldId === fieldId) {
507
358
  this.clearSort();
@@ -510,36 +361,12 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
510
361
  }
511
362
  }
512
363
 
513
- //
514
- // Interactions
515
- //
516
- public handleGridClick = (event: React.MouseEvent): void => {
517
- if (!this._modalController.handleClick(event)) {
518
- const target = event.target as HTMLElement;
519
-
520
- const selectionCheckbox = target.closest(`input[${tableControls.checkbox.attributes.checkbox}]`);
521
- if (selectionCheckbox) {
522
- const isHeader = selectionCheckbox.hasAttribute(tableControls.checkbox.attributes.header);
523
- if (isHeader) {
524
- if (this._selection.allRowsSeleted.value) {
525
- this._selection.setSelection('none');
526
- } else {
527
- this._selection.setSelection('all');
528
- }
529
- } else {
530
- const rowIndex = Number(selectionCheckbox.getAttribute(tableControls.checkbox.attributes.checkbox));
531
- this._selection.toggleSelectionForRowIndex(rowIndex);
532
- }
533
- }
534
- }
535
- };
536
-
537
364
  //
538
365
  // Resize
539
366
  //
540
367
 
541
368
  public setColumnWidth(columnIndex: number, width: number): void {
542
- const fields = this.table.view?.fields ?? [];
369
+ const fields = this.table.view?.target?.fields ?? [];
543
370
  if (columnIndex < fields.length) {
544
371
  const newWidth = Math.max(0, width);
545
372
  const field = fields[columnIndex];