@dxos/react-ui-table 0.8.3 → 0.8.4-main.5acf9ea
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 +246 -1641
- 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 +246 -1641
- 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 +55 -43
- 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 +57 -34
- 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);
|
|
@@ -268,7 +282,12 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
268
282
|
switch (event.key) {
|
|
269
283
|
case 'Backspace':
|
|
270
284
|
case 'Delete': {
|
|
271
|
-
|
|
285
|
+
try {
|
|
286
|
+
model.setCellData(cell, undefined);
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
} catch {
|
|
289
|
+
// Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
|
|
290
|
+
}
|
|
272
291
|
break;
|
|
273
292
|
}
|
|
274
293
|
}
|
|
@@ -307,7 +326,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
307
326
|
const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
|
|
308
327
|
async ({ field, props }, text) => {
|
|
309
328
|
if (model && props.referenceSchema && field.referencePath) {
|
|
310
|
-
const space = model.
|
|
329
|
+
const space = getSpace(model.view);
|
|
311
330
|
invariant(space);
|
|
312
331
|
|
|
313
332
|
let schema;
|
|
@@ -349,6 +368,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
349
368
|
[model, client, t],
|
|
350
369
|
);
|
|
351
370
|
|
|
371
|
+
const handleSave = useCallback(() => {
|
|
372
|
+
dxGrid?.updateCells(true);
|
|
373
|
+
}, [dxGrid]);
|
|
374
|
+
|
|
352
375
|
if (!model || !modals) {
|
|
353
376
|
return <span role='none' className='attention-surface' />;
|
|
354
377
|
}
|
|
@@ -362,18 +385,18 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
362
385
|
onEnter={handleEnter}
|
|
363
386
|
onFocus={handleFocus}
|
|
364
387
|
onQuery={handleQuery}
|
|
388
|
+
onSave={handleSave}
|
|
365
389
|
/>
|
|
366
390
|
<Grid.Content
|
|
367
391
|
className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
368
392
|
frozen={frozen}
|
|
369
|
-
// getCells={getCells}
|
|
370
393
|
columns={model.columnMeta.value}
|
|
371
394
|
limitRows={model.getRowCount() ?? 0}
|
|
372
|
-
limitColumns={model.
|
|
395
|
+
limitColumns={model.projection.fields.length}
|
|
373
396
|
overscroll='trap'
|
|
374
397
|
onAxisResize={handleAxisResize}
|
|
375
398
|
onClick={handleGridClick}
|
|
376
|
-
|
|
399
|
+
onKeyDownCapture={handleKeyDown}
|
|
377
400
|
onWheelCapture={handleWheel}
|
|
378
401
|
ref={setDxGrid}
|
|
379
402
|
/>
|
|
@@ -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
|
});
|