@dxos/react-ui-table 0.8.2-main.f11618f → 0.8.2-main.fbd8ed0
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-YWNRUF4J.mjs → chunk-PFFO5LYM.mjs} +7 -6
- package/dist/lib/browser/chunk-PFFO5LYM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +290 -105
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node/{chunk-TGG6OZ54.cjs → chunk-VREQOYRW.cjs} +9 -8
- package/dist/lib/node/chunk-VREQOYRW.cjs.map +7 -0
- package/dist/lib/node/index.cjs +263 -81
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types/index.cjs +3 -3
- package/dist/lib/node/types/index.cjs.map +1 -1
- package/dist/lib/node-esm/{chunk-KKLP54L3.mjs → chunk-34JG3SDI.mjs} +7 -6
- package/dist/lib/node-esm/chunk-34JG3SDI.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +290 -105
- 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 +1 -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/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +3 -4
- package/dist/types/src/components/Table/DynamicTable.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 +2 -0
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +1 -0
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +13 -0
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -0
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -0
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +39 -39
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/types/table.d.ts +44 -43
- package/dist/types/src/types/table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +4 -3
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts +12 -0
- package/dist/types/src/util/schema.d.ts.map +1 -0
- package/dist/types/src/util/signals.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/src/util/table.d.ts +2 -2
- package/dist/types/src/util/table.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 +34 -33
- package/src/components/Table/CreateRefPanel.tsx +3 -3
- package/src/components/Table/DynamicTable.stories.tsx +2 -2
- package/src/components/Table/DynamicTable.tsx +4 -5
- package/src/components/Table/Table.stories.tsx +65 -19
- package/src/components/Table/Table.tsx +8 -5
- package/src/components/Table/relations.stories.tsx +16 -18
- package/src/components/TableCellEditor/FormCellEditor.tsx +94 -0
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +8 -7
- package/src/components/TableCellEditor/TableCellEditor.tsx +45 -4
- package/src/components/TableToolbar/TableToolbar.tsx +10 -11
- package/src/components/testing.ts +7 -6
- package/src/model/table-model.test.ts +4 -3
- package/src/model/table-presentation.test.ts +4 -3
- package/src/model/table-presentation.ts +52 -6
- package/src/types/table.ts +15 -7
- package/src/util/dynamic-table.ts +7 -4
- package/src/util/index.ts +1 -0
- package/src/util/schema.ts +36 -0
- package/src/util/table.ts +11 -10
- package/dist/lib/browser/chunk-YWNRUF4J.mjs.map +0 -7
- package/dist/lib/node/chunk-TGG6OZ54.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-KKLP54L3.mjs.map +0 -7
|
@@ -5,14 +5,17 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type StoryObj, type Meta } from '@storybook/react';
|
|
8
|
+
import { type Schema } from 'effect';
|
|
9
|
+
import { SchemaAST } from 'effect';
|
|
8
10
|
import React, { useEffect, useMemo } from 'react';
|
|
9
11
|
|
|
10
|
-
import {
|
|
12
|
+
import { type BaseObject, getSchemaTypename, type HasId, toJsonSchema } from '@dxos/echo-schema';
|
|
11
13
|
import { getAnnotation } from '@dxos/effect';
|
|
14
|
+
import { invariant } from '@dxos/invariant';
|
|
12
15
|
import { faker } from '@dxos/random';
|
|
13
16
|
import { live, makeRef } from '@dxos/react-client/echo';
|
|
14
17
|
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
15
|
-
import {
|
|
18
|
+
import { DataType, createView, ViewProjection, ViewType } from '@dxos/schema';
|
|
16
19
|
import { createAsyncGenerator, type ValueGenerator } from '@dxos/schema/testing';
|
|
17
20
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
18
21
|
|
|
@@ -29,14 +32,15 @@ const generator: ValueGenerator = faker as any;
|
|
|
29
32
|
// TODO(burdon): Mutable and immutable views.
|
|
30
33
|
// TODO(burdon): Reconcile schemas types and utils (see API PR).
|
|
31
34
|
// TODO(burdon): Base type for T (with id); see ECHO API PR?
|
|
32
|
-
const useTestModel = <T extends BaseObject & HasId>(schema:
|
|
35
|
+
const useTestModel = <T extends BaseObject & HasId>(schema: Schema.Schema<T>, count: number) => {
|
|
33
36
|
const { space } = useClientProvider();
|
|
34
37
|
|
|
38
|
+
const jsonSchema = useMemo(() => toJsonSchema(schema), [schema]);
|
|
35
39
|
const table = useMemo(() => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const name = getAnnotation<string>(
|
|
39
|
-
const view = createView({ name, typename, jsonSchema
|
|
40
|
+
const typename = getSchemaTypename(schema);
|
|
41
|
+
invariant(typename);
|
|
42
|
+
const name = getAnnotation<string>(SchemaAST.TitleAnnotationId)(schema.ast) ?? typename;
|
|
43
|
+
const view = createView({ name, typename, jsonSchema });
|
|
40
44
|
return live(TableType, { view: makeRef(view) });
|
|
41
45
|
}, [schema]);
|
|
42
46
|
|
|
@@ -46,7 +50,7 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
// TODO(burdon): Just pass in view? Reuse same jsonSchema instance? View determines if mutable, etc.
|
|
49
|
-
return new ViewProjection(
|
|
53
|
+
return new ViewProjection(jsonSchema, table.view.target);
|
|
50
54
|
}, [schema, table]);
|
|
51
55
|
|
|
52
56
|
const features = useMemo<TableFeatures>(
|
|
@@ -66,8 +70,6 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
|
|
|
66
70
|
});
|
|
67
71
|
}, [model, space]);
|
|
68
72
|
|
|
69
|
-
console.log(projection?.getFieldProjections());
|
|
70
|
-
|
|
71
73
|
const presentation = useMemo(() => {
|
|
72
74
|
if (!model) {
|
|
73
75
|
return;
|
|
@@ -80,13 +82,9 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
|
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
const DefaultStory = () => {
|
|
83
|
-
|
|
84
|
-
const orgSchema = useMemo(() => new ImmutableSchema(Organization), []);
|
|
85
|
-
const { model: orgModel, presentation: orgPresentation } = useTestModel<Organization>(orgSchema, 50);
|
|
86
|
-
|
|
85
|
+
const { model: orgModel, presentation: orgPresentation } = useTestModel(DataType.Organization, 50);
|
|
87
86
|
// TODO(burdon): Generate links with references.
|
|
88
|
-
const
|
|
89
|
-
const { model: contactModel, presentation: contactPresentation } = useTestModel<Contact>(contactSchema, 50);
|
|
87
|
+
const { model: contactModel, presentation: contactPresentation } = useTestModel(DataType.Person, 50);
|
|
90
88
|
|
|
91
89
|
return (
|
|
92
90
|
<div className='is-full bs-full grid grid-cols-2 divide-x divide-separator'>
|
|
@@ -106,12 +104,12 @@ const meta: Meta<typeof DefaultStory> = {
|
|
|
106
104
|
parameters: { translations },
|
|
107
105
|
decorators: [
|
|
108
106
|
withClientProvider({
|
|
109
|
-
types: [TableType, ViewType, Organization,
|
|
107
|
+
types: [TableType, ViewType, DataType.Organization, DataType.Person],
|
|
110
108
|
createIdentity: true,
|
|
111
109
|
createSpace: true,
|
|
112
110
|
}),
|
|
113
111
|
withTheme,
|
|
114
|
-
withLayout({ fullscreen: true
|
|
112
|
+
withLayout({ fullscreen: true }),
|
|
115
113
|
],
|
|
116
114
|
};
|
|
117
115
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Schema } from 'effect';
|
|
6
|
+
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
7
|
+
|
|
8
|
+
import { getSnapshot } from '@dxos/echo-schema';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
10
|
+
import { Popover } from '@dxos/react-ui';
|
|
11
|
+
import { Form } from '@dxos/react-ui-form';
|
|
12
|
+
import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
|
|
13
|
+
import { type FieldProjection } from '@dxos/schema';
|
|
14
|
+
import { getDeep, setDeep } from '@dxos/util';
|
|
15
|
+
|
|
16
|
+
import { type TableModel } from '../../model';
|
|
17
|
+
import { narrowSchema } from '../../util';
|
|
18
|
+
|
|
19
|
+
type FormCellEditorProps = {
|
|
20
|
+
fieldProjection: FieldProjection;
|
|
21
|
+
model?: TableModel;
|
|
22
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
23
|
+
__gridScope: any;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }: FormCellEditorProps) => {
|
|
27
|
+
const { editing, setEditing } = useGridContext('ArrayEditor', __gridScope);
|
|
28
|
+
const cellRef = useRef<HTMLButtonElement | null>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (editing && editing.cellElement) {
|
|
32
|
+
cellRef.current = editing.cellElement as HTMLButtonElement;
|
|
33
|
+
}
|
|
34
|
+
}, [editing?.cellElement]);
|
|
35
|
+
|
|
36
|
+
const narrowedSchema = useMemo(() => {
|
|
37
|
+
if (!schema) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return narrowSchema(schema, [fieldProjection.field.path]);
|
|
41
|
+
}, [JSON.stringify(schema), fieldProjection.field.path]);
|
|
42
|
+
|
|
43
|
+
const originalRow = useMemo(() => {
|
|
44
|
+
if (model && editing) {
|
|
45
|
+
const cell = parseCellIndex(editing.index);
|
|
46
|
+
const row = model.getRowAt(cell.row);
|
|
47
|
+
invariant(row);
|
|
48
|
+
|
|
49
|
+
return row;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return undefined;
|
|
53
|
+
}, [model, editing]);
|
|
54
|
+
|
|
55
|
+
const formValues = useMemo(() => {
|
|
56
|
+
if (originalRow) {
|
|
57
|
+
// NOTE(ZaymonFC): Important to get a snapshot to eject from the live object.
|
|
58
|
+
return getSnapshot(originalRow);
|
|
59
|
+
}
|
|
60
|
+
}, [originalRow]);
|
|
61
|
+
|
|
62
|
+
const handleSave = useCallback(
|
|
63
|
+
(values: any) => {
|
|
64
|
+
const path = fieldProjection.field.path;
|
|
65
|
+
const value = getDeep(values, [path]);
|
|
66
|
+
setDeep(originalRow, [path], value);
|
|
67
|
+
setEditing(null);
|
|
68
|
+
},
|
|
69
|
+
[fieldProjection.field.path, originalRow],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleOpenChange = useCallback(
|
|
73
|
+
(nextOpen: boolean) => {
|
|
74
|
+
if (nextOpen === false) {
|
|
75
|
+
setEditing(null);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[setEditing],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!editing) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Popover.Root open={editing !== null} onOpenChange={handleOpenChange}>
|
|
87
|
+
<Popover.VirtualTrigger virtualRef={cellRef} />
|
|
88
|
+
<Popover.Content tabIndex={-1} classNames='popover-consistent-width'>
|
|
89
|
+
<Popover.Arrow />
|
|
90
|
+
<Form values={formValues} schema={narrowedSchema as any} onSave={handleSave} />
|
|
91
|
+
</Popover.Content>
|
|
92
|
+
</Popover.Root>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
@@ -7,12 +7,12 @@ import '@dxos-theme';
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
8
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { type EchoSchema, isMutable } from '@dxos/echo-schema';
|
|
11
11
|
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { faker } from '@dxos/random';
|
|
13
13
|
import { Filter, useQuery, live } from '@dxos/react-client/echo';
|
|
14
14
|
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
15
|
-
import {
|
|
15
|
+
import { defaultRowSize, Grid, type GridEditing } from '@dxos/react-ui-grid';
|
|
16
16
|
import { ViewProjection, ViewType } from '@dxos/schema';
|
|
17
17
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
18
18
|
|
|
@@ -31,7 +31,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
31
31
|
const { space } = useClientProvider();
|
|
32
32
|
invariant(space);
|
|
33
33
|
|
|
34
|
-
const tables = useQuery(space, Filter.
|
|
34
|
+
const tables = useQuery(space, Filter.type(TableType));
|
|
35
35
|
const [table, setTable] = useState<TableType>();
|
|
36
36
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
37
37
|
useEffect(() => {
|
|
@@ -53,9 +53,9 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
53
53
|
() => ({
|
|
54
54
|
selection: { enabled: true, mode: 'multiple' },
|
|
55
55
|
dataEditable: true,
|
|
56
|
-
schemaEditable:
|
|
56
|
+
schemaEditable: schema && isMutable(schema),
|
|
57
57
|
}),
|
|
58
|
-
[],
|
|
58
|
+
[schema],
|
|
59
59
|
);
|
|
60
60
|
|
|
61
61
|
const model = useTableModel({ table, projection, features });
|
|
@@ -76,7 +76,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
return (
|
|
79
|
-
<div className='flex w-[300px] border border-separator' style={{ height:
|
|
79
|
+
<div className='flex w-[300px] border border-separator' style={{ height: defaultRowSize }}>
|
|
80
80
|
<Grid.Root id='test' editing={editing}>
|
|
81
81
|
<TableCellEditor model={model} onQuery={handleQuery} />
|
|
82
82
|
</Grid.Root>
|
|
@@ -107,7 +107,7 @@ const meta: Meta<StoryProps> = {
|
|
|
107
107
|
},
|
|
108
108
|
}),
|
|
109
109
|
withTheme,
|
|
110
|
-
withLayout(
|
|
110
|
+
withLayout(),
|
|
111
111
|
],
|
|
112
112
|
};
|
|
113
113
|
|
|
@@ -120,6 +120,7 @@ export const Default: Story = {
|
|
|
120
120
|
editing: {
|
|
121
121
|
index: 'grid,0,3',
|
|
122
122
|
initialContent: 'Test',
|
|
123
|
+
cellElement: null,
|
|
123
124
|
},
|
|
124
125
|
},
|
|
125
126
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
|
+
import { type Schema } from 'effect/Schema';
|
|
6
7
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
7
8
|
|
|
8
|
-
import { FormatEnum } from '@dxos/echo-schema';
|
|
9
|
+
import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
|
|
9
10
|
import { invariant } from '@dxos/invariant';
|
|
10
11
|
import { type DxGrid } from '@dxos/lit-grid';
|
|
11
12
|
import { useThemeContext } from '@dxos/react-ui';
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
import { tagPickerExtension, createLinks } from '@dxos/react-ui-tag-picker';
|
|
27
28
|
import { type FieldProjection } from '@dxos/schema';
|
|
28
29
|
|
|
30
|
+
import { FormCellEditor } from './FormCellEditor';
|
|
29
31
|
import { completion } from './extension';
|
|
30
32
|
import { type TableModel, type ModalController } from '../../model';
|
|
31
33
|
|
|
@@ -43,11 +45,52 @@ export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
|
43
45
|
export type TableCellEditorProps = {
|
|
44
46
|
model?: TableModel;
|
|
45
47
|
modals?: ModalController;
|
|
48
|
+
schema?: Schema.AnyNoContext;
|
|
46
49
|
onEnter?: (cell: DxGridPlanePosition) => void;
|
|
47
50
|
onFocus?: DxGrid['refocus'];
|
|
48
51
|
onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
|
|
49
52
|
};
|
|
50
53
|
|
|
54
|
+
export const TableValueEditor = ({
|
|
55
|
+
model,
|
|
56
|
+
modals,
|
|
57
|
+
schema,
|
|
58
|
+
onEnter,
|
|
59
|
+
onFocus,
|
|
60
|
+
onQuery,
|
|
61
|
+
__gridScope,
|
|
62
|
+
}: GridScopedProps<TableCellEditorProps>) => {
|
|
63
|
+
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
64
|
+
|
|
65
|
+
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
66
|
+
if (!model || !editing) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { col } = parseCellIndex(editing.index);
|
|
71
|
+
const field = model.projection.view.fields[col];
|
|
72
|
+
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
73
|
+
invariant(fieldProjection);
|
|
74
|
+
return fieldProjection;
|
|
75
|
+
}, [model, editing]);
|
|
76
|
+
|
|
77
|
+
if (fieldProjection?.props.type === TypeEnum.Array) {
|
|
78
|
+
return <FormCellEditor fieldProjection={fieldProjection} model={model} schema={schema} __gridScope={__gridScope} />;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// For all other types, use the existing cell editor
|
|
82
|
+
return (
|
|
83
|
+
<TableCellEditor
|
|
84
|
+
model={model}
|
|
85
|
+
modals={modals}
|
|
86
|
+
onEnter={onEnter}
|
|
87
|
+
onFocus={onFocus}
|
|
88
|
+
onQuery={onQuery}
|
|
89
|
+
__gridScope={__gridScope}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
51
94
|
export const TableCellEditor = ({
|
|
52
95
|
model,
|
|
53
96
|
modals,
|
|
@@ -261,11 +304,9 @@ export const TableCellEditor = ({
|
|
|
261
304
|
}
|
|
262
305
|
}
|
|
263
306
|
|
|
264
|
-
// Return empty string if no value
|
|
265
307
|
return '';
|
|
266
308
|
}
|
|
267
309
|
|
|
268
|
-
// Default behavior for other formats
|
|
269
310
|
const value = model.getCellData(cell);
|
|
270
311
|
return value !== undefined ? String(value) : '';
|
|
271
312
|
}
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
createGapSeparator,
|
|
11
11
|
createMenuAction,
|
|
12
12
|
type MenuAction,
|
|
13
|
-
type MenuActionHandler,
|
|
14
13
|
MenuProvider,
|
|
15
14
|
ToolbarMenu,
|
|
16
15
|
useMenuActions,
|
|
@@ -27,18 +26,18 @@ export type TableToolbarActionType = 'add-row' | 'comment' | 'save-view';
|
|
|
27
26
|
type TableToolbarState = Partial<{ viewDirty: boolean }>;
|
|
28
27
|
|
|
29
28
|
export type TableToolbarProps = ThemedClassName<
|
|
30
|
-
TableToolbarState & { onAction
|
|
29
|
+
TableToolbarState & { onAction: (action: TableToolbarAction) => void; attendableId?: string }
|
|
31
30
|
>;
|
|
32
31
|
|
|
33
|
-
const createTableToolbarActions = (state: TableToolbarState) => {
|
|
34
|
-
const add = createMenuAction<TableToolbarActionProperties>('add-row', {
|
|
35
|
-
type: 'add-row',
|
|
32
|
+
const createTableToolbarActions = (state: TableToolbarState, onAction: (action: TableToolbarAction) => void) => {
|
|
33
|
+
const add = createMenuAction<TableToolbarActionProperties>('add-row', () => onAction(add), {
|
|
34
|
+
type: 'add-row' as const,
|
|
36
35
|
icon: 'ph--plus--regular',
|
|
37
36
|
label: ['add row', { ns: translationKey }],
|
|
38
37
|
testId: 'table.toolbar.add-row',
|
|
39
38
|
});
|
|
40
|
-
const save = createMenuAction<TableToolbarActionProperties>('save-view', {
|
|
41
|
-
type: 'save-view',
|
|
39
|
+
const save = createMenuAction<TableToolbarActionProperties>('save-view', () => onAction(save), {
|
|
40
|
+
type: 'save-view' as const,
|
|
42
41
|
icon: 'ph--floppy-disk--regular',
|
|
43
42
|
label: ['save view label', { ns: translationKey }],
|
|
44
43
|
testId: 'table.toolbar.save-view',
|
|
@@ -46,8 +45,8 @@ const createTableToolbarActions = (state: TableToolbarState) => {
|
|
|
46
45
|
hidden: !state.viewDirty,
|
|
47
46
|
});
|
|
48
47
|
const gap = createGapSeparator();
|
|
49
|
-
const comment = createMenuAction('comment', {
|
|
50
|
-
type: 'comment',
|
|
48
|
+
const comment = createMenuAction('comment', () => onAction(comment), {
|
|
49
|
+
type: 'comment' as const,
|
|
51
50
|
icon: 'ph--chat-text--regular',
|
|
52
51
|
label: ['create comment', { ns: translationKey }],
|
|
53
52
|
testId: 'table.toolbar.comment',
|
|
@@ -61,11 +60,11 @@ const createTableToolbarActions = (state: TableToolbarState) => {
|
|
|
61
60
|
|
|
62
61
|
export const TableToolbar = ({ classNames, viewDirty, attendableId, onAction }: TableToolbarProps) => {
|
|
63
62
|
const state = useMemo(() => live<TableToolbarState>({ viewDirty }), []);
|
|
64
|
-
const actionsCreator = useCallback(() => createTableToolbarActions(state), [state]);
|
|
63
|
+
const actionsCreator = useCallback(() => createTableToolbarActions(state, onAction), [state, onAction]);
|
|
65
64
|
const menu = useMenuActions(actionsCreator);
|
|
66
65
|
|
|
67
66
|
return (
|
|
68
|
-
<MenuProvider {...menu} attendableId={attendableId}
|
|
67
|
+
<MenuProvider {...menu} attendableId={attendableId}>
|
|
69
68
|
<ToolbarMenu classNames={classNames} />
|
|
70
69
|
</MenuProvider>
|
|
71
70
|
);
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Schema } from 'effect';
|
|
5
6
|
import { useEffect } from 'react';
|
|
6
7
|
|
|
7
|
-
import { setValue, toJsonSchema,
|
|
8
|
+
import { setValue, toJsonSchema, TypeEnum, TypedObject, FormatEnum, ObjectId } from '@dxos/echo-schema';
|
|
8
9
|
import { faker } from '@dxos/random';
|
|
9
10
|
import { live, makeRef } from '@dxos/react-client/echo';
|
|
10
11
|
import { createView, type ViewProjection } from '@dxos/schema';
|
|
@@ -12,11 +13,11 @@ import { createView, type ViewProjection } from '@dxos/schema';
|
|
|
12
13
|
import { TableType } from '../types';
|
|
13
14
|
|
|
14
15
|
export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
15
|
-
id:
|
|
16
|
-
name:
|
|
17
|
-
age:
|
|
18
|
-
active:
|
|
19
|
-
netWorth:
|
|
16
|
+
id: ObjectId,
|
|
17
|
+
name: Schema.optional(Schema.String),
|
|
18
|
+
age: Schema.optional(Schema.Number),
|
|
19
|
+
active: Schema.optional(Schema.Boolean),
|
|
20
|
+
netWorth: Schema.optional(Schema.Number),
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
export const createTable = (schema = TestSchema) => {
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { computed } from '@preact/signals-core';
|
|
6
|
+
import { Schema } from 'effect';
|
|
6
7
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
+
import { TypedObject } from '@dxos/echo-schema';
|
|
9
10
|
import { updateCounter } from '@dxos/echo-schema/testing';
|
|
10
11
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
11
12
|
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
@@ -99,8 +100,8 @@ describe('TableModel', () => {
|
|
|
99
100
|
});
|
|
100
101
|
|
|
101
102
|
class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
102
|
-
title:
|
|
103
|
-
completed:
|
|
103
|
+
title: Schema.String,
|
|
104
|
+
completed: Schema.Boolean,
|
|
104
105
|
}) {}
|
|
105
106
|
|
|
106
107
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Schema } from 'effect';
|
|
5
6
|
import { describe, expect, it, beforeEach } from 'vitest';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import { TypedObject } from '@dxos/echo-schema';
|
|
8
9
|
import { live, makeRef } from '@dxos/live-object';
|
|
9
10
|
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
10
11
|
import { createView, ViewProjection } from '@dxos/schema';
|
|
@@ -89,8 +90,8 @@ describe('TablePresentation', () => {
|
|
|
89
90
|
});
|
|
90
91
|
|
|
91
92
|
class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
92
|
-
title:
|
|
93
|
-
completed:
|
|
93
|
+
title: Schema.String,
|
|
94
|
+
completed: Schema.Boolean,
|
|
94
95
|
}) {}
|
|
95
96
|
|
|
96
97
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
|
+
import { isNotNullable } from 'effect/Predicate';
|
|
6
7
|
|
|
7
|
-
import { FormatEnum, getDXN, getValue } from '@dxos/echo-schema';
|
|
8
|
+
import { FormatEnum, getDXN, getValue, TypeEnum } from '@dxos/echo-schema';
|
|
8
9
|
import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
|
|
9
10
|
import {
|
|
10
11
|
type DxGridPlane,
|
|
@@ -84,6 +85,10 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
84
85
|
return '';
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
if (props.type === 'array') {
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
switch (props.format) {
|
|
88
93
|
case FormatEnum.Boolean:
|
|
89
94
|
case FormatEnum.SingleSelect:
|
|
@@ -121,6 +126,47 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
121
126
|
cell.className = mx(classes.flat());
|
|
122
127
|
}
|
|
123
128
|
|
|
129
|
+
if (props.type === TypeEnum.Array) {
|
|
130
|
+
const targetArray = getValue(obj, field.path);
|
|
131
|
+
if (targetArray && Array.isArray(targetArray)) {
|
|
132
|
+
// TODO(ZaymonFC): For arrays of objects, objects should have a 'label' annotation on the field
|
|
133
|
+
// that can be used to render tags.
|
|
134
|
+
// TODO(ZaymonFC): Move to util.
|
|
135
|
+
const getLabel = (value: any) => {
|
|
136
|
+
// If the value is falsy, return undefined
|
|
137
|
+
// If the object is an array, check for 'name' property
|
|
138
|
+
// If the object is a primitive type (string, number, boolean) stringify
|
|
139
|
+
// If the object is an array return 'Array'
|
|
140
|
+
if (!value) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
144
|
+
return String(value);
|
|
145
|
+
}
|
|
146
|
+
if (Array.isArray(value)) {
|
|
147
|
+
return 'Array';
|
|
148
|
+
}
|
|
149
|
+
if (typeof value === 'object' && value.name) {
|
|
150
|
+
return value.name;
|
|
151
|
+
}
|
|
152
|
+
if (typeof value === 'object' && value.value) {
|
|
153
|
+
return `${value.value}`;
|
|
154
|
+
}
|
|
155
|
+
return 'Object';
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const tags = targetArray
|
|
159
|
+
.map(getLabel)
|
|
160
|
+
.filter(isNotNullable)
|
|
161
|
+
.map((title) => {
|
|
162
|
+
return `<span class="dx-tag rounded-xl" data-hue="neutral">${title}</span>`;
|
|
163
|
+
})
|
|
164
|
+
.join('');
|
|
165
|
+
|
|
166
|
+
cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
124
170
|
if (props.format === FormatEnum.Ref && props.referenceSchema) {
|
|
125
171
|
const targetObj = getValue(obj, field.path)?.target;
|
|
126
172
|
if (targetObj) {
|
|
@@ -166,7 +212,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
166
212
|
.join(' ');
|
|
167
213
|
|
|
168
214
|
if (tags) {
|
|
169
|
-
cell.accessoryHtml = tags
|
|
215
|
+
cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1">${tags}</div>`;
|
|
170
216
|
}
|
|
171
217
|
}
|
|
172
218
|
}
|
|
@@ -206,7 +252,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
206
252
|
${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
|
|
207
253
|
${tableButtons.columnSettings.render({ fieldId: field.id })}
|
|
208
254
|
`,
|
|
209
|
-
className: '!bg-
|
|
255
|
+
className: '!bg-toolbarSurface',
|
|
210
256
|
};
|
|
211
257
|
}
|
|
212
258
|
|
|
@@ -249,7 +295,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
249
295
|
if (!this.model.features.selection.enabled || this.model.selection.selectionMode === 'single') {
|
|
250
296
|
return {
|
|
251
297
|
[toPlaneCellIndex({ col: 0, row: 0 })]: {
|
|
252
|
-
className: '!bg-
|
|
298
|
+
className: '!bg-toolbarSurface',
|
|
253
299
|
readonly: true,
|
|
254
300
|
value: '',
|
|
255
301
|
},
|
|
@@ -263,7 +309,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
263
309
|
header: true,
|
|
264
310
|
checked: this.model.selection.allRowsSeleted.value,
|
|
265
311
|
}),
|
|
266
|
-
className: '!bg-
|
|
312
|
+
className: '!bg-toolbarSurface',
|
|
267
313
|
readonly: true,
|
|
268
314
|
value: '',
|
|
269
315
|
},
|
|
@@ -278,7 +324,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
|
|
|
278
324
|
disabled: (this.model.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
|
|
279
325
|
})
|
|
280
326
|
: undefined,
|
|
281
|
-
className: '!bg-
|
|
327
|
+
className: '!bg-toolbarSurface',
|
|
282
328
|
readonly: true,
|
|
283
329
|
value: '',
|
|
284
330
|
},
|
package/src/types/table.ts
CHANGED
|
@@ -2,20 +2,28 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
|
|
6
7
|
// import { ThreadType } from '@dxos/plugin-space/types';
|
|
8
|
+
import { LabelAnnotationId, ObjectId, Ref, Expando, EchoObject } from '@dxos/echo-schema';
|
|
7
9
|
import { ViewType } from '@dxos/schema';
|
|
8
10
|
|
|
9
|
-
export const TableSchema =
|
|
11
|
+
export const TableSchema = Schema.Struct({
|
|
10
12
|
id: ObjectId,
|
|
11
|
-
name:
|
|
12
|
-
view:
|
|
13
|
+
name: Schema.optional(Schema.String),
|
|
14
|
+
view: Schema.optional(Ref(ViewType)),
|
|
13
15
|
// TODO(burdon): Document why threads is included here?
|
|
14
|
-
threads:
|
|
16
|
+
threads: Schema.optional(Schema.Array(Ref(Expando /* ThreadType */))),
|
|
15
17
|
}).annotations({
|
|
16
18
|
// TODO(burdon): Move annotation to property.
|
|
17
19
|
[LabelAnnotationId]: 'name',
|
|
18
20
|
});
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
+
// TODO(burdon): Move out of react-ui-xxx.
|
|
23
|
+
export const TableType = TableSchema.pipe(
|
|
24
|
+
EchoObject({
|
|
25
|
+
typename: 'dxos.org/type/Table',
|
|
26
|
+
version: '0.1.0',
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
export interface TableType extends Schema.Schema.Type<typeof TableType> {}
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { type Schema } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { getTypename, toJsonSchema } from '@dxos/echo-schema';
|
|
8
|
+
import type { JsonSchemaType, SortDirectionType } from '@dxos/echo-schema';
|
|
6
9
|
import { live, makeRef } from '@dxos/live-object';
|
|
7
10
|
import {
|
|
8
11
|
createView,
|
|
@@ -27,7 +30,7 @@ export type TablePropertyDefinition = SchemaPropertyDefinition & Partial<Propert
|
|
|
27
30
|
* @deprecated
|
|
28
31
|
*/
|
|
29
32
|
// TODO(burdon): Remove variance.
|
|
30
|
-
export const
|
|
33
|
+
export const getBaseSchema = ({
|
|
31
34
|
typename,
|
|
32
35
|
properties,
|
|
33
36
|
jsonSchema,
|
|
@@ -36,13 +39,13 @@ export const getBaseSchems = ({
|
|
|
36
39
|
typename?: string;
|
|
37
40
|
properties?: TablePropertyDefinition[];
|
|
38
41
|
jsonSchema?: JsonSchemaType;
|
|
39
|
-
schema?:
|
|
42
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
40
43
|
}): { typename: string; jsonSchema: JsonSchemaType } => {
|
|
41
44
|
if (typename && properties) {
|
|
42
45
|
const schema = getSchemaFromPropertyDefinitions(typename, properties);
|
|
43
46
|
return { typename: schema.typename, jsonSchema: schema.jsonSchema };
|
|
44
47
|
} else if (schema) {
|
|
45
|
-
return { typename: schema
|
|
48
|
+
return { typename: getTypename(schema)!, jsonSchema: toJsonSchema(schema) };
|
|
46
49
|
} else if (typename && jsonSchema) {
|
|
47
50
|
return { typename, jsonSchema };
|
|
48
51
|
} else {
|
package/src/util/index.ts
CHANGED