@dxos/react-ui-table 0.8.1-staging.391c573 → 0.8.1-staging.97aedb1

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 (191) hide show
  1. package/dist/lib/browser/{chunk-3FR6NHPD.mjs → chunk-YWNRUF4J.mjs} +8 -6
  2. package/dist/lib/browser/chunk-YWNRUF4J.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +625 -225
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types/index.mjs +1 -1
  7. package/dist/lib/node/{chunk-NB3SFHGA.cjs → chunk-TGG6OZ54.cjs} +10 -8
  8. package/dist/lib/node/chunk-TGG6OZ54.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +618 -220
  10. package/dist/lib/node/index.cjs.map +4 -4
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/types/index.cjs +3 -3
  13. package/dist/lib/node/types/index.cjs.map +1 -1
  14. package/dist/lib/node-esm/{chunk-RLVVOZW5.mjs → chunk-KKLP54L3.mjs} +8 -6
  15. package/dist/lib/node-esm/chunk-KKLP54L3.mjs.map +7 -0
  16. package/dist/lib/node-esm/index.mjs +625 -225
  17. package/dist/lib/node-esm/index.mjs.map +4 -4
  18. package/dist/lib/node-esm/meta.json +1 -1
  19. package/dist/lib/node-esm/types/index.mjs +1 -1
  20. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  21. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  22. package/dist/types/src/components/Table/DynamicTable.d.ts +24 -0
  23. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -0
  24. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +10 -0
  25. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -0
  26. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/Table.d.ts +1 -0
  28. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Table.stories.d.ts +4 -3
  30. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/index.d.ts +1 -0
  32. package/dist/types/src/components/Table/index.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/relations.stories.d.ts +9 -0
  34. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -0
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/testing.d.ts +7 -7
  38. package/dist/types/src/components/testing.d.ts.map +1 -1
  39. package/dist/types/src/hooks/useTableModel.d.ts +8 -9
  40. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  41. package/dist/types/src/model/index.d.ts +1 -0
  42. package/dist/types/src/model/index.d.ts.map +1 -1
  43. package/dist/types/src/model/selection-model.d.ts +6 -3
  44. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  45. package/dist/types/src/model/table-model.d.ts +42 -16
  46. package/dist/types/src/model/table-model.d.ts.map +1 -1
  47. package/dist/types/src/model/table-presentation.d.ts +4 -4
  48. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  49. package/dist/types/src/model/table-sorting.d.ts +2 -2
  50. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  51. package/dist/types/src/types/table.d.ts +7 -32
  52. package/dist/types/src/types/table.d.ts.map +1 -1
  53. package/dist/types/src/util/dynamic-table.d.ts +31 -0
  54. package/dist/types/src/util/dynamic-table.d.ts.map +1 -0
  55. package/dist/types/src/util/index.d.ts +1 -0
  56. package/dist/types/src/util/index.d.ts.map +1 -1
  57. package/dist/types/src/util/table-controls.d.ts +2 -2
  58. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  59. package/dist/types/src/util/table.d.ts +5 -3
  60. package/dist/types/src/util/table.d.ts.map +1 -1
  61. package/package.json +37 -36
  62. package/src/components/Table/ColumnActionsMenu.tsx +6 -4
  63. package/src/components/Table/ColumnSettings.tsx +5 -6
  64. package/src/components/Table/CreateRefPanel.tsx +3 -3
  65. package/src/components/Table/DynamicTable.stories.tsx +155 -0
  66. package/src/components/Table/DynamicTable.tsx +104 -0
  67. package/src/components/Table/RefPanel.tsx +3 -3
  68. package/src/components/Table/RowActionsMenu.tsx +18 -3
  69. package/src/components/Table/Table.stories.tsx +170 -115
  70. package/src/components/Table/Table.tsx +71 -26
  71. package/src/components/Table/index.ts +1 -0
  72. package/src/components/Table/relations.stories.tsx +113 -0
  73. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +15 -5
  74. package/src/components/TableCellEditor/TableCellEditor.tsx +102 -4
  75. package/src/components/testing.ts +0 -1
  76. package/src/hooks/useTableModel.ts +35 -14
  77. package/src/model/index.ts +1 -0
  78. package/src/model/selection-model.ts +12 -2
  79. package/src/model/table-model.test.ts +2 -2
  80. package/src/model/table-model.ts +117 -32
  81. package/src/model/table-presentation.test.ts +2 -2
  82. package/src/model/table-presentation.ts +94 -22
  83. package/src/model/table-sorting.ts +3 -2
  84. package/src/playwright/smoke.spec.ts +2 -3
  85. package/src/types/table.ts +9 -10
  86. package/src/util/dynamic-table.ts +103 -0
  87. package/src/util/index.ts +1 -0
  88. package/src/util/table-controls.ts +4 -4
  89. package/src/util/table.ts +68 -65
  90. package/dist/lib/browser/_deprecated/index.mjs +0 -1483
  91. package/dist/lib/browser/_deprecated/index.mjs.map +0 -7
  92. package/dist/lib/browser/chunk-3FR6NHPD.mjs.map +0 -7
  93. package/dist/lib/node/_deprecated/index.cjs +0 -1511
  94. package/dist/lib/node/_deprecated/index.cjs.map +0 -7
  95. package/dist/lib/node/chunk-NB3SFHGA.cjs.map +0 -7
  96. package/dist/lib/node-esm/_deprecated/index.mjs +0 -1485
  97. package/dist/lib/node-esm/_deprecated/index.mjs.map +0 -7
  98. package/dist/lib/node-esm/chunk-RLVVOZW5.mjs.map +0 -7
  99. package/dist/types/src/_deprecated/components/Cell/Cell.d.ts +0 -12
  100. package/dist/types/src/_deprecated/components/Cell/Cell.d.ts.map +0 -1
  101. package/dist/types/src/_deprecated/components/Cell/CellCombobox.d.ts +0 -13
  102. package/dist/types/src/_deprecated/components/Cell/CellCombobox.d.ts.map +0 -1
  103. package/dist/types/src/_deprecated/components/Cell/index.d.ts +0 -2
  104. package/dist/types/src/_deprecated/components/Cell/index.d.ts.map +0 -1
  105. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.d.ts +0 -23
  106. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.d.ts.map +0 -1
  107. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.stories.d.ts +0 -36
  108. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.stories.d.ts.map +0 -1
  109. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.d.ts +0 -15
  110. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.d.ts.map +0 -1
  111. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.stories.d.ts +0 -84
  112. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.stories.d.ts.map +0 -1
  113. package/dist/types/src/_deprecated/components/ColumnMenu/index.d.ts +0 -3
  114. package/dist/types/src/_deprecated/components/ColumnMenu/index.d.ts.map +0 -1
  115. package/dist/types/src/_deprecated/components/Table/Table.d.ts +0 -20
  116. package/dist/types/src/_deprecated/components/Table/Table.d.ts.map +0 -1
  117. package/dist/types/src/_deprecated/components/Table/Table.stories.d.ts +0 -116
  118. package/dist/types/src/_deprecated/components/Table/Table.stories.d.ts.map +0 -1
  119. package/dist/types/src/_deprecated/components/Table/TableBody.d.ts +0 -12
  120. package/dist/types/src/_deprecated/components/Table/TableBody.d.ts.map +0 -1
  121. package/dist/types/src/_deprecated/components/Table/TableContext.d.ts +0 -13
  122. package/dist/types/src/_deprecated/components/Table/TableContext.d.ts.map +0 -1
  123. package/dist/types/src/_deprecated/components/Table/TableHead.d.ts +0 -9
  124. package/dist/types/src/_deprecated/components/Table/TableHead.d.ts.map +0 -1
  125. package/dist/types/src/_deprecated/components/Table/TableRootContext.d.ts +0 -17
  126. package/dist/types/src/_deprecated/components/Table/TableRootContext.d.ts.map +0 -1
  127. package/dist/types/src/_deprecated/components/Table/index.d.ts +0 -5
  128. package/dist/types/src/_deprecated/components/Table/index.d.ts.map +0 -1
  129. package/dist/types/src/_deprecated/components/Table/props.d.ts +0 -42
  130. package/dist/types/src/_deprecated/components/Table/props.d.ts.map +0 -1
  131. package/dist/types/src/_deprecated/components/index.d.ts +0 -4
  132. package/dist/types/src/_deprecated/components/index.d.ts.map +0 -1
  133. package/dist/types/src/_deprecated/helpers.d.ts +0 -91
  134. package/dist/types/src/_deprecated/helpers.d.ts.map +0 -1
  135. package/dist/types/src/_deprecated/hooks/index.d.ts +0 -5
  136. package/dist/types/src/_deprecated/hooks/index.d.ts.map +0 -1
  137. package/dist/types/src/_deprecated/hooks/useColumnSizing.d.ts +0 -12
  138. package/dist/types/src/_deprecated/hooks/useColumnSizing.d.ts.map +0 -1
  139. package/dist/types/src/_deprecated/hooks/useColumnSorting.d.ts +0 -15
  140. package/dist/types/src/_deprecated/hooks/useColumnSorting.d.ts.map +0 -1
  141. package/dist/types/src/_deprecated/hooks/usePinLastRow.d.ts +0 -3
  142. package/dist/types/src/_deprecated/hooks/usePinLastRow.d.ts.map +0 -1
  143. package/dist/types/src/_deprecated/hooks/useRowSelection.d.ts +0 -7
  144. package/dist/types/src/_deprecated/hooks/useRowSelection.d.ts.map +0 -1
  145. package/dist/types/src/_deprecated/index.d.ts +0 -8
  146. package/dist/types/src/_deprecated/index.d.ts.map +0 -1
  147. package/dist/types/src/_deprecated/schema/Schema.stories.d.ts +0 -11
  148. package/dist/types/src/_deprecated/schema/Schema.stories.d.ts.map +0 -1
  149. package/dist/types/src/_deprecated/schema/column-utils.d.ts +0 -7
  150. package/dist/types/src/_deprecated/schema/column-utils.d.ts.map +0 -1
  151. package/dist/types/src/_deprecated/schema/index.d.ts +0 -3
  152. package/dist/types/src/_deprecated/schema/index.d.ts.map +0 -1
  153. package/dist/types/src/_deprecated/schema/types.d.ts +0 -27
  154. package/dist/types/src/_deprecated/schema/types.d.ts.map +0 -1
  155. package/dist/types/src/_deprecated/theme.d.ts +0 -29
  156. package/dist/types/src/_deprecated/theme.d.ts.map +0 -1
  157. package/dist/types/src/_deprecated/translations.d.ts +0 -21
  158. package/dist/types/src/_deprecated/translations.d.ts.map +0 -1
  159. package/dist/types/src/_deprecated/types.d.ts +0 -26
  160. package/dist/types/src/_deprecated/types.d.ts.map +0 -1
  161. package/src/_deprecated/components/Cell/Cell.tsx +0 -88
  162. package/src/_deprecated/components/Cell/CellCombobox.tsx +0 -103
  163. package/src/_deprecated/components/Cell/index.ts +0 -5
  164. package/src/_deprecated/components/ColumnMenu/ColumnMenu.stories.tsx +0 -129
  165. package/src/_deprecated/components/ColumnMenu/ColumnMenu.tsx +0 -182
  166. package/src/_deprecated/components/ColumnMenu/ColumnSettings.stories.tsx +0 -108
  167. package/src/_deprecated/components/ColumnMenu/ColumnSettings.tsx +0 -207
  168. package/src/_deprecated/components/ColumnMenu/index.ts +0 -6
  169. package/src/_deprecated/components/Table/Table.stories.tsx +0 -508
  170. package/src/_deprecated/components/Table/Table.tsx +0 -301
  171. package/src/_deprecated/components/Table/TableBody.tsx +0 -82
  172. package/src/_deprecated/components/Table/TableContext.tsx +0 -34
  173. package/src/_deprecated/components/Table/TableHead.tsx +0 -78
  174. package/src/_deprecated/components/Table/TableRootContext.ts +0 -39
  175. package/src/_deprecated/components/Table/index.ts +0 -8
  176. package/src/_deprecated/components/Table/props.ts +0 -60
  177. package/src/_deprecated/components/index.ts +0 -7
  178. package/src/_deprecated/helpers.tsx +0 -526
  179. package/src/_deprecated/hooks/index.ts +0 -8
  180. package/src/_deprecated/hooks/useColumnSizing.ts +0 -46
  181. package/src/_deprecated/hooks/useColumnSorting.ts +0 -64
  182. package/src/_deprecated/hooks/usePinLastRow.ts +0 -47
  183. package/src/_deprecated/hooks/useRowSelection.ts +0 -40
  184. package/src/_deprecated/index.ts +0 -12
  185. package/src/_deprecated/schema/Schema.stories.tsx +0 -59
  186. package/src/_deprecated/schema/column-utils.ts +0 -54
  187. package/src/_deprecated/schema/index.ts +0 -6
  188. package/src/_deprecated/schema/types.ts +0 -40
  189. package/src/_deprecated/theme.ts +0 -82
  190. package/src/_deprecated/translations.ts +0 -26
  191. package/src/_deprecated/types.ts +0 -38
@@ -4,61 +4,73 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import { type Meta } from '@storybook/react';
8
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
+ import { type StoryObj, type Meta } from '@storybook/react';
8
+ import React, { useCallback, useMemo, useRef } from 'react';
9
9
 
10
+ import { FormatEnum, ImmutableSchema } from '@dxos/echo-schema';
10
11
  import { invariant } from '@dxos/invariant';
11
12
  import { useGlobalFilteredObjects } from '@dxos/plugin-search';
12
13
  import { faker } from '@dxos/random';
14
+ import { useClient } from '@dxos/react-client';
13
15
  import { Filter, useQuery, useSchema, create } from '@dxos/react-client/echo';
14
16
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
15
- import { useDefaultValue } from '@dxos/react-ui';
16
17
  import { ViewEditor } from '@dxos/react-ui-form';
17
18
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
18
- import { ViewProjection, ViewType } from '@dxos/schema';
19
+ import { getSchemaFromPropertyDefinitions, ViewProjection, ViewType } from '@dxos/schema';
20
+ import { Testing, createObjectFactory } from '@dxos/schema/testing';
19
21
  import { withLayout, withTheme } from '@dxos/storybook-utils';
20
22
 
21
23
  import { Table, type TableController } from './Table';
22
- import { useTableModel, type UseTableModelParams } from '../../hooks';
24
+ import { useTableModel } from '../../hooks';
23
25
  import { TablePresentation } from '../../model';
24
26
  import translations from '../../translations';
25
27
  import { TableType } from '../../types';
26
28
  import { initializeTable } from '../../util';
27
29
  import { TableToolbar } from '../TableToolbar';
28
- import { createItems, createTable, type SimulatorProps, useSimulator } from '../testing';
29
30
 
30
31
  // NOTE(ZaymonFC): We rely on this seed being 0 in the smoke tests.
31
32
  faker.seed(0);
32
33
 
33
- //
34
- // Story components.
35
- //
36
-
37
- const DefaultStory = () => {
34
+ /**
35
+ * Custom hook to create and manage a test table model for storybook demonstrations.
36
+ * Provides table data, schema, and handlers for table operations.
37
+ */
38
+ const useTestTableModel = () => {
39
+ const client = useClient();
38
40
  const { space } = useClientProvider();
39
- invariant(space);
40
-
41
- const tables = useQuery(space, Filter.schema(TableType));
42
- const [table, setTable] = useState<TableType>();
43
- const schema = useSchema(space, table?.view?.target?.query.typename);
44
41
 
45
- useEffect(() => {
46
- if (space && tables.length && !table) {
47
- const table = tables[0];
48
- invariant(table.view);
49
- setTable(table);
50
- }
51
- }, [space, tables]);
42
+ const filter = useMemo(() => Filter.schema(TableType), []);
43
+ const tables = useQuery(space, filter);
44
+ const table = useMemo(() => tables.at(0), [tables]);
45
+ const schema = useSchema(client, space, table?.view?.target?.query.typename);
52
46
 
53
47
  const projection = useMemo(() => {
54
48
  if (schema && table?.view?.target) {
55
- return new ViewProjection(schema, table.view.target);
49
+ return new ViewProjection(schema.jsonSchema, table.view.target);
56
50
  }
57
51
  }, [schema, table?.view?.target]);
58
52
 
53
+ const features = useMemo(
54
+ () => ({
55
+ selection: { enabled: true, mode: 'multiple' as const },
56
+ dataEditable: true,
57
+ schemaEditable: !(schema instanceof ImmutableSchema),
58
+ }),
59
+ [schema],
60
+ );
61
+
59
62
  const objects = useQuery(space, schema ? Filter.schema(schema) : Filter.nothing());
60
63
  const filteredObjects = useGlobalFilteredObjects(objects);
61
64
 
65
+ const tableRef = useRef<TableController>(null);
66
+ const handleCellUpdate = useCallback((cell: any) => {
67
+ tableRef.current?.update?.(cell);
68
+ }, []);
69
+
70
+ const handleRowOrderChange = useCallback(() => {
71
+ tableRef.current?.update?.();
72
+ }, []);
73
+
62
74
  const handleInsertRow = useCallback(() => {
63
75
  if (space && schema) {
64
76
  space.db.add(create(schema, {}));
@@ -68,7 +80,7 @@ const DefaultStory = () => {
68
80
  const handleDeleteRows = useCallback(
69
81
  (_: number, objects: any[]) => {
70
82
  for (const object of objects) {
71
- space.db.remove(object);
83
+ space?.db.remove(object);
72
84
  }
73
85
  },
74
86
  [space],
@@ -80,27 +92,19 @@ const DefaultStory = () => {
80
92
  projection.deleteFieldProjection(fieldId);
81
93
  }
82
94
  },
83
- [table, projection],
95
+ [projection],
84
96
  );
85
97
 
86
- const tableRef = useRef<TableController>(null);
87
- const handleCellUpdate = useCallback((cell: any) => {
88
- tableRef.current?.update?.(cell);
89
- }, []);
90
-
91
- const handleRowOrderChanged = useCallback(() => {
92
- tableRef.current?.update?.();
93
- }, []);
94
-
95
98
  const model = useTableModel({
96
99
  table,
97
100
  projection,
98
- objects: filteredObjects,
101
+ features,
102
+ rows: filteredObjects,
99
103
  onInsertRow: handleInsertRow,
100
104
  onDeleteRows: handleDeleteRows,
101
105
  onDeleteColumn: handleDeleteColumn,
102
106
  onCellUpdate: handleCellUpdate,
103
- onRowOrderChanged: handleRowOrderChanged,
107
+ onRowOrderChange: handleRowOrderChange,
104
108
  });
105
109
 
106
110
  const presentation = useMemo(() => {
@@ -109,7 +113,7 @@ const DefaultStory = () => {
109
113
  }
110
114
  }, [model]);
111
115
 
112
- const handleAction = useCallback(
116
+ const handleToolbarAction = useCallback(
113
117
  (action: { type: string }) => {
114
118
  switch (action.type) {
115
119
  case 'on-thread-create': {
@@ -129,16 +133,55 @@ const DefaultStory = () => {
129
133
  [table, model],
130
134
  );
131
135
 
132
- const onTypenameChanged = useCallback(
136
+ return {
137
+ schema,
138
+ table,
139
+ tableRef,
140
+ model,
141
+ presentation,
142
+ space,
143
+ handleToolbarAction,
144
+ handleDeleteRows,
145
+ handleDeleteColumn,
146
+ };
147
+ };
148
+
149
+ const StoryViewEditor = () => {
150
+ const { table, space, schema, handleDeleteColumn } = useTestTableModel();
151
+
152
+ const handleTypenameChanged = useCallback(
133
153
  (typename: string) => {
134
154
  if (table?.view?.target) {
135
- schema?.updateTypename(typename);
155
+ invariant(schema);
156
+ schema.mutable.updateTypename(typename);
136
157
  table.view.target.query.typename = typename;
137
158
  }
138
159
  },
139
- [table?.view?.target, schema],
160
+ [schema, table?.view?.target],
140
161
  );
141
162
 
163
+ if (!table || !schema || !table.view?.target) {
164
+ return null;
165
+ }
166
+
167
+ return (
168
+ <ViewEditor
169
+ registry={space?.db.schemaRegistry}
170
+ schema={schema}
171
+ view={table.view.target!}
172
+ onTypenameChanged={handleTypenameChanged}
173
+ onDelete={handleDeleteColumn}
174
+ />
175
+ );
176
+ };
177
+
178
+ //
179
+ // Story components.
180
+ //
181
+
182
+ const DefaultStory = () => {
183
+ const { schema, table, tableRef, model, presentation, handleToolbarAction } = useTestTableModel();
184
+
142
185
  if (!schema || !table) {
143
186
  return <div />;
144
187
  }
@@ -146,22 +189,13 @@ const DefaultStory = () => {
146
189
  return (
147
190
  <div className='grow grid grid-cols-[1fr_350px]'>
148
191
  <div className='grid grid-rows-[min-content_1fr] min-bs-0 overflow-hidden'>
149
- <TableToolbar classNames='border-b border-separator' onAction={handleAction} />
192
+ <TableToolbar classNames='border-be border-separator' onAction={handleToolbarAction} />
150
193
  <Table.Root>
151
194
  <Table.Main ref={tableRef} model={model} presentation={presentation} ignoreAttention />
152
195
  </Table.Root>
153
196
  </div>
154
197
  <div className='flex flex-col h-full border-l border-separator overflow-y-auto'>
155
- {table.view?.target && (
156
- <ViewEditor
157
- registry={space?.db.schemaRegistry}
158
- schema={schema}
159
- view={table.view.target!}
160
- onTypenameChanged={onTypenameChanged}
161
- onDelete={handleDeleteColumn}
162
- />
163
- )}
164
-
198
+ <StoryViewEditor />
165
199
  <SyntaxHighlighter language='json' className='w-full text-xs'>
166
200
  {JSON.stringify({ view: table.view?.target, schema }, null, 2)}
167
201
  </SyntaxHighlighter>
@@ -170,57 +204,16 @@ const DefaultStory = () => {
170
204
  );
171
205
  };
172
206
 
173
- type StoryProps = {
174
- rows?: number;
175
- } & Pick<SimulatorProps, 'insertInterval' | 'updateInterval'>;
176
-
177
- const _TablePerformanceStory = (props: StoryProps) => {
178
- const getDefaultRows = useCallback(() => 10, []);
179
- const rows = useDefaultValue(props.rows, getDefaultRows);
180
- const table = useMemo(() => createTable(), []);
181
- const items = useMemo(() => createItems(rows), [rows]);
182
- const itemsRef = useRef(items);
183
- const simulatorProps = useMemo(() => ({ table, items, ...props }), [table, items, props]);
184
- useSimulator(simulatorProps);
185
-
186
- const handleDeleteRows = useCallback<NonNullable<UseTableModelParams<any>['onDeleteRows']>>((row) => {
187
- itemsRef.current.splice(row, 1);
188
- }, []);
189
-
190
- const handleDeleteColumn = useCallback<NonNullable<UseTableModelParams<any>['onDeleteColumn']>>(
191
- (fieldId) => {
192
- if (table && table.view?.target) {
193
- const fieldPosition = table.view.target!.fields.findIndex((field) => field.id === fieldId);
194
- table.view.target!.fields.splice(fieldPosition, 1);
195
- }
196
- },
197
- [table],
198
- );
199
-
200
- const tableRef = useRef<TableController>(null);
201
- const model = useTableModel({
202
- table,
203
- objects: items as any[],
204
- onDeleteRows: handleDeleteRows,
205
- onDeleteColumn: handleDeleteColumn,
206
- onCellUpdate: (cell) => tableRef.current?.update?.(cell),
207
- onRowOrderChanged: () => tableRef.current?.update?.(),
208
- });
209
-
210
- return (
211
- <Table.Root>
212
- <Table.Main ref={tableRef} model={model} />
213
- </Table.Root>
214
- );
215
- };
207
+ type StoryProps = { rows?: number };
216
208
 
217
209
  //
218
210
  // Story definitions.
219
211
  //
220
212
 
213
+ // TODO(burdon): Need super simple story.
214
+
221
215
  const meta: Meta<StoryProps> = {
222
216
  title: 'ui/react-ui-table/Table',
223
- component: Table.Main as any,
224
217
  render: DefaultStory,
225
218
  parameters: { translations },
226
219
  decorators: [
@@ -228,9 +221,9 @@ const meta: Meta<StoryProps> = {
228
221
  types: [TableType, ViewType],
229
222
  createIdentity: true,
230
223
  createSpace: true,
231
- onSpaceCreated: async ({ space }) => {
224
+ onSpaceCreated: async ({ client, space }) => {
232
225
  const table = space.db.add(create(TableType, {}));
233
- const schema = await initializeTable({ space, table, initialRow: false });
226
+ const schema = await initializeTable({ client, space, table, initialRow: false });
234
227
  Array.from({ length: 10 }).map(() => {
235
228
  return space.db.add(
236
229
  create(schema, {
@@ -249,20 +242,82 @@ export default meta;
249
242
 
250
243
  export const Default = {};
251
244
 
252
- // TODO(ZaymonFC): Restore the performance stories.
253
- // type Story = StoryObj<StoryProps>;
254
- // export const Mutations: Story = {
255
- // render: TablePerformanceStory,
256
- // args: {
257
- // rows: 1000,
258
- // updateInterval: 1,
259
- // },
260
- // };
261
-
262
- // export const RapidInsertions: Story = {
263
- // render: TablePerformanceStory,
264
- // args: {
265
- // rows: 0,
266
- // insertInterval: 100,
267
- // },
268
- // };
245
+ export const StaticSchema: StoryObj = {
246
+ render: DefaultStory,
247
+ parameters: { translations },
248
+ decorators: [
249
+ withClientProvider({
250
+ types: [TableType, ViewType, Testing.Contact, Testing.Org],
251
+ createIdentity: true,
252
+ createSpace: true,
253
+ onSpaceCreated: async ({ client, space }) => {
254
+ const table = space.db.add(create(TableType, {}));
255
+ await initializeTable({ client, space, table, typename: Testing.Contact.typename });
256
+
257
+ const factory = createObjectFactory(space.db, faker as any);
258
+ await factory([
259
+ { type: Testing.Contact, count: 10 },
260
+ { type: Testing.Org, count: 1 },
261
+ ]);
262
+ },
263
+ }),
264
+ withTheme,
265
+ withLayout({ fullscreen: true, tooltips: true }),
266
+ ],
267
+ };
268
+
269
+ export const Tags: Meta<StoryProps> = {
270
+ title: 'ui/react-ui-table/Table',
271
+ render: DefaultStory,
272
+ parameters: { translations },
273
+ decorators: [
274
+ withClientProvider({
275
+ types: [TableType, ViewType],
276
+ createIdentity: true,
277
+ createSpace: true,
278
+ onSpaceCreated: async ({ client, space }) => {
279
+ // Configure schema.
280
+ const typename = 'example.com/SingleSelect';
281
+ const selectOptions = [
282
+ { id: 'one', title: 'One', color: 'emerald' },
283
+ { id: 'two', title: 'Two', color: 'blue' },
284
+ { id: 'three', title: 'Three', color: 'indigo' },
285
+ { id: 'four', title: 'Four', color: 'red' },
286
+ { id: 'longer_option', title: 'LONGER OPTION', color: 'amber' },
287
+ ];
288
+
289
+ const selectOptionIds = selectOptions.map((o) => o.id);
290
+
291
+ const schema = getSchemaFromPropertyDefinitions(typename, [
292
+ {
293
+ name: 'single',
294
+ format: FormatEnum.SingleSelect,
295
+ config: { options: selectOptions },
296
+ },
297
+ {
298
+ name: 'multiple',
299
+ format: FormatEnum.MultiSelect,
300
+ config: { options: selectOptions },
301
+ },
302
+ ]);
303
+ const [storedSchema] = await space.db.schemaRegistry.register([schema]);
304
+
305
+ // Initialize table.
306
+ const table = space.db.add(create(TableType, {}));
307
+ await initializeTable({ client, space, table, initialRow: false, typename });
308
+
309
+ // Populate.
310
+ Array.from({ length: 10 }).map(() => {
311
+ return space.db.add(
312
+ create(storedSchema, {
313
+ single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
314
+ multiple: faker.helpers.randomSubset(selectOptionIds),
315
+ }),
316
+ );
317
+ });
318
+ },
319
+ }),
320
+ withTheme,
321
+ withLayout({ fullscreen: true, tooltips: true }),
322
+ ],
323
+ };
@@ -3,24 +3,25 @@
3
3
  //
4
4
 
5
5
  import React, {
6
- forwardRef,
6
+ type MouseEvent,
7
7
  type PropsWithChildren,
8
+ type WheelEvent,
9
+ forwardRef,
8
10
  useCallback,
9
11
  useImperativeHandle,
10
12
  useState,
11
- type WheelEvent,
12
- type MouseEvent,
13
13
  useMemo,
14
+ useEffect,
14
15
  } from 'react';
15
16
 
16
17
  import { getValue } from '@dxos/echo-schema';
17
18
  import { invariant } from '@dxos/invariant';
18
- import { Filter, getSpace, fullyQualifiedId } from '@dxos/react-client/echo';
19
+ import { Filter } from '@dxos/react-client/echo';
19
20
  import { useTranslation } from '@dxos/react-ui';
20
21
  import { useAttention } from '@dxos/react-ui-attention';
21
- import { type DxGridElement, Grid, type GridContentProps, closestCell, type DxGridPosition } from '@dxos/react-ui-grid';
22
+ import { closestCell, type DxGridElement, type DxGridPosition, type GridContentProps, Grid } from '@dxos/react-ui-grid';
22
23
  import { mx } from '@dxos/react-ui-theme';
23
- import { isNotFalsy } from '@dxos/util';
24
+ import { isNotFalsy, safeParseInt } from '@dxos/util';
24
25
 
25
26
  import { ColumnActionsMenu } from './ColumnActionsMenu';
26
27
  import { ColumnSettings } from './ColumnSettings';
@@ -39,10 +40,8 @@ const inlineEndLine =
39
40
  const blockEndLine =
40
41
  '[&>.dx-grid]:before:absolute [&>.dx-grid]:before:inset-inline-0 [&>.dx-grid]:before:-block-end-px [&>.dx-grid]:before:bs-px [&>.dx-grid]:before:bg-separator';
41
42
 
42
- const frozen = { frozenRowsStart: 1, frozenColsStart: 1, frozenColsEnd: 1 };
43
-
44
43
  //
45
- // Root
44
+ // Table.Root
46
45
  //
47
46
 
48
47
  export type TableRootProps = PropsWithChildren<{ role?: string }>;
@@ -52,8 +51,8 @@ const TableRoot = ({ children, role }: TableRootProps) => {
52
51
  <div
53
52
  role='none'
54
53
  className={mx(
55
- 'relative border-bs !border-separator',
56
- role === 'section'
54
+ 'relative !border-separator',
55
+ role === 'section' // TODO(burdon): This leaks composer plugin concepts? Standardize for react-ui?
57
56
  ? 'attention-surface overflow-hidden [&_.dx-grid]:max-is-[--dx-grid-content-inline-size]'
58
57
  : 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:bs-0 [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
59
58
  )}
@@ -64,7 +63,7 @@ const TableRoot = ({ children, role }: TableRootProps) => {
64
63
  };
65
64
 
66
65
  //
67
- // Main
66
+ // Table.Main
68
67
  //
69
68
 
70
69
  export type TableController = {
@@ -74,16 +73,46 @@ export type TableController = {
74
73
  export type TableMainProps = {
75
74
  model?: TableModel;
76
75
  presentation?: TablePresentation;
76
+ // TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
77
77
  ignoreAttention?: boolean;
78
+ onRowClick?: (row: any) => void;
78
79
  };
79
80
 
80
81
  const TableMain = forwardRef<TableController, TableMainProps>(
81
- ({ model, presentation, ignoreAttention }, forwardedRef) => {
82
+ ({ model, presentation, ignoreAttention, onRowClick }, forwardedRef) => {
82
83
  const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
83
- const { hasAttention } = useAttention(model?.table ? fullyQualifiedId(model.table) : 'table');
84
+ const { hasAttention } = useAttention(model?.id ?? 'table');
84
85
  const { t } = useTranslation(translationKey);
85
86
  const modals = useMemo(() => new ModalController(), []);
86
87
 
88
+ const frozen = useMemo(() => {
89
+ const noActionColumn =
90
+ model?.features.dataEditable === false &&
91
+ model?.features.schemaEditable === false &&
92
+ model.rowActions.length === 0;
93
+
94
+ return {
95
+ frozenRowsStart: 1,
96
+ frozenColsStart: model?.features.selection.enabled ? 1 : 0,
97
+ frozenColsEnd: noActionColumn ? 0 : 1,
98
+ };
99
+ }, [model]);
100
+
101
+ // TODO(burdon): Replace useEffect below.
102
+ // const getCells = useCallback<GridContentProps['getCells']>(
103
+ // (range: DxGridRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
104
+ // [presentation],
105
+ // );
106
+
107
+ useEffect(() => {
108
+ if (!presentation || !dxGrid) {
109
+ return;
110
+ }
111
+
112
+ // TODO(burdon): Pass to Grid.Content?
113
+ dxGrid.getCells = (range, plane) => presentation.getCells(range, plane);
114
+ }, [presentation, dxGrid]);
115
+
87
116
  /**
88
117
  * Provides an external controller that can be called to repaint the table.
89
118
  */
@@ -94,7 +123,6 @@ const TableMain = forwardRef<TableController, TableMainProps>(
94
123
  return {};
95
124
  }
96
125
 
97
- dxGrid.getCells = (range, plane) => presentation.getCells(range, plane);
98
126
  return {
99
127
  update: (cell) => {
100
128
  if (cell) {
@@ -111,6 +139,18 @@ const TableMain = forwardRef<TableController, TableMainProps>(
111
139
 
112
140
  const handleGridClick = useCallback(
113
141
  (event: MouseEvent) => {
142
+ const rowIndex = safeParseInt((event.target as HTMLElement).ariaRowIndex ?? '');
143
+ if (rowIndex != null) {
144
+ if (onRowClick) {
145
+ const row = model?.getRowAt(rowIndex);
146
+ row && onRowClick(row);
147
+ }
148
+
149
+ if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
150
+ model.selection.toggleSelectionForRowIndex(rowIndex);
151
+ }
152
+ }
153
+
114
154
  if (!modals) {
115
155
  return;
116
156
  }
@@ -197,6 +237,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
197
237
 
198
238
  const handleEnter = useCallback<NonNullable<TableCellEditorProps['onEnter']>>(
199
239
  (cell) => {
240
+ if (!model?.features.dataEditable) {
241
+ return;
242
+ }
243
+
200
244
  // TODO(burdon): Insert row only if bottom row isn't completely blank already.
201
245
  if (model && cell.row === model.getRowCount() - 1) {
202
246
  model.insertRow(cell.row);
@@ -213,6 +257,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
213
257
 
214
258
  const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
215
259
  (event) => {
260
+ if (!model?.features.dataEditable) {
261
+ return;
262
+ }
263
+
216
264
  const cell = closestCell(event.target);
217
265
  if (!model || !cell) {
218
266
  return;
@@ -260,7 +308,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
260
308
  const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
261
309
  async ({ field, props }, text) => {
262
310
  if (model && props.referenceSchema && field.referencePath) {
263
- const space = getSpace(model.table);
311
+ const space = model.space;
264
312
  invariant(space);
265
313
  const schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
266
314
  if (schema) {
@@ -291,7 +339,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
291
339
 
292
340
  return [];
293
341
  },
294
- [model],
342
+ [model, t],
295
343
  );
296
344
 
297
345
  if (!model || !modals) {
@@ -299,7 +347,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
299
347
  }
300
348
 
301
349
  return (
302
- <Grid.Root id={model.table.id ?? 'table-grid'}>
350
+ <Grid.Root id={model.id ?? 'table-grid'}>
303
351
  <TableCellEditor
304
352
  model={model}
305
353
  modals={modals}
@@ -308,32 +356,29 @@ const TableMain = forwardRef<TableController, TableMainProps>(
308
356
  onQuery={handleQuery}
309
357
  />
310
358
  <Grid.Content
311
- onWheelCapture={handleWheel}
312
359
  className={mx('[--dx-grid-base:var(--surface-bg)]', inlineEndLine, blockEndLine)}
313
360
  frozen={frozen}
361
+ // getCells={getCells}
314
362
  columns={model.columnMeta.value}
315
363
  limitRows={model.getRowCount() ?? 0}
316
- limitColumns={model.table.view?.target?.fields?.length ?? 0}
364
+ limitColumns={model.view?.fields?.length ?? 0}
365
+ overscroll='trap'
317
366
  onAxisResize={handleAxisResize}
318
367
  onClick={handleGridClick}
319
368
  onKeyDown={handleKeyDown}
320
- overscroll='trap'
369
+ onWheelCapture={handleWheel}
321
370
  ref={setDxGrid}
322
371
  />
323
372
  <RowActionsMenu model={model} modals={modals} />
324
373
  <ColumnActionsMenu model={model} modals={modals} />
325
374
  <ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
326
- <RefPanel model={model} modals={modals} />
327
375
  <CreateRefPanel model={model} modals={modals} />
376
+ <RefPanel model={model} modals={modals} />
328
377
  </Grid.Root>
329
378
  );
330
379
  },
331
380
  );
332
381
 
333
- //
334
- // CellEditor
335
- //
336
-
337
382
  export const Table = {
338
383
  Root: TableRoot,
339
384
  Main: TableMain,
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './Table';
6
+ export * from './DynamicTable';