@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.
- package/dist/lib/browser/{chunk-EWGUNGJC.mjs → chunk-IYCSJDMB.mjs} +5 -3
- package/dist/lib/browser/chunk-IYCSJDMB.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +461 -237
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types/index.mjs +3 -1
- package/dist/lib/node/{chunk-A2N7XWW6.cjs → chunk-MHBMVCFZ.cjs} +8 -5
- package/dist/lib/node/chunk-MHBMVCFZ.cjs.map +7 -0
- package/dist/lib/node/index.cjs +471 -253
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types/index.cjs +4 -2
- package/dist/lib/node/types/index.cjs.map +2 -2
- package/dist/lib/node-esm/{chunk-SABDOZ6I.mjs → chunk-BMWGPXPL.mjs} +5 -3
- package/dist/lib/node-esm/chunk-BMWGPXPL.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +461 -237
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/types/index.mjs +3 -1
- package/dist/types/src/_deprecated/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +8 -0
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -0
- package/dist/types/src/components/Table/RefPanel.d.ts +8 -0
- package/dist/types/src/components/Table/RefPanel.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +2 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +16 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/extension.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/model/modal-controller.d.ts +11 -0
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +4 -5
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/table.d.ts +57 -2
- package/dist/types/src/types/table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts +8 -0
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/package.json +29 -28
- package/src/_deprecated/components/Table/Table.stories.tsx +1 -1
- package/src/components/Table/ColumnActionsMenu.tsx +0 -1
- package/src/components/Table/ColumnSettings.tsx +7 -4
- package/src/components/Table/CreateRefPanel.tsx +83 -0
- package/src/components/Table/RefPanel.tsx +52 -0
- package/src/components/Table/RowActionsMenu.tsx +1 -2
- package/src/components/Table/Table.stories.tsx +2 -2
- package/src/components/Table/Table.tsx +60 -33
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +2 -2
- package/src/components/TableCellEditor/TableCellEditor.tsx +72 -23
- package/src/components/TableCellEditor/extension.ts +3 -4
- package/src/components/testing.ts +2 -1
- package/src/model/modal-controller.ts +43 -0
- package/src/model/table-model.test.ts +5 -3
- package/src/model/table-model.ts +34 -22
- package/src/playwright/smoke.spec.ts +0 -4
- package/src/translations.ts +1 -0
- package/src/types/table.ts +5 -5
- package/src/util/index.ts +0 -1
- package/src/util/table-buttons.ts +18 -1
- package/dist/lib/browser/chunk-EWGUNGJC.mjs.map +0 -7
- package/dist/lib/node/chunk-A2N7XWW6.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-SABDOZ6I.mjs.map +0 -7
- package/dist/types/src/util/grid.d.ts +0 -14
- package/dist/types/src/util/grid.d.ts.map +0 -1
- package/src/util/grid.ts +0 -23
|
@@ -2,32 +2,46 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
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
|
-
|
|
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:
|
|
42
|
+
onEnter?: (cell: DxGridPlanePosition) => void;
|
|
29
43
|
onFocus?: DxGrid['refocus'];
|
|
30
|
-
onQuery?: (field: FieldProjection, text: string) => Promise<
|
|
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 } =
|
|
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
|
|
54
|
-
(value
|
|
55
|
-
if (!model || !editing
|
|
69
|
+
const handleEnter = useCallback(
|
|
70
|
+
(value: any) => {
|
|
71
|
+
if (!model || !editing) {
|
|
56
72
|
return;
|
|
57
73
|
}
|
|
58
74
|
|
|
59
|
-
const cell =
|
|
75
|
+
const cell = parseCellIndex(editing.index);
|
|
76
|
+
model.setCellData(cell, value);
|
|
60
77
|
onEnter?.(cell);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
78
|
+
onFocus?.();
|
|
79
|
+
setEditing(null);
|
|
64
80
|
},
|
|
65
|
-
[model, editing
|
|
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 =
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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(
|
|
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 {
|
|
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 {
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import { S, TypedObject
|
|
9
|
-
import {
|
|
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';
|
package/src/model/table-model.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
301
|
-
|
|
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
|
|
326
|
+
return cells;
|
|
316
327
|
};
|
|
317
328
|
|
|
318
329
|
private getHeaderCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
|
|
319
|
-
const
|
|
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
|
-
|
|
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
|
|
343
|
+
return cells;
|
|
332
344
|
};
|
|
333
345
|
|
|
334
346
|
private getSelectionColumnCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
|
|
335
|
-
const
|
|
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
|
-
|
|
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
|
|
359
|
+
return cells;
|
|
348
360
|
};
|
|
349
361
|
|
|
350
362
|
private getActionColumnCells = (range: DxGridPlaneRange): DxGridPlaneCells => {
|
|
351
|
-
const
|
|
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
|
-
|
|
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
|
|
375
|
+
return cells;
|
|
364
376
|
};
|
|
365
377
|
|
|
366
378
|
private getSelectAllCell = (): DxGridPlaneCells => {
|
|
367
379
|
return {
|
|
368
|
-
[
|
|
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
|
-
[
|
|
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 }:
|
|
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 }:
|
|
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
|
});
|
package/src/translations.ts
CHANGED
package/src/types/table.ts
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
|
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,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
|
-
};
|