@dxos/react-ui-table 0.8.3-staging.0fa589b → 0.8.4-main.f9ba587

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.
Files changed (68) hide show
  1. package/dist/lib/browser/{chunk-TL7CKQQV.mjs → chunk-Q5QQXJUO.mjs} +2 -2
  2. package/dist/lib/browser/index.mjs +552 -227
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/types/index.mjs +1 -1
  6. package/dist/lib/node-esm/{chunk-GBT6GD4G.mjs → chunk-ZNOU6LHG.mjs} +2 -2
  7. package/dist/lib/node-esm/index.mjs +552 -227
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/lib/node-esm/types/index.mjs +1 -1
  11. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
  12. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  14. package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
  15. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/Table/relations.stories.d.ts +1 -1
  17. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
  18. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
  19. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  20. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  21. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -1
  22. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  23. package/dist/types/src/components/testing.d.ts +2 -2
  24. package/dist/types/src/hooks/index.d.ts +1 -0
  25. package/dist/types/src/hooks/index.d.ts.map +1 -1
  26. package/dist/types/src/hooks/useAddRow.d.ts +13 -0
  27. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
  28. package/dist/types/src/index.d.ts +1 -1
  29. package/dist/types/src/index.d.ts.map +1 -1
  30. package/dist/types/src/model/table-model.d.ts +42 -6
  31. package/dist/types/src/model/table-model.d.ts.map +1 -1
  32. package/dist/types/src/model/table-presentation.d.ts +8 -0
  33. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  34. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  35. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  36. package/dist/types/src/translations.d.ts +3 -4
  37. package/dist/types/src/translations.d.ts.map +1 -1
  38. package/dist/types/src/types/table.d.ts +4 -4
  39. package/dist/types/src/util/table-buttons.d.ts +15 -1
  40. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  41. package/dist/types/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +38 -40
  43. package/src/components/Table/DynamicTable.stories.tsx +2 -2
  44. package/src/components/Table/Table.stories.tsx +18 -12
  45. package/src/components/Table/Table.tsx +17 -4
  46. package/src/components/Table/relations.stories.tsx +4 -5
  47. package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -1
  48. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +3 -3
  49. package/src/components/TableCellEditor/TableCellEditor.tsx +54 -15
  50. package/src/hooks/index.ts +1 -0
  51. package/src/hooks/useAddRow.ts +36 -0
  52. package/src/index.ts +1 -1
  53. package/src/model/table-model.ts +220 -34
  54. package/src/model/table-presentation.ts +213 -130
  55. package/src/playwright/playwright.config.ts +17 -0
  56. package/src/playwright/smoke.spec.ts +9 -7
  57. package/src/translations.ts +5 -3
  58. package/src/util/table-buttons.ts +24 -1
  59. package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
  60. package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
  61. package/dist/lib/node/index.cjs +0 -2739
  62. package/dist/lib/node/index.cjs.map +0 -7
  63. package/dist/lib/node/meta.json +0 -1
  64. package/dist/lib/node/types/index.cjs +0 -31
  65. package/dist/lib/node/types/index.cjs.map +0 -7
  66. package/src/playwright/playwright.config.cts +0 -18
  67. /package/dist/lib/browser/{chunk-TL7CKQQV.mjs.map → chunk-Q5QQXJUO.mjs.map} +0 -0
  68. /package/dist/lib/node-esm/{chunk-GBT6GD4G.mjs.map → chunk-ZNOU6LHG.mjs.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-table",
3
- "version": "0.8.3-staging.0fa589b",
3
+ "version": "0.8.4-main.f9ba587",
4
4
  "description": "A table component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -53,60 +53,58 @@
53
53
  "@radix-ui/react-use-controllable-state": "1.1.0",
54
54
  "date-fns": "^3.3.1",
55
55
  "lodash.orderby": "^4.6.0",
56
- "@dxos/context": "0.8.3-staging.0fa589b",
57
- "@dxos/debug": "0.8.3-staging.0fa589b",
58
- "@dxos/echo-schema": "0.8.3-staging.0fa589b",
59
- "@dxos/effect": "0.8.3-staging.0fa589b",
60
- "@dxos/echo": "0.8.3-staging.0fa589b",
61
- "@dxos/invariant": "0.8.3-staging.0fa589b",
62
- "@dxos/lit-grid": "0.8.3-staging.0fa589b",
63
- "@dxos/live-object": "0.8.3-staging.0fa589b",
64
- "@dxos/keys": "0.8.3-staging.0fa589b",
65
- "@dxos/log": "0.8.3-staging.0fa589b",
66
- "@dxos/plugin-search": "0.8.3-staging.0fa589b",
67
- "@dxos/plugin-space": "0.8.3-staging.0fa589b",
68
- "@dxos/react-client": "0.8.3-staging.0fa589b",
69
- "@dxos/react-ui-attention": "0.8.3-staging.0fa589b",
70
- "@dxos/react-ui-editor": "0.8.3-staging.0fa589b",
71
- "@dxos/react-ui-form": "0.8.3-staging.0fa589b",
72
- "@dxos/react-ui-menu": "0.8.3-staging.0fa589b",
73
- "@dxos/react-ui-grid": "0.8.3-staging.0fa589b",
74
- "@dxos/react-ui-searchlist": "0.8.3-staging.0fa589b",
75
- "@dxos/react-ui-stack": "0.8.3-staging.0fa589b",
76
- "@dxos/react-ui-syntax-highlighter": "0.8.3-staging.0fa589b",
77
- "@dxos/react-ui-types": "0.8.3-staging.0fa589b",
78
- "@dxos/react-ui-tag-picker": "0.8.3-staging.0fa589b",
79
- "@dxos/schema": "0.8.3-staging.0fa589b",
80
- "@dxos/util": "0.8.3-staging.0fa589b"
56
+ "@dxos/context": "0.8.4-main.f9ba587",
57
+ "@dxos/debug": "0.8.4-main.f9ba587",
58
+ "@dxos/echo": "0.8.4-main.f9ba587",
59
+ "@dxos/echo-schema": "0.8.4-main.f9ba587",
60
+ "@dxos/effect": "0.8.4-main.f9ba587",
61
+ "@dxos/invariant": "0.8.4-main.f9ba587",
62
+ "@dxos/keys": "0.8.4-main.f9ba587",
63
+ "@dxos/lit-grid": "0.8.4-main.f9ba587",
64
+ "@dxos/log": "0.8.4-main.f9ba587",
65
+ "@dxos/live-object": "0.8.4-main.f9ba587",
66
+ "@dxos/plugin-search": "0.8.4-main.f9ba587",
67
+ "@dxos/plugin-space": "0.8.4-main.f9ba587",
68
+ "@dxos/react-client": "0.8.4-main.f9ba587",
69
+ "@dxos/react-ui-attention": "0.8.4-main.f9ba587",
70
+ "@dxos/react-ui-editor": "0.8.4-main.f9ba587",
71
+ "@dxos/react-ui-form": "0.8.4-main.f9ba587",
72
+ "@dxos/react-ui-grid": "0.8.4-main.f9ba587",
73
+ "@dxos/react-ui-menu": "0.8.4-main.f9ba587",
74
+ "@dxos/react-ui-searchlist": "0.8.4-main.f9ba587",
75
+ "@dxos/react-ui-stack": "0.8.4-main.f9ba587",
76
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.f9ba587",
77
+ "@dxos/react-ui-tag-picker": "0.8.4-main.f9ba587",
78
+ "@dxos/react-ui-types": "0.8.4-main.f9ba587",
79
+ "@dxos/schema": "0.8.4-main.f9ba587",
80
+ "@dxos/util": "0.8.4-main.f9ba587"
81
81
  },
82
82
  "devDependencies": {
83
- "@effect-rx/rx-react": "^0.34.1",
84
- "@effect/platform": "0.80.12",
85
- "@phosphor-icons/react": "^2.1.5",
83
+ "@effect-rx/rx-react": "0.38.0",
84
+ "@effect/platform": "0.88.0",
86
85
  "@types/lodash.orderby": "^4.6.9",
87
86
  "@types/react": "~18.2.0",
88
87
  "@types/react-dom": "~18.2.0",
89
- "effect": "3.14.21",
88
+ "effect": "3.16.13",
90
89
  "fast-check": "^3.19.0",
91
90
  "react": "~18.2.0",
92
91
  "react-dom": "~18.2.0",
93
92
  "vite": "5.4.7",
94
- "@dxos/echo-signals": "0.8.3-staging.0fa589b",
95
- "@dxos/random": "0.8.3-staging.0fa589b",
96
- "@dxos/react-ui": "0.8.3-staging.0fa589b",
97
- "@dxos/react-ui-theme": "0.8.3-staging.0fa589b",
98
- "@dxos/storybook-utils": "0.8.3-staging.0fa589b",
99
- "@dxos/test-utils": "0.8.3-staging.0fa589b"
93
+ "@dxos/echo-signals": "0.8.4-main.f9ba587",
94
+ "@dxos/random": "0.8.4-main.f9ba587",
95
+ "@dxos/react-ui": "0.8.4-main.f9ba587",
96
+ "@dxos/react-ui-theme": "0.8.4-main.f9ba587",
97
+ "@dxos/storybook-utils": "0.8.4-main.f9ba587",
98
+ "@dxos/test-utils": "0.8.4-main.f9ba587"
100
99
  },
101
100
  "peerDependencies": {
102
101
  "@effect-rx/rx-react": "^0.34.1",
103
- "@effect/platform": "0.80.12",
104
- "@phosphor-icons/react": "^2.1.5",
102
+ "@effect/platform": "^0.80.12",
105
103
  "effect": "^3.13.3",
106
104
  "react": "~18.2.0",
107
105
  "react-dom": "~18.2.0",
108
- "@dxos/react-ui": "0.8.3-staging.0fa589b",
109
- "@dxos/react-ui-theme": "0.8.3-staging.0fa589b"
106
+ "@dxos/react-ui": "0.8.4-main.f9ba587",
107
+ "@dxos/react-ui-theme": "0.8.4-main.f9ba587"
110
108
  },
111
109
  "publishConfig": {
112
110
  "access": "public"
@@ -4,7 +4,7 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import { type StoryObj, type Meta } from '@storybook/react';
7
+ import { type StoryObj, type Meta } from '@storybook/react-vite';
8
8
  import React, { useMemo, useState } from 'react';
9
9
 
10
10
  import { Obj } from '@dxos/echo';
@@ -19,7 +19,7 @@ import { withLayout, withTheme } from '@dxos/storybook-utils';
19
19
 
20
20
  import { DynamicTable } from './DynamicTable';
21
21
  import { type TableFeatures } from '../../model';
22
- import translations from '../../translations';
22
+ import { translations } from '../../translations';
23
23
 
24
24
  faker.seed(0);
25
25
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import { type StoryObj, type Meta } from '@storybook/react';
7
+ import { type StoryObj, type Meta } from '@storybook/react-vite';
8
8
  import { Schema } from 'effect';
9
9
  import React, { useCallback, useMemo, useRef } from 'react';
10
10
 
@@ -23,9 +23,9 @@ import { Testing, createObjectFactory } from '@dxos/schema/testing';
23
23
  import { withLayout, withTheme } from '@dxos/storybook-utils';
24
24
 
25
25
  import { Table, type TableController } from './Table';
26
- import { useTableModel } from '../../hooks';
26
+ import { useTableModel, useAddRow } from '../../hooks';
27
27
  import { TablePresentation } from '../../model';
28
- import translations from '../../translations';
28
+ import { translations } from '../../translations';
29
29
  import { TableType } from '../../types';
30
30
  import { initializeTable } from '../../util';
31
31
  import { TableToolbar } from '../TableToolbar';
@@ -72,11 +72,7 @@ const useTestTableModel = () => {
72
72
  tableRef.current?.update?.();
73
73
  }, []);
74
74
 
75
- const handleInsertRow = useCallback(() => {
76
- if (space && schema) {
77
- space.db.add(live(schema, {}));
78
- }
79
- }, [space, schema]);
75
+ const addRow = useAddRow({ space, schema });
80
76
 
81
77
  const handleDeleteRows = useCallback(
82
78
  (_: number, objects: any[]) => {
@@ -101,13 +97,17 @@ const useTestTableModel = () => {
101
97
  projection,
102
98
  features,
103
99
  rows: filteredObjects,
104
- onInsertRow: handleInsertRow,
100
+ onInsertRow: addRow,
105
101
  onDeleteRows: handleDeleteRows,
106
102
  onDeleteColumn: handleDeleteColumn,
107
103
  onCellUpdate: handleCellUpdate,
108
104
  onRowOrderChange: handleRowOrderChange,
109
105
  });
110
106
 
107
+ const handleInsertRow = useCallback(() => {
108
+ model?.insertRow();
109
+ }, [model]);
110
+
111
111
  const handleSaveView = useCallback(() => {
112
112
  model?.saveView();
113
113
  }, [model]);
@@ -209,8 +209,16 @@ type StoryProps = { rows?: number };
209
209
  const meta: Meta<StoryProps> = {
210
210
  title: 'ui/react-ui-table/Table',
211
211
  render: DefaultStory,
212
- parameters: { translations, controls: { disable: true } },
212
+ parameters: {
213
+ translations,
214
+ layout: 'fullscreen',
215
+ controls: {
216
+ disable: true,
217
+ },
218
+ },
213
219
  decorators: [
220
+ withTheme,
221
+ withLayout({ fullscreen: true }),
214
222
  withClientProvider({
215
223
  types: [TableType, ViewType],
216
224
  createIdentity: true,
@@ -227,8 +235,6 @@ const meta: Meta<StoryProps> = {
227
235
  });
228
236
  },
229
237
  }),
230
- withLayout({ fullscreen: true }),
231
- withTheme,
232
238
  ],
233
239
  };
234
240
 
@@ -91,6 +91,8 @@ const TableMain = forwardRef<TableController, TableMainProps>(
91
91
  const { t } = useTranslation(translationKey);
92
92
  const modals = useMemo(() => new ModalController(), []);
93
93
 
94
+ const draftRowCount = model?.getDraftRowCount() ?? 0;
95
+
94
96
  const frozen = useMemo(() => {
95
97
  const noActionColumn =
96
98
  model?.features.dataEditable === false &&
@@ -99,10 +101,11 @@ const TableMain = forwardRef<TableController, TableMainProps>(
99
101
 
100
102
  return {
101
103
  frozenRowsStart: 1,
104
+ frozenRowsEnd: draftRowCount,
102
105
  frozenColsStart: model?.features.selection.enabled ? 1 : 0,
103
106
  frozenColsEnd: noActionColumn ? 0 : 1,
104
107
  };
105
- }, [model]);
108
+ }, [model, draftRowCount]);
106
109
 
107
110
  const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
108
111
  (range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
@@ -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(cell.row);
259
+ model.insertRow();
246
260
  if (dxGrid) {
247
261
  requestAnimationFrame(() => {
248
262
  dxGrid?.scrollToRow(cell.row + 1);
@@ -366,7 +380,6 @@ const TableMain = forwardRef<TableController, TableMainProps>(
366
380
  <Grid.Content
367
381
  className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
368
382
  frozen={frozen}
369
- // getCells={getCells}
370
383
  columns={model.columnMeta.value}
371
384
  limitRows={model.getRowCount() ?? 0}
372
385
  limitColumns={model.view?.fields?.length ?? 0}
@@ -4,17 +4,16 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import { type StoryObj, type Meta } from '@storybook/react';
7
+ import { type StoryObj, type Meta } from '@storybook/react-vite';
8
8
  import { SchemaAST } from 'effect';
9
9
  import React, { useEffect, useMemo } from 'react';
10
10
 
11
- import { Obj, type Type } from '@dxos/echo';
11
+ import { Obj, Ref, type Type } from '@dxos/echo';
12
12
  import { getSchemaTypename, toJsonSchema } from '@dxos/echo-schema';
13
13
  import { getAnnotation } from '@dxos/effect';
14
14
  import { invariant } from '@dxos/invariant';
15
15
  import { faker } from '@dxos/random';
16
16
  import { useClient } from '@dxos/react-client';
17
- import { makeRef } from '@dxos/react-client/echo';
18
17
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
19
18
  import { DataType, createView, ViewProjection, ViewType } from '@dxos/schema';
20
19
  import { createAsyncGenerator, type ValueGenerator } from '@dxos/schema/testing';
@@ -23,7 +22,7 @@ import { withLayout, withTheme } from '@dxos/storybook-utils';
23
22
  import { Table } from './Table';
24
23
  import { useTableModel } from '../../hooks';
25
24
  import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
26
- import translations from '../../translations';
25
+ import { translations } from '../../translations';
27
26
  import { TableType } from '../../types';
28
27
 
29
28
  faker.seed(1);
@@ -45,7 +44,7 @@ const useTestModel = <S extends Type.Obj.Any>(schema: S, count: number) => {
45
44
  invariant(typename);
46
45
  const name = getAnnotation<string>(SchemaAST.TitleAnnotationId)(schema.ast) ?? typename;
47
46
  const view = createView({ name, typename, jsonSchema });
48
- return space.db.add(Obj.make(TableType, { view: makeRef(view) }));
47
+ return space.db.add(Obj.make(TableType, { view: Ref.make(view) }));
49
48
  }, [schema, space, jsonSchema]);
50
49
 
51
50
  const projection = useMemo(() => {
@@ -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='absolute bg-errorSurface text-errorSurfaceText rounded-bs-sm text-xs p-1'
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,
@@ -4,7 +4,7 @@
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';
@@ -20,7 +20,7 @@ import { withLayout, withTheme } from '@dxos/storybook-utils';
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';
23
+ import { translations } from '../../translations';
24
24
  import { TableType } from '../../types';
25
25
  import { initializeTable } from '../../util';
26
26
 
@@ -87,7 +87,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
87
87
  };
88
88
 
89
89
  const meta: Meta<StoryProps> = {
90
- title: 'plugins/plugin-table/TableCellEditor',
90
+ title: 'ui/react-ui-table/TableCellEditor',
91
91
  component: DefaultStory,
92
92
  render: DefaultStory,
93
93
  parameters: { translations, layout: 'centered' },
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { type Completion } from '@codemirror/autocomplete';
6
6
  import { type Schema } from 'effect/Schema';
7
- import React, { useCallback, useMemo, useRef, useState } from 'react';
7
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
8
 
9
9
  import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
10
10
  import { invariant } from '@dxos/invariant';
@@ -24,7 +24,7 @@ import {
24
24
  type GridCellEditorProps,
25
25
  type GridScopedProps,
26
26
  } from '@dxos/react-ui-grid';
27
- import { tagPickerExtension, createLinks } from '@dxos/react-ui-tag-picker';
27
+ import { tagPicker, createLinks } from '@dxos/react-ui-tag-picker';
28
28
  import { type FieldProjection } from '@dxos/schema';
29
29
 
30
30
  import { CellValidationMessage } from './CellValidationMessage';
@@ -92,6 +92,8 @@ export const TableValueEditor = ({
92
92
  );
93
93
  };
94
94
 
95
+ const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
96
+
95
97
  export const TableCellEditor = ({
96
98
  model,
97
99
  modals,
@@ -104,6 +106,7 @@ export const TableCellEditor = ({
104
106
  const suppressNextBlur = useRef(false);
105
107
  const { themeMode } = useThemeContext();
106
108
  const [_validationError, setValidationError] = useState<string | null>(null);
109
+ const [_validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
107
110
 
108
111
  const fieldProjection = useMemo<FieldProjection | undefined>(() => {
109
112
  if (!model || !editing) {
@@ -117,6 +120,38 @@ export const TableCellEditor = ({
117
120
  return fieldProjection;
118
121
  }, [model, editing]);
119
122
 
123
+ useEffect(() => {
124
+ // Check for existing validation errors when editing starts (for draft rows).
125
+ if (!model || !editing || !fieldProjection) {
126
+ setValidationError(null);
127
+ return;
128
+ }
129
+
130
+ const cell = parseCellIndex(editing.index);
131
+ const { row, col } = cell;
132
+
133
+ if (model.isDraftCell(cell)) {
134
+ const field = model.projection.view.fields[col];
135
+ const hasValidationError = model.hasDraftRowValidationError(row, field.path);
136
+
137
+ if (hasValidationError) {
138
+ const draftRows = model.draftRows.value;
139
+ if (row >= 0 && row < draftRows.length) {
140
+ const draftRow = draftRows[row];
141
+ const validationError = draftRow.validationErrors?.find((error) => error.path === field.path);
142
+ if (validationError) {
143
+ setValidationError(validationError.message);
144
+ setValidationVariant('warning');
145
+ }
146
+ }
147
+ } else {
148
+ setValidationError(null);
149
+ }
150
+ } else {
151
+ setValidationError(null);
152
+ }
153
+ }, [model, editing, fieldProjection]);
154
+
120
155
  const handleEnter = useCallback(
121
156
  async (value: any) => {
122
157
  if (!model || !editing) {
@@ -124,19 +159,17 @@ export const TableCellEditor = ({
124
159
  }
125
160
 
126
161
  const cell = parseCellIndex(editing.index);
162
+ const validationResult = await model.validateCellData(cell, value);
127
163
 
128
- // Validate the value
129
- const result = await model.validateCellData(cell, value);
130
-
131
- if (result.valid) {
164
+ if (validationResult.valid) {
132
165
  setValidationError(null);
133
166
  model.setCellData(cell, value);
134
167
  onEnter?.(cell);
135
168
  onFocus?.();
136
169
  setEditing(null);
137
170
  } else {
138
- setValidationError(result.error);
139
- // Editor stays open on validation failure
171
+ setValidationError(validationResult.error);
172
+ setValidationVariant('error');
140
173
  }
141
174
  },
142
175
  [model, editing, onEnter, onFocus, setEditing],
@@ -178,6 +211,7 @@ export const TableCellEditor = ({
178
211
  }
179
212
  } else {
180
213
  setValidationError(result.error);
214
+ setValidationVariant('error');
181
215
  }
182
216
  } else {
183
217
  setValidationError(null);
@@ -214,9 +248,9 @@ export const TableCellEditor = ({
214
248
  const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
215
249
 
216
250
  extension.push(
217
- tagPickerExtension({
251
+ tagPicker({
218
252
  mode,
219
- inGrid: true,
253
+ keymap: false,
220
254
  onSearch: (text, selectedIds) => {
221
255
  return options
222
256
  .filter(
@@ -300,9 +334,10 @@ export const TableCellEditor = ({
300
334
  if (value !== undefined) {
301
335
  const options = fieldProjection.props.options || [];
302
336
 
303
- if (fieldProjection.props.format === FormatEnum.MultiSelect && Array.isArray(value)) {
337
+ if (fieldProjection.props.format === FormatEnum.MultiSelect) {
304
338
  const tagItems = value
305
- .map((id) => {
339
+ .split(',')
340
+ .map((id: string) => {
306
341
  const option = options.find((o) => o.id === id);
307
342
  if (option) {
308
343
  return {
@@ -313,7 +348,7 @@ export const TableCellEditor = ({
313
348
  }
314
349
  return undefined;
315
350
  })
316
- .filter((item): item is { id: any; label: string; hue: any } => item !== undefined);
351
+ .filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
317
352
 
318
353
  return createLinks(tagItems);
319
354
  } else {
@@ -341,8 +376,12 @@ export const TableCellEditor = ({
341
376
 
342
377
  return (
343
378
  <>
344
- <CellValidationMessage validationError={_validationError} __gridScope={__gridScope} />
345
- <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} />
379
+ <CellValidationMessage
380
+ validationError={_validationError}
381
+ variant={_validationVariant}
382
+ __gridScope={__gridScope}
383
+ />
384
+ <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
346
385
  </>
347
386
  );
348
387
  };
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './useTableModel';
6
+ export * from './useAddRow';
@@ -0,0 +1,36 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Schema } from 'effect';
6
+ import { useCallback } from 'react';
7
+
8
+ import { type Space } from '@dxos/client/echo';
9
+ import { Obj } from '@dxos/echo';
10
+
11
+ export type UseAddRowParams = {
12
+ space?: Space;
13
+ schema?: Schema.Schema.AnyNoContext;
14
+ };
15
+
16
+ /**
17
+ * Hook that provides a safe row creation function that handles schema validation failures.
18
+ * Returns a callback that attempts to create an object and returns boolean success/failure.
19
+ * Can accept optional data to create objects with specific content (used for committing draft rows).
20
+ */
21
+ export const useAddRow = ({ space, schema }: UseAddRowParams) => {
22
+ return useCallback(
23
+ (data?: any) => {
24
+ if (space && schema) {
25
+ try {
26
+ space.db.add(Obj.make(schema, data ?? {}));
27
+ return true;
28
+ } catch (error) {
29
+ return false;
30
+ }
31
+ }
32
+ return false;
33
+ },
34
+ [space, schema],
35
+ );
36
+ };
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import translations from './translations';
5
+ import { translations } from './translations';
6
6
 
7
7
  export * from './components';
8
8
  export * from './hooks';