@dxos/react-ui-table 0.7.2-staging.6d26b2a → 0.7.3-main.2dd075e

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 (75) hide show
  1. package/dist/lib/browser/{chunk-EWGUNGJC.mjs → chunk-IYCSJDMB.mjs} +5 -3
  2. package/dist/lib/browser/chunk-IYCSJDMB.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +461 -237
  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 +3 -1
  7. package/dist/lib/node/{chunk-A2N7XWW6.cjs → chunk-MHBMVCFZ.cjs} +8 -5
  8. package/dist/lib/node/chunk-MHBMVCFZ.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +471 -253
  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 +4 -2
  13. package/dist/lib/node/types/index.cjs.map +2 -2
  14. package/dist/lib/node-esm/{chunk-SABDOZ6I.mjs → chunk-BMWGPXPL.mjs} +5 -3
  15. package/dist/lib/node-esm/chunk-BMWGPXPL.mjs.map +7 -0
  16. package/dist/lib/node-esm/index.mjs +461 -237
  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 +3 -1
  20. package/dist/types/src/_deprecated/components/Table/Table.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  22. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  24. package/dist/types/src/components/Table/CreateRefPanel.d.ts +8 -0
  25. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -0
  26. package/dist/types/src/components/Table/RefPanel.d.ts +8 -0
  27. package/dist/types/src/components/Table/RefPanel.d.ts.map +1 -0
  28. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Table.d.ts +2 -2
  30. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  31. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +16 -7
  32. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  33. package/dist/types/src/components/TableCellEditor/extension.d.ts +2 -1
  34. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
  35. package/dist/types/src/components/testing.d.ts +1 -1
  36. package/dist/types/src/components/testing.d.ts.map +1 -1
  37. package/dist/types/src/model/modal-controller.d.ts +11 -0
  38. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  39. package/dist/types/src/model/table-model.d.ts +4 -5
  40. package/dist/types/src/model/table-model.d.ts.map +1 -1
  41. package/dist/types/src/translations.d.ts +1 -0
  42. package/dist/types/src/translations.d.ts.map +1 -1
  43. package/dist/types/src/types/table.d.ts +57 -2
  44. package/dist/types/src/types/table.d.ts.map +1 -1
  45. package/dist/types/src/util/index.d.ts +0 -1
  46. package/dist/types/src/util/index.d.ts.map +1 -1
  47. package/dist/types/src/util/table-buttons.d.ts +8 -0
  48. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  49. package/package.json +29 -28
  50. package/src/_deprecated/components/Table/Table.stories.tsx +1 -1
  51. package/src/components/Table/ColumnActionsMenu.tsx +0 -1
  52. package/src/components/Table/ColumnSettings.tsx +7 -4
  53. package/src/components/Table/CreateRefPanel.tsx +83 -0
  54. package/src/components/Table/RefPanel.tsx +52 -0
  55. package/src/components/Table/RowActionsMenu.tsx +1 -2
  56. package/src/components/Table/Table.stories.tsx +2 -2
  57. package/src/components/Table/Table.tsx +60 -33
  58. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +2 -2
  59. package/src/components/TableCellEditor/TableCellEditor.tsx +72 -23
  60. package/src/components/TableCellEditor/extension.ts +3 -4
  61. package/src/components/testing.ts +2 -1
  62. package/src/model/modal-controller.ts +43 -0
  63. package/src/model/table-model.test.ts +5 -3
  64. package/src/model/table-model.ts +34 -22
  65. package/src/playwright/smoke.spec.ts +0 -4
  66. package/src/translations.ts +1 -0
  67. package/src/types/table.ts +5 -5
  68. package/src/util/index.ts +0 -1
  69. package/src/util/table-buttons.ts +18 -1
  70. package/dist/lib/browser/chunk-EWGUNGJC.mjs.map +0 -7
  71. package/dist/lib/node/chunk-A2N7XWW6.cjs.map +0 -7
  72. package/dist/lib/node-esm/chunk-SABDOZ6I.mjs.map +0 -7
  73. package/dist/types/src/util/grid.d.ts +0 -14
  74. package/dist/types/src/util/grid.d.ts.map +0 -1
  75. package/src/util/grid.ts +0 -23
@@ -2,32 +2,46 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { useCallback, useMemo } from 'react';
5
+ import { type Completion } from '@codemirror/autocomplete';
6
+ import React, { useCallback, useMemo, useRef } from 'react';
6
7
 
7
8
  import { FormatEnum } from '@dxos/echo-schema';
8
9
  import { invariant } from '@dxos/invariant';
9
10
  import { type DxGrid } from '@dxos/lit-grid';
10
11
  import {
12
+ cellQuery,
11
13
  editorKeys,
14
+ parseCellIndex,
15
+ useGridContext,
16
+ type DxGridPlanePosition,
12
17
  type EditorKeyEvent,
13
- GridCellEditor,
14
- type GridCellEditorProps,
15
18
  type EditorKeyOrBlurHandler,
16
19
  type EditorBlurHandler,
20
+ GridCellEditor,
21
+ type GridCellEditorProps,
17
22
  type GridScopedProps,
18
- useGridContext,
19
23
  } from '@dxos/react-ui-grid';
20
24
  import { type FieldProjection } from '@dxos/schema';
21
25
 
22
26
  import { completion } from './extension';
23
27
  import { type TableModel } from '../../model';
24
- import { type GridCell, toGridCell } from '../../util';
28
+
29
+ const newValue = Symbol.for('newValue');
30
+
31
+ /**
32
+ * Option to create new object/value.
33
+ */
34
+ export const createOption = (text: string) => ({ [newValue]: true, text });
35
+
36
+ const isCreateOption = (data: any) => typeof data === 'object' && data[newValue];
37
+
38
+ export type QueryResult = Pick<Completion, 'label'> & { data: any };
25
39
 
26
40
  export type TableCellEditorProps = {
27
41
  model?: TableModel;
28
- onEnter?: (cell: GridCell) => void;
42
+ onEnter?: (cell: DxGridPlanePosition) => void;
29
43
  onFocus?: DxGrid['refocus'];
30
- onQuery?: (field: FieldProjection, text: string) => Promise<{ label: string; data: any }[]>;
44
+ onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
31
45
  };
32
46
 
33
47
  export const TableCellEditor = ({
@@ -37,32 +51,34 @@ export const TableCellEditor = ({
37
51
  onQuery,
38
52
  __gridScope,
39
53
  }: GridScopedProps<TableCellEditorProps>) => {
40
- const { editing } = useGridContext('TableCellEditor', __gridScope);
54
+ const { id: gridId, editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
55
+ const suppressNextBlur = useRef(false);
56
+
41
57
  const fieldProjection = useMemo<FieldProjection | undefined>(() => {
42
58
  if (!model || !editing) {
43
59
  return;
44
60
  }
45
61
 
46
- const { col } = toGridCell(editing.index);
62
+ const { col } = parseCellIndex(editing.index);
47
63
  const field = model.projection.view.fields[col];
48
64
  const fieldProjection = model.projection.getFieldProjection(field.id);
49
65
  invariant(fieldProjection);
50
66
  return fieldProjection;
51
67
  }, [model, editing]);
52
68
 
53
- const handleClose = useCallback<EditorKeyOrBlurHandler>(
54
- (value, event) => {
55
- if (!model || !editing || !fieldProjection) {
69
+ const handleEnter = useCallback(
70
+ (value: any) => {
71
+ if (!model || !editing) {
56
72
  return;
57
73
  }
58
74
 
59
- const cell = toGridCell(editing.index);
75
+ const cell = parseCellIndex(editing.index);
76
+ model.setCellData(cell, value);
60
77
  onEnter?.(cell);
61
- if (event && onFocus) {
62
- onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
63
- }
78
+ onFocus?.();
79
+ setEditing(null);
64
80
  },
65
- [model, editing, onFocus, onEnter, fieldProjection, determineNavigationAxis, determineNavigationDelta],
81
+ [model, editing],
66
82
  );
67
83
 
68
84
  const handleBlur = useCallback<EditorBlurHandler>(
@@ -70,8 +86,12 @@ export const TableCellEditor = ({
70
86
  if (!model || !editing) {
71
87
  return;
72
88
  }
89
+ if (suppressNextBlur.current) {
90
+ suppressNextBlur.current = false;
91
+ return;
92
+ }
73
93
 
74
- const cell = toGridCell(editing.index);
94
+ const cell = parseCellIndex(editing.index);
75
95
  if (value !== undefined) {
76
96
  model.setCellData(cell, value);
77
97
  }
@@ -79,6 +99,21 @@ export const TableCellEditor = ({
79
99
  [model, editing],
80
100
  );
81
101
 
102
+ const handleClose = useCallback<EditorKeyOrBlurHandler>(
103
+ (value, event) => {
104
+ if (!model || !editing || !fieldProjection) {
105
+ return;
106
+ }
107
+
108
+ const cell = parseCellIndex(editing.index);
109
+ onEnter?.(cell);
110
+ if (event && onFocus) {
111
+ onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
112
+ }
113
+ },
114
+ [model, editing, onFocus, onEnter, fieldProjection, determineNavigationAxis, determineNavigationDelta],
115
+ );
116
+
82
117
  const extension = useMemo(() => {
83
118
  if (!fieldProjection) {
84
119
  return [];
@@ -99,10 +134,24 @@ export const TableCellEditor = ({
99
134
  onQuery: (text) => onQuery(fieldProjection, text),
100
135
  onMatch: (data) => {
101
136
  if (model && editing) {
102
- const cell = toGridCell(editing.index);
103
- model.setCellData(cell, data);
104
- onEnter?.(cell);
105
- onFocus?.();
137
+ if (isCreateOption(data)) {
138
+ const { field, props } = fieldProjection;
139
+ if (props.referenceSchema) {
140
+ suppressNextBlur.current = true;
141
+ model.modalController.openCreateRef(
142
+ props.referenceSchema,
143
+ document.querySelector(cellQuery(editing.index, gridId)),
144
+ {
145
+ [field.referencePath!]: data.text,
146
+ },
147
+ (data) => {
148
+ handleEnter(data);
149
+ },
150
+ );
151
+ }
152
+ } else {
153
+ handleEnter(data);
154
+ }
106
155
  }
107
156
  },
108
157
  }),
@@ -117,7 +166,7 @@ export const TableCellEditor = ({
117
166
 
118
167
  const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
119
168
  if (model && editing) {
120
- const value = model.getCellData(toGridCell(editing.index));
169
+ const value = model.getCellData(parseCellIndex(editing.index));
121
170
  return value !== undefined ? String(value) : '';
122
171
  }
123
172
  }, [model, editing]);
@@ -12,6 +12,7 @@ import { type Extension } from '@codemirror/state';
12
12
  import { EditorView } from '@codemirror/view';
13
13
 
14
14
  export type CompletionOptions = {
15
+ debug?: boolean;
15
16
  onQuery: (text: string) => Promise<CompletionResult['options']>;
16
17
  onMatch: (data: any) => void;
17
18
  };
@@ -20,7 +21,7 @@ export type CompletionOptions = {
20
21
  * Autocomplete Codemirror extension.
21
22
  * https://codemirror.net/docs/ref/#autocomplete.autocompletion
22
23
  */
23
- export const completion = ({ onQuery, onMatch }: CompletionOptions): Extension => {
24
+ export const completion = ({ debug, onQuery, onMatch }: CompletionOptions): Extension => {
24
25
  return [
25
26
  EditorView.theme({
26
27
  '.cm-completionDialog': {
@@ -31,6 +32,7 @@ export const completion = ({ onQuery, onMatch }: CompletionOptions): Extension =
31
32
  autocompletion({
32
33
  icons: false,
33
34
  activateOnTyping: true,
35
+ closeOnBlur: !debug,
34
36
  override: [
35
37
  async (context: CompletionContext): Promise<CompletionResult> => {
36
38
  const text = context.state.doc.toString();
@@ -44,9 +46,6 @@ export const completion = ({ onQuery, onMatch }: CompletionOptions): Extension =
44
46
  // TODO(burdon): Consts from grid (to line-up with grid).
45
47
  tooltipClass: () => 'cm-completionDialog !mt-[8px] !-ml-[4px] [&>ul]:!max-h-[264px]',
46
48
  optionClass: () => 'flex h-[33px] items-center',
47
-
48
- // Uncomment to debug.
49
- // closeOnBlur: true,
50
49
  }),
51
50
 
52
51
  // Check for accepted completion metadata in the transaction.
@@ -4,8 +4,9 @@
4
4
 
5
5
  import { useEffect } from 'react';
6
6
 
7
- import { create, setValue, toJsonSchema, S, TypeEnum, TypedObject, FormatEnum } from '@dxos/echo-schema';
7
+ import { setValue, toJsonSchema, S, TypeEnum, TypedObject, FormatEnum } from '@dxos/echo-schema';
8
8
  import { faker } from '@dxos/random';
9
+ import { create } from '@dxos/react-client/echo';
9
10
  import { createView, type ViewProjection } from '@dxos/schema';
10
11
  import {} from '@dxos/schema';
11
12
 
@@ -5,6 +5,9 @@
5
5
  import { signal } from '@preact/signals-core';
6
6
  import { type RefObject, type MouseEvent, type MutableRefObject } from 'react';
7
7
 
8
+ import { type ReactiveObject } from '@dxos/live-object';
9
+ import { log } from '@dxos/log';
10
+
8
11
  import { tableButtons } from '../util';
9
12
 
10
13
  export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: string };
@@ -12,6 +15,13 @@ export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: s
12
15
  export type ModalState =
13
16
  | { type: 'row'; rowIndex: number }
14
17
  | { type: 'column'; fieldId: string }
18
+ | { type: 'refPanel'; targetId: string; typename: string }
19
+ | {
20
+ type: 'createRefPanel';
21
+ typename: string;
22
+ initialValues?: Record<string, string>;
23
+ onCreate?: (obj: ReactiveObject<any>) => void;
24
+ }
15
25
  | { type: 'columnSettings'; mode: ColumnSettingsMode }
16
26
  | { type: 'closed' };
17
27
 
@@ -30,6 +40,9 @@ export class ModalController {
30
40
  public handleClick = (event: MouseEvent): boolean => {
31
41
  const target = event.target as HTMLElement;
32
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
+
33
46
  const rowButton = target.closest(`button[${tableButtons.rowMenu.attr}]`);
34
47
  if (rowButton) {
35
48
  this._triggerRef.current = rowButton as HTMLElement;
@@ -61,6 +74,17 @@ export class ModalController {
61
74
  return true;
62
75
  }
63
76
 
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
+ }
87
+
64
88
  return false;
65
89
  };
66
90
 
@@ -77,6 +101,25 @@ export class ModalController {
77
101
  }
78
102
  };
79
103
 
104
+ public openCreateRef = (
105
+ typename: string,
106
+ anchorCell: Element | null,
107
+ initialValues?: Record<string, string>,
108
+ onCreate?: (obj: ReactiveObject<any>) => void,
109
+ ) => {
110
+ if (anchorCell) {
111
+ this._triggerRef.current = anchorCell as HTMLElement;
112
+ this._state.value = {
113
+ type: 'createRefPanel',
114
+ typename,
115
+ initialValues,
116
+ onCreate,
117
+ };
118
+ } else {
119
+ log.warn('anchor cell not found while creating new ref');
120
+ }
121
+ };
122
+
80
123
  public close = () => {
81
124
  this._state.value = { type: 'closed' };
82
125
  };
@@ -3,11 +3,13 @@
3
3
  //
4
4
 
5
5
  import { computed } from '@preact/signals-core';
6
- import { describe, it, expect, afterEach, beforeEach } from 'vitest';
6
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
7
7
 
8
- import { S, TypedObject, create } from '@dxos/echo-schema';
9
- import { createMutableSchema, updateCounter } from '@dxos/echo-schema/testing';
8
+ import { S, TypedObject } from '@dxos/echo-schema';
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
13
  import { createView, ViewProjection } from '@dxos/schema';
12
14
 
13
15
  import { TableModel, type TableModelProps } from './table-model';
@@ -22,6 +22,9 @@ import {
22
22
  type DxGridPlane,
23
23
  type DxGridPlaneCells,
24
24
  type DxGridPlaneRange,
25
+ type DxGridPlanePosition,
26
+ toPlaneCellIndex,
27
+ type DxGridPosition,
25
28
  } from '@dxos/react-ui-grid';
26
29
  import { mx } from '@dxos/react-ui-theme';
27
30
  import { VIEW_FIELD_LIMIT, type ViewProjection, type FieldType } from '@dxos/schema';
@@ -29,7 +32,6 @@ import { VIEW_FIELD_LIMIT, type ViewProjection, type FieldType } from '@dxos/sch
29
32
  import { ModalController } from './modal-controller';
30
33
  import { SelectionModel } from './selection-model';
31
34
  import { type TableType } from '../types';
32
- import { fromGridCell, type GridCell } from '../util';
33
35
  import { tableButtons, touch } from '../util';
34
36
  import { tableControls } from '../util/table-controls';
35
37
 
@@ -46,7 +48,7 @@ export type TableModelProps<T extends BaseTableRow = { id: string }> = {
46
48
  onInsertRow?: (index?: number) => void;
47
49
  onDeleteRows?: (index: number, obj: T[]) => void;
48
50
  onDeleteColumn?: (fieldId: string) => void;
49
- onCellUpdate?: (cell: GridCell) => void;
51
+ onCellUpdate?: (cell: DxGridPosition) => void;
50
52
  onRowOrderChanged?: () => void;
51
53
  };
52
54
 
@@ -214,7 +216,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
214
216
  effect(() => {
215
217
  const obj = this._sortedRows.value[row];
216
218
  this?._table?.view?.fields.forEach((field) => touch(getValue(obj, field.path)));
217
- this._onCellUpdate?.({ row, col: start.col });
219
+ this._onCellUpdate?.({ row, col: start.col, plane: 'grid' });
218
220
  }),
219
221
  );
220
222
  }
@@ -256,12 +258,13 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
256
258
  };
257
259
 
258
260
  private getMainGridCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
259
- const values: DxGridPlaneCells = {};
261
+ const cells: DxGridPlaneCells = {};
260
262
  const fields = this._table.view?.fields ?? [];
261
263
 
262
264
  const addCell = (obj: T, field: FieldType, colIndex: number, displayIndex: number): void => {
263
265
  const { props } = this._projection.getFieldProjection(field.id);
264
- const value: DxGridCellValue = {
266
+
267
+ const cell: DxGridCellValue = {
265
268
  get value() {
266
269
  const value = getValue(obj, field.path);
267
270
  if (value == null) {
@@ -294,11 +297,19 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
294
297
  classes.push(rowSelectionClasses);
295
298
  }
296
299
  if (classes.length > 0) {
297
- value.className = mx(classes.flat());
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
+ });
298
309
  }
299
310
 
300
- const idx = fromGridCell({ col: colIndex, row: displayIndex });
301
- values[idx] = value;
311
+ const idx = toPlaneCellIndex({ col: colIndex, row: displayIndex });
312
+ cells[idx] = cell;
302
313
  };
303
314
 
304
315
  for (let row = range.start.row; row <= range.end.row && row < this._sortedRows.value.length; row++) {
@@ -312,15 +323,16 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
312
323
  }
313
324
  }
314
325
 
315
- return values;
326
+ return cells;
316
327
  };
317
328
 
318
329
  private getHeaderCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
319
- const values: DxGridPlaneCells = {};
330
+ const cells: DxGridPlaneCells = {};
320
331
  const fields = this.table.view?.fields ?? [];
321
332
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
322
333
  const { field, props } = this._projection.getFieldProjection(fields[col].id);
323
- values[fromGridCell({ col, row: 0 })] = {
334
+ cells[toPlaneCellIndex({ col, row: 0 })] = {
335
+ // TODO(burdon): Use same logic as form for fallback title.
324
336
  value: props.title ?? field.path,
325
337
  readonly: true,
326
338
  resizeHandle: 'col',
@@ -328,15 +340,15 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
328
340
  };
329
341
  }
330
342
 
331
- return values;
343
+ return cells;
332
344
  };
333
345
 
334
346
  private getSelectionColumnCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
335
- const values: DxGridPlaneCells = {};
347
+ const cells: DxGridPlaneCells = {};
336
348
  for (let row = range.start.row; row <= range.end.row && row < this._rows.value.length; row++) {
337
349
  const isSelected = this._selection.isRowIndexSelected(row);
338
350
  const classes = cellClassesForRowSelection(isSelected);
339
- values[fromGridCell({ col: 0, row })] = {
351
+ cells[toPlaneCellIndex({ col: 0, row })] = {
340
352
  value: '',
341
353
  readonly: true,
342
354
  className: classes ? mx(classes) : undefined,
@@ -344,15 +356,15 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
344
356
  };
345
357
  }
346
358
 
347
- return values;
359
+ return cells;
348
360
  };
349
361
 
350
362
  private getActionColumnCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
351
- const values: DxGridPlaneCells = {};
363
+ const cells: DxGridPlaneCells = {};
352
364
  for (let row = range.start.row; row <= range.end.row && row < this._rows.value.length; row++) {
353
365
  const isSelected = this._selection.isRowIndexSelected(row);
354
366
  const classes = cellClassesForRowSelection(isSelected);
355
- values[fromGridCell({ col: 0, row })] = {
367
+ cells[toPlaneCellIndex({ col: 0, row })] = {
356
368
  value: '',
357
369
  readonly: true,
358
370
  className: classes ? mx(classes) : undefined,
@@ -360,12 +372,12 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
360
372
  };
361
373
  }
362
374
 
363
- return values;
375
+ return cells;
364
376
  };
365
377
 
366
378
  private getSelectAllCell = (): DxGridPlaneCells => {
367
379
  return {
368
- [fromGridCell({ col: 0, row: 0 })]: {
380
+ [toPlaneCellIndex({ col: 0, row: 0 })]: {
369
381
  value: '',
370
382
  accessoryHtml: tableControls.checkbox.render({
371
383
  rowIndex: 0,
@@ -379,7 +391,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
379
391
 
380
392
  private getNewColumnCell = (): DxGridPlaneCells => {
381
393
  return {
382
- [fromGridCell({ col: 0, row: 0 })]: {
394
+ [toPlaneCellIndex({ col: 0, row: 0 })]: {
383
395
  value: '',
384
396
  accessoryHtml: tableButtons.addColumn.render({
385
397
  disabled: (this._table.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
@@ -421,7 +433,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
421
433
  this._onDeleteRows?.(row, objectsToDelete);
422
434
  };
423
435
 
424
- public getCellData = ({ col, row }: GridCell): any => {
436
+ public getCellData = ({ col, row }: DxGridPlanePosition): any => {
425
437
  const fields = this.table.view?.fields ?? [];
426
438
  if (col < 0 || col >= fields.length) {
427
439
  return undefined;
@@ -450,7 +462,7 @@ export class TableModel<T extends BaseTableRow = { id: string }> extends Resourc
450
462
  }
451
463
  };
452
464
 
453
- public setCellData = ({ col, row }: GridCell, value: any): void => {
465
+ public setCellData = ({ col, row }: DxGridPlanePosition, value: any): void => {
454
466
  const rowIdx = this._displayToDataIndex.get(row) ?? row;
455
467
  const fields = this.table.view?.fields ?? [];
456
468
  if (col < 0 || col >= fields.length) {
@@ -95,10 +95,6 @@ test.describe('Table', () => {
95
95
 
96
96
  await table.addColumn({ label: newColumnLabel, format: 'number' });
97
97
 
98
- // Delete first two columns to get the new column into view
99
- await table.deleteColumn(0);
100
- await table.deleteColumn(0);
101
-
102
98
  await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
103
99
  await page.close();
104
100
  });
@@ -27,6 +27,7 @@ export default [
27
27
  'bulk delete row label': 'Delete selected rows',
28
28
  'column deleted label': 'Column deleted',
29
29
  'new column button label': 'Create column',
30
+ 'create new object label': 'Create new object “{{text}}”',
30
31
  },
31
32
  },
32
33
  },
@@ -2,19 +2,19 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { ref, S, TypedObject } from '@dxos/echo-schema';
5
+ import { ref, ObjectId, S, TypedObject } from '@dxos/echo-schema';
6
6
  import { ThreadType } from '@dxos/plugin-space';
7
7
  import { ViewType } from '@dxos/schema';
8
8
 
9
- // TODO(burdon): Factor out echo dependency and define pattern for react-ui-xxx.
10
- const TableSchema = S.Struct({
11
- id: S.String,
9
+ export const TableSchema = S.Struct({
10
+ id: ObjectId, // TODO(burdon): Where should this be?
12
11
  name: S.optional(S.String),
13
12
  view: S.optional(ref(ViewType)),
13
+ // TODO(burdon): Should not import from plugin. Either factor out type or use reverse deps when supported.
14
14
  threads: S.optional(S.Array(ref(ThreadType))),
15
15
  });
16
16
 
17
- type _TableType = S.Schema.Type<typeof TableSchema>;
17
+ // type TableType = S.Schema.Type<typeof TableSchema>;
18
18
 
19
19
  export class TableType extends TypedObject({
20
20
  typename: 'dxos.org/type/Table',
package/src/util/index.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './grid';
6
5
  export * from './signals';
7
6
  export * from './table';
8
7
  export * from './table-buttons';
@@ -2,15 +2,22 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- type TableButton = 'columnSettings' | 'rowMenu' | 'newColumn';
5
+ type TableButton = 'columnSettings' | 'rowMenu' | 'newColumn' | 'referencedCell';
6
+
7
+ // TODO(thure): Lots of copy-pasted business here, this could by DRYed out substantially.
8
+
9
+ // TODO(thure): These unique attributes come with more caveats than e.g. applying multiple attributes – one key-value
10
+ // pair for identifying the button type and another for passing the fieldId.
6
11
 
7
12
  const TABLE_ATTRS: { [K in TableButton]: string } = {
13
+ referencedCell: 'data-table-ref-cell-button',
8
14
  columnSettings: 'data-table-column-settings-button',
9
15
  rowMenu: 'data-table-row-menu-button',
10
16
  newColumn: 'data-table-new-column-button',
11
17
  } as const;
12
18
 
13
19
  const ICONS: { [K in TableButton]: string } = {
20
+ referencedCell: 'ph--link-simple-horizontal--regular',
14
21
  columnSettings: 'ph--caret-down--regular',
15
22
  rowMenu: 'ph--dots-three--regular',
16
23
  newColumn: 'ph--plus--regular',
@@ -32,6 +39,16 @@ const createButtonHtml = ({
32
39
  };
33
40
 
34
41
  export const tableButtons = {
42
+ referencedCell: {
43
+ attr: TABLE_ATTRS.referencedCell,
44
+ icon: ICONS.referencedCell,
45
+ render: ({ targetId, schemaId }: { targetId: string; schemaId: string }) =>
46
+ createButtonHtml({
47
+ button: 'referencedCell',
48
+ value: `${schemaId}#${targetId}`,
49
+ testId: 'table-ref-cell-button',
50
+ }),
51
+ },
35
52
  columnSettings: {
36
53
  attr: TABLE_ATTRS.columnSettings,
37
54
  icon: ICONS.columnSettings,
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types/table.ts"],
4
- "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { ref, S, TypedObject } from '@dxos/echo-schema';\nimport { ThreadType } from '@dxos/plugin-space';\nimport { ViewType } from '@dxos/schema';\n\n// TODO(burdon): Factor out echo dependency and define pattern for react-ui-xxx.\nconst TableSchema = S.Struct({\n id: S.String,\n name: S.optional(S.String),\n view: S.optional(ref(ViewType)),\n threads: S.optional(S.Array(ref(ThreadType))),\n});\n\ntype _TableType = S.Schema.Type<typeof TableSchema>;\n\nexport class TableType extends TypedObject({\n typename: 'dxos.org/type/Table',\n version: '0.1.0',\n})(TableSchema.fields) {}\n"],
5
- "mappings": ";AAIA,SAASA,KAAKC,GAAGC,mBAAmB;AACpC,SAASC,kBAAkB;AAC3B,SAASC,gBAAgB;AAGzB,IAAMC,cAAcC,EAAEC,OAAO;EAC3BC,IAAIF,EAAEG;EACNC,MAAMJ,EAAEK,SAASL,EAAEG,MAAM;EACzBG,MAAMN,EAAEK,SAASE,IAAIC,QAAAA,CAAAA;EACrBC,SAAST,EAAEK,SAASL,EAAEU,MAAMH,IAAII,UAAAA,CAAAA,CAAAA;AAClC,CAAA;AAIO,IAAMC,YAAN,cAAwBC,YAAY;EACzCC,UAAU;EACVC,SAAS;AACX,CAAA,EAAGhB,YAAYiB,MAAM,EAAA;AAAG;",
6
- "names": ["ref", "S", "TypedObject", "ThreadType", "ViewType", "TableSchema", "S", "Struct", "id", "String", "name", "optional", "view", "ref", "ViewType", "threads", "Array", "ThreadType", "TableType", "TypedObject", "typename", "version", "fields"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types/table.ts"],
4
- "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { ref, S, TypedObject } from '@dxos/echo-schema';\nimport { ThreadType } from '@dxos/plugin-space';\nimport { ViewType } from '@dxos/schema';\n\n// TODO(burdon): Factor out echo dependency and define pattern for react-ui-xxx.\nconst TableSchema = S.Struct({\n id: S.String,\n name: S.optional(S.String),\n view: S.optional(ref(ViewType)),\n threads: S.optional(S.Array(ref(ThreadType))),\n});\n\ntype _TableType = S.Schema.Type<typeof TableSchema>;\n\nexport class TableType extends TypedObject({\n typename: 'dxos.org/type/Table',\n version: '0.1.0',\n})(TableSchema.fields) {}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;AAIA,yBAAoC;AACpC,0BAA2B;AAC3B,oBAAyB;AAGzB,IAAMA,cAAcC,qBAAEC,OAAO;EAC3BC,IAAIF,qBAAEG;EACNC,MAAMJ,qBAAEK,SAASL,qBAAEG,MAAM;EACzBG,MAAMN,qBAAEK,aAASE,wBAAIC,sBAAAA,CAAAA;EACrBC,SAAST,qBAAEK,SAASL,qBAAEU,UAAMH,wBAAII,8BAAAA,CAAAA,CAAAA;AAClC,CAAA;AAIO,IAAMC,YAAN,kBAAwBC,gCAAY;EACzCC,UAAU;EACVC,SAAS;AACX,CAAA,EAAGhB,YAAYiB,MAAM,EAAA;AAAG;",
6
- "names": ["TableSchema", "S", "Struct", "id", "String", "name", "optional", "view", "ref", "ViewType", "threads", "Array", "ThreadType", "TableType", "TypedObject", "typename", "version", "fields"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types/table.ts"],
4
- "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { ref, S, TypedObject } from '@dxos/echo-schema';\nimport { ThreadType } from '@dxos/plugin-space';\nimport { ViewType } from '@dxos/schema';\n\n// TODO(burdon): Factor out echo dependency and define pattern for react-ui-xxx.\nconst TableSchema = S.Struct({\n id: S.String,\n name: S.optional(S.String),\n view: S.optional(ref(ViewType)),\n threads: S.optional(S.Array(ref(ThreadType))),\n});\n\ntype _TableType = S.Schema.Type<typeof TableSchema>;\n\nexport class TableType extends TypedObject({\n typename: 'dxos.org/type/Table',\n version: '0.1.0',\n})(TableSchema.fields) {}\n"],
5
- "mappings": ";;;AAIA,SAASA,KAAKC,GAAGC,mBAAmB;AACpC,SAASC,kBAAkB;AAC3B,SAASC,gBAAgB;AAGzB,IAAMC,cAAcC,EAAEC,OAAO;EAC3BC,IAAIF,EAAEG;EACNC,MAAMJ,EAAEK,SAASL,EAAEG,MAAM;EACzBG,MAAMN,EAAEK,SAASE,IAAIC,QAAAA,CAAAA;EACrBC,SAAST,EAAEK,SAASL,EAAEU,MAAMH,IAAII,UAAAA,CAAAA,CAAAA;AAClC,CAAA;AAIO,IAAMC,YAAN,cAAwBC,YAAY;EACzCC,UAAU;EACVC,SAAS;AACX,CAAA,EAAGhB,YAAYiB,MAAM,EAAA;AAAG;",
6
- "names": ["ref", "S", "TypedObject", "ThreadType", "ViewType", "TableSchema", "S", "Struct", "id", "String", "name", "optional", "view", "ref", "ViewType", "threads", "Array", "ThreadType", "TableType", "TypedObject", "typename", "version", "fields"]
7
- }
@@ -1,14 +0,0 @@
1
- import { type DxGridPlaneCellIndex } from '@dxos/react-ui-grid';
2
- export type GridCell = {
3
- col: number;
4
- row: number;
5
- };
6
- /**
7
- * Generates a string key for a cell based on its column and row indices.
8
- */
9
- export declare const fromGridCell: ({ col, row }: GridCell) => DxGridPlaneCellIndex;
10
- /**
11
- * Converts a cell key string back into column and row coordinates.
12
- */
13
- export declare const toGridCell: (cell: DxGridPlaneCellIndex) => GridCell;
14
- //# sourceMappingURL=grid.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../../src/util/grid.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,MAAM,MAAM,QAAQ,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;GAEG;AACH,eAAO,MAAM,YAAY,iBAAkB,QAAQ,KAAG,oBAErD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,SAAU,oBAAoB,KAAG,QAGvD,CAAC"}
package/src/util/grid.ts DELETED
@@ -1,23 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { type DxGridPlaneCellIndex } from '@dxos/react-ui-grid';
6
-
7
- // TODO(burdon): Factor out to grid.
8
- export type GridCell = { col: number; row: number };
9
-
10
- /**
11
- * Generates a string key for a cell based on its column and row indices.
12
- */
13
- export const fromGridCell = ({ col, row }: GridCell): DxGridPlaneCellIndex => {
14
- return `${col},${row}` as const; // TODO(burdon): ":" delim.
15
- };
16
-
17
- /**
18
- * Converts a cell key string back into column and row coordinates.
19
- */
20
- export const toGridCell = (cell: DxGridPlaneCellIndex): GridCell => {
21
- const [col, row] = cell.split(',').map(Number);
22
- return { row, col };
23
- };