@dxos/react-ui-table 0.8.4-main.2c6827d → 0.8.4-main.3c1ae3b
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-CJQ6T4LL.mjs +77 -0
- package/dist/lib/browser/chunk-CJQ6T4LL.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GS2UNGJK.mjs → chunk-V7A7ZA5E.mjs} +119 -111
- package/dist/lib/browser/chunk-V7A7ZA5E.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +162 -147
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +15 -31
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +3 -3
- package/dist/lib/node-esm/{chunk-TRR3C5NX.mjs → chunk-P73ALX3N.mjs} +119 -111
- package/dist/lib/node-esm/chunk-P73ALX3N.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs +78 -0
- package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +162 -147
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +15 -31
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +3 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +4 -5
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +3 -17
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +10 -6
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +8 -8
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +70 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +10 -11
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +3 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +0 -2
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +4 -5
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +6 -8
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +7 -3
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +1 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +19 -15
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -2
- package/dist/types/src/types/Table.d.ts +28 -0
- package/dist/types/src/types/Table.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +9 -9
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +38 -38
- package/src/components/Table/ColumnActionsMenu.tsx +4 -1
- package/src/components/Table/ColumnSettings.tsx +2 -3
- package/src/components/Table/DynamicTable.stories.tsx +7 -9
- package/src/components/Table/DynamicTable.tsx +18 -10
- package/src/components/Table/Relations.stories.tsx +9 -18
- package/src/components/Table/Table.stories.tsx +52 -59
- package/src/components/Table/Table.tsx +317 -304
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +101 -108
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +11 -8
- package/src/components/TableCellEditor/TableCellEditor.tsx +29 -29
- package/src/components/TableToolbar/TableToolbar.tsx +1 -1
- package/src/components/testing.ts +3 -3
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useProjectionModel.ts +19 -0
- package/src/hooks/useTableModel.ts +11 -13
- package/src/model/table-model.test.ts +4 -4
- package/src/model/table-model.ts +29 -38
- package/src/model/table-presentation.test.ts +4 -4
- package/src/model/table-presentation.ts +9 -9
- package/src/model/table-sorting.ts +21 -12
- package/src/playwright/TableManager.ts +5 -5
- package/src/playwright/smoke.spec.ts +1 -1
- package/src/testing/useTestTableModel.ts +31 -27
- package/src/translations.ts +2 -2
- package/src/types/Table.ts +90 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.ts +11 -16
- package/dist/lib/browser/chunk-2PFAPQL4.mjs +0 -66
- package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +0 -7
- package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs +0 -67
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +0 -7
- package/dist/types/src/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/types/schema.ts +0 -73
|
@@ -12,9 +12,9 @@ export type CellValidationMessageProps = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export const CellValidationMessage = ({
|
|
15
|
+
__gridScope,
|
|
15
16
|
validationError,
|
|
16
17
|
variant = 'error',
|
|
17
|
-
__gridScope,
|
|
18
18
|
}: GridScopedProps<CellValidationMessageProps>) => {
|
|
19
19
|
const { editing, editBox: box } = useGridContext('GridSheetCellEditor', __gridScope);
|
|
20
20
|
|
|
@@ -2,97 +2,96 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import * as Schema from 'effect/Schema';
|
|
5
|
+
import type * as Schema from 'effect/Schema';
|
|
6
6
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { Filter, Obj } from '@dxos/echo';
|
|
9
|
-
import {
|
|
8
|
+
import { Filter, Obj, type Type } from '@dxos/echo';
|
|
9
|
+
import { Format, Ref, getValue } from '@dxos/echo/internal';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
11
|
import { getSnapshot } from '@dxos/live-object';
|
|
12
|
-
import { type Client } from '@dxos/react-client';
|
|
13
12
|
import { getSpace } from '@dxos/react-client/echo';
|
|
14
|
-
import { Popover } from '@dxos/react-ui';
|
|
15
|
-
import { Form, type
|
|
13
|
+
import { type Label, Popover } from '@dxos/react-ui';
|
|
14
|
+
import { Form, type FormRootProps, omitId } from '@dxos/react-ui-form';
|
|
16
15
|
import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
|
|
17
16
|
import { type FieldProjection } from '@dxos/schema';
|
|
18
17
|
import { getDeep, isTruthy, setDeep } from '@dxos/util';
|
|
19
18
|
|
|
20
|
-
import { type ModalController, type TableModel } from '../../model';
|
|
19
|
+
import { type ModalController, type TableModel, type TableRow } from '../../model';
|
|
21
20
|
import { translationKey } from '../../translations';
|
|
22
21
|
import { narrowSchema } from '../../util';
|
|
23
22
|
|
|
23
|
+
const createOptionLabel: Label = ['create new object label', { ns: translationKey }];
|
|
24
|
+
|
|
24
25
|
export type OnCreateHandler = (schema: Schema.Schema.AnyNoContext, values: any) => Parameters<typeof Ref.make>[0];
|
|
25
26
|
|
|
26
|
-
export type FormCellEditorProps = {
|
|
27
|
-
|
|
27
|
+
export type FormCellEditorProps<T extends Type.Entity.Any = Type.Entity.Any> = {
|
|
28
|
+
__gridScope: any;
|
|
29
|
+
schema?: T;
|
|
28
30
|
model?: TableModel;
|
|
29
|
-
|
|
31
|
+
fieldProjection: FieldProjection;
|
|
32
|
+
modals?: ModalController; // TODO(burdon): Not used. Remove?
|
|
30
33
|
onSave?: () => void;
|
|
31
34
|
onCreate?: OnCreateHandler;
|
|
32
|
-
|
|
33
|
-
modals?: ModalController;
|
|
34
|
-
__gridScope: any;
|
|
35
|
-
} & Omit<FormProps<any>, 'values' | 'schema' | 'onCreate'>;
|
|
36
|
-
|
|
37
|
-
const createOptionLabel = ['create new object label', { ns: translationKey }] as [string, { ns: string }];
|
|
35
|
+
} & Omit<FormRootProps<any>, 'values' | 'schema' | 'onCreate'>;
|
|
38
36
|
|
|
39
|
-
export const FormCellEditor = ({
|
|
40
|
-
|
|
41
|
-
model,
|
|
37
|
+
export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
38
|
+
__gridScope,
|
|
42
39
|
schema,
|
|
40
|
+
model,
|
|
41
|
+
fieldProjection,
|
|
43
42
|
onSave,
|
|
44
|
-
client,
|
|
45
|
-
modals,
|
|
46
43
|
onCreate,
|
|
47
|
-
__gridScope,
|
|
48
44
|
...formProps
|
|
49
|
-
}: FormCellEditorProps) => {
|
|
50
|
-
const { editing: contextEditing, setEditing, id: gridId } = useGridContext('ArrayEditor', __gridScope);
|
|
51
|
-
const [editing, setLocalEditing] = useState(false);
|
|
45
|
+
}: FormCellEditorProps<T>) => {
|
|
52
46
|
const anchorRef = useRef<HTMLButtonElement | null>(null);
|
|
47
|
+
const { id: _gridId, editing: contextEditing, setEditing } = useGridContext('ArrayEditor', __gridScope);
|
|
48
|
+
const [editing, setLocalEditing] = useState(false);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (contextEditing && contextEditing.cellElement) {
|
|
52
|
+
anchorRef.current = (contextEditing.cellElement as HTMLElement).querySelector(
|
|
53
|
+
'.dx-grid__cell__content',
|
|
54
|
+
) as HTMLButtonElement;
|
|
55
|
+
setLocalEditing(true);
|
|
56
|
+
} else {
|
|
57
|
+
anchorRef.current = null;
|
|
58
|
+
setLocalEditing(false);
|
|
59
|
+
}
|
|
60
|
+
}, [contextEditing]);
|
|
61
|
+
|
|
62
|
+
const narrowedSchema = useMemo<Schema.Schema.AnyNoContext | undefined>(() => {
|
|
63
|
+
if (!schema) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return narrowSchema(schema, [fieldProjection.field.path]);
|
|
68
|
+
}, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
|
|
53
69
|
|
|
54
70
|
const getSchema = useCallback(
|
|
55
|
-
(
|
|
71
|
+
({ typename }: { typename: string }) => {
|
|
56
72
|
const space = getSpace(model!.view);
|
|
57
73
|
invariant(space);
|
|
58
|
-
|
|
59
|
-
let schema;
|
|
60
|
-
if (client) {
|
|
61
|
-
schema = client.graph.schemaRegistry.getSchema(typeAnnotation.typename);
|
|
62
|
-
}
|
|
63
|
-
if (!schema) {
|
|
64
|
-
schema = space.db.schemaRegistry.getSchema(typeAnnotation.typename);
|
|
65
|
-
}
|
|
74
|
+
const schema = space.db.schemaRegistry.query({ typename, location: ['database', 'runtime'] }).runSync()[0];
|
|
66
75
|
return { space, schema };
|
|
67
76
|
},
|
|
68
|
-
[
|
|
77
|
+
[model],
|
|
69
78
|
);
|
|
70
79
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const { schema
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return objects
|
|
77
|
-
.map((obj) => {
|
|
78
|
-
return {
|
|
79
|
-
dxn: Obj.getDXN(obj),
|
|
80
|
-
label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
|
|
81
|
-
};
|
|
82
|
-
})
|
|
83
|
-
.filter(isTruthy);
|
|
84
|
-
}
|
|
80
|
+
const refSchema = useMemo(() => {
|
|
81
|
+
if (fieldProjection.props.format === Format.TypeFormat.Ref && fieldProjection.props.referenceSchema) {
|
|
82
|
+
const { schema } = getSchema({ typename: fieldProjection.props.referenceSchema });
|
|
83
|
+
return schema;
|
|
84
|
+
}
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
[client, model],
|
|
89
|
-
);
|
|
86
|
+
return null;
|
|
87
|
+
}, [fieldProjection.props.format, fieldProjection.props.referenceSchema, getSchema]);
|
|
90
88
|
|
|
91
|
-
const
|
|
92
|
-
if (model && contextEditing) {
|
|
93
|
-
const cell = parseCellIndex(contextEditing.index);
|
|
89
|
+
const createSchema = useMemo(() => (refSchema ? omitId(refSchema) : null), [refSchema]);
|
|
94
90
|
|
|
91
|
+
const originalRow = useMemo<TableRow | undefined>(() => {
|
|
92
|
+
if (model && contextEditing) {
|
|
95
93
|
// Check if this is a draft cell and get the appropriate row data
|
|
94
|
+
const cell = parseCellIndex(contextEditing.index);
|
|
96
95
|
if (model.isDraftCell(cell)) {
|
|
97
96
|
const draftRow = model.draftRows.value[cell.row];
|
|
98
97
|
invariant(draftRow);
|
|
@@ -107,38 +106,6 @@ export const FormCellEditor = ({
|
|
|
107
106
|
return undefined;
|
|
108
107
|
}, [model, contextEditing]);
|
|
109
108
|
|
|
110
|
-
const handleSave = useCallback(
|
|
111
|
-
(values: any) => {
|
|
112
|
-
const path = fieldProjection.field.path;
|
|
113
|
-
const value = getDeep(values, [path]);
|
|
114
|
-
setDeep(originalRow, [path], value);
|
|
115
|
-
contextEditing?.cellElement?.focus();
|
|
116
|
-
setEditing(null);
|
|
117
|
-
setLocalEditing(false);
|
|
118
|
-
onSave?.();
|
|
119
|
-
},
|
|
120
|
-
[fieldProjection.field.path, onSave, contextEditing, originalRow],
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (contextEditing && contextEditing.cellElement) {
|
|
125
|
-
anchorRef.current = (contextEditing.cellElement as HTMLElement).querySelector(
|
|
126
|
-
'.dx-grid__cell__content',
|
|
127
|
-
) as HTMLButtonElement;
|
|
128
|
-
setLocalEditing(true);
|
|
129
|
-
} else {
|
|
130
|
-
anchorRef.current = null;
|
|
131
|
-
setLocalEditing(false);
|
|
132
|
-
}
|
|
133
|
-
}, [contextEditing]);
|
|
134
|
-
|
|
135
|
-
const narrowedSchema = useMemo(() => {
|
|
136
|
-
if (!schema) {
|
|
137
|
-
return undefined;
|
|
138
|
-
}
|
|
139
|
-
return narrowSchema(schema, [fieldProjection.field.path]);
|
|
140
|
-
}, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
|
|
141
|
-
|
|
142
109
|
// NOTE: Important to get a snapshot to eject from the live object.
|
|
143
110
|
const formValues = useMemo(() => (originalRow ? getSnapshot(originalRow) : {}), [originalRow]);
|
|
144
111
|
|
|
@@ -150,22 +117,21 @@ export const FormCellEditor = ({
|
|
|
150
117
|
setLocalEditing(nextOpen);
|
|
151
118
|
}, []);
|
|
152
119
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}, [refSchema]);
|
|
120
|
+
const handleSave = useCallback<NonNullable<FormRootProps<any>['onSave']>>(
|
|
121
|
+
(values) => {
|
|
122
|
+
const path = fieldProjection.field.path;
|
|
123
|
+
const value = getDeep(values, [path]);
|
|
124
|
+
setDeep(originalRow, [path], value);
|
|
125
|
+
contextEditing?.cellElement?.focus();
|
|
126
|
+
setEditing(null);
|
|
127
|
+
setLocalEditing(false);
|
|
128
|
+
onSave?.();
|
|
129
|
+
},
|
|
130
|
+
[fieldProjection.field.path, onSave, contextEditing, originalRow],
|
|
131
|
+
);
|
|
166
132
|
|
|
167
|
-
const handleCreate = useCallback(
|
|
168
|
-
(values
|
|
133
|
+
const handleCreate = useCallback<NonNullable<FormRootProps<any>['onCreate']>>(
|
|
134
|
+
(values) => {
|
|
169
135
|
if (refSchema && onCreate) {
|
|
170
136
|
const objectWithId = onCreate(refSchema, values);
|
|
171
137
|
if (objectWithId) {
|
|
@@ -182,6 +148,26 @@ export const FormCellEditor = ({
|
|
|
182
148
|
[fieldProjection.field.path, onSave, contextEditing, originalRow, refSchema, onCreate],
|
|
183
149
|
);
|
|
184
150
|
|
|
151
|
+
const handleQueryRefOptions = useCallback<NonNullable<FormRootProps<any>['onQueryRefOptions']>>(
|
|
152
|
+
async ({ typename }) => {
|
|
153
|
+
const { schema, space } = getSchema({ typename });
|
|
154
|
+
if (model && schema && space) {
|
|
155
|
+
const objects = await space.db.query(Filter.type(schema)).run();
|
|
156
|
+
return objects
|
|
157
|
+
.map((obj) => {
|
|
158
|
+
return {
|
|
159
|
+
dxn: Obj.getDXN(obj),
|
|
160
|
+
label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
|
|
161
|
+
};
|
|
162
|
+
})
|
|
163
|
+
.filter(isTruthy);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return [];
|
|
167
|
+
},
|
|
168
|
+
[model],
|
|
169
|
+
);
|
|
170
|
+
|
|
185
171
|
if (!editing) {
|
|
186
172
|
return null;
|
|
187
173
|
}
|
|
@@ -193,21 +179,28 @@ export const FormCellEditor = ({
|
|
|
193
179
|
<Popover.Content tabIndex={-1} classNames='popover-card-width density-fine'>
|
|
194
180
|
<Popover.Arrow />
|
|
195
181
|
<Popover.Viewport>
|
|
196
|
-
<Form
|
|
182
|
+
<Form.Root
|
|
183
|
+
{...formProps}
|
|
197
184
|
autoFocus
|
|
185
|
+
schema={narrowedSchema}
|
|
198
186
|
values={formValues}
|
|
199
|
-
schema={narrowedSchema as any}
|
|
200
187
|
onSave={handleSave}
|
|
201
|
-
{...formProps}
|
|
202
188
|
onQueryRefOptions={handleQueryRefOptions}
|
|
203
189
|
{...(createSchema && {
|
|
204
|
-
onCreate: handleCreate,
|
|
205
190
|
createSchema,
|
|
206
191
|
createInitialValuePath: fieldProjection.field.referencePath,
|
|
207
192
|
createOptionIcon: 'ph--plus--regular',
|
|
208
193
|
createOptionLabel,
|
|
194
|
+
onCreate: handleCreate,
|
|
209
195
|
})}
|
|
210
|
-
|
|
196
|
+
>
|
|
197
|
+
<Form.Viewport>
|
|
198
|
+
<Form.Content>
|
|
199
|
+
<Form.FieldSet />
|
|
200
|
+
<Form.Actions />
|
|
201
|
+
</Form.Content>
|
|
202
|
+
</Form.Viewport>
|
|
203
|
+
</Form.Root>
|
|
211
204
|
</Popover.Viewport>
|
|
212
205
|
</Popover.Content>
|
|
213
206
|
</Popover.Portal>
|
|
@@ -8,7 +8,7 @@ import React from 'react';
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
9
|
import { faker } from '@dxos/random';
|
|
10
10
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
11
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
12
12
|
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
13
13
|
import { View } from '@dxos/schema';
|
|
14
14
|
import { Task } from '@dxos/types';
|
|
@@ -23,15 +23,16 @@ type StoryProps = {
|
|
|
23
23
|
editing: GridEditing;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
// TODO(burdon): Broken layout.
|
|
26
27
|
const DefaultStory = ({ editing }: StoryProps) => {
|
|
27
|
-
const { model,
|
|
28
|
+
const { model, table } = useTestTableModel();
|
|
28
29
|
|
|
29
|
-
if (!model || !
|
|
30
|
+
if (!model || !table) {
|
|
30
31
|
return <div />;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
return (
|
|
34
|
-
<div className='
|
|
35
|
+
<div className='border border-separator' style={{ height: defaultRowSize }}>
|
|
35
36
|
<Grid.Root id='test' editing={editing}>
|
|
36
37
|
<TableCellEditor model={model} schema={Task.Task} />
|
|
37
38
|
</Grid.Root>
|
|
@@ -45,13 +46,15 @@ const meta = {
|
|
|
45
46
|
render: DefaultStory,
|
|
46
47
|
decorators: [
|
|
47
48
|
withTheme,
|
|
49
|
+
withLayout({ container: 'column' }),
|
|
48
50
|
withClientProvider({
|
|
49
51
|
types: [View.View, Task.Task, Table.Table],
|
|
50
52
|
createIdentity: true,
|
|
51
53
|
createSpace: true,
|
|
52
|
-
onCreateSpace: async ({
|
|
53
|
-
const { view } = await
|
|
54
|
-
|
|
54
|
+
onCreateSpace: async ({ space }) => {
|
|
55
|
+
const { view, jsonSchema } = await View.makeFromSpace({ space, typename: Task.Task.typename });
|
|
56
|
+
const table = Table.make({ view, jsonSchema });
|
|
57
|
+
space.db.add(table);
|
|
55
58
|
Array.from({ length: 10 }).forEach(() => {
|
|
56
59
|
space.db.add(
|
|
57
60
|
Obj.make(Task.Task, {
|
|
@@ -65,8 +68,8 @@ const meta = {
|
|
|
65
68
|
}),
|
|
66
69
|
],
|
|
67
70
|
parameters: {
|
|
71
|
+
layout: 'fullscreen',
|
|
68
72
|
translations,
|
|
69
|
-
layout: 'centered',
|
|
70
73
|
},
|
|
71
74
|
} satisfies Meta<typeof DefaultStory>;
|
|
72
75
|
|
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
6
|
import { EditorView } from '@codemirror/view';
|
|
7
|
-
import type * as Schema from 'effect/Schema';
|
|
8
7
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
9
8
|
|
|
10
9
|
import { debounce } from '@dxos/async';
|
|
11
|
-
import type
|
|
12
|
-
import {
|
|
10
|
+
import { type Type } from '@dxos/echo';
|
|
11
|
+
import { Format, TypeEnum } from '@dxos/echo/internal';
|
|
13
12
|
import { invariant } from '@dxos/invariant';
|
|
14
13
|
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
15
14
|
import {
|
|
@@ -30,34 +29,37 @@ import { type ModalController, type TableModel } from '../../model';
|
|
|
30
29
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
31
30
|
import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
|
|
32
31
|
|
|
32
|
+
const editorSlots = {
|
|
33
|
+
scroll: {
|
|
34
|
+
className: '!plb-[--dx-grid-cell-editor-padding-block]',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
33
38
|
/**
|
|
34
39
|
* Option to create new object/value.
|
|
35
40
|
*/
|
|
36
|
-
export type QueryResult = Pick<Completion, 'label'> & {
|
|
41
|
+
export type QueryResult = Pick<Completion, 'label'> & {
|
|
42
|
+
data: any;
|
|
43
|
+
};
|
|
37
44
|
|
|
38
|
-
export type TableCellEditorProps = {
|
|
45
|
+
export type TableCellEditorProps<T extends Type.Entity.Any = Type.Entity.Any> = {
|
|
46
|
+
schema?: T;
|
|
39
47
|
model?: TableModel;
|
|
40
48
|
modals?: ModalController;
|
|
41
|
-
schema?: Schema.Schema.AnyNoContext;
|
|
42
49
|
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
43
50
|
onCreate?: OnCreateHandler;
|
|
44
51
|
onSave?: () => void;
|
|
45
|
-
client?: Client;
|
|
46
52
|
};
|
|
47
53
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
export const TableValueEditor = ({
|
|
54
|
+
export const TableValueEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
55
|
+
__gridScope,
|
|
56
|
+
schema,
|
|
52
57
|
model,
|
|
53
58
|
modals,
|
|
54
|
-
schema,
|
|
55
59
|
onFocus,
|
|
56
60
|
onSave,
|
|
57
61
|
onCreate,
|
|
58
|
-
|
|
59
|
-
__gridScope,
|
|
60
|
-
}: GridScopedProps<TableCellEditorProps>) => {
|
|
62
|
+
}: GridScopedProps<TableCellEditorProps<T>>) => {
|
|
61
63
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
62
64
|
|
|
63
65
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
@@ -74,36 +76,33 @@ export const TableValueEditor = ({
|
|
|
74
76
|
|
|
75
77
|
if (
|
|
76
78
|
fieldProjection?.props.type === TypeEnum.Array ||
|
|
77
|
-
fieldProjection?.props.format ===
|
|
78
|
-
fieldProjection?.props.format ===
|
|
79
|
-
// TODO(thure): Support `
|
|
79
|
+
fieldProjection?.props.format === Format.TypeFormat.SingleSelect ||
|
|
80
|
+
fieldProjection?.props.format === Format.TypeFormat.Ref
|
|
81
|
+
// TODO(thure): Support `Format.TypeFormat.MultiSelect`
|
|
80
82
|
) {
|
|
81
83
|
return (
|
|
82
|
-
<FormCellEditor
|
|
83
|
-
fieldProjection={fieldProjection}
|
|
84
|
-
model={model}
|
|
85
|
-
schema={schema}
|
|
84
|
+
<FormCellEditor<T>
|
|
86
85
|
__gridScope={__gridScope}
|
|
86
|
+
schema={schema}
|
|
87
|
+
model={model}
|
|
88
|
+
fieldProjection={fieldProjection}
|
|
89
|
+
modals={modals}
|
|
87
90
|
onSave={onSave}
|
|
88
91
|
onCreate={onCreate}
|
|
89
|
-
client={client}
|
|
90
|
-
modals={modals}
|
|
91
92
|
/>
|
|
92
93
|
);
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
// For all other types, use the existing cell editor
|
|
96
|
+
// For all other types, use the existing cell editor.
|
|
96
97
|
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
97
98
|
};
|
|
98
99
|
|
|
99
|
-
const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
100
|
-
|
|
101
100
|
export const TableCellEditor = ({
|
|
101
|
+
__gridScope,
|
|
102
102
|
model,
|
|
103
103
|
modals,
|
|
104
104
|
onFocus,
|
|
105
105
|
onSave,
|
|
106
|
-
__gridScope,
|
|
107
106
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
108
107
|
const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
109
108
|
const suppressNextBlur = useRef(false);
|
|
@@ -122,6 +121,7 @@ export const TableCellEditor = ({
|
|
|
122
121
|
return fieldProjection;
|
|
123
122
|
}, [model, editing]);
|
|
124
123
|
|
|
124
|
+
// TOOD(burdon): Attach to event handler?
|
|
125
125
|
const handleEnter = useCallback(
|
|
126
126
|
async (value: any) => {
|
|
127
127
|
if (!model || !editing) {
|
|
@@ -138,7 +138,7 @@ export const TableCellEditor = ({
|
|
|
138
138
|
setEditing(null);
|
|
139
139
|
onSave?.();
|
|
140
140
|
} else {
|
|
141
|
-
setValidationError(
|
|
141
|
+
setValidationError(validationResult.error);
|
|
142
142
|
setValidationVariant('error');
|
|
143
143
|
}
|
|
144
144
|
},
|
|
@@ -58,7 +58,7 @@ const createTableToolbarActions = ({
|
|
|
58
58
|
const add = createMenuAction<TableToolbarActionProperties>('add-row', onAdd, {
|
|
59
59
|
type: 'add-row' as const,
|
|
60
60
|
icon: 'ph--plus--regular',
|
|
61
|
-
label: ['add row', { ns: translationKey }],
|
|
61
|
+
label: ['add row label', { ns: translationKey }],
|
|
62
62
|
testId: 'table.toolbar.add-row',
|
|
63
63
|
});
|
|
64
64
|
nodes.push(add);
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Format } from '@dxos/echo';
|
|
9
|
+
import { TypeEnum, TypedObject, setValue } from '@dxos/echo/internal';
|
|
9
10
|
import { live } from '@dxos/live-object';
|
|
10
11
|
import { faker } from '@dxos/random';
|
|
11
12
|
import { type ProjectionModel } from '@dxos/schema';
|
|
12
13
|
|
|
13
14
|
export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
14
|
-
id: ObjectId,
|
|
15
15
|
name: Schema.optional(Schema.String),
|
|
16
16
|
age: Schema.optional(Schema.Number),
|
|
17
17
|
active: Schema.optional(Schema.Boolean),
|
|
@@ -88,7 +88,7 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
|
|
|
88
88
|
|
|
89
89
|
if (format) {
|
|
90
90
|
switch (format) {
|
|
91
|
-
case
|
|
91
|
+
case Format.TypeFormat.Currency: {
|
|
92
92
|
item[path] = Math.floor(Math.random() * 1000);
|
|
93
93
|
break;
|
|
94
94
|
}
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Type } from '@dxos/echo';
|
|
8
|
+
import { ProjectionModel } from '@dxos/schema';
|
|
9
|
+
|
|
10
|
+
import { type Table } from '../types';
|
|
11
|
+
|
|
12
|
+
export const useProjectionModel = <S extends Type.Entity.Any>(schema: S | undefined, table: Table.Table | undefined) =>
|
|
13
|
+
useMemo(() => {
|
|
14
|
+
if (schema && table?.view.target?.projection) {
|
|
15
|
+
const projection = new ProjectionModel(Type.toJsonSchema(schema), table.view.target.projection);
|
|
16
|
+
projection.normalizeView();
|
|
17
|
+
return projection;
|
|
18
|
+
}
|
|
19
|
+
}, [schema, table?.view.target?.projection]);
|
|
@@ -7,17 +7,16 @@ import orderBy from 'lodash.orderby';
|
|
|
7
7
|
import { useEffect, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
9
|
import { Obj } from '@dxos/echo';
|
|
10
|
-
import { type JsonSchemaType } from '@dxos/echo/internal';
|
|
11
10
|
import { type Live } from '@dxos/live-object';
|
|
12
11
|
import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
13
|
-
import { type ProjectionModel
|
|
12
|
+
import { type ProjectionModel } from '@dxos/schema';
|
|
14
13
|
import { isNonNullable } from '@dxos/util';
|
|
15
14
|
|
|
16
15
|
import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
|
|
16
|
+
import { type Table } from '../types';
|
|
17
17
|
|
|
18
18
|
export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
19
|
-
|
|
20
|
-
schema?: JsonSchemaType;
|
|
19
|
+
object?: Table.Table;
|
|
21
20
|
projection?: ProjectionModel;
|
|
22
21
|
rows?: Live<T>[];
|
|
23
22
|
rowActions?: TableRowAction[];
|
|
@@ -29,8 +28,7 @@ export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
|
29
28
|
>;
|
|
30
29
|
|
|
31
30
|
export const useTableModel = <T extends TableRow = TableRow>({
|
|
32
|
-
|
|
33
|
-
schema,
|
|
31
|
+
object,
|
|
34
32
|
projection,
|
|
35
33
|
rows,
|
|
36
34
|
rowActions,
|
|
@@ -39,20 +37,19 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
39
37
|
onRowAction,
|
|
40
38
|
...props
|
|
41
39
|
}: UseTableModelParams<T>): TableModel<T> | undefined => {
|
|
42
|
-
const selected = useSelected(
|
|
43
|
-
const initialSelection = useMemo(() => selected, [
|
|
40
|
+
const selected = useSelected(object && Obj.getDXN(object).toString(), 'multi');
|
|
41
|
+
const initialSelection = useMemo(() => selected, [object]);
|
|
44
42
|
|
|
45
43
|
const [model, setModel] = useState<TableModel<T>>();
|
|
46
44
|
useEffect(() => {
|
|
47
|
-
if (!
|
|
45
|
+
if (!object || !projection) {
|
|
48
46
|
return;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
let model: TableModel<T> | undefined;
|
|
52
50
|
const t = setTimeout(async () => {
|
|
53
51
|
model = new TableModel<T>({
|
|
54
|
-
|
|
55
|
-
schema,
|
|
52
|
+
object,
|
|
56
53
|
projection,
|
|
57
54
|
features,
|
|
58
55
|
rowActions,
|
|
@@ -68,16 +65,17 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
68
65
|
clearTimeout(t);
|
|
69
66
|
void model?.close();
|
|
70
67
|
};
|
|
71
|
-
|
|
68
|
+
// TODO(burdon): Trigger if callbacks change?
|
|
69
|
+
}, [object, projection, features, rowActions, initialSelection]);
|
|
72
70
|
|
|
73
71
|
// Update data.
|
|
74
72
|
useEffect(() => {
|
|
75
73
|
if (rows) {
|
|
76
|
-
// TODO(ZaymonFC): Remove this workaround once unstable query ordering issue is resolved
|
|
77
74
|
/*
|
|
78
75
|
* Sort all objects by string id field as a temporary workaround for query ordering issues
|
|
79
76
|
* Reference: https://github.com/dxos/dxos/pull/9409
|
|
80
77
|
*/
|
|
78
|
+
// TODO(ZaymonFC): Remove this workaround once unstable query ordering issue is resolved
|
|
81
79
|
const sortedRows = orderBy(rows, [(row) => String(row.id)], ['asc']);
|
|
82
80
|
model?.setRows(sortedRows);
|
|
83
81
|
}
|
|
@@ -12,7 +12,7 @@ import { updateCounter } from '@dxos/echo/testing';
|
|
|
12
12
|
import { createEchoSchema } from '@dxos/echo/testing';
|
|
13
13
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
14
14
|
import { live } from '@dxos/live-object';
|
|
15
|
-
import { View } from '@dxos/schema';
|
|
15
|
+
import { ProjectionModel, View } from '@dxos/schema';
|
|
16
16
|
|
|
17
17
|
import { Table } from '../types';
|
|
18
18
|
|
|
@@ -112,11 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
112
112
|
|
|
113
113
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
114
114
|
const schema = createEchoSchema(Test);
|
|
115
|
-
const table = Table.make();
|
|
116
115
|
const view = View.make({
|
|
117
116
|
query: Query.select(Filter.type(schema)),
|
|
118
117
|
jsonSchema: schema.jsonSchema,
|
|
119
|
-
presentation: table,
|
|
120
118
|
});
|
|
121
|
-
|
|
119
|
+
const object = Table.make({ view });
|
|
120
|
+
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
121
|
+
return new TableModel({ object, projection, ...props });
|
|
122
122
|
};
|