@dxos/react-ui-table 0.8.4-main.3c1ae3b → 0.8.4-main.3fbcb4aa9b
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 → chunk-FBURZBPH.mjs} +19 -26
- package/dist/lib/browser/chunk-FBURZBPH.mjs.map +7 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/browser/{chunk-V7A7ZA5E.mjs → chunk-YGC7LZJN.mjs} +611 -699
- package/dist/lib/browser/chunk-YGC7LZJN.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1105 -1032
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +23 -18
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/translations.mjs +36 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +2 -1
- package/dist/lib/node-esm/{chunk-T6PXZQ6W.mjs → chunk-DKKMSVKP.mjs} +19 -26
- package/dist/lib/node-esm/chunk-DKKMSVKP.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-P73ALX3N.mjs → chunk-GKIWDEZ7.mjs} +611 -698
- package/dist/lib/node-esm/chunk-GKIWDEZ7.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1105 -1032
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +23 -18
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/translations.mjs +37 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +2 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +5 -4
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +21 -23
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +84 -84
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +20 -21
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +86 -86
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts +17 -0
- package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +3 -4
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +2 -2
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +23 -25
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +22 -13
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +5 -6
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +2 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +6 -5
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +5 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +18 -9
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +100 -18
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +3 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +2 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +4 -5
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +21 -23
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +13 -15
- package/dist/types/src/types/Table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +11 -8
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
- package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts +7 -0
- package/dist/types/src/util/query-ast.d.ts.map +1 -0
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/src/util/signals.d.ts.map +1 -1
- package/dist/types/src/util/sort.d.ts +12 -0
- package/dist/types/src/util/sort.d.ts.map +1 -0
- package/dist/types/src/util/table-buttons.d.ts +20 -20
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts +4 -4
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/src/util/tag.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +66 -76
- package/src/components/Table/ColumnActionsMenu.tsx +13 -11
- package/src/components/Table/ColumnSettings.tsx +7 -7
- package/src/components/Table/DynamicTable.stories.tsx +24 -22
- package/src/components/Table/DynamicTable.tsx +28 -36
- package/src/components/Table/Relations.stories.tsx +95 -47
- package/src/components/Table/RowActionsMenu.tsx +6 -4
- package/src/components/Table/Table.stories.tsx +87 -64
- package/src/components/Table/Table.tsx +42 -419
- package/src/components/Table/TableContent.tsx +421 -0
- package/src/components/{TableToolbar → Table}/TableToolbar.tsx +44 -45
- package/src/components/TableCellEditor/CellValidationMessage.tsx +4 -5
- package/src/components/TableCellEditor/FormCellEditor.tsx +61 -70
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +14 -13
- package/src/components/TableCellEditor/TableCellEditor.tsx +14 -12
- package/src/components/index.ts +0 -1
- package/src/components/testing.ts +42 -16
- package/src/hooks/useAddRow.ts +8 -10
- package/src/hooks/useProjectionModel.ts +29 -9
- package/src/hooks/useTableModel.ts +27 -23
- package/src/index.ts +0 -4
- package/src/model/modal-controller.ts +23 -12
- package/src/model/selection-model.ts +75 -44
- package/src/model/table-model.test.ts +81 -54
- package/src/model/table-model.ts +411 -139
- package/src/model/table-presentation.test.ts +58 -49
- package/src/model/table-presentation.ts +34 -29
- package/src/playwright/TableManager.ts +10 -3
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +51 -7
- package/src/testing/useTestTableModel.ts +25 -22
- package/src/translations.ts +20 -22
- package/src/types/Table.ts +19 -30
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +36 -17
- package/src/util/index.ts +2 -0
- package/src/util/query-ast.ts +22 -0
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +1 -1
- package/src/util/table-controls.ts +2 -2
- package/dist/lib/browser/chunk-CJQ6T4LL.mjs.map +0 -7
- package/dist/lib/browser/chunk-V7A7ZA5E.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-P73ALX3N.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs.map +0 -7
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
- package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
- package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
- package/dist/types/src/model/table-sorting.d.ts +0 -56
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/src/components/TableToolbar/index.ts +0 -5
- package/src/model/table-sorting.ts +0 -234
|
@@ -5,26 +5,25 @@
|
|
|
5
5
|
import type * as Schema from 'effect/Schema';
|
|
6
6
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { Entity, type Type } from '@dxos/echo';
|
|
9
|
+
import { Ref, getValue } from '@dxos/echo/internal';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { getSnapshot } from '@dxos/live-object';
|
|
12
|
-
import { getSpace } from '@dxos/react-client/echo';
|
|
13
11
|
import { type Label, Popover } from '@dxos/react-ui';
|
|
14
|
-
import { Form, type FormRootProps,
|
|
12
|
+
import { Form, type FormRootProps, type RefFieldProps } from '@dxos/react-ui-form';
|
|
15
13
|
import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
|
|
16
14
|
import { type FieldProjection } from '@dxos/schema';
|
|
17
15
|
import { getDeep, isTruthy, setDeep } from '@dxos/util';
|
|
18
16
|
|
|
17
|
+
import { translationKey } from '#translations';
|
|
18
|
+
|
|
19
19
|
import { type ModalController, type TableModel, type TableRow } from '../../model';
|
|
20
|
-
import { translationKey } from '../../translations';
|
|
21
20
|
import { narrowSchema } from '../../util';
|
|
22
21
|
|
|
23
|
-
const createOptionLabel: Label = ['create
|
|
22
|
+
const createOptionLabel: Label = ['create-new-object.label', { ns: translationKey }];
|
|
24
23
|
|
|
25
|
-
export type OnCreateHandler = (schema:
|
|
24
|
+
export type OnCreateHandler = (schema: Type.AnyEntity, values: any) => Parameters<typeof Ref.make>[0];
|
|
26
25
|
|
|
27
|
-
export type FormCellEditorProps<T extends Type.
|
|
26
|
+
export type FormCellEditorProps<T extends Type.AnyEntity = Type.AnyEntity> = {
|
|
28
27
|
__gridScope: any;
|
|
29
28
|
schema?: T;
|
|
30
29
|
model?: TableModel;
|
|
@@ -34,7 +33,7 @@ export type FormCellEditorProps<T extends Type.Entity.Any = Type.Entity.Any> = {
|
|
|
34
33
|
onCreate?: OnCreateHandler;
|
|
35
34
|
} & Omit<FormRootProps<any>, 'values' | 'schema' | 'onCreate'>;
|
|
36
35
|
|
|
37
|
-
export const FormCellEditor = <T extends Type.
|
|
36
|
+
export const FormCellEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
38
37
|
__gridScope,
|
|
39
38
|
schema,
|
|
40
39
|
model,
|
|
@@ -67,33 +66,12 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
|
67
66
|
return narrowSchema(schema, [fieldProjection.field.path]);
|
|
68
67
|
}, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
|
|
69
68
|
|
|
70
|
-
const getSchema = useCallback(
|
|
71
|
-
({ typename }: { typename: string }) => {
|
|
72
|
-
const space = getSpace(model!.view);
|
|
73
|
-
invariant(space);
|
|
74
|
-
const schema = space.db.schemaRegistry.query({ typename, location: ['database', 'runtime'] }).runSync()[0];
|
|
75
|
-
return { space, schema };
|
|
76
|
-
},
|
|
77
|
-
[model],
|
|
78
|
-
);
|
|
79
|
-
|
|
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
|
-
|
|
86
|
-
return null;
|
|
87
|
-
}, [fieldProjection.props.format, fieldProjection.props.referenceSchema, getSchema]);
|
|
88
|
-
|
|
89
|
-
const createSchema = useMemo(() => (refSchema ? omitId(refSchema) : null), [refSchema]);
|
|
90
|
-
|
|
91
69
|
const originalRow = useMemo<TableRow | undefined>(() => {
|
|
92
70
|
if (model && contextEditing) {
|
|
93
71
|
// Check if this is a draft cell and get the appropriate row data
|
|
94
72
|
const cell = parseCellIndex(contextEditing.index);
|
|
95
73
|
if (model.isDraftCell(cell)) {
|
|
96
|
-
const draftRow = model.
|
|
74
|
+
const draftRow = model.getDraftRows()[cell.row];
|
|
97
75
|
invariant(draftRow);
|
|
98
76
|
return draftRow.data;
|
|
99
77
|
} else {
|
|
@@ -106,9 +84,14 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
|
106
84
|
return undefined;
|
|
107
85
|
}, [model, contextEditing]);
|
|
108
86
|
|
|
109
|
-
// NOTE: Important to get a
|
|
110
|
-
|
|
87
|
+
// NOTE: Important to get a mutable deep clone to eject from the echo object.
|
|
88
|
+
// TODO(wittjosiah): Consider using something like Obj.clone for this use case.
|
|
89
|
+
const initialFormValues = useMemo(() => (originalRow ? JSON.parse(JSON.stringify(originalRow)) : {}), [originalRow]);
|
|
90
|
+
const [formValues, setFormValues] = useState<any>(initialFormValues);
|
|
111
91
|
|
|
92
|
+
const handleValuesChanged = useCallback<NonNullable<FormRootProps<any>['onValuesChanged']>>((values) => {
|
|
93
|
+
setFormValues(values);
|
|
94
|
+
}, []);
|
|
112
95
|
const handleOpenChange = useCallback((nextOpen: boolean) => {
|
|
113
96
|
if (nextOpen === false) {
|
|
114
97
|
setEditing(null);
|
|
@@ -119,24 +102,39 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
|
119
102
|
|
|
120
103
|
const handleSave = useCallback<NonNullable<FormRootProps<any>['onSave']>>(
|
|
121
104
|
(values) => {
|
|
105
|
+
if (!originalRow) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
122
108
|
const path = fieldProjection.field.path;
|
|
123
109
|
const value = getDeep(values, [path]);
|
|
124
|
-
|
|
110
|
+
// Use model's changeRow for consistent mutation handling.
|
|
111
|
+
if (model) {
|
|
112
|
+
model.changeRow(originalRow, (mutableRow) => {
|
|
113
|
+
setDeep(mutableRow, [path], value);
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
setDeep(originalRow, [path], value);
|
|
117
|
+
}
|
|
125
118
|
contextEditing?.cellElement?.focus();
|
|
126
119
|
setEditing(null);
|
|
127
120
|
setLocalEditing(false);
|
|
128
121
|
onSave?.();
|
|
129
122
|
},
|
|
130
|
-
[fieldProjection.field.path, onSave, contextEditing, originalRow],
|
|
123
|
+
[fieldProjection.field.path, onSave, contextEditing, originalRow, model],
|
|
131
124
|
);
|
|
132
125
|
|
|
133
126
|
const handleCreate = useCallback<NonNullable<FormRootProps<any>['onCreate']>>(
|
|
134
|
-
(values) => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
(schema, values) => {
|
|
128
|
+
const objectWithId = onCreate?.(schema, values);
|
|
129
|
+
if (objectWithId && originalRow) {
|
|
130
|
+
const ref = Ref.make(objectWithId);
|
|
131
|
+
const path = fieldProjection.field.path;
|
|
132
|
+
// Use model's changeRow for consistent mutation handling.
|
|
133
|
+
if (model) {
|
|
134
|
+
model.changeRow(originalRow, (mutableRow) => {
|
|
135
|
+
setDeep(mutableRow, [path], ref);
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
140
138
|
setDeep(originalRow, [path], ref);
|
|
141
139
|
}
|
|
142
140
|
}
|
|
@@ -145,27 +143,20 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
|
145
143
|
setLocalEditing(false);
|
|
146
144
|
onSave?.();
|
|
147
145
|
},
|
|
148
|
-
[fieldProjection.field.path, onSave, contextEditing, originalRow,
|
|
146
|
+
[fieldProjection.field.path, onSave, contextEditing, originalRow, onCreate, model],
|
|
149
147
|
);
|
|
150
148
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
})
|
|
163
|
-
.filter(isTruthy);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return [];
|
|
167
|
-
},
|
|
168
|
-
[model],
|
|
149
|
+
const getOptions = useCallback<NonNullable<RefFieldProps['getOptions']>>(
|
|
150
|
+
(results) =>
|
|
151
|
+
results
|
|
152
|
+
.map((obj) => {
|
|
153
|
+
return {
|
|
154
|
+
id: Entity.getDXN(obj).toString(),
|
|
155
|
+
label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
|
|
156
|
+
};
|
|
157
|
+
})
|
|
158
|
+
.filter(isTruthy),
|
|
159
|
+
[fieldProjection],
|
|
169
160
|
);
|
|
170
161
|
|
|
171
162
|
if (!editing) {
|
|
@@ -176,7 +167,7 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
|
176
167
|
<Popover.Root open={editing} onOpenChange={handleOpenChange}>
|
|
177
168
|
<Popover.VirtualTrigger virtualRef={anchorRef} />
|
|
178
169
|
<Popover.Portal>
|
|
179
|
-
<Popover.Content tabIndex={-1} classNames='
|
|
170
|
+
<Popover.Content tabIndex={-1} classNames='dx-card-popover-width dx-density-fine'>
|
|
180
171
|
<Popover.Arrow />
|
|
181
172
|
<Popover.Viewport>
|
|
182
173
|
<Form.Root
|
|
@@ -184,15 +175,15 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
|
184
175
|
autoFocus
|
|
185
176
|
schema={narrowedSchema}
|
|
186
177
|
values={formValues}
|
|
178
|
+
onValuesChanged={handleValuesChanged}
|
|
179
|
+
projection={model?.projection}
|
|
180
|
+
createInitialValuePath={fieldProjection.field.referencePath}
|
|
181
|
+
createOptionIcon='ph--plus--regular'
|
|
182
|
+
createOptionLabel={createOptionLabel}
|
|
183
|
+
db={model?.db}
|
|
184
|
+
getOptions={getOptions}
|
|
185
|
+
onCreate={handleCreate}
|
|
187
186
|
onSave={handleSave}
|
|
188
|
-
onQueryRefOptions={handleQueryRefOptions}
|
|
189
|
-
{...(createSchema && {
|
|
190
|
-
createSchema,
|
|
191
|
-
createInitialValuePath: fieldProjection.field.referencePath,
|
|
192
|
-
createOptionIcon: 'ph--plus--regular',
|
|
193
|
-
createOptionLabel,
|
|
194
|
-
onCreate: handleCreate,
|
|
195
|
-
})}
|
|
196
187
|
>
|
|
197
188
|
<Form.Viewport>
|
|
198
189
|
<Form.Content>
|
|
@@ -6,25 +6,26 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import {
|
|
9
|
+
import { View } from '@dxos/echo';
|
|
10
|
+
import { random } from '@dxos/random';
|
|
10
11
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
11
|
-
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
12
12
|
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
13
|
-
import {
|
|
13
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
14
|
+
import { ViewModel } from '@dxos/schema';
|
|
14
15
|
import { Task } from '@dxos/types';
|
|
15
16
|
|
|
17
|
+
import { translations } from '#translations';
|
|
18
|
+
|
|
16
19
|
import { useTestTableModel } from '../../testing';
|
|
17
|
-
import { translations } from '../../translations';
|
|
18
20
|
import { Table } from '../../types';
|
|
19
|
-
|
|
20
21
|
import { TableCellEditor } from './TableCellEditor';
|
|
21
22
|
|
|
22
|
-
type
|
|
23
|
+
type DefaultStoryProps = {
|
|
23
24
|
editing: GridEditing;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
// TODO(burdon): Broken layout.
|
|
27
|
-
const DefaultStory = ({ editing }:
|
|
28
|
+
const DefaultStory = ({ editing }: DefaultStoryProps) => {
|
|
28
29
|
const { model, table } = useTestTableModel();
|
|
29
30
|
|
|
30
31
|
if (!model || !table) {
|
|
@@ -45,22 +46,22 @@ const meta = {
|
|
|
45
46
|
component: DefaultStory,
|
|
46
47
|
render: DefaultStory,
|
|
47
48
|
decorators: [
|
|
48
|
-
withTheme,
|
|
49
|
-
withLayout({
|
|
49
|
+
withTheme(),
|
|
50
|
+
withLayout({ layout: 'column' }),
|
|
50
51
|
withClientProvider({
|
|
51
52
|
types: [View.View, Task.Task, Table.Table],
|
|
52
53
|
createIdentity: true,
|
|
53
54
|
createSpace: true,
|
|
54
55
|
onCreateSpace: async ({ space }) => {
|
|
55
|
-
const { view, jsonSchema } = await
|
|
56
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: Task.Task.typename });
|
|
56
57
|
const table = Table.make({ view, jsonSchema });
|
|
57
58
|
space.db.add(table);
|
|
58
59
|
Array.from({ length: 10 }).forEach(() => {
|
|
59
60
|
space.db.add(
|
|
60
61
|
Obj.make(Task.Task, {
|
|
61
|
-
title:
|
|
62
|
-
status:
|
|
63
|
-
description:
|
|
62
|
+
title: random.person.fullName(),
|
|
63
|
+
status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
64
|
+
description: random.lorem.sentence(),
|
|
64
65
|
}),
|
|
65
66
|
);
|
|
66
67
|
});
|
|
@@ -25,16 +25,9 @@ import {
|
|
|
25
25
|
import { type FieldProjection } from '@dxos/schema';
|
|
26
26
|
|
|
27
27
|
import { type ModalController, type TableModel } from '../../model';
|
|
28
|
-
|
|
29
28
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
30
29
|
import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
|
|
31
30
|
|
|
32
|
-
const editorSlots = {
|
|
33
|
-
scroll: {
|
|
34
|
-
className: '!plb-[--dx-grid-cell-editor-padding-block]',
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
|
|
38
31
|
/**
|
|
39
32
|
* Option to create new object/value.
|
|
40
33
|
*/
|
|
@@ -42,7 +35,7 @@ export type QueryResult = Pick<Completion, 'label'> & {
|
|
|
42
35
|
data: any;
|
|
43
36
|
};
|
|
44
37
|
|
|
45
|
-
export type TableCellEditorProps<T extends Type.
|
|
38
|
+
export type TableCellEditorProps<T extends Type.AnyEntity = Type.AnyEntity> = {
|
|
46
39
|
schema?: T;
|
|
47
40
|
model?: TableModel;
|
|
48
41
|
modals?: ModalController;
|
|
@@ -51,7 +44,7 @@ export type TableCellEditorProps<T extends Type.Entity.Any = Type.Entity.Any> =
|
|
|
51
44
|
onSave?: () => void;
|
|
52
45
|
};
|
|
53
46
|
|
|
54
|
-
export const TableValueEditor = <T extends Type.
|
|
47
|
+
export const TableValueEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
55
48
|
__gridScope,
|
|
56
49
|
schema,
|
|
57
50
|
model,
|
|
@@ -68,7 +61,7 @@ export const TableValueEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
|
68
61
|
}
|
|
69
62
|
|
|
70
63
|
const { col } = parseCellIndex(editing.index);
|
|
71
|
-
const field = model.projection.
|
|
64
|
+
const field = model.projection.getFields()[col];
|
|
72
65
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
73
66
|
invariant(fieldProjection);
|
|
74
67
|
return fieldProjection;
|
|
@@ -115,7 +108,7 @@ export const TableCellEditor = ({
|
|
|
115
108
|
}
|
|
116
109
|
|
|
117
110
|
const { col } = parseCellIndex(editing.index);
|
|
118
|
-
const field = model.projection.
|
|
111
|
+
const field = model.projection.getFields()[col];
|
|
119
112
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
120
113
|
invariant(fieldProjection);
|
|
121
114
|
return fieldProjection;
|
|
@@ -250,7 +243,16 @@ export const TableCellEditor = ({
|
|
|
250
243
|
return (
|
|
251
244
|
<>
|
|
252
245
|
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
253
|
-
<GridCellEditor
|
|
246
|
+
<GridCellEditor
|
|
247
|
+
extensions={extensions}
|
|
248
|
+
slots={{
|
|
249
|
+
content: {
|
|
250
|
+
className: '!py-(--dx-grid-cell-editor-padding-block)',
|
|
251
|
+
},
|
|
252
|
+
}}
|
|
253
|
+
getCellContent={getCellContent}
|
|
254
|
+
onBlur={handleBlur}
|
|
255
|
+
/>
|
|
254
256
|
</>
|
|
255
257
|
);
|
|
256
258
|
};
|
package/src/components/index.ts
CHANGED
|
@@ -2,33 +2,59 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
5
6
|
import * as Schema from 'effect/Schema';
|
|
6
7
|
import { useEffect } from 'react';
|
|
7
8
|
|
|
8
|
-
import { Format } from '@dxos/echo';
|
|
9
|
-
import { TypeEnum
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { Format, Type } from '@dxos/echo';
|
|
10
|
+
import { TypeEnum } from '@dxos/echo/internal';
|
|
11
|
+
import { setValue } from '@dxos/effect';
|
|
12
|
+
import { random } from '@dxos/random';
|
|
12
13
|
import { type ProjectionModel } from '@dxos/schema';
|
|
13
14
|
|
|
14
|
-
export const TestSchema =
|
|
15
|
+
export const TestSchema = Schema.Struct({
|
|
15
16
|
name: Schema.optional(Schema.String),
|
|
16
17
|
age: Schema.optional(Schema.Number),
|
|
17
18
|
active: Schema.optional(Schema.Boolean),
|
|
18
19
|
netWorth: Schema.optional(Schema.Number),
|
|
19
|
-
})
|
|
20
|
+
}).pipe(
|
|
21
|
+
Type.object({
|
|
22
|
+
typename: 'com.example.type.test',
|
|
23
|
+
version: '0.1.0',
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
20
26
|
|
|
21
|
-
export
|
|
22
|
-
|
|
27
|
+
export type TestItem = {
|
|
28
|
+
name: string;
|
|
29
|
+
age: number;
|
|
30
|
+
active: boolean;
|
|
31
|
+
netWorth: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates an Atom containing test items data.
|
|
36
|
+
*/
|
|
37
|
+
export const createItemsAtom = (n: number) => {
|
|
38
|
+
return Atom.make<{ data: TestItem[] }>({
|
|
23
39
|
data: Array.from({ length: n }, () => ({
|
|
24
|
-
name:
|
|
25
|
-
age:
|
|
26
|
-
active:
|
|
27
|
-
netWorth:
|
|
40
|
+
name: random.person.fullName(),
|
|
41
|
+
age: random.number.int({ min: 20, max: 70 }),
|
|
42
|
+
active: random.datatype.boolean(),
|
|
43
|
+
netWorth: random.number.int(),
|
|
28
44
|
})),
|
|
29
|
-
});
|
|
45
|
+
}).pipe(Atom.keepAlive);
|
|
46
|
+
};
|
|
30
47
|
|
|
31
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Creates plain test items (non-reactive).
|
|
50
|
+
*/
|
|
51
|
+
export const createItems = (n: number): TestItem[] => {
|
|
52
|
+
return Array.from({ length: n }, () => ({
|
|
53
|
+
name: random.person.fullName(),
|
|
54
|
+
age: random.number.int({ min: 20, max: 70 }),
|
|
55
|
+
active: random.datatype.boolean(),
|
|
56
|
+
netWorth: random.number.int(),
|
|
57
|
+
}));
|
|
32
58
|
};
|
|
33
59
|
|
|
34
60
|
export type SimulatorProps = {
|
|
@@ -59,7 +85,7 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
|
|
|
59
85
|
|
|
60
86
|
const i = setInterval(() => {
|
|
61
87
|
const rowIdx = Math.floor(Math.random() * items.length);
|
|
62
|
-
const fields = projection.
|
|
88
|
+
const fields = projection.getFields() ?? [];
|
|
63
89
|
const columnIdx = Math.floor(Math.random() * fields.length);
|
|
64
90
|
const field = fields[columnIdx];
|
|
65
91
|
const item = items[rowIdx];
|
|
@@ -98,5 +124,5 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
|
|
|
98
124
|
}, updateInterval);
|
|
99
125
|
|
|
100
126
|
return () => clearInterval(i);
|
|
101
|
-
}, [items, projection.
|
|
127
|
+
}, [items, projection.getFields(), updateInterval]);
|
|
102
128
|
};
|
package/src/hooks/useAddRow.ts
CHANGED
|
@@ -2,17 +2,15 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import type * as Schema from 'effect/Schema';
|
|
6
5
|
import { useCallback } from 'react';
|
|
7
6
|
|
|
8
|
-
import { type
|
|
9
|
-
import { Obj } from '@dxos/echo';
|
|
7
|
+
import { type Database, Obj, type Type } from '@dxos/echo';
|
|
10
8
|
|
|
11
9
|
import { type InsertRowResult } from '../model';
|
|
12
10
|
|
|
13
|
-
export type
|
|
14
|
-
|
|
15
|
-
schema?:
|
|
11
|
+
export type UseAddRowProps = {
|
|
12
|
+
db?: Database.Database;
|
|
13
|
+
schema?: Type.AnyObj;
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
/**
|
|
@@ -20,12 +18,12 @@ export type UseAddRowParams = {
|
|
|
20
18
|
* Returns a callback that attempts to create an object and returns boolean success/failure.
|
|
21
19
|
* Can accept optional data to create objects with specific content (used for committing draft rows).
|
|
22
20
|
*/
|
|
23
|
-
export const useAddRow = ({
|
|
21
|
+
export const useAddRow = ({ db, schema }: UseAddRowProps) => {
|
|
24
22
|
return useCallback(
|
|
25
23
|
(data?: any): InsertRowResult => {
|
|
26
|
-
if (
|
|
24
|
+
if (db && schema) {
|
|
27
25
|
try {
|
|
28
|
-
|
|
26
|
+
db.add(Obj.make(schema, data ?? {}));
|
|
29
27
|
return 'final';
|
|
30
28
|
} catch (error) {
|
|
31
29
|
return 'draft';
|
|
@@ -33,6 +31,6 @@ export const useAddRow = ({ space, schema }: UseAddRowParams) => {
|
|
|
33
31
|
}
|
|
34
32
|
return 'final';
|
|
35
33
|
},
|
|
36
|
-
[
|
|
34
|
+
[db, schema],
|
|
37
35
|
);
|
|
38
36
|
};
|
|
@@ -2,18 +2,38 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import { useState } from 'react';
|
|
6
7
|
|
|
7
|
-
import { Type } from '@dxos/echo';
|
|
8
|
-
import {
|
|
8
|
+
import { JsonSchema, Type } from '@dxos/echo';
|
|
9
|
+
import { useAsyncEffect } from '@dxos/react-ui';
|
|
10
|
+
import { ProjectionModel, createEchoChangeCallback } from '@dxos/schema';
|
|
9
11
|
|
|
10
12
|
import { type Table } from '../types';
|
|
11
13
|
|
|
12
|
-
export const useProjectionModel = <S extends Type.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
export const useProjectionModel = <S extends Type.AnyEntity>(
|
|
15
|
+
schema: S | undefined,
|
|
16
|
+
table: Table.Table | undefined,
|
|
17
|
+
registry: Registry.Registry,
|
|
18
|
+
) => {
|
|
19
|
+
const [projection, setProjection] = useState<ProjectionModel | undefined>();
|
|
20
|
+
|
|
21
|
+
useAsyncEffect(async () => {
|
|
22
|
+
if (schema && table) {
|
|
23
|
+
const view = await table.view.load();
|
|
24
|
+
// For mutable schemas (EchoSchema), use the live jsonSchema reference for reactivity.
|
|
25
|
+
// For immutable schemas, create a snapshot.
|
|
26
|
+
const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
|
|
27
|
+
|
|
28
|
+
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
29
|
+
// Pass schema only when mutable to allow schema mutations.
|
|
30
|
+
const change = createEchoChangeCallback(view, Type.isMutable(schema) ? schema : undefined);
|
|
31
|
+
|
|
32
|
+
const projection = new ProjectionModel({ registry, view, baseSchema: jsonSchema, change });
|
|
16
33
|
projection.normalizeView();
|
|
17
|
-
|
|
34
|
+
setProjection(projection);
|
|
18
35
|
}
|
|
19
|
-
}, [schema, table
|
|
36
|
+
}, [schema, table, registry]);
|
|
37
|
+
|
|
38
|
+
return projection;
|
|
39
|
+
};
|
|
@@ -2,41 +2,48 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import { useContext, useEffect, useMemo, useState } from 'react';
|
|
8
7
|
|
|
9
|
-
import { Obj } from '@dxos/echo';
|
|
10
|
-
import { type Live } from '@dxos/live-object';
|
|
8
|
+
import { type Database, Obj } from '@dxos/echo';
|
|
11
9
|
import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
12
10
|
import { type ProjectionModel } from '@dxos/schema';
|
|
13
11
|
import { isNonNullable } from '@dxos/util';
|
|
14
12
|
|
|
15
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
TableModel,
|
|
15
|
+
type TableModelProps,
|
|
16
|
+
type TableRow,
|
|
17
|
+
type TableRowAction,
|
|
18
|
+
createEchoChangeCallback,
|
|
19
|
+
} from '../model';
|
|
16
20
|
import { type Table } from '../types';
|
|
17
21
|
|
|
18
|
-
export type
|
|
22
|
+
export type UseTableModelProps<T extends TableRow = TableRow> = {
|
|
19
23
|
object?: Table.Table;
|
|
20
24
|
projection?: ProjectionModel;
|
|
21
|
-
|
|
25
|
+
db?: Database.Database;
|
|
26
|
+
rows?: T[];
|
|
22
27
|
rowActions?: TableRowAction[];
|
|
23
28
|
onSelectionChanged?: (selection: string[]) => void;
|
|
24
29
|
onRowAction?: (actionId: string, data: T) => void;
|
|
25
30
|
} & Pick<
|
|
26
31
|
TableModelProps<T>,
|
|
27
|
-
'features' | 'onInsertRow' | 'onDeleteRows' | '
|
|
32
|
+
'features' | 'onInsertRow' | 'onDeleteRows' | 'onColumnDelete' | 'onCellUpdate' | 'onRowOrderChange'
|
|
28
33
|
>;
|
|
29
34
|
|
|
30
35
|
export const useTableModel = <T extends TableRow = TableRow>({
|
|
31
36
|
object,
|
|
32
37
|
projection,
|
|
38
|
+
db,
|
|
33
39
|
rows,
|
|
34
40
|
rowActions,
|
|
35
41
|
features,
|
|
36
42
|
onSelectionChanged,
|
|
37
43
|
onRowAction,
|
|
38
44
|
...props
|
|
39
|
-
}:
|
|
45
|
+
}: UseTableModelProps<T>): TableModel<T> | undefined => {
|
|
46
|
+
const registry = useContext(RegistryContext);
|
|
40
47
|
const selected = useSelected(object && Obj.getDXN(object).toString(), 'multi');
|
|
41
48
|
const initialSelection = useMemo(() => selected, [object]);
|
|
42
49
|
|
|
@@ -49,8 +56,11 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
49
56
|
let model: TableModel<T> | undefined;
|
|
50
57
|
const t = setTimeout(async () => {
|
|
51
58
|
model = new TableModel<T>({
|
|
59
|
+
registry,
|
|
52
60
|
object,
|
|
53
61
|
projection,
|
|
62
|
+
db,
|
|
63
|
+
change: createEchoChangeCallback<T>(object),
|
|
54
64
|
features,
|
|
55
65
|
rowActions,
|
|
56
66
|
initialSelection,
|
|
@@ -66,18 +76,12 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
66
76
|
void model?.close();
|
|
67
77
|
};
|
|
68
78
|
// TODO(burdon): Trigger if callbacks change?
|
|
69
|
-
}, [object, projection, features, rowActions, initialSelection]);
|
|
79
|
+
}, [registry, object, projection, features, rowActions, initialSelection]);
|
|
70
80
|
|
|
71
|
-
// Update data.
|
|
81
|
+
// Update data when rows change.
|
|
72
82
|
useEffect(() => {
|
|
73
|
-
if (rows) {
|
|
74
|
-
|
|
75
|
-
* Sort all objects by string id field as a temporary workaround for query ordering issues
|
|
76
|
-
* Reference: https://github.com/dxos/dxos/pull/9409
|
|
77
|
-
*/
|
|
78
|
-
// TODO(ZaymonFC): Remove this workaround once unstable query ordering issue is resolved
|
|
79
|
-
const sortedRows = orderBy(rows, [(row) => String(row.id)], ['asc']);
|
|
80
|
-
model?.setRows(sortedRows);
|
|
83
|
+
if (rows && model) {
|
|
84
|
+
model.setRows(rows);
|
|
81
85
|
}
|
|
82
86
|
}, [model, rows]);
|
|
83
87
|
|
|
@@ -88,8 +92,8 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
88
92
|
return;
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
const unsubscribe =
|
|
92
|
-
const selectedItems = [...model.selection.selection
|
|
95
|
+
const unsubscribe = registry.subscribe(model.selection.selectionAtom, () => {
|
|
96
|
+
const selectedItems = [...model.selection.selection];
|
|
93
97
|
multiSelect(selectedItems);
|
|
94
98
|
onSelectionChanged?.(selectedItems);
|
|
95
99
|
});
|
|
@@ -99,7 +103,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
99
103
|
clear();
|
|
100
104
|
unsubscribe();
|
|
101
105
|
};
|
|
102
|
-
}, [model, onSelectionChanged]);
|
|
106
|
+
}, [registry, model, onSelectionChanged]);
|
|
103
107
|
|
|
104
108
|
return model;
|
|
105
109
|
};
|