@dxos/react-ui-table 0.10.0 → 0.11.1
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/chunk-types.mjs +65 -0
- package/dist/lib/chunk-types.mjs.map +1 -0
- package/dist/lib/chunk-useTableModel.mjs +1595 -0
- package/dist/lib/chunk-useTableModel.mjs.map +1 -0
- package/dist/lib/index.mjs +1133 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/testing.mjs +88 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/lib/translations.mjs +30 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/types/src/components/Table/DynamicTable.d.ts +4 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +3 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +1 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +3 -9
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +2 -0
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContext.d.ts +10 -0
- package/dist/types/src/components/Table/TableContext.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts +1 -1
- package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/Table/index.d.ts +1 -0
- package/dist/types/src/components/Table/index.d.ts.map +1 -1
- 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/testing.d.ts +1 -1
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +8 -1
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +42 -7
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +20 -5
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-view-state.d.ts +5 -1
- package/dist/types/src/model/table-view-state.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +1 -1
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts +5 -3
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +48 -50
- package/src/components/Table/DynamicTable.stories.tsx +59 -1
- package/src/components/Table/DynamicTable.tsx +16 -8
- package/src/components/Table/Relations.stories.tsx +2 -2
- package/src/components/Table/Table.stories.tsx +76 -2
- package/src/components/Table/Table.tsx +3 -15
- package/src/components/Table/TableContent.tsx +6 -5
- package/src/components/Table/TableContext.ts +21 -0
- package/src/components/Table/index.ts +2 -0
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +2 -3
- package/src/components/TableCellEditor/TableCellEditor.tsx +27 -22
- package/src/components/testing.ts +1 -1
- package/src/hooks/useProjectionModel.ts +1 -1
- package/src/hooks/useTableModel.ts +18 -6
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +1 -1
- package/src/model/table-model.test.ts +3 -3
- package/src/model/table-model.ts +148 -31
- package/src/model/table-presentation.test.ts +59 -1
- package/src/model/table-presentation.ts +171 -81
- package/src/model/table-view-state.ts +2 -2
- package/src/testing/useTestTableModel.ts +1 -1
- package/src/types/index.ts +4 -0
- package/src/util/dynamic-table.test.ts +1 -1
- package/src/util/dynamic-table.ts +1 -1
- package/src/util/table-buttons.ts +1 -1
- package/src/util/table-controls.ts +18 -10
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/browser/chunk-K4J2CKTZ.mjs +0 -1751
- package/dist/lib/browser/chunk-K4J2CKTZ.mjs.map +0 -7
- package/dist/lib/browser/chunk-VV3P3ADH.mjs +0 -67
- package/dist/lib/browser/chunk-VV3P3ADH.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -1416
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs +0 -115
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/browser/translations.mjs +0 -39
- package/dist/lib/browser/translations.mjs.map +0 -7
- package/dist/lib/browser/types/index.mjs +0 -8
- package/dist/lib/browser/types/index.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-E6V274ZW.mjs +0 -1753
- package/dist/lib/node-esm/chunk-E6V274ZW.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RPEWNOZZ.mjs +0 -68
- package/dist/lib/node-esm/chunk-RPEWNOZZ.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -1417
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -116
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/translations.mjs +0 -40
- package/dist/lib/node-esm/translations.mjs.map +0 -7
- package/dist/lib/node-esm/types/index.mjs +0 -9
- package/dist/lib/node-esm/types/index.mjs.map +0 -7
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type Registry } from '@effect-atom/atom';
|
|
6
6
|
import * as Predicate from 'effect/Predicate';
|
|
7
7
|
|
|
8
8
|
import { Obj, type View } from '@dxos/echo';
|
|
9
9
|
import { Format, TypeEnum } from '@dxos/echo/Format';
|
|
10
10
|
import { SchemaEx } from '@dxos/effect';
|
|
11
|
-
import { cellClassesForFieldType
|
|
11
|
+
import { cellClassesForFieldType } from '@dxos/react-ui-form';
|
|
12
12
|
import {
|
|
13
13
|
type DxGridCellValue,
|
|
14
14
|
type DxGridPlane,
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type DxGridPlaneRange,
|
|
17
17
|
toPlaneCellIndex,
|
|
18
18
|
} from '@dxos/react-ui-grid';
|
|
19
|
+
import { formatForDisplay } from '@dxos/schema';
|
|
19
20
|
import { VIEW_FIELD_LIMIT } from '@dxos/schema';
|
|
20
21
|
import { mx } from '@dxos/ui-theme';
|
|
21
22
|
|
|
@@ -30,7 +31,6 @@ import { type TableModel, type TableRow } from './table-model';
|
|
|
30
31
|
*/
|
|
31
32
|
export class TablePresentation<T extends TableRow = TableRow> {
|
|
32
33
|
private readonly _registry: Registry.Registry;
|
|
33
|
-
private readonly _visibleRange: Atom.Writable<DxGridPlaneRange>;
|
|
34
34
|
private fieldProjectionCache = new Map<string, ReturnType<typeof this.model.projection.getFieldProjection>>();
|
|
35
35
|
|
|
36
36
|
constructor(
|
|
@@ -38,10 +38,6 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
38
38
|
private readonly model: TableModel<T>,
|
|
39
39
|
) {
|
|
40
40
|
this._registry = registry;
|
|
41
|
-
this._visibleRange = Atom.make<DxGridPlaneRange>({
|
|
42
|
-
start: { row: 0, col: 0 },
|
|
43
|
-
end: { row: 0, col: 0 },
|
|
44
|
-
});
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
public getCells(range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells {
|
|
@@ -52,20 +48,21 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
52
48
|
|
|
53
49
|
switch (plane) {
|
|
54
50
|
case 'grid':
|
|
55
|
-
|
|
51
|
+
// Report the visible range to the model so it can (re)subscribe to those rows' mutations.
|
|
52
|
+
this.model.setVisibleRange(range);
|
|
56
53
|
cells = this.getMainGridCells(range);
|
|
57
54
|
break;
|
|
58
55
|
case 'frozenRowsStart':
|
|
59
56
|
cells = this.getHeaderCells(range);
|
|
60
57
|
break;
|
|
61
58
|
case 'frozenColsStart':
|
|
62
|
-
cells = this.
|
|
59
|
+
cells = this.getFrozenColsStartCells(range);
|
|
63
60
|
break;
|
|
64
61
|
case 'frozenColsEnd':
|
|
65
62
|
cells = this.getActionColumnCells(range);
|
|
66
63
|
break;
|
|
67
64
|
case 'fixedStartStart':
|
|
68
|
-
cells = this.
|
|
65
|
+
cells = this.getFixedStartStartCells();
|
|
69
66
|
break;
|
|
70
67
|
case 'fixedStartEnd':
|
|
71
68
|
cells = this.getNewColumnCell();
|
|
@@ -74,7 +71,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
74
71
|
cells = this.getDraftRowCells(range);
|
|
75
72
|
break;
|
|
76
73
|
case 'fixedEndStart':
|
|
77
|
-
cells = this.
|
|
74
|
+
cells = this.getFixedEndStartCells(range);
|
|
78
75
|
break;
|
|
79
76
|
case 'fixedEndEnd':
|
|
80
77
|
cells = this.getDraftActionCells(range);
|
|
@@ -110,6 +107,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
110
107
|
field: View.FieldType,
|
|
111
108
|
colIndex: number,
|
|
112
109
|
displayIndex: number,
|
|
110
|
+
plane: DxGridPlane = 'grid',
|
|
113
111
|
): void {
|
|
114
112
|
const { props } = this.getFieldProjection(field.id);
|
|
115
113
|
|
|
@@ -210,7 +208,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
210
208
|
const targetObj = SchemaEx.getValue(obj, field.path)?.target;
|
|
211
209
|
if (targetObj) {
|
|
212
210
|
const uri = Obj.getURI(targetObj);
|
|
213
|
-
cell.accessoryHtml = `<div role="none" class="
|
|
211
|
+
cell.accessoryHtml = `<div role="none" class="dx-grid__cell__block"><dx-anchor uri=${uri} class="dx-button w-6 aspect-square min-h-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
|
|
214
212
|
}
|
|
215
213
|
}
|
|
216
214
|
|
|
@@ -219,7 +217,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
219
217
|
const value = SchemaEx.getValue(obj, field.path);
|
|
220
218
|
const href = typeof value === 'string' ? safeHttpUrl(value) : undefined;
|
|
221
219
|
if (href) {
|
|
222
|
-
cell.accessoryHtml = `<div role="none" class="
|
|
220
|
+
cell.accessoryHtml = `<div role="none" class="dx-grid__cell__block"><a href="${escapeHtmlAttribute(href)}" target="_blank" rel="noopener noreferrer" class="dx-button w-6 aspect-square min-h-0" data-dx-grid-action="accessory"><dx-icon icon="ph--arrow-square-out--regular"/></a></div>`;
|
|
223
221
|
}
|
|
224
222
|
}
|
|
225
223
|
|
|
@@ -229,6 +227,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
229
227
|
cell.accessoryHtml = tableControls.switch.render({
|
|
230
228
|
colIndex,
|
|
231
229
|
rowIndex: displayIndex,
|
|
230
|
+
plane,
|
|
232
231
|
checked: value ?? false,
|
|
233
232
|
});
|
|
234
233
|
cell.readonly = 'no-text-select';
|
|
@@ -275,15 +274,18 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
275
274
|
private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
276
275
|
const cells: DxGridPlaneCells = {};
|
|
277
276
|
const fields = this.model.projection?.getFields() ?? [];
|
|
277
|
+
// Pinned fields live in the frozen-column planes; the scrolling grid holds the remainder.
|
|
278
|
+
const pin = this.model.pinColumns;
|
|
279
|
+
const scrollingFields = fields.length - pin;
|
|
278
280
|
|
|
279
281
|
for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
|
|
280
|
-
for (let col = range.start.col; col <= range.end.col && col <
|
|
281
|
-
const field = fields[col];
|
|
282
|
+
for (let col = range.start.col; col <= range.end.col && col < scrollingFields; col++) {
|
|
283
|
+
const field = fields[pin + col];
|
|
282
284
|
if (!field) {
|
|
283
285
|
continue;
|
|
284
286
|
}
|
|
285
287
|
|
|
286
|
-
this.createDataCell(cells, this.model.getRows()[row], field, col, row);
|
|
288
|
+
this.createDataCell(cells, this.model.getRows()[row], field, col, row, 'grid');
|
|
287
289
|
}
|
|
288
290
|
}
|
|
289
291
|
|
|
@@ -295,9 +297,13 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
295
297
|
const fields = this.model.projection?.getFields() ?? [];
|
|
296
298
|
const draftRows = this.model.getDraftRows();
|
|
297
299
|
|
|
300
|
+
// Pinned fields' draft cells render in the fixedEndStart corner; the scrolling draft row holds the remainder.
|
|
301
|
+
const pin = this.model.pinColumns;
|
|
302
|
+
const scrollingFields = fields.length - pin;
|
|
303
|
+
|
|
298
304
|
// Return cells of the CTA row if no draft row is active
|
|
299
305
|
if (draftRows.length === 0) {
|
|
300
|
-
for (let col = range.start.col; col <= range.end.col && col <
|
|
306
|
+
for (let col = range.start.col; col <= range.end.col && col < scrollingFields; col++) {
|
|
301
307
|
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
302
308
|
value: '',
|
|
303
309
|
readonly: true,
|
|
@@ -307,23 +313,13 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
307
313
|
} else {
|
|
308
314
|
for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
|
|
309
315
|
const draftRow = draftRows[row];
|
|
310
|
-
for (let col = range.start.col; col <= range.end.col && col <
|
|
311
|
-
const
|
|
312
|
-
const field = fields[col];
|
|
316
|
+
for (let col = range.start.col; col <= range.end.col && col < scrollingFields; col++) {
|
|
317
|
+
const field = fields[pin + col];
|
|
313
318
|
if (!field) {
|
|
314
319
|
continue;
|
|
315
320
|
}
|
|
316
321
|
|
|
317
|
-
this.
|
|
318
|
-
|
|
319
|
-
if (this.model.hasDraftRowValidationError(row, field.path)) {
|
|
320
|
-
const cellValue = cells[cellIndex];
|
|
321
|
-
if (cellValue) {
|
|
322
|
-
const existingClasses = cellValue.className || '';
|
|
323
|
-
const draftClasses = 'dx-grid__cell--flagged';
|
|
324
|
-
cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
322
|
+
this.createDraftDataCell(cells, draftRow.data, field, col, row, 'frozenRowsEnd');
|
|
327
323
|
}
|
|
328
324
|
}
|
|
329
325
|
}
|
|
@@ -331,41 +327,96 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
331
327
|
return cells;
|
|
332
328
|
}
|
|
333
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Renders a draft-row data cell and applies the validation-error flag when the field is invalid.
|
|
332
|
+
*/
|
|
333
|
+
private createDraftDataCell(
|
|
334
|
+
cells: DxGridPlaneCells,
|
|
335
|
+
obj: T,
|
|
336
|
+
field: View.FieldType,
|
|
337
|
+
planeCol: number,
|
|
338
|
+
rowIndex: number,
|
|
339
|
+
plane: DxGridPlane,
|
|
340
|
+
): void {
|
|
341
|
+
const cellIndex = toPlaneCellIndex({ col: planeCol, row: rowIndex });
|
|
342
|
+
this.createDataCell(cells, obj, field, planeCol, rowIndex, plane);
|
|
343
|
+
|
|
344
|
+
if (this.model.hasDraftRowValidationError(rowIndex, field.path)) {
|
|
345
|
+
const cellValue = cells[cellIndex];
|
|
346
|
+
if (cellValue) {
|
|
347
|
+
const existingClasses = cellValue.className || '';
|
|
348
|
+
const draftClasses = 'dx-grid__cell--flagged';
|
|
349
|
+
cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
334
354
|
private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
335
355
|
const cells: DxGridPlaneCells = {};
|
|
336
356
|
const fields = this.model.projection?.getFields() ?? [];
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
cells[toPlaneCellIndex({ col, row: 0 })] = {
|
|
343
|
-
// TODO(burdon): Use same logic as form for fallback title.
|
|
344
|
-
value: '',
|
|
345
|
-
resizeHandle: 'col',
|
|
346
|
-
accessoryHtml: `
|
|
347
|
-
<span class="grow min-w-0 truncate">${props.title ?? field.path}</span>
|
|
348
|
-
${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
|
|
349
|
-
${tableButtons.columnSettings.render({ fieldId: field.id })}
|
|
350
|
-
`,
|
|
351
|
-
className: 'bg-axis-surface! text-axis-text! [&>div]:flex [&>div]:items-stretch',
|
|
352
|
-
};
|
|
357
|
+
// Pinned fields' headers render in the fixedStartStart corner; the scrolling header holds the remainder.
|
|
358
|
+
const pin = this.model.pinColumns;
|
|
359
|
+
const scrollingFields = fields.length - pin;
|
|
360
|
+
for (let col = range.start.col; col <= range.end.col && col < scrollingFields; col++) {
|
|
361
|
+
this.createHeaderCell(cells, fields[pin + col].id, col);
|
|
353
362
|
}
|
|
354
363
|
|
|
355
364
|
return cells;
|
|
356
365
|
}
|
|
357
366
|
|
|
358
|
-
private
|
|
367
|
+
private createHeaderCell(cells: DxGridPlaneCells, fieldId: string, planeCol: number): void {
|
|
368
|
+
const currentSort = this.model.getSorting();
|
|
369
|
+
const { field, props } = this.model.projection.getFieldProjection(fieldId);
|
|
370
|
+
const direction = currentSort?.fieldId === field.id ? currentSort.direction : undefined;
|
|
371
|
+
|
|
372
|
+
cells[toPlaneCellIndex({ col: planeCol, row: 0 })] = {
|
|
373
|
+
// TODO(burdon): Use same logic as form for fallback title.
|
|
374
|
+
value: '',
|
|
375
|
+
resizeHandle: 'col',
|
|
376
|
+
accessoryHtml: `
|
|
377
|
+
<span class="grow min-w-0 truncate">${props.title ?? field.path}</span>
|
|
378
|
+
${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
|
|
379
|
+
${tableButtons.columnSettings.render({ fieldId: field.id })}
|
|
380
|
+
`,
|
|
381
|
+
className: 'bg-axis-surface! text-axis-text! [&>div]:flex [&>div]:items-stretch',
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Renders the frozenColsStart plane: the selection column (if enabled) followed by the pinned data columns.
|
|
387
|
+
*/
|
|
388
|
+
private getFrozenColsStartCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
359
389
|
const cells: DxGridPlaneCells = {};
|
|
390
|
+
const selectionColumns = this.model.selectionColumns;
|
|
391
|
+
const fields = this.model.projection?.getFields() ?? [];
|
|
392
|
+
const pin = this.model.pinColumns;
|
|
393
|
+
|
|
360
394
|
for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
395
|
+
if (selectionColumns > 0) {
|
|
396
|
+
const isSelected = this.model.selection.isRowIndexSelected(row);
|
|
397
|
+
const classes = cellClassesForRowSelection(isSelected, this.model.selection.selectionMode);
|
|
398
|
+
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
399
|
+
value: '',
|
|
400
|
+
readonly: 'no-text-select',
|
|
401
|
+
className: classes ? mx(classes) : undefined,
|
|
402
|
+
accessoryHtml: tableControls.checkbox.render({ rowIndex: row, checked: isSelected }),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (let dataCol = 0; dataCol < pin; dataCol++) {
|
|
407
|
+
const field = fields[dataCol];
|
|
408
|
+
if (!field) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
this.createDataCell(
|
|
412
|
+
cells,
|
|
413
|
+
this.model.getRows()[row],
|
|
414
|
+
field,
|
|
415
|
+
selectionColumns + dataCol,
|
|
416
|
+
row,
|
|
417
|
+
'frozenColsStart',
|
|
418
|
+
);
|
|
419
|
+
}
|
|
369
420
|
}
|
|
370
421
|
|
|
371
422
|
return cells;
|
|
@@ -387,29 +438,41 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
387
438
|
return cells;
|
|
388
439
|
}
|
|
389
440
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
},
|
|
398
|
-
};
|
|
399
|
-
}
|
|
441
|
+
/**
|
|
442
|
+
* Renders the fixedStartStart corner (frozen header row x frozen columns): the select-all cell (if the
|
|
443
|
+
* selection column is enabled) followed by the pinned columns' header cells.
|
|
444
|
+
*/
|
|
445
|
+
private getFixedStartStartCells(): DxGridPlaneCells {
|
|
446
|
+
const cells: DxGridPlaneCells = {};
|
|
447
|
+
const selectionColumns = this.model.selectionColumns;
|
|
400
448
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
449
|
+
if (selectionColumns > 0) {
|
|
450
|
+
const showSelectAll = this.model.features.selection.enabled && this.model.selection.selectionMode !== 'single';
|
|
451
|
+
cells[toPlaneCellIndex({ col: 0, row: 0 })] = {
|
|
452
|
+
accessoryHtml: showSelectAll
|
|
453
|
+
? tableControls.checkbox.render({
|
|
454
|
+
rowIndex: 0,
|
|
455
|
+
header: true,
|
|
456
|
+
checked: this.model.selection.allRowsSelected,
|
|
457
|
+
})
|
|
458
|
+
: undefined,
|
|
408
459
|
className: 'bg-axis-surface!',
|
|
409
460
|
readonly: true,
|
|
410
461
|
value: '',
|
|
411
|
-
}
|
|
412
|
-
}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const fields = this.model.projection?.getFields() ?? [];
|
|
466
|
+
const pin = this.model.pinColumns;
|
|
467
|
+
for (let dataCol = 0; dataCol < pin; dataCol++) {
|
|
468
|
+
const field = fields[dataCol];
|
|
469
|
+
if (!field) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
this.createHeaderCell(cells, field.id, selectionColumns + dataCol);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return cells;
|
|
413
476
|
}
|
|
414
477
|
|
|
415
478
|
private getNewColumnCell(): DxGridPlaneCells {
|
|
@@ -454,17 +517,44 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
454
517
|
return cells;
|
|
455
518
|
}
|
|
456
519
|
|
|
457
|
-
|
|
520
|
+
/**
|
|
521
|
+
* Renders the fixedEndStart corner (draft row x frozen columns): the draft "+" icon (if the selection
|
|
522
|
+
* column is enabled) followed by the pinned columns' draft cells.
|
|
523
|
+
*/
|
|
524
|
+
private getFixedEndStartCells(range: DxGridPlaneRange): DxGridPlaneCells {
|
|
458
525
|
const cells: DxGridPlaneCells = {};
|
|
459
526
|
const draftRows = this.model.getDraftRows();
|
|
527
|
+
const selectionColumns = this.model.selectionColumns;
|
|
528
|
+
const fields = this.model.projection?.getFields() ?? [];
|
|
529
|
+
const pin = this.model.pinColumns;
|
|
460
530
|
|
|
461
531
|
for (let row = range.start.row; row <= range.end.row; row++) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
532
|
+
if (selectionColumns > 0) {
|
|
533
|
+
cells[toPlaneCellIndex({ col: 0, row })] = {
|
|
534
|
+
value: '',
|
|
535
|
+
readonly: true,
|
|
536
|
+
accessoryHtml:
|
|
537
|
+
'<div role="none" class="dx-grid__cell__block"><dx-icon icon="ph--plus--regular" class="contents"></dx-icon></div>',
|
|
538
|
+
className: mx(draftRows.length < 1 && 'dx-grid__row--cta__cell'),
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
for (let dataCol = 0; dataCol < pin; dataCol++) {
|
|
543
|
+
const field = fields[dataCol];
|
|
544
|
+
if (!field) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const planeCol = selectionColumns + dataCol;
|
|
548
|
+
if (draftRows.length === 0) {
|
|
549
|
+
cells[toPlaneCellIndex({ col: planeCol, row })] = {
|
|
550
|
+
value: '',
|
|
551
|
+
readonly: true,
|
|
552
|
+
className: 'dx-grid__row--cta__cell',
|
|
553
|
+
};
|
|
554
|
+
} else if (row < draftRows.length) {
|
|
555
|
+
this.createDraftDataCell(cells, draftRows[row].data, field, planeCol, row, 'fixedEndStart');
|
|
556
|
+
}
|
|
557
|
+
}
|
|
468
558
|
}
|
|
469
559
|
|
|
470
560
|
return cells;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as Schema from 'effect/Schema';
|
|
6
6
|
|
|
7
7
|
import { QueryAST } from '@dxos/echo';
|
|
8
|
-
import {
|
|
8
|
+
import { ViewState } from '@dxos/react-ui-attention';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Field sort configuration.
|
|
@@ -22,7 +22,7 @@ export type FieldSortType = Schema.Schema.Type<typeof FieldSortSchema>;
|
|
|
22
22
|
* viewer's chosen sort survives remounts and reloads without writing to the shared view query
|
|
23
23
|
* (which `TableModel.saveView` does explicitly).
|
|
24
24
|
*/
|
|
25
|
-
export const tableSortAspect =
|
|
25
|
+
export const tableSortAspect = ViewState.define<FieldSortType | undefined>({
|
|
26
26
|
key: 'table-sort',
|
|
27
27
|
backend: 'local',
|
|
28
28
|
schema: Schema.UndefinedOr(FieldSortSchema),
|
|
@@ -6,8 +6,8 @@ import { RegistryContext } from '@effect-atom/atom-react';
|
|
|
6
6
|
import { type RefObject, useCallback, useContext, useMemo, useRef } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type Database, Filter, Type } from '@dxos/echo';
|
|
9
|
+
import { useQuery, useType } from '@dxos/echo-react';
|
|
9
10
|
import { random } from '@dxos/random';
|
|
10
|
-
import { useQuery, useType } from '@dxos/react-client/echo';
|
|
11
11
|
import { useClientStory } from '@dxos/react-client/testing';
|
|
12
12
|
import { useGlobalFilteredObjects } from '@dxos/react-ui-search';
|
|
13
13
|
import { type ProjectionModel, getTypeURIFromQuery } from '@dxos/schema';
|
package/src/types/index.ts
CHANGED
|
@@ -2,4 +2,8 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
// A UI-free entrypoint: the Table schema with no React attached, so operation handlers and node
|
|
6
|
+
// plugin variants can use it without pulling the table components.
|
|
7
|
+
// TODO(wittjosiah): Factor this out into a package that does not carry a UI dependency at all.
|
|
8
|
+
|
|
5
9
|
export * as Table from './Table';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Registry } from '@effect-atom/atom
|
|
5
|
+
import { type Registry } from '@effect-atom/atom';
|
|
6
6
|
import type * as Types from 'effect/Types';
|
|
7
7
|
|
|
8
8
|
import { Filter, type JsonSchema, Obj, Order, Query, type QueryAST, Ref, Type, type View } from '@dxos/echo';
|
|
@@ -39,7 +39,7 @@ const createButton = ({
|
|
|
39
39
|
.map(([k, v]) => `${k}="${v}"`)
|
|
40
40
|
.join(' ');
|
|
41
41
|
|
|
42
|
-
return `<button ${attr} data-testid="${testId}" class="dx-button w-6 aspect-square px-0.5 min-h-0" ${dataAttrs} ${disabled ? 'disabled' : ''} data-dx-grid-action="accessory"><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
|
|
42
|
+
return `<div role="none" class="dx-grid__cell__block"><button ${attr} data-testid="${testId}" class="dx-button w-6 aspect-square px-0.5 min-h-0" ${dataAttrs} ${disabled ? 'disabled' : ''} data-dx-grid-action="accessory"><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button></div>`;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const addColumnButton = {
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
import { accessoryHandlesPointerdownAttrs } from '@dxos/lit-grid';
|
|
5
|
+
import { type DxGridPlane, accessoryHandlesPointerdownAttrs } from '@dxos/lit-grid';
|
|
7
6
|
|
|
8
7
|
export type TableControl = 'checkbox' | 'switch';
|
|
9
8
|
|
|
10
9
|
export type ControlData =
|
|
11
10
|
| { type: 'checkbox'; rowIndex: number; header?: boolean }
|
|
12
|
-
| { type: 'switch'; colIndex: number; rowIndex: number };
|
|
11
|
+
| { type: 'switch'; colIndex: number; rowIndex: number; plane: DxGridPlane };
|
|
13
12
|
|
|
14
13
|
type CommonRenderProps = {
|
|
15
14
|
checked?: boolean;
|
|
@@ -25,8 +24,8 @@ export const CONTROL_IDENTIFIERS = {
|
|
|
25
24
|
} as const;
|
|
26
25
|
|
|
27
26
|
const BASE_CLASSES = {
|
|
28
|
-
checkbox: '
|
|
29
|
-
switch: '
|
|
27
|
+
checkbox: 'dx-checkbox',
|
|
28
|
+
switch: 'dx-checkbox--switch',
|
|
30
29
|
} as const;
|
|
31
30
|
|
|
32
31
|
const renderAttributes = (data: Record<string, string>) => {
|
|
@@ -43,10 +42,10 @@ const renderInput = (
|
|
|
43
42
|
preventToggle = false,
|
|
44
43
|
testId: string,
|
|
45
44
|
) => {
|
|
46
|
-
return `<input type="checkbox" class="${baseClass}" ${renderAttributes(attrs)} ${checked ? 'checked' : ''} ${preventToggle ? 'onclick="return false"' : ''} ${disabled ? 'disabled' : ''} data-testid="${testId}" data-dx-grid-action="accessory"
|
|
45
|
+
return `<div role="none" class="dx-grid__cell__block"><input type="checkbox" class="${baseClass}" ${renderAttributes(attrs)} ${checked ? 'checked' : ''} ${preventToggle ? 'onclick="return false"' : ''} ${disabled ? 'disabled' : ''} data-testid="${testId}" data-dx-grid-action="accessory"/></div>`;
|
|
47
46
|
};
|
|
48
47
|
|
|
49
|
-
export const
|
|
48
|
+
export const CheckboxComponent = ({
|
|
50
49
|
rowIndex,
|
|
51
50
|
header = false,
|
|
52
51
|
checked = false,
|
|
@@ -61,11 +60,18 @@ export const CheckboxStory = ({
|
|
|
61
60
|
return renderInput(BASE_CLASSES.checkbox, attrs, checked, disabled, true, 'table-selection');
|
|
62
61
|
};
|
|
63
62
|
|
|
64
|
-
export const
|
|
63
|
+
export const SwitchComponent = ({
|
|
64
|
+
colIndex,
|
|
65
|
+
rowIndex,
|
|
66
|
+
plane,
|
|
67
|
+
checked = false,
|
|
68
|
+
disabled = false,
|
|
69
|
+
}: RenderSwitchProps): string => {
|
|
65
70
|
const attrs = {
|
|
66
71
|
[CONTROL_IDENTIFIERS.switch]: '',
|
|
67
72
|
'data-row-index': rowIndex.toString(),
|
|
68
73
|
'data-col-index': colIndex.toString(),
|
|
74
|
+
'data-plane': plane,
|
|
69
75
|
...accessoryHandlesPointerdownAttrs,
|
|
70
76
|
};
|
|
71
77
|
|
|
@@ -75,7 +81,7 @@ export const SwitchStory = ({ colIndex, rowIndex, checked = false, disabled = fa
|
|
|
75
81
|
export const tableControls = {
|
|
76
82
|
checkbox: {
|
|
77
83
|
attr: CONTROL_IDENTIFIERS.checkbox,
|
|
78
|
-
render:
|
|
84
|
+
render: CheckboxComponent,
|
|
79
85
|
getData: (el: HTMLElement): Extract<ControlData, { type: 'checkbox' }> => ({
|
|
80
86
|
type: 'checkbox',
|
|
81
87
|
rowIndex: Number(el.getAttribute('data-row-index')),
|
|
@@ -84,11 +90,13 @@ export const tableControls = {
|
|
|
84
90
|
},
|
|
85
91
|
switch: {
|
|
86
92
|
attr: CONTROL_IDENTIFIERS.switch,
|
|
87
|
-
render:
|
|
93
|
+
render: SwitchComponent,
|
|
88
94
|
getData: (el: HTMLElement): Extract<ControlData, { type: 'switch' }> => ({
|
|
89
95
|
type: 'switch',
|
|
90
96
|
rowIndex: Number(el.getAttribute('data-row-index')),
|
|
91
97
|
colIndex: Number(el.getAttribute('data-col-index')),
|
|
98
|
+
// The plane round-trips through a DOM attribute string written by the switch renderer.
|
|
99
|
+
plane: (el.getAttribute('data-plane') as DxGridPlane | null) ?? 'grid',
|
|
92
100
|
}),
|
|
93
101
|
},
|
|
94
102
|
} as const;
|