@dxos/react-ui-table 0.8.3 → 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.
Files changed (125) hide show
  1. package/dist/lib/browser/chunk-5XLSHLOG.mjs +1751 -0
  2. package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-L2LSB6KV.mjs +18 -0
  4. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +240 -1639
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +121 -0
  9. package/dist/lib/browser/testing/index.mjs.map +7 -0
  10. package/dist/lib/browser/types/index.mjs +3 -5
  11. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +20 -0
  12. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-Y66YLSTC.mjs +1752 -0
  14. package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +240 -1639
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +122 -0
  19. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  20. package/dist/lib/node-esm/types/index.mjs +3 -5
  21. package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -1
  22. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
  24. package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
  25. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  26. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
  30. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/relations.stories.d.ts +1 -1
  32. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
  33. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
  34. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  35. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +2 -1
  36. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  37. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -2
  38. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -1
  40. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  41. package/dist/types/src/components/testing.d.ts +3 -40
  42. package/dist/types/src/components/testing.d.ts.map +1 -1
  43. package/dist/types/src/hooks/index.d.ts +1 -0
  44. package/dist/types/src/hooks/index.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useAddRow.d.ts +13 -0
  46. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
  47. package/dist/types/src/hooks/useTableModel.d.ts +7 -6
  48. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  49. package/dist/types/src/index.d.ts +1 -2
  50. package/dist/types/src/index.d.ts.map +1 -1
  51. package/dist/types/src/model/selection-model.d.ts +2 -1
  52. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  53. package/dist/types/src/model/table-model.d.ts +56 -20
  54. package/dist/types/src/model/table-model.d.ts.map +1 -1
  55. package/dist/types/src/model/table-presentation.d.ts +8 -0
  56. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  57. package/dist/types/src/model/table-sorting.d.ts +2 -2
  58. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  59. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  60. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  61. package/dist/types/src/testing/index.d.ts +2 -0
  62. package/dist/types/src/testing/index.d.ts.map +1 -0
  63. package/dist/types/src/testing/useTestTableModel.d.ts +41 -0
  64. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
  65. package/dist/types/src/translations.d.ts +3 -4
  66. package/dist/types/src/translations.d.ts.map +1 -1
  67. package/dist/types/src/types/index.d.ts +1 -1
  68. package/dist/types/src/types/index.d.ts.map +1 -1
  69. package/dist/types/src/types/schema.d.ts +8 -0
  70. package/dist/types/src/types/schema.d.ts.map +1 -0
  71. package/dist/types/src/util/dynamic-table.d.ts +3 -4
  72. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  73. package/dist/types/src/util/table-buttons.d.ts +15 -1
  74. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  75. package/dist/types/src/util/table.d.ts +8 -11
  76. package/dist/types/src/util/table.d.ts.map +1 -1
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +48 -40
  79. package/src/components/Table/ColumnSettings.tsx +14 -10
  80. package/src/components/Table/CreateRefPanel.tsx +5 -3
  81. package/src/components/Table/DynamicTable.stories.tsx +2 -2
  82. package/src/components/Table/DynamicTable.tsx +13 -7
  83. package/src/components/Table/Table.stories.tsx +104 -148
  84. package/src/components/Table/Table.tsx +50 -32
  85. package/src/components/Table/relations.stories.tsx +27 -35
  86. package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -1
  87. package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
  88. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -28
  89. package/src/components/TableCellEditor/TableCellEditor.tsx +79 -21
  90. package/src/components/testing.ts +7 -24
  91. package/src/hooks/index.ts +1 -0
  92. package/src/hooks/useAddRow.ts +36 -0
  93. package/src/hooks/useTableModel.ts +20 -16
  94. package/src/index.ts +1 -2
  95. package/src/model/selection-model.ts +4 -2
  96. package/src/model/table-model.test.ts +7 -8
  97. package/src/model/table-model.ts +267 -78
  98. package/src/model/table-presentation.test.ts +6 -7
  99. package/src/model/table-presentation.ts +215 -132
  100. package/src/model/table-sorting.ts +9 -9
  101. package/src/playwright/playwright.config.ts +17 -0
  102. package/src/playwright/smoke.spec.ts +21 -20
  103. package/src/testing/index.ts +5 -0
  104. package/src/testing/useTestTableModel.ts +122 -0
  105. package/src/translations.ts +5 -3
  106. package/src/types/index.ts +2 -2
  107. package/src/types/schema.ts +16 -0
  108. package/src/util/dynamic-table.ts +19 -17
  109. package/src/util/table-buttons.ts +24 -1
  110. package/src/util/table.ts +33 -111
  111. package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
  112. package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
  113. package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
  114. package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
  115. package/dist/lib/node/index.cjs +0 -2739
  116. package/dist/lib/node/index.cjs.map +0 -7
  117. package/dist/lib/node/meta.json +0 -1
  118. package/dist/lib/node/types/index.cjs +0 -31
  119. package/dist/lib/node/types/index.cjs.map +0 -7
  120. package/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
  121. package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
  122. package/dist/types/src/types/table.d.ts +0 -56
  123. package/dist/types/src/types/table.d.ts.map +0 -1
  124. package/src/playwright/playwright.config.cts +0 -18
  125. package/src/types/table.ts +0 -27
@@ -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';
@@ -49,6 +49,7 @@ export type TableCellEditorProps = {
49
49
  schema?: Schema.AnyNoContext;
50
50
  onEnter?: (cell: DxGridPlanePosition) => void;
51
51
  onFocus?: DxGrid['refocus'];
52
+ onSave?: () => void;
52
53
  onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
53
54
  };
54
55
 
@@ -58,6 +59,7 @@ export const TableValueEditor = ({
58
59
  schema,
59
60
  onEnter,
60
61
  onFocus,
62
+ onSave,
61
63
  onQuery,
62
64
  __gridScope,
63
65
  }: GridScopedProps<TableCellEditorProps>) => {
@@ -69,14 +71,26 @@ export const TableValueEditor = ({
69
71
  }
70
72
 
71
73
  const { col } = parseCellIndex(editing.index);
72
- const field = model.projection.view.fields[col];
74
+ const field = model.projection.fields[col];
73
75
  const fieldProjection = model.projection.getFieldProjection(field.id);
74
76
  invariant(fieldProjection);
75
77
  return fieldProjection;
76
78
  }, [model, editing]);
77
79
 
78
- if (fieldProjection?.props.type === TypeEnum.Array) {
79
- return <FormCellEditor fieldProjection={fieldProjection} model={model} schema={schema} __gridScope={__gridScope} />;
80
+ if (
81
+ fieldProjection?.props.type === TypeEnum.Array ||
82
+ fieldProjection?.props.format === FormatEnum.SingleSelect
83
+ // TODO(thure): Support `FormatEnum.MultiSelect`
84
+ ) {
85
+ return (
86
+ <FormCellEditor
87
+ fieldProjection={fieldProjection}
88
+ model={model}
89
+ schema={schema}
90
+ __gridScope={__gridScope}
91
+ onSave={onSave}
92
+ />
93
+ );
80
94
  }
81
95
 
82
96
  // For all other types, use the existing cell editor
@@ -87,23 +101,28 @@ export const TableValueEditor = ({
87
101
  onEnter={onEnter}
88
102
  onFocus={onFocus}
89
103
  onQuery={onQuery}
104
+ onSave={onSave}
90
105
  __gridScope={__gridScope}
91
106
  />
92
107
  );
93
108
  };
94
109
 
110
+ const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
111
+
95
112
  export const TableCellEditor = ({
96
113
  model,
97
114
  modals,
98
115
  onEnter,
99
116
  onFocus,
100
117
  onQuery,
118
+ onSave,
101
119
  __gridScope,
102
120
  }: GridScopedProps<TableCellEditorProps>) => {
103
121
  const { id: gridId, editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
104
122
  const suppressNextBlur = useRef(false);
105
123
  const { themeMode } = useThemeContext();
106
124
  const [_validationError, setValidationError] = useState<string | null>(null);
125
+ const [_validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
107
126
 
108
127
  const fieldProjection = useMemo<FieldProjection | undefined>(() => {
109
128
  if (!model || !editing) {
@@ -111,12 +130,44 @@ export const TableCellEditor = ({
111
130
  }
112
131
 
113
132
  const { col } = parseCellIndex(editing.index);
114
- const field = model.projection.view.fields[col];
133
+ const field = model.projection.fields[col];
115
134
  const fieldProjection = model.projection.getFieldProjection(field.id);
116
135
  invariant(fieldProjection);
117
136
  return fieldProjection;
118
137
  }, [model, editing]);
119
138
 
139
+ useEffect(() => {
140
+ // Check for existing validation errors when editing starts (for draft rows).
141
+ if (!model || !editing || !fieldProjection) {
142
+ setValidationError(null);
143
+ return;
144
+ }
145
+
146
+ const cell = parseCellIndex(editing.index);
147
+ const { row, col } = cell;
148
+
149
+ if (model.isDraftCell(cell)) {
150
+ const field = model.projection.fields[col];
151
+ const hasValidationError = model.hasDraftRowValidationError(row, field.path);
152
+
153
+ if (hasValidationError) {
154
+ const draftRows = model.draftRows.value;
155
+ if (row >= 0 && row < draftRows.length) {
156
+ const draftRow = draftRows[row];
157
+ const validationError = draftRow.validationErrors?.find((error) => error.path === field.path);
158
+ if (validationError) {
159
+ setValidationError(validationError.message);
160
+ setValidationVariant('warning');
161
+ }
162
+ }
163
+ } else {
164
+ setValidationError(null);
165
+ }
166
+ } else {
167
+ setValidationError(null);
168
+ }
169
+ }, [model, editing, fieldProjection]);
170
+
120
171
  const handleEnter = useCallback(
121
172
  async (value: any) => {
122
173
  if (!model || !editing) {
@@ -124,19 +175,18 @@ export const TableCellEditor = ({
124
175
  }
125
176
 
126
177
  const cell = parseCellIndex(editing.index);
178
+ const validationResult = await model.validateCellData(cell, value);
127
179
 
128
- // Validate the value
129
- const result = await model.validateCellData(cell, value);
130
-
131
- if (result.valid) {
180
+ if (validationResult.valid) {
132
181
  setValidationError(null);
133
182
  model.setCellData(cell, value);
134
183
  onEnter?.(cell);
135
184
  onFocus?.();
136
185
  setEditing(null);
186
+ onSave?.();
137
187
  } else {
138
- setValidationError(result.error);
139
- // Editor stays open on validation failure
188
+ setValidationError(validationResult.error);
189
+ setValidationVariant('error');
140
190
  }
141
191
  },
142
192
  [model, editing, onEnter, onFocus, setEditing],
@@ -172,22 +222,25 @@ export const TableCellEditor = ({
172
222
  setValidationError(null);
173
223
  model.setCellData(cell, value);
174
224
  setEditing(null);
225
+ onSave?.();
175
226
  onEnter?.(cell);
176
227
  if (event && onFocus) {
177
228
  onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
178
229
  }
179
230
  } else {
180
231
  setValidationError(result.error);
232
+ setValidationVariant('error');
181
233
  }
182
234
  } else {
183
235
  setValidationError(null);
184
236
  setEditing(null);
237
+ onSave?.();
185
238
  if (event && onFocus) {
186
239
  onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
187
240
  }
188
241
  }
189
242
  },
190
- [model, editing, onFocus, onEnter, fieldProjection, setEditing],
243
+ [model, editing, onFocus, onEnter, fieldProjection, setEditing, onSave],
191
244
  );
192
245
 
193
246
  const extension = useMemo(() => {
@@ -214,9 +267,9 @@ export const TableCellEditor = ({
214
267
  const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
215
268
 
216
269
  extension.push(
217
- tagPickerExtension({
270
+ tagPicker({
218
271
  mode,
219
- inGrid: true,
272
+ keymap: false,
220
273
  onSearch: (text, selectedIds) => {
221
274
  return options
222
275
  .filter(
@@ -288,7 +341,7 @@ export const TableCellEditor = ({
288
341
  if (model && editing) {
289
342
  const cell = parseCellIndex(editing.index);
290
343
  const { col } = cell;
291
- const field = model.projection.view.fields[col];
344
+ const field = model.projection.fields[col];
292
345
  const fieldProjection = model.projection.getFieldProjection(field.id);
293
346
 
294
347
  if (
@@ -300,9 +353,10 @@ export const TableCellEditor = ({
300
353
  if (value !== undefined) {
301
354
  const options = fieldProjection.props.options || [];
302
355
 
303
- if (fieldProjection.props.format === FormatEnum.MultiSelect && Array.isArray(value)) {
356
+ if (fieldProjection.props.format === FormatEnum.MultiSelect) {
304
357
  const tagItems = value
305
- .map((id) => {
358
+ .split(',')
359
+ .map((id: string) => {
306
360
  const option = options.find((o) => o.id === id);
307
361
  if (option) {
308
362
  return {
@@ -313,7 +367,7 @@ export const TableCellEditor = ({
313
367
  }
314
368
  return undefined;
315
369
  })
316
- .filter((item): item is { id: any; label: string; hue: any } => item !== undefined);
370
+ .filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
317
371
 
318
372
  return createLinks(tagItems);
319
373
  } else {
@@ -341,8 +395,12 @@ export const TableCellEditor = ({
341
395
 
342
396
  return (
343
397
  <>
344
- <CellValidationMessage validationError={_validationError} __gridScope={__gridScope} />
345
- <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} />
398
+ <CellValidationMessage
399
+ validationError={_validationError}
400
+ variant={_validationVariant}
401
+ __gridScope={__gridScope}
402
+ />
403
+ <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
346
404
  </>
347
405
  );
348
406
  };
@@ -5,13 +5,10 @@
5
5
  import { Schema } from 'effect';
6
6
  import { useEffect } from 'react';
7
7
 
8
- import { Obj, Ref } from '@dxos/echo';
9
- import { FormatEnum, ObjectId, setValue, toJsonSchema, TypedObject, TypeEnum } from '@dxos/echo-schema';
8
+ import { FormatEnum, ObjectId, setValue, TypedObject, TypeEnum } from '@dxos/echo-schema';
9
+ import { live } from '@dxos/live-object';
10
10
  import { faker } from '@dxos/random';
11
- import { live } from '@dxos/react-client/echo';
12
- import { createView, type ViewProjection } from '@dxos/schema';
13
-
14
- import { TableType } from '../types';
11
+ import { type ProjectionModel } from '@dxos/schema';
15
12
 
16
13
  export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
17
14
  id: ObjectId,
@@ -21,18 +18,6 @@ export const TestSchema = TypedObject({ typename: 'example.com/type/Test', versi
21
18
  netWorth: Schema.optional(Schema.Number),
22
19
  });
23
20
 
24
- export const createTable = (schema = TestSchema) => {
25
- return Obj.make(TableType, {
26
- view: Ref.make(
27
- createView({
28
- name: 'Test',
29
- typename: schema.typename,
30
- jsonSchema: toJsonSchema(schema),
31
- }),
32
- ),
33
- });
34
- };
35
-
36
21
  export const createItems = (n: number) => {
37
22
  const { data } = live({
38
23
  data: Array.from({ length: n }, () => ({
@@ -47,13 +32,13 @@ export const createItems = (n: number) => {
47
32
  };
48
33
 
49
34
  export type SimulatorProps = {
50
- table: TableType;
35
+ projection: ProjectionModel;
51
36
  items: any[];
52
37
  insertInterval?: number;
53
38
  updateInterval?: number;
54
39
  };
55
40
 
56
- export const useSimulator = ({ items, table, insertInterval, updateInterval }: SimulatorProps) => {
41
+ export const useSimulator = ({ items, projection, insertInterval, updateInterval }: SimulatorProps) => {
57
42
  useEffect(() => {
58
43
  if (!insertInterval) {
59
44
  return;
@@ -74,10 +59,8 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
74
59
 
75
60
  const i = setInterval(() => {
76
61
  const rowIdx = Math.floor(Math.random() * items.length);
77
- const fields = table.view?.target?.fields ?? [];
62
+ const fields = projection.fields ?? [];
78
63
  const columnIdx = Math.floor(Math.random() * fields.length);
79
- // TODO(ZaymonFC): ... This is borked.
80
- const projection: ViewProjection = (table as any)._projection;
81
64
  const field = fields[columnIdx];
82
65
  const item = items[rowIdx];
83
66
 
@@ -115,5 +98,5 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
115
98
  }, updateInterval);
116
99
 
117
100
  return () => clearInterval(i);
118
- }, [items, table.view?.target?.fields, updateInterval]);
101
+ }, [items, projection.fields, updateInterval]);
119
102
  };
@@ -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
+ };
@@ -4,19 +4,21 @@
4
4
 
5
5
  import { effect } from '@preact/signals-core';
6
6
  import orderBy from 'lodash.orderby';
7
- import { useEffect, useState } from 'react';
7
+ import { useEffect, useMemo, useState } from 'react';
8
8
 
9
- import { fullyQualifiedId, getSpace, type Live } from '@dxos/react-client/echo';
10
- import { useSelectionActions } from '@dxos/react-ui-attention';
11
- import { type ViewProjection } from '@dxos/schema';
9
+ import { type JsonSchemaType } from '@dxos/echo-schema';
10
+ import { type Live } from '@dxos/live-object';
11
+ import { fullyQualifiedId } from '@dxos/react-client/echo';
12
+ import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
13
+ import { type DataType, type ProjectionModel } from '@dxos/schema';
12
14
  import { isNonNullable } from '@dxos/util';
13
15
 
14
16
  import { type TableRow, TableModel, type TableModelProps, type TableRowAction } from '../model';
15
- import { type TableType } from '../types';
16
17
 
17
18
  export type UseTableModelParams<T extends TableRow = TableRow> = {
18
- table?: TableType;
19
- projection?: ViewProjection;
19
+ view?: DataType.View;
20
+ schema?: JsonSchemaType;
21
+ projection?: ProjectionModel;
20
22
  rows?: Live<T>[];
21
23
  rowActions?: TableRowAction[];
22
24
  onSelectionChanged?: (selection: string[]) => void;
@@ -27,7 +29,8 @@ export type UseTableModelParams<T extends TableRow = TableRow> = {
27
29
  >;
28
30
 
29
31
  export const useTableModel = <T extends TableRow = TableRow>({
30
- table,
32
+ view,
33
+ schema,
31
34
  projection,
32
35
  rows,
33
36
  rowActions,
@@ -36,21 +39,24 @@ export const useTableModel = <T extends TableRow = TableRow>({
36
39
  onRowAction,
37
40
  ...props
38
41
  }: UseTableModelParams<T>): TableModel<T> | undefined => {
42
+ const selected = useSelected(view && fullyQualifiedId(view), 'multi');
43
+ const initialSelection = useMemo(() => selected, [view]);
44
+
39
45
  const [model, setModel] = useState<TableModel<T>>();
40
46
  useEffect(() => {
41
- if (!table || !projection) {
47
+ if (!view || !schema) {
42
48
  return;
43
49
  }
44
50
 
45
51
  let model: TableModel<T> | undefined;
46
52
  const t = setTimeout(async () => {
47
53
  model = new TableModel<T>({
48
- id: fullyQualifiedId(table),
49
- space: getSpace(table),
50
- view: table.view?.target,
54
+ view,
55
+ schema,
51
56
  projection,
52
57
  features,
53
58
  rowActions,
59
+ initialSelection,
54
60
  onRowAction,
55
61
  ...props,
56
62
  });
@@ -62,7 +68,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
62
68
  clearTimeout(t);
63
69
  void model?.close();
64
70
  };
65
- }, [table, projection, table?.view?.target, features, rowActions]); // TODO(burdon): Trigger if callbacks change?
71
+ }, [view, schema, projection, features, rowActions, initialSelection]); // TODO(burdon): Trigger if callbacks change?
66
72
 
67
73
  // Update data.
68
74
  useEffect(() => {
@@ -77,9 +83,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
77
83
  }
78
84
  }, [model, rows]);
79
85
 
80
- const { multiSelect, clear } = useSelectionActions(
81
- [table?.id, table?.view?.target?.query.typename].filter(isNonNullable),
82
- );
86
+ const { multiSelect, clear } = useSelectionActions([model?.id].filter(isNonNullable));
83
87
 
84
88
  useEffect(() => {
85
89
  if (!model) {
package/src/index.ts CHANGED
@@ -2,12 +2,11 @@
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';
9
9
  export * from './model';
10
- export * from './types';
11
10
  export * from './util';
12
11
 
13
12
  export { translations };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { computed, effect, signal, type ReadonlySignal } from '@preact/signals-core';
5
+ import { computed, effect, type Signal, signal, type ReadonlySignal } from '@preact/signals-core';
6
6
 
7
7
  import { Resource } from '@dxos/context';
8
8
 
@@ -12,7 +12,7 @@ import { touch } from '../util';
12
12
  export type SelectionMode = 'single' | 'multiple';
13
13
 
14
14
  export class SelectionModel<T extends TableRow> extends Resource {
15
- private readonly _selection = signal<Set<string>>(new Set());
15
+ private readonly _selection: Signal<Set<string>>;
16
16
 
17
17
  private readonly _validSelectedIds = computed<Set<string>>(() => {
18
18
  const rows = this._rows.value;
@@ -38,9 +38,11 @@ export class SelectionModel<T extends TableRow> extends Resource {
38
38
  constructor(
39
39
  private readonly _rows: ReadonlySignal<T[]>,
40
40
  private readonly _selectionMode: SelectionMode,
41
+ private readonly _initialSelection: string[],
41
42
  private readonly _onSelectionChanged?: () => void,
42
43
  ) {
43
44
  super();
45
+ this._selection = signal(new Set(this._initialSelection));
44
46
  }
45
47
 
46
48
  protected override async _open(): Promise<void> {
@@ -6,16 +6,16 @@ import { computed } from '@preact/signals-core';
6
6
  import { Schema } from 'effect';
7
7
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
8
 
9
- import { Obj, Ref } from '@dxos/echo';
9
+ import { Obj } from '@dxos/echo';
10
10
  import { TypedObject } from '@dxos/echo-schema';
11
11
  import { updateCounter } from '@dxos/echo-schema/testing';
12
12
  import { registerSignalsRuntime } from '@dxos/echo-signals';
13
+ import { live } from '@dxos/live-object';
13
14
  import { createEchoSchema } from '@dxos/live-object/testing';
14
- import { live } from '@dxos/react-client/echo';
15
- import { createView, ViewProjection } from '@dxos/schema';
15
+ import { createView } from '@dxos/schema';
16
16
 
17
17
  import { TableModel, type TableModelProps } from './table-model';
18
- import { TableType } from '../types';
18
+ import { TableView } from '../types';
19
19
 
20
20
  // TODO(burdon): Tests are disabled in project.json since they bring in plugin deps.
21
21
  // Restore once factored out into react-ui-table.
@@ -107,8 +107,7 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
107
107
 
108
108
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
109
109
  const schema = createEchoSchema(Test);
110
- const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: schema.jsonSchema });
111
- const projection = new ViewProjection(schema.jsonSchema, view);
112
- const table = Obj.make(TableType, { view: Ref.make(view) });
113
- return new TableModel({ id: table.id, space: undefined, view, projection, ...props });
110
+ const table = Obj.make(TableView, { sizes: {} });
111
+ const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
112
+ return new TableModel({ view, schema: schema.jsonSchema, ...props });
114
113
  };