@dxos/react-ui-table 0.8.3-staging.0fa589b → 0.8.4-main.c1de068
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-5XLSHLOG.mjs +1751 -0
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +7 -0
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +18 -0
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +240 -1639
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +121 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +3 -5
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +20 -0
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs +1752 -0
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +240 -1639
- 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 +122 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +3 -5
- package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -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 +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/relations.stories.d.ts +1 -1
- package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -2
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -40
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +13 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +7 -6
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +2 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +56 -20
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +8 -0
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +2 -2
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/useTestTableModel.d.ts +41 -0
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +3 -4
- package/dist/types/src/translations.d.ts.map +1 -1
- 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/types/schema.d.ts +8 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util/dynamic-table.d.ts +3 -4
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts +15 -1
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table.d.ts +8 -11
- package/dist/types/src/util/table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +48 -40
- package/src/components/Table/ColumnSettings.tsx +14 -10
- package/src/components/Table/CreateRefPanel.tsx +5 -3
- package/src/components/Table/DynamicTable.stories.tsx +2 -2
- package/src/components/Table/DynamicTable.tsx +13 -7
- package/src/components/Table/Table.stories.tsx +104 -148
- package/src/components/Table/Table.tsx +50 -32
- package/src/components/Table/relations.stories.tsx +27 -35
- package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -28
- package/src/components/TableCellEditor/TableCellEditor.tsx +79 -21
- package/src/components/testing.ts +7 -24
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddRow.ts +36 -0
- package/src/hooks/useTableModel.ts +20 -16
- package/src/index.ts +1 -2
- package/src/model/selection-model.ts +4 -2
- package/src/model/table-model.test.ts +7 -8
- package/src/model/table-model.ts +267 -78
- package/src/model/table-presentation.test.ts +6 -7
- package/src/model/table-presentation.ts +215 -132
- package/src/model/table-sorting.ts +9 -9
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +21 -20
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +122 -0
- package/src/translations.ts +5 -3
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +16 -0
- package/src/util/dynamic-table.ts +19 -17
- package/src/util/table-buttons.ts +24 -1
- package/src/util/table.ts +33 -111
- package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
- package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
- package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
- package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -2739
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/types/index.cjs +0 -31
- package/dist/lib/node/types/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
- package/dist/types/src/types/table.d.ts +0 -56
- package/dist/types/src/types/table.d.ts.map +0 -1
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
|
@@ -16,9 +16,11 @@ import React, {
|
|
|
16
16
|
} from 'react';
|
|
17
17
|
|
|
18
18
|
import { type Client } from '@dxos/client';
|
|
19
|
+
import { Filter } from '@dxos/echo';
|
|
19
20
|
import { getValue } from '@dxos/echo-schema';
|
|
20
21
|
import { invariant } from '@dxos/invariant';
|
|
21
|
-
|
|
22
|
+
// TODO(wittjosiah): Remove dependency on react-client.
|
|
23
|
+
import { getSpace } from '@dxos/react-client/echo';
|
|
22
24
|
import { useTranslation } from '@dxos/react-ui';
|
|
23
25
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
24
26
|
import {
|
|
@@ -50,15 +52,17 @@ import { createOption, TableValueEditor, type TableCellEditorProps } from '../Ta
|
|
|
50
52
|
|
|
51
53
|
export type TableRootProps = PropsWithChildren<{ role?: string }>;
|
|
52
54
|
|
|
53
|
-
const TableRoot = ({ children, role }: TableRootProps) => {
|
|
55
|
+
const TableRoot = ({ children, role = 'article' }: TableRootProps) => {
|
|
54
56
|
return (
|
|
55
57
|
<div
|
|
56
58
|
role='none'
|
|
57
59
|
className={mx(
|
|
58
|
-
'relative !border-separator',
|
|
59
|
-
role === '
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
'relative !border-separator [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
|
|
61
|
+
role === 'card--popover' && 'popover-card-height',
|
|
62
|
+
role === 'section' && 'attention-surface',
|
|
63
|
+
role === 'card--intrinsic' && '[&_.dx-grid]:bs-[--dx-grid-content-block-size]',
|
|
64
|
+
['card--popover', 'section', 'card--extrinsic'].includes(role) && 'overflow-hidden',
|
|
65
|
+
['article', 'slide'].includes(role) && 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0',
|
|
62
66
|
)}
|
|
63
67
|
>
|
|
64
68
|
{children}
|
|
@@ -91,6 +95,8 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
91
95
|
const { t } = useTranslation(translationKey);
|
|
92
96
|
const modals = useMemo(() => new ModalController(), []);
|
|
93
97
|
|
|
98
|
+
const draftRowCount = model?.getDraftRowCount() ?? 0;
|
|
99
|
+
|
|
94
100
|
const frozen = useMemo(() => {
|
|
95
101
|
const noActionColumn =
|
|
96
102
|
model?.features.dataEditable === false &&
|
|
@@ -99,10 +105,11 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
99
105
|
|
|
100
106
|
return {
|
|
101
107
|
frozenRowsStart: 1,
|
|
108
|
+
frozenRowsEnd: draftRowCount,
|
|
102
109
|
frozenColsStart: model?.features.selection.enabled ? 1 : 0,
|
|
103
110
|
frozenColsEnd: noActionColumn ? 0 : 1,
|
|
104
111
|
};
|
|
105
|
-
}, [model]);
|
|
112
|
+
}, [model, draftRowCount]);
|
|
106
113
|
|
|
107
114
|
const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
|
|
108
115
|
(range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
|
|
@@ -119,30 +126,26 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
119
126
|
/**
|
|
120
127
|
* Provides an external controller that can be called to repaint the table.
|
|
121
128
|
*/
|
|
122
|
-
useImperativeHandle<TableController, TableController>(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return {};
|
|
127
|
-
}
|
|
129
|
+
useImperativeHandle<TableController, TableController>(forwardedRef, () => {
|
|
130
|
+
if (!presentation || !dxGrid) {
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
128
133
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
[presentation, dxGrid],
|
|
141
|
-
);
|
|
134
|
+
return {
|
|
135
|
+
update: (cell) => {
|
|
136
|
+
if (cell) {
|
|
137
|
+
dxGrid.updateIfWithinBounds(cell, true);
|
|
138
|
+
} else {
|
|
139
|
+
dxGrid.updateCells(true);
|
|
140
|
+
dxGrid.requestUpdate();
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}, [presentation, dxGrid]);
|
|
142
145
|
|
|
143
146
|
const handleGridClick = useCallback(
|
|
144
147
|
(event: MouseEvent) => {
|
|
145
|
-
const rowIndex = safeParseInt((event.target as HTMLElement).ariaRowIndex ?? '');
|
|
148
|
+
const rowIndex = safeParseInt((event.target as HTMLElement).closest('[aria-rowindex]')?.ariaRowIndex ?? '');
|
|
146
149
|
if (rowIndex != null) {
|
|
147
150
|
if (onRowClick) {
|
|
148
151
|
const row = model?.getRowAt(rowIndex);
|
|
@@ -188,6 +191,17 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
188
191
|
model?.sorting?.toggleSort(data.fieldId);
|
|
189
192
|
break;
|
|
190
193
|
}
|
|
194
|
+
case 'saveDraftRow': {
|
|
195
|
+
if (model) {
|
|
196
|
+
const didCommitSuccessfully = model.commitDraftRow(data.rowIndex);
|
|
197
|
+
if (dxGrid && didCommitSuccessfully) {
|
|
198
|
+
requestAnimationFrame(() => {
|
|
199
|
+
dxGrid.scrollToEndRow();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
191
205
|
}
|
|
192
206
|
return;
|
|
193
207
|
}
|
|
@@ -222,7 +236,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
222
236
|
}
|
|
223
237
|
}
|
|
224
238
|
},
|
|
225
|
-
[model, modals],
|
|
239
|
+
[model, modals, dxGrid],
|
|
226
240
|
);
|
|
227
241
|
|
|
228
242
|
const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
|
|
@@ -242,7 +256,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
242
256
|
|
|
243
257
|
// TODO(burdon): Insert row only if bottom row isn't completely blank already.
|
|
244
258
|
if (model && cell.row === model.getRowCount() - 1) {
|
|
245
|
-
model.insertRow(
|
|
259
|
+
model.insertRow();
|
|
246
260
|
if (dxGrid) {
|
|
247
261
|
requestAnimationFrame(() => {
|
|
248
262
|
dxGrid?.scrollToRow(cell.row + 1);
|
|
@@ -307,7 +321,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
307
321
|
const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
|
|
308
322
|
async ({ field, props }, text) => {
|
|
309
323
|
if (model && props.referenceSchema && field.referencePath) {
|
|
310
|
-
const space = model.
|
|
324
|
+
const space = getSpace(model.view);
|
|
311
325
|
invariant(space);
|
|
312
326
|
|
|
313
327
|
let schema;
|
|
@@ -349,6 +363,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
349
363
|
[model, client, t],
|
|
350
364
|
);
|
|
351
365
|
|
|
366
|
+
const handleSave = useCallback(() => {
|
|
367
|
+
dxGrid?.updateCells(true);
|
|
368
|
+
}, [dxGrid]);
|
|
369
|
+
|
|
352
370
|
if (!model || !modals) {
|
|
353
371
|
return <span role='none' className='attention-surface' />;
|
|
354
372
|
}
|
|
@@ -362,14 +380,14 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
362
380
|
onEnter={handleEnter}
|
|
363
381
|
onFocus={handleFocus}
|
|
364
382
|
onQuery={handleQuery}
|
|
383
|
+
onSave={handleSave}
|
|
365
384
|
/>
|
|
366
385
|
<Grid.Content
|
|
367
386
|
className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
368
387
|
frozen={frozen}
|
|
369
|
-
// getCells={getCells}
|
|
370
388
|
columns={model.columnMeta.value}
|
|
371
389
|
limitRows={model.getRowCount() ?? 0}
|
|
372
|
-
limitColumns={model.
|
|
390
|
+
limitColumns={model.projection.fields.length}
|
|
373
391
|
overscroll='trap'
|
|
374
392
|
onAxisResize={handleAxisResize}
|
|
375
393
|
onClick={handleGridClick}
|
|
@@ -4,27 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
-
import { type StoryObj, type Meta } from '@storybook/react';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import { getSchemaTypename, toJsonSchema } from '@dxos/echo-schema';
|
|
13
|
-
import { getAnnotation } from '@dxos/effect';
|
|
14
|
-
import { invariant } from '@dxos/invariant';
|
|
7
|
+
import { type StoryObj, type Meta } from '@storybook/react-vite';
|
|
8
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
9
|
+
|
|
10
|
+
import { Type } from '@dxos/echo';
|
|
11
|
+
import { type JsonSchemaType } from '@dxos/echo-schema';
|
|
15
12
|
import { faker } from '@dxos/random';
|
|
16
13
|
import { useClient } from '@dxos/react-client';
|
|
17
|
-
import { makeRef } from '@dxos/react-client/echo';
|
|
18
14
|
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
19
|
-
import { DataType
|
|
15
|
+
import { DataType } from '@dxos/schema';
|
|
20
16
|
import { createAsyncGenerator, type ValueGenerator } from '@dxos/schema/testing';
|
|
21
17
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
22
18
|
|
|
23
19
|
import { Table } from './Table';
|
|
24
20
|
import { useTableModel } from '../../hooks';
|
|
25
21
|
import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
|
|
26
|
-
import translations from '../../translations';
|
|
27
|
-
import {
|
|
22
|
+
import { translations } from '../../translations';
|
|
23
|
+
import { TableView } from '../../types';
|
|
24
|
+
import { createTable } from '../../util';
|
|
28
25
|
|
|
29
26
|
faker.seed(1);
|
|
30
27
|
const generator: ValueGenerator = faker as any;
|
|
@@ -34,35 +31,30 @@ const generator: ValueGenerator = faker as any;
|
|
|
34
31
|
// TODO(burdon): Reconcile schemas types and utils (see API PR).
|
|
35
32
|
// TODO(burdon): Base type for T (with id); see ECHO API PR?
|
|
36
33
|
const useTestModel = <S extends Type.Obj.Any>(schema: S, count: number) => {
|
|
34
|
+
const client = useClient();
|
|
37
35
|
const { space } = useClientProvider();
|
|
38
|
-
|
|
39
|
-
const jsonSchema =
|
|
40
|
-
const table = useMemo(() => {
|
|
41
|
-
if (!space) {
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
const typename = getSchemaTypename(schema);
|
|
45
|
-
invariant(typename);
|
|
46
|
-
const name = getAnnotation<string>(SchemaAST.TitleAnnotationId)(schema.ast) ?? typename;
|
|
47
|
-
const view = createView({ name, typename, jsonSchema });
|
|
48
|
-
return space.db.add(Obj.make(TableType, { view: makeRef(view) }));
|
|
49
|
-
}, [schema, space, jsonSchema]);
|
|
50
|
-
|
|
51
|
-
const projection = useMemo(() => {
|
|
52
|
-
if (!table?.view?.target) {
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// TODO(burdon): Just pass in view? Reuse same jsonSchema instance? View determines if mutable, etc.
|
|
57
|
-
return new ViewProjection(jsonSchema, table.view.target);
|
|
58
|
-
}, [jsonSchema, table]);
|
|
36
|
+
const [view, setView] = useState<DataType.View>();
|
|
37
|
+
const [jsonSchema, setJsonSchema] = useState<JsonSchemaType>();
|
|
59
38
|
|
|
60
39
|
const features = useMemo<TableFeatures>(
|
|
61
40
|
() => ({ schemaEditable: false, dataEditable: true, selection: { enabled: false } }),
|
|
62
41
|
[],
|
|
63
42
|
);
|
|
64
43
|
|
|
65
|
-
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!space) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const timeout = setTimeout(async () => {
|
|
50
|
+
const { jsonSchema, view } = await createTable({ client, space, typename: Type.getTypename(schema) });
|
|
51
|
+
setJsonSchema(jsonSchema);
|
|
52
|
+
setView(view);
|
|
53
|
+
});
|
|
54
|
+
return () => clearTimeout(timeout);
|
|
55
|
+
}, [client, space, schema]);
|
|
56
|
+
|
|
57
|
+
const model = useTableModel<TableRow>({ view, schema: jsonSchema, rows: [], features });
|
|
66
58
|
useEffect(() => {
|
|
67
59
|
if (!model || !space) {
|
|
68
60
|
return;
|
|
@@ -108,7 +100,7 @@ const meta: Meta<typeof DefaultStory> = {
|
|
|
108
100
|
parameters: { translations, controls: { disable: true } },
|
|
109
101
|
decorators: [
|
|
110
102
|
withClientProvider({
|
|
111
|
-
types: [
|
|
103
|
+
types: [DataType.View, DataType.Organization, DataType.Person, TableView],
|
|
112
104
|
createIdentity: true,
|
|
113
105
|
createSpace: true,
|
|
114
106
|
}),
|
|
@@ -8,10 +8,12 @@ import { useGridContext, type GridScopedProps } from '@dxos/react-ui-grid';
|
|
|
8
8
|
|
|
9
9
|
export type CellValidationMessageProps = {
|
|
10
10
|
validationError: string | null;
|
|
11
|
+
variant?: 'error' | 'warning';
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export const CellValidationMessage = ({
|
|
14
15
|
validationError,
|
|
16
|
+
variant = 'error',
|
|
15
17
|
__gridScope,
|
|
16
18
|
}: GridScopedProps<CellValidationMessageProps>) => {
|
|
17
19
|
const { editing, editBox: box } = useGridContext('GridSheetCellEditor', __gridScope);
|
|
@@ -20,10 +22,13 @@ export const CellValidationMessage = ({
|
|
|
20
22
|
return null;
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
const bgClass = variant === 'error' ? 'bg-errorSurface' : 'bg-warningSurface';
|
|
26
|
+
const textClass = variant === 'error' ? 'text-errorSurfaceText' : 'text-warningSurfaceText';
|
|
27
|
+
|
|
23
28
|
return (
|
|
24
29
|
<div
|
|
25
30
|
role='none'
|
|
26
|
-
className=
|
|
31
|
+
className={`absolute ${bgClass} ${textClass} rounded-bs-sm text-xs p-1`}
|
|
27
32
|
style={{
|
|
28
33
|
...{ '--dx-gridCellWidth': `${box?.inlineSize ?? 200}px` },
|
|
29
34
|
zIndex: 10,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Schema } from 'effect';
|
|
6
|
-
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
6
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { getSnapshot } from '@dxos/echo-schema';
|
|
9
9
|
import { invariant } from '@dxos/invariant';
|
|
@@ -20,18 +20,26 @@ type FormCellEditorProps = {
|
|
|
20
20
|
fieldProjection: FieldProjection;
|
|
21
21
|
model?: TableModel;
|
|
22
22
|
schema?: Schema.Schema.AnyNoContext;
|
|
23
|
+
onSave?: () => void;
|
|
23
24
|
__gridScope: any;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }: FormCellEditorProps) => {
|
|
27
|
-
const { editing, setEditing } = useGridContext('ArrayEditor', __gridScope);
|
|
28
|
-
const
|
|
27
|
+
export const FormCellEditor = ({ fieldProjection, model, schema, onSave, __gridScope }: FormCellEditorProps) => {
|
|
28
|
+
const { editing: contextEditing, setEditing } = useGridContext('ArrayEditor', __gridScope);
|
|
29
|
+
const [editing, setLocalEditing] = useState(false);
|
|
30
|
+
const anchorRef = useRef<HTMLButtonElement | null>(null);
|
|
29
31
|
|
|
30
32
|
useEffect(() => {
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
+
if (contextEditing && contextEditing.cellElement) {
|
|
34
|
+
anchorRef.current = (contextEditing.cellElement as HTMLElement).querySelector(
|
|
35
|
+
'.dx-grid__cell__content',
|
|
36
|
+
) as HTMLButtonElement;
|
|
37
|
+
setLocalEditing(true);
|
|
38
|
+
} else {
|
|
39
|
+
anchorRef.current = null;
|
|
40
|
+
setLocalEditing(false);
|
|
33
41
|
}
|
|
34
|
-
}, [
|
|
42
|
+
}, [contextEditing]);
|
|
35
43
|
|
|
36
44
|
const narrowedSchema = useMemo(() => {
|
|
37
45
|
if (!schema) {
|
|
@@ -41,8 +49,8 @@ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }:
|
|
|
41
49
|
}, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
|
|
42
50
|
|
|
43
51
|
const originalRow = useMemo(() => {
|
|
44
|
-
if (model &&
|
|
45
|
-
const cell = parseCellIndex(
|
|
52
|
+
if (model && contextEditing) {
|
|
53
|
+
const cell = parseCellIndex(contextEditing.index);
|
|
46
54
|
const row = model.getRowAt(cell.row);
|
|
47
55
|
invariant(row);
|
|
48
56
|
|
|
@@ -50,7 +58,7 @@ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }:
|
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
return undefined;
|
|
53
|
-
}, [model,
|
|
61
|
+
}, [model, contextEditing]);
|
|
54
62
|
|
|
55
63
|
const formValues = useMemo(() => {
|
|
56
64
|
if (originalRow) {
|
|
@@ -65,27 +73,28 @@ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }:
|
|
|
65
73
|
const value = getDeep(values, [path]);
|
|
66
74
|
setDeep(originalRow, [path], value);
|
|
67
75
|
setEditing(null);
|
|
76
|
+
setLocalEditing(false);
|
|
77
|
+
onSave?.();
|
|
68
78
|
},
|
|
69
79
|
[fieldProjection.field.path, originalRow],
|
|
70
80
|
);
|
|
71
81
|
|
|
72
|
-
const handleOpenChange = useCallback(
|
|
73
|
-
(nextOpen
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
82
|
+
const handleOpenChange = useCallback((nextOpen: boolean) => {
|
|
83
|
+
if (nextOpen === false) {
|
|
84
|
+
setEditing(null);
|
|
85
|
+
onSave?.();
|
|
86
|
+
}
|
|
87
|
+
setLocalEditing(nextOpen);
|
|
88
|
+
}, []);
|
|
80
89
|
|
|
81
90
|
if (!editing) {
|
|
82
91
|
return null;
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
return (
|
|
86
|
-
<Popover.Root open={editing
|
|
87
|
-
<Popover.VirtualTrigger virtualRef={
|
|
88
|
-
<Popover.Content tabIndex={-1} classNames='popover-card-width'>
|
|
95
|
+
<Popover.Root open={editing} onOpenChange={handleOpenChange}>
|
|
96
|
+
<Popover.VirtualTrigger virtualRef={anchorRef} />
|
|
97
|
+
<Popover.Content tabIndex={-1} classNames='popover-card-width density-fine'>
|
|
89
98
|
<Popover.Arrow />
|
|
90
99
|
<Popover.Viewport>
|
|
91
100
|
<Form values={formValues} schema={narrowedSchema as any} onSave={handleSave} />
|
|
@@ -4,25 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
-
import { type Meta, type StoryObj } from '@storybook/react';
|
|
7
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
8
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
10
|
import { Obj } from '@dxos/echo';
|
|
11
11
|
import { type EchoSchema, isMutable } from '@dxos/echo-schema';
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
13
13
|
import { faker } from '@dxos/random';
|
|
14
|
-
import { Filter, useQuery
|
|
14
|
+
import { Filter, useQuery } from '@dxos/react-client/echo';
|
|
15
15
|
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
16
16
|
import { defaultRowSize, Grid, type GridEditing } from '@dxos/react-ui-grid';
|
|
17
|
-
import {
|
|
17
|
+
import { createDefaultSchema, DataType } from '@dxos/schema';
|
|
18
18
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
19
19
|
|
|
20
20
|
import { TableCellEditor, type TableCellEditorProps } from './TableCellEditor';
|
|
21
21
|
import { useTableModel } from '../../hooks';
|
|
22
22
|
import { type TableFeatures } from '../../model';
|
|
23
|
-
import translations from '../../translations';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
23
|
+
import { translations } from '../../translations';
|
|
24
|
+
import { TableView } from '../../types';
|
|
25
|
+
import { createTable } from '../../util';
|
|
26
26
|
|
|
27
27
|
type StoryProps = {
|
|
28
28
|
editing: GridEditing;
|
|
@@ -32,23 +32,16 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
32
32
|
const { space } = useClientProvider();
|
|
33
33
|
invariant(space);
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
const [
|
|
35
|
+
const views = useQuery(space, Filter.type(DataType.View));
|
|
36
|
+
const [view, setView] = useState<DataType.View>();
|
|
37
37
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
38
38
|
useEffect(() => {
|
|
39
|
-
if (space &&
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
setSchema(space.db.schemaRegistry.getSchema(table.view.target!.query.typename!));
|
|
39
|
+
if (space && views.length && !view) {
|
|
40
|
+
const view = views[0];
|
|
41
|
+
setView(view);
|
|
42
|
+
setSchema(space.db.schemaRegistry.getSchema(view.query.typename!));
|
|
44
43
|
}
|
|
45
|
-
}, [space,
|
|
46
|
-
|
|
47
|
-
const projection = useMemo(() => {
|
|
48
|
-
if (schema && table?.view) {
|
|
49
|
-
return new ViewProjection(schema.jsonSchema, table.view.target!);
|
|
50
|
-
}
|
|
51
|
-
}, [schema, table?.view]);
|
|
44
|
+
}, [space, views, view]);
|
|
52
45
|
|
|
53
46
|
const features: Partial<TableFeatures> = useMemo(
|
|
54
47
|
() => ({
|
|
@@ -59,7 +52,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
59
52
|
[schema],
|
|
60
53
|
);
|
|
61
54
|
|
|
62
|
-
const model = useTableModel({
|
|
55
|
+
const model = useTableModel({ view, schema: schema?.jsonSchema, features });
|
|
63
56
|
|
|
64
57
|
const handleQuery: TableCellEditorProps['onQuery'] = async ({ field }) => {
|
|
65
58
|
// TODO(dmaretskyi): If no schema query nothing
|
|
@@ -73,7 +66,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
73
66
|
});
|
|
74
67
|
};
|
|
75
68
|
|
|
76
|
-
if (!model || !schema || !
|
|
69
|
+
if (!model || !schema || !view) {
|
|
77
70
|
return <div />;
|
|
78
71
|
}
|
|
79
72
|
|
|
@@ -87,22 +80,25 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
87
80
|
};
|
|
88
81
|
|
|
89
82
|
const meta: Meta<StoryProps> = {
|
|
90
|
-
title: '
|
|
83
|
+
title: 'ui/react-ui-table/TableCellEditor',
|
|
91
84
|
component: DefaultStory,
|
|
92
85
|
render: DefaultStory,
|
|
93
86
|
parameters: { translations, layout: 'centered' },
|
|
94
87
|
decorators: [
|
|
95
88
|
withClientProvider({
|
|
96
|
-
types: [
|
|
89
|
+
types: [DataType.View, TableView],
|
|
97
90
|
createIdentity: true,
|
|
98
91
|
createSpace: true,
|
|
99
92
|
onSpaceCreated: async ({ client, space }) => {
|
|
100
|
-
const
|
|
101
|
-
const
|
|
93
|
+
const schema = createDefaultSchema();
|
|
94
|
+
const { view } = await createTable({ client, space, typename: schema.typename });
|
|
95
|
+
space.db.add(view);
|
|
102
96
|
Array.from({ length: 10 }).forEach(() => {
|
|
103
97
|
space.db.add(
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
Obj.make(schema, {
|
|
99
|
+
title: faker.person.fullName(),
|
|
100
|
+
status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
101
|
+
description: faker.lorem.sentence(),
|
|
106
102
|
}),
|
|
107
103
|
);
|
|
108
104
|
});
|