@dxos/react-ui-table 0.8.4-main.3c1ae3b → 0.8.4-main.3fbcb4aa9b

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 (151) hide show
  1. package/dist/lib/browser/{chunk-CJQ6T4LL.mjs → chunk-FBURZBPH.mjs} +19 -26
  2. package/dist/lib/browser/chunk-FBURZBPH.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  4. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-V7A7ZA5E.mjs → chunk-YGC7LZJN.mjs} +611 -699
  6. package/dist/lib/browser/chunk-YGC7LZJN.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +1105 -1032
  8. package/dist/lib/browser/index.mjs.map +4 -4
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/testing/index.mjs +23 -18
  11. package/dist/lib/browser/testing/index.mjs.map +3 -3
  12. package/dist/lib/browser/translations.mjs +36 -0
  13. package/dist/lib/browser/translations.mjs.map +7 -0
  14. package/dist/lib/browser/types/index.mjs +2 -1
  15. package/dist/lib/node-esm/{chunk-T6PXZQ6W.mjs → chunk-DKKMSVKP.mjs} +19 -26
  16. package/dist/lib/node-esm/chunk-DKKMSVKP.mjs.map +7 -0
  17. package/dist/lib/node-esm/{chunk-P73ALX3N.mjs → chunk-GKIWDEZ7.mjs} +611 -698
  18. package/dist/lib/node-esm/chunk-GKIWDEZ7.mjs.map +7 -0
  19. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  20. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  21. package/dist/lib/node-esm/index.mjs +1105 -1032
  22. package/dist/lib/node-esm/index.mjs.map +4 -4
  23. package/dist/lib/node-esm/meta.json +1 -1
  24. package/dist/lib/node-esm/testing/index.mjs +23 -18
  25. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  26. package/dist/lib/node-esm/translations.mjs +37 -0
  27. package/dist/lib/node-esm/translations.mjs.map +7 -0
  28. package/dist/lib/node-esm/types/index.mjs +2 -1
  29. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  30. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/DynamicTable.d.ts +5 -4
  32. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +21 -23
  34. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/Relations.stories.d.ts +84 -84
  36. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  38. package/dist/types/src/components/Table/Table.d.ts +20 -21
  39. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  40. package/dist/types/src/components/Table/Table.stories.d.ts +86 -86
  41. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  42. package/dist/types/src/components/Table/TableContent.d.ts +17 -0
  43. package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
  44. package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
  45. package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
  46. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  47. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +3 -4
  48. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  49. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +2 -2
  50. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  51. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +23 -25
  52. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  53. package/dist/types/src/components/index.d.ts +0 -1
  54. package/dist/types/src/components/index.d.ts.map +1 -1
  55. package/dist/types/src/components/testing.d.ts +22 -13
  56. package/dist/types/src/components/testing.d.ts.map +1 -1
  57. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  58. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  59. package/dist/types/src/hooks/useProjectionModel.d.ts +2 -1
  60. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
  61. package/dist/types/src/hooks/useTableModel.d.ts +6 -5
  62. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  63. package/dist/types/src/index.d.ts +0 -2
  64. package/dist/types/src/index.d.ts.map +1 -1
  65. package/dist/types/src/model/modal-controller.d.ts +5 -1
  66. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  67. package/dist/types/src/model/selection-model.d.ts +18 -9
  68. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  69. package/dist/types/src/model/table-model.d.ts +100 -18
  70. package/dist/types/src/model/table-model.d.ts.map +1 -1
  71. package/dist/types/src/model/table-presentation.d.ts +3 -1
  72. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  73. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  74. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  75. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  76. package/dist/types/src/testing/useTestTableModel.d.ts +4 -5
  77. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  78. package/dist/types/src/translations.d.ts +21 -23
  79. package/dist/types/src/translations.d.ts.map +1 -1
  80. package/dist/types/src/types/Table.d.ts +13 -15
  81. package/dist/types/src/types/Table.d.ts.map +1 -1
  82. package/dist/types/src/util/dynamic-table.d.ts +11 -8
  83. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  84. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  85. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  86. package/dist/types/src/util/index.d.ts +2 -0
  87. package/dist/types/src/util/index.d.ts.map +1 -1
  88. package/dist/types/src/util/query-ast.d.ts +7 -0
  89. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  90. package/dist/types/src/util/schema.d.ts.map +1 -1
  91. package/dist/types/src/util/signals.d.ts.map +1 -1
  92. package/dist/types/src/util/sort.d.ts +12 -0
  93. package/dist/types/src/util/sort.d.ts.map +1 -0
  94. package/dist/types/src/util/table-buttons.d.ts +20 -20
  95. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  96. package/dist/types/src/util/table-controls.d.ts +4 -4
  97. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  98. package/dist/types/src/util/tag.d.ts.map +1 -1
  99. package/dist/types/tsconfig.tsbuildinfo +1 -1
  100. package/package.json +66 -76
  101. package/src/components/Table/ColumnActionsMenu.tsx +13 -11
  102. package/src/components/Table/ColumnSettings.tsx +7 -7
  103. package/src/components/Table/DynamicTable.stories.tsx +24 -22
  104. package/src/components/Table/DynamicTable.tsx +28 -36
  105. package/src/components/Table/Relations.stories.tsx +95 -47
  106. package/src/components/Table/RowActionsMenu.tsx +6 -4
  107. package/src/components/Table/Table.stories.tsx +87 -64
  108. package/src/components/Table/Table.tsx +42 -419
  109. package/src/components/Table/TableContent.tsx +421 -0
  110. package/src/components/{TableToolbar → Table}/TableToolbar.tsx +44 -45
  111. package/src/components/TableCellEditor/CellValidationMessage.tsx +4 -5
  112. package/src/components/TableCellEditor/FormCellEditor.tsx +61 -70
  113. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +14 -13
  114. package/src/components/TableCellEditor/TableCellEditor.tsx +14 -12
  115. package/src/components/index.ts +0 -1
  116. package/src/components/testing.ts +42 -16
  117. package/src/hooks/useAddRow.ts +8 -10
  118. package/src/hooks/useProjectionModel.ts +29 -9
  119. package/src/hooks/useTableModel.ts +27 -23
  120. package/src/index.ts +0 -4
  121. package/src/model/modal-controller.ts +23 -12
  122. package/src/model/selection-model.ts +75 -44
  123. package/src/model/table-model.test.ts +81 -54
  124. package/src/model/table-model.ts +411 -139
  125. package/src/model/table-presentation.test.ts +58 -49
  126. package/src/model/table-presentation.ts +34 -29
  127. package/src/playwright/TableManager.ts +10 -3
  128. package/src/playwright/playwright.config.ts +1 -1
  129. package/src/playwright/smoke.spec.ts +51 -7
  130. package/src/testing/useTestTableModel.ts +25 -22
  131. package/src/translations.ts +20 -22
  132. package/src/types/Table.ts +19 -30
  133. package/src/util/dynamic-table.test.ts +70 -0
  134. package/src/util/dynamic-table.ts +36 -17
  135. package/src/util/index.ts +2 -0
  136. package/src/util/query-ast.ts +22 -0
  137. package/src/util/sort.ts +90 -0
  138. package/src/util/table-buttons.ts +1 -1
  139. package/src/util/table-controls.ts +2 -2
  140. package/dist/lib/browser/chunk-CJQ6T4LL.mjs.map +0 -7
  141. package/dist/lib/browser/chunk-V7A7ZA5E.mjs.map +0 -7
  142. package/dist/lib/node-esm/chunk-P73ALX3N.mjs.map +0 -7
  143. package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs.map +0 -7
  144. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  145. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  146. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  147. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  148. package/dist/types/src/model/table-sorting.d.ts +0 -56
  149. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  150. package/src/components/TableToolbar/index.ts +0 -5
  151. package/src/model/table-sorting.ts +0 -234
@@ -5,26 +5,25 @@
5
5
  import type * as Schema from 'effect/Schema';
6
6
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
7
 
8
- import { Filter, Obj, type Type } from '@dxos/echo';
9
- import { Format, Ref, getValue } from '@dxos/echo/internal';
8
+ import { Entity, type Type } from '@dxos/echo';
9
+ import { Ref, getValue } from '@dxos/echo/internal';
10
10
  import { invariant } from '@dxos/invariant';
11
- import { getSnapshot } from '@dxos/live-object';
12
- import { getSpace } from '@dxos/react-client/echo';
13
11
  import { type Label, Popover } from '@dxos/react-ui';
14
- import { Form, type FormRootProps, omitId } from '@dxos/react-ui-form';
12
+ import { Form, type FormRootProps, type RefFieldProps } from '@dxos/react-ui-form';
15
13
  import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
16
14
  import { type FieldProjection } from '@dxos/schema';
17
15
  import { getDeep, isTruthy, setDeep } from '@dxos/util';
18
16
 
17
+ import { translationKey } from '#translations';
18
+
19
19
  import { type ModalController, type TableModel, type TableRow } from '../../model';
20
- import { translationKey } from '../../translations';
21
20
  import { narrowSchema } from '../../util';
22
21
 
23
- const createOptionLabel: Label = ['create new object label', { ns: translationKey }];
22
+ const createOptionLabel: Label = ['create-new-object.label', { ns: translationKey }];
24
23
 
25
- export type OnCreateHandler = (schema: Schema.Schema.AnyNoContext, values: any) => Parameters<typeof Ref.make>[0];
24
+ export type OnCreateHandler = (schema: Type.AnyEntity, values: any) => Parameters<typeof Ref.make>[0];
26
25
 
27
- export type FormCellEditorProps<T extends Type.Entity.Any = Type.Entity.Any> = {
26
+ export type FormCellEditorProps<T extends Type.AnyEntity = Type.AnyEntity> = {
28
27
  __gridScope: any;
29
28
  schema?: T;
30
29
  model?: TableModel;
@@ -34,7 +33,7 @@ export type FormCellEditorProps<T extends Type.Entity.Any = Type.Entity.Any> = {
34
33
  onCreate?: OnCreateHandler;
35
34
  } & Omit<FormRootProps<any>, 'values' | 'schema' | 'onCreate'>;
36
35
 
37
- export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
36
+ export const FormCellEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
38
37
  __gridScope,
39
38
  schema,
40
39
  model,
@@ -67,33 +66,12 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
67
66
  return narrowSchema(schema, [fieldProjection.field.path]);
68
67
  }, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
69
68
 
70
- const getSchema = useCallback(
71
- ({ typename }: { typename: string }) => {
72
- const space = getSpace(model!.view);
73
- invariant(space);
74
- const schema = space.db.schemaRegistry.query({ typename, location: ['database', 'runtime'] }).runSync()[0];
75
- return { space, schema };
76
- },
77
- [model],
78
- );
79
-
80
- const refSchema = useMemo(() => {
81
- if (fieldProjection.props.format === Format.TypeFormat.Ref && fieldProjection.props.referenceSchema) {
82
- const { schema } = getSchema({ typename: fieldProjection.props.referenceSchema });
83
- return schema;
84
- }
85
-
86
- return null;
87
- }, [fieldProjection.props.format, fieldProjection.props.referenceSchema, getSchema]);
88
-
89
- const createSchema = useMemo(() => (refSchema ? omitId(refSchema) : null), [refSchema]);
90
-
91
69
  const originalRow = useMemo<TableRow | undefined>(() => {
92
70
  if (model && contextEditing) {
93
71
  // Check if this is a draft cell and get the appropriate row data
94
72
  const cell = parseCellIndex(contextEditing.index);
95
73
  if (model.isDraftCell(cell)) {
96
- const draftRow = model.draftRows.value[cell.row];
74
+ const draftRow = model.getDraftRows()[cell.row];
97
75
  invariant(draftRow);
98
76
  return draftRow.data;
99
77
  } else {
@@ -106,9 +84,14 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
106
84
  return undefined;
107
85
  }, [model, contextEditing]);
108
86
 
109
- // NOTE: Important to get a snapshot to eject from the live object.
110
- const formValues = useMemo(() => (originalRow ? getSnapshot(originalRow) : {}), [originalRow]);
87
+ // NOTE: Important to get a mutable deep clone to eject from the echo object.
88
+ // TODO(wittjosiah): Consider using something like Obj.clone for this use case.
89
+ const initialFormValues = useMemo(() => (originalRow ? JSON.parse(JSON.stringify(originalRow)) : {}), [originalRow]);
90
+ const [formValues, setFormValues] = useState<any>(initialFormValues);
111
91
 
92
+ const handleValuesChanged = useCallback<NonNullable<FormRootProps<any>['onValuesChanged']>>((values) => {
93
+ setFormValues(values);
94
+ }, []);
112
95
  const handleOpenChange = useCallback((nextOpen: boolean) => {
113
96
  if (nextOpen === false) {
114
97
  setEditing(null);
@@ -119,24 +102,39 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
119
102
 
120
103
  const handleSave = useCallback<NonNullable<FormRootProps<any>['onSave']>>(
121
104
  (values) => {
105
+ if (!originalRow) {
106
+ return;
107
+ }
122
108
  const path = fieldProjection.field.path;
123
109
  const value = getDeep(values, [path]);
124
- setDeep(originalRow, [path], value);
110
+ // Use model's changeRow for consistent mutation handling.
111
+ if (model) {
112
+ model.changeRow(originalRow, (mutableRow) => {
113
+ setDeep(mutableRow, [path], value);
114
+ });
115
+ } else {
116
+ setDeep(originalRow, [path], value);
117
+ }
125
118
  contextEditing?.cellElement?.focus();
126
119
  setEditing(null);
127
120
  setLocalEditing(false);
128
121
  onSave?.();
129
122
  },
130
- [fieldProjection.field.path, onSave, contextEditing, originalRow],
123
+ [fieldProjection.field.path, onSave, contextEditing, originalRow, model],
131
124
  );
132
125
 
133
126
  const handleCreate = useCallback<NonNullable<FormRootProps<any>['onCreate']>>(
134
- (values) => {
135
- if (refSchema && onCreate) {
136
- const objectWithId = onCreate(refSchema, values);
137
- if (objectWithId) {
138
- const ref = Ref.make(objectWithId);
139
- const path = fieldProjection.field.path;
127
+ (schema, values) => {
128
+ const objectWithId = onCreate?.(schema, values);
129
+ if (objectWithId && originalRow) {
130
+ const ref = Ref.make(objectWithId);
131
+ const path = fieldProjection.field.path;
132
+ // Use model's changeRow for consistent mutation handling.
133
+ if (model) {
134
+ model.changeRow(originalRow, (mutableRow) => {
135
+ setDeep(mutableRow, [path], ref);
136
+ });
137
+ } else {
140
138
  setDeep(originalRow, [path], ref);
141
139
  }
142
140
  }
@@ -145,27 +143,20 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
145
143
  setLocalEditing(false);
146
144
  onSave?.();
147
145
  },
148
- [fieldProjection.field.path, onSave, contextEditing, originalRow, refSchema, onCreate],
146
+ [fieldProjection.field.path, onSave, contextEditing, originalRow, onCreate, model],
149
147
  );
150
148
 
151
- const handleQueryRefOptions = useCallback<NonNullable<FormRootProps<any>['onQueryRefOptions']>>(
152
- async ({ typename }) => {
153
- const { schema, space } = getSchema({ typename });
154
- if (model && schema && space) {
155
- const objects = await space.db.query(Filter.type(schema)).run();
156
- return objects
157
- .map((obj) => {
158
- return {
159
- dxn: Obj.getDXN(obj),
160
- label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
161
- };
162
- })
163
- .filter(isTruthy);
164
- }
165
-
166
- return [];
167
- },
168
- [model],
149
+ const getOptions = useCallback<NonNullable<RefFieldProps['getOptions']>>(
150
+ (results) =>
151
+ results
152
+ .map((obj) => {
153
+ return {
154
+ id: Entity.getDXN(obj).toString(),
155
+ label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
156
+ };
157
+ })
158
+ .filter(isTruthy),
159
+ [fieldProjection],
169
160
  );
170
161
 
171
162
  if (!editing) {
@@ -176,7 +167,7 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
176
167
  <Popover.Root open={editing} onOpenChange={handleOpenChange}>
177
168
  <Popover.VirtualTrigger virtualRef={anchorRef} />
178
169
  <Popover.Portal>
179
- <Popover.Content tabIndex={-1} classNames='popover-card-width density-fine'>
170
+ <Popover.Content tabIndex={-1} classNames='dx-card-popover-width dx-density-fine'>
180
171
  <Popover.Arrow />
181
172
  <Popover.Viewport>
182
173
  <Form.Root
@@ -184,15 +175,15 @@ export const FormCellEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
184
175
  autoFocus
185
176
  schema={narrowedSchema}
186
177
  values={formValues}
178
+ onValuesChanged={handleValuesChanged}
179
+ projection={model?.projection}
180
+ createInitialValuePath={fieldProjection.field.referencePath}
181
+ createOptionIcon='ph--plus--regular'
182
+ createOptionLabel={createOptionLabel}
183
+ db={model?.db}
184
+ getOptions={getOptions}
185
+ onCreate={handleCreate}
187
186
  onSave={handleSave}
188
- onQueryRefOptions={handleQueryRefOptions}
189
- {...(createSchema && {
190
- createSchema,
191
- createInitialValuePath: fieldProjection.field.referencePath,
192
- createOptionIcon: 'ph--plus--regular',
193
- createOptionLabel,
194
- onCreate: handleCreate,
195
- })}
196
187
  >
197
188
  <Form.Viewport>
198
189
  <Form.Content>
@@ -6,25 +6,26 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React from 'react';
7
7
 
8
8
  import { Obj } from '@dxos/echo';
9
- import { faker } from '@dxos/random';
9
+ import { View } from '@dxos/echo';
10
+ import { random } from '@dxos/random';
10
11
  import { withClientProvider } from '@dxos/react-client/testing';
11
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
13
- import { View } from '@dxos/schema';
13
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
14
+ import { ViewModel } from '@dxos/schema';
14
15
  import { Task } from '@dxos/types';
15
16
 
17
+ import { translations } from '#translations';
18
+
16
19
  import { useTestTableModel } from '../../testing';
17
- import { translations } from '../../translations';
18
20
  import { Table } from '../../types';
19
-
20
21
  import { TableCellEditor } from './TableCellEditor';
21
22
 
22
- type StoryProps = {
23
+ type DefaultStoryProps = {
23
24
  editing: GridEditing;
24
25
  };
25
26
 
26
27
  // TODO(burdon): Broken layout.
27
- const DefaultStory = ({ editing }: StoryProps) => {
28
+ const DefaultStory = ({ editing }: DefaultStoryProps) => {
28
29
  const { model, table } = useTestTableModel();
29
30
 
30
31
  if (!model || !table) {
@@ -45,22 +46,22 @@ const meta = {
45
46
  component: DefaultStory,
46
47
  render: DefaultStory,
47
48
  decorators: [
48
- withTheme,
49
- withLayout({ container: 'column' }),
49
+ withTheme(),
50
+ withLayout({ layout: 'column' }),
50
51
  withClientProvider({
51
52
  types: [View.View, Task.Task, Table.Table],
52
53
  createIdentity: true,
53
54
  createSpace: true,
54
55
  onCreateSpace: async ({ space }) => {
55
- const { view, jsonSchema } = await View.makeFromSpace({ space, typename: Task.Task.typename });
56
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: Task.Task.typename });
56
57
  const table = Table.make({ view, jsonSchema });
57
58
  space.db.add(table);
58
59
  Array.from({ length: 10 }).forEach(() => {
59
60
  space.db.add(
60
61
  Obj.make(Task.Task, {
61
- title: faker.person.fullName(),
62
- status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
63
- description: faker.lorem.sentence(),
62
+ title: random.person.fullName(),
63
+ status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
64
+ description: random.lorem.sentence(),
64
65
  }),
65
66
  );
66
67
  });
@@ -25,16 +25,9 @@ import {
25
25
  import { type FieldProjection } from '@dxos/schema';
26
26
 
27
27
  import { type ModalController, type TableModel } from '../../model';
28
-
29
28
  import { CellValidationMessage } from './CellValidationMessage';
30
29
  import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
31
30
 
32
- const editorSlots = {
33
- scroll: {
34
- className: '!plb-[--dx-grid-cell-editor-padding-block]',
35
- },
36
- };
37
-
38
31
  /**
39
32
  * Option to create new object/value.
40
33
  */
@@ -42,7 +35,7 @@ export type QueryResult = Pick<Completion, 'label'> & {
42
35
  data: any;
43
36
  };
44
37
 
45
- export type TableCellEditorProps<T extends Type.Entity.Any = Type.Entity.Any> = {
38
+ export type TableCellEditorProps<T extends Type.AnyEntity = Type.AnyEntity> = {
46
39
  schema?: T;
47
40
  model?: TableModel;
48
41
  modals?: ModalController;
@@ -51,7 +44,7 @@ export type TableCellEditorProps<T extends Type.Entity.Any = Type.Entity.Any> =
51
44
  onSave?: () => void;
52
45
  };
53
46
 
54
- export const TableValueEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
47
+ export const TableValueEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
55
48
  __gridScope,
56
49
  schema,
57
50
  model,
@@ -68,7 +61,7 @@ export const TableValueEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
68
61
  }
69
62
 
70
63
  const { col } = parseCellIndex(editing.index);
71
- const field = model.projection.fields[col];
64
+ const field = model.projection.getFields()[col];
72
65
  const fieldProjection = model.projection.getFieldProjection(field.id);
73
66
  invariant(fieldProjection);
74
67
  return fieldProjection;
@@ -115,7 +108,7 @@ export const TableCellEditor = ({
115
108
  }
116
109
 
117
110
  const { col } = parseCellIndex(editing.index);
118
- const field = model.projection.fields[col];
111
+ const field = model.projection.getFields()[col];
119
112
  const fieldProjection = model.projection.getFieldProjection(field.id);
120
113
  invariant(fieldProjection);
121
114
  return fieldProjection;
@@ -250,7 +243,16 @@ export const TableCellEditor = ({
250
243
  return (
251
244
  <>
252
245
  <CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
253
- <GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
246
+ <GridCellEditor
247
+ extensions={extensions}
248
+ slots={{
249
+ content: {
250
+ className: '!py-(--dx-grid-cell-editor-padding-block)',
251
+ },
252
+ }}
253
+ getCellContent={getCellContent}
254
+ onBlur={handleBlur}
255
+ />
254
256
  </>
255
257
  );
256
258
  };
@@ -3,4 +3,3 @@
3
3
  //
4
4
 
5
5
  export * from './Table';
6
- export * from './TableToolbar';
@@ -2,33 +2,59 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { Atom } from '@effect-atom/atom-react';
5
6
  import * as Schema from 'effect/Schema';
6
7
  import { useEffect } from 'react';
7
8
 
8
- import { Format } from '@dxos/echo';
9
- import { TypeEnum, TypedObject, setValue } from '@dxos/echo/internal';
10
- import { live } from '@dxos/live-object';
11
- import { faker } from '@dxos/random';
9
+ import { Format, Type } from '@dxos/echo';
10
+ import { TypeEnum } from '@dxos/echo/internal';
11
+ import { setValue } from '@dxos/effect';
12
+ import { random } from '@dxos/random';
12
13
  import { type ProjectionModel } from '@dxos/schema';
13
14
 
14
- export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
15
+ export const TestSchema = Schema.Struct({
15
16
  name: Schema.optional(Schema.String),
16
17
  age: Schema.optional(Schema.Number),
17
18
  active: Schema.optional(Schema.Boolean),
18
19
  netWorth: Schema.optional(Schema.Number),
19
- });
20
+ }).pipe(
21
+ Type.object({
22
+ typename: 'com.example.type.test',
23
+ version: '0.1.0',
24
+ }),
25
+ );
20
26
 
21
- export const createItems = (n: number) => {
22
- const { data } = live({
27
+ export type TestItem = {
28
+ name: string;
29
+ age: number;
30
+ active: boolean;
31
+ netWorth: number;
32
+ };
33
+
34
+ /**
35
+ * Creates an Atom containing test items data.
36
+ */
37
+ export const createItemsAtom = (n: number) => {
38
+ return Atom.make<{ data: TestItem[] }>({
23
39
  data: Array.from({ length: n }, () => ({
24
- name: faker.person.fullName(),
25
- age: faker.number.int({ min: 20, max: 70 }),
26
- active: faker.datatype.boolean(),
27
- netWorth: faker.number.int(),
40
+ name: random.person.fullName(),
41
+ age: random.number.int({ min: 20, max: 70 }),
42
+ active: random.datatype.boolean(),
43
+ netWorth: random.number.int(),
28
44
  })),
29
- });
45
+ }).pipe(Atom.keepAlive);
46
+ };
30
47
 
31
- return data;
48
+ /**
49
+ * Creates plain test items (non-reactive).
50
+ */
51
+ export const createItems = (n: number): TestItem[] => {
52
+ return Array.from({ length: n }, () => ({
53
+ name: random.person.fullName(),
54
+ age: random.number.int({ min: 20, max: 70 }),
55
+ active: random.datatype.boolean(),
56
+ netWorth: random.number.int(),
57
+ }));
32
58
  };
33
59
 
34
60
  export type SimulatorProps = {
@@ -59,7 +85,7 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
59
85
 
60
86
  const i = setInterval(() => {
61
87
  const rowIdx = Math.floor(Math.random() * items.length);
62
- const fields = projection.fields ?? [];
88
+ const fields = projection.getFields() ?? [];
63
89
  const columnIdx = Math.floor(Math.random() * fields.length);
64
90
  const field = fields[columnIdx];
65
91
  const item = items[rowIdx];
@@ -98,5 +124,5 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
98
124
  }, updateInterval);
99
125
 
100
126
  return () => clearInterval(i);
101
- }, [items, projection.fields, updateInterval]);
127
+ }, [items, projection.getFields(), updateInterval]);
102
128
  };
@@ -2,17 +2,15 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import type * as Schema from 'effect/Schema';
6
5
  import { useCallback } from 'react';
7
6
 
8
- import { type Space } from '@dxos/client/echo';
9
- import { Obj } from '@dxos/echo';
7
+ import { type Database, Obj, type Type } from '@dxos/echo';
10
8
 
11
9
  import { type InsertRowResult } from '../model';
12
10
 
13
- export type UseAddRowParams = {
14
- space?: Space;
15
- schema?: Schema.Schema.AnyNoContext;
11
+ export type UseAddRowProps = {
12
+ db?: Database.Database;
13
+ schema?: Type.AnyObj;
16
14
  };
17
15
 
18
16
  /**
@@ -20,12 +18,12 @@ export type UseAddRowParams = {
20
18
  * Returns a callback that attempts to create an object and returns boolean success/failure.
21
19
  * Can accept optional data to create objects with specific content (used for committing draft rows).
22
20
  */
23
- export const useAddRow = ({ space, schema }: UseAddRowParams) => {
21
+ export const useAddRow = ({ db, schema }: UseAddRowProps) => {
24
22
  return useCallback(
25
23
  (data?: any): InsertRowResult => {
26
- if (space && schema) {
24
+ if (db && schema) {
27
25
  try {
28
- space.db.add(Obj.make(schema, data ?? {}));
26
+ db.add(Obj.make(schema, data ?? {}));
29
27
  return 'final';
30
28
  } catch (error) {
31
29
  return 'draft';
@@ -33,6 +31,6 @@ export const useAddRow = ({ space, schema }: UseAddRowParams) => {
33
31
  }
34
32
  return 'final';
35
33
  },
36
- [space, schema],
34
+ [db, schema],
37
35
  );
38
36
  };
@@ -2,18 +2,38 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { useMemo } from 'react';
5
+ import { type Registry } from '@effect-atom/atom-react';
6
+ import { useState } from 'react';
6
7
 
7
- import { Type } from '@dxos/echo';
8
- import { ProjectionModel } from '@dxos/schema';
8
+ import { JsonSchema, Type } from '@dxos/echo';
9
+ import { useAsyncEffect } from '@dxos/react-ui';
10
+ import { ProjectionModel, createEchoChangeCallback } from '@dxos/schema';
9
11
 
10
12
  import { type Table } from '../types';
11
13
 
12
- export const useProjectionModel = <S extends Type.Entity.Any>(schema: S | undefined, table: Table.Table | undefined) =>
13
- useMemo(() => {
14
- if (schema && table?.view.target?.projection) {
15
- const projection = new ProjectionModel(Type.toJsonSchema(schema), table.view.target.projection);
14
+ export const useProjectionModel = <S extends Type.AnyEntity>(
15
+ schema: S | undefined,
16
+ table: Table.Table | undefined,
17
+ registry: Registry.Registry,
18
+ ) => {
19
+ const [projection, setProjection] = useState<ProjectionModel | undefined>();
20
+
21
+ useAsyncEffect(async () => {
22
+ if (schema && table) {
23
+ const view = await table.view.load();
24
+ // For mutable schemas (EchoSchema), use the live jsonSchema reference for reactivity.
25
+ // For immutable schemas, create a snapshot.
26
+ const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
27
+
28
+ // Always use createEchoChangeCallback since the view is ECHO-backed.
29
+ // Pass schema only when mutable to allow schema mutations.
30
+ const change = createEchoChangeCallback(view, Type.isMutable(schema) ? schema : undefined);
31
+
32
+ const projection = new ProjectionModel({ registry, view, baseSchema: jsonSchema, change });
16
33
  projection.normalizeView();
17
- return projection;
34
+ setProjection(projection);
18
35
  }
19
- }, [schema, table?.view.target?.projection]);
36
+ }, [schema, table, registry]);
37
+
38
+ return projection;
39
+ };
@@ -2,41 +2,48 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { effect } from '@preact/signals-core';
6
- import orderBy from 'lodash.orderby';
7
- import { useEffect, useMemo, useState } from 'react';
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
+ import { useContext, useEffect, useMemo, useState } from 'react';
8
7
 
9
- import { Obj } from '@dxos/echo';
10
- import { type Live } from '@dxos/live-object';
8
+ import { type Database, Obj } from '@dxos/echo';
11
9
  import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
12
10
  import { type ProjectionModel } from '@dxos/schema';
13
11
  import { isNonNullable } from '@dxos/util';
14
12
 
15
- import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
13
+ import {
14
+ TableModel,
15
+ type TableModelProps,
16
+ type TableRow,
17
+ type TableRowAction,
18
+ createEchoChangeCallback,
19
+ } from '../model';
16
20
  import { type Table } from '../types';
17
21
 
18
- export type UseTableModelParams<T extends TableRow = TableRow> = {
22
+ export type UseTableModelProps<T extends TableRow = TableRow> = {
19
23
  object?: Table.Table;
20
24
  projection?: ProjectionModel;
21
- rows?: Live<T>[];
25
+ db?: Database.Database;
26
+ rows?: T[];
22
27
  rowActions?: TableRowAction[];
23
28
  onSelectionChanged?: (selection: string[]) => void;
24
29
  onRowAction?: (actionId: string, data: T) => void;
25
30
  } & Pick<
26
31
  TableModelProps<T>,
27
- 'features' | 'onInsertRow' | 'onDeleteRows' | 'onDeleteColumn' | 'onCellUpdate' | 'onRowOrderChange'
32
+ 'features' | 'onInsertRow' | 'onDeleteRows' | 'onColumnDelete' | 'onCellUpdate' | 'onRowOrderChange'
28
33
  >;
29
34
 
30
35
  export const useTableModel = <T extends TableRow = TableRow>({
31
36
  object,
32
37
  projection,
38
+ db,
33
39
  rows,
34
40
  rowActions,
35
41
  features,
36
42
  onSelectionChanged,
37
43
  onRowAction,
38
44
  ...props
39
- }: UseTableModelParams<T>): TableModel<T> | undefined => {
45
+ }: UseTableModelProps<T>): TableModel<T> | undefined => {
46
+ const registry = useContext(RegistryContext);
40
47
  const selected = useSelected(object && Obj.getDXN(object).toString(), 'multi');
41
48
  const initialSelection = useMemo(() => selected, [object]);
42
49
 
@@ -49,8 +56,11 @@ export const useTableModel = <T extends TableRow = TableRow>({
49
56
  let model: TableModel<T> | undefined;
50
57
  const t = setTimeout(async () => {
51
58
  model = new TableModel<T>({
59
+ registry,
52
60
  object,
53
61
  projection,
62
+ db,
63
+ change: createEchoChangeCallback<T>(object),
54
64
  features,
55
65
  rowActions,
56
66
  initialSelection,
@@ -66,18 +76,12 @@ export const useTableModel = <T extends TableRow = TableRow>({
66
76
  void model?.close();
67
77
  };
68
78
  // TODO(burdon): Trigger if callbacks change?
69
- }, [object, projection, features, rowActions, initialSelection]);
79
+ }, [registry, object, projection, features, rowActions, initialSelection]);
70
80
 
71
- // Update data.
81
+ // Update data when rows change.
72
82
  useEffect(() => {
73
- if (rows) {
74
- /*
75
- * Sort all objects by string id field as a temporary workaround for query ordering issues
76
- * Reference: https://github.com/dxos/dxos/pull/9409
77
- */
78
- // TODO(ZaymonFC): Remove this workaround once unstable query ordering issue is resolved
79
- const sortedRows = orderBy(rows, [(row) => String(row.id)], ['asc']);
80
- model?.setRows(sortedRows);
83
+ if (rows && model) {
84
+ model.setRows(rows);
81
85
  }
82
86
  }, [model, rows]);
83
87
 
@@ -88,8 +92,8 @@ export const useTableModel = <T extends TableRow = TableRow>({
88
92
  return;
89
93
  }
90
94
 
91
- const unsubscribe = effect(() => {
92
- const selectedItems = [...model.selection.selection.value];
95
+ const unsubscribe = registry.subscribe(model.selection.selectionAtom, () => {
96
+ const selectedItems = [...model.selection.selection];
93
97
  multiSelect(selectedItems);
94
98
  onSelectionChanged?.(selectedItems);
95
99
  });
@@ -99,7 +103,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
99
103
  clear();
100
104
  unsubscribe();
101
105
  };
102
- }, [model, onSelectionChanged]);
106
+ }, [registry, model, onSelectionChanged]);
103
107
 
104
108
  return model;
105
109
  };
package/src/index.ts CHANGED
@@ -2,11 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { translations } from './translations';
6
-
7
5
  export * from './components';
8
6
  export * from './hooks';
9
7
  export * from './model';
10
8
  export * from './util';
11
-
12
- export { translations };