@dxos/react-ui-table 0.8.1-staging.391c573 → 0.8.1-staging.5be625a

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 (201) hide show
  1. package/dist/lib/browser/{chunk-3FR6NHPD.mjs → chunk-5MKYB7NF.mjs} +5 -7
  2. package/dist/lib/browser/chunk-5MKYB7NF.mjs.map +7 -0
  3. package/dist/lib/browser/{_deprecated → deprecated}/index.mjs +22 -22
  4. package/dist/lib/browser/{_deprecated → deprecated}/index.mjs.map +1 -1
  5. package/dist/lib/browser/index.mjs +394 -192
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/types/index.mjs +1 -1
  9. package/dist/lib/node/{chunk-NB3SFHGA.cjs → chunk-NRFKBRPV.cjs} +7 -9
  10. package/dist/lib/node/chunk-NRFKBRPV.cjs.map +7 -0
  11. package/dist/lib/node/{_deprecated → deprecated}/index.cjs +1 -1
  12. package/dist/lib/node/{_deprecated → deprecated}/index.cjs.map +1 -1
  13. package/dist/lib/node/index.cjs +386 -188
  14. package/dist/lib/node/index.cjs.map +4 -4
  15. package/dist/lib/node/meta.json +1 -1
  16. package/dist/lib/node/types/index.cjs +3 -3
  17. package/dist/lib/node/types/index.cjs.map +1 -1
  18. package/dist/lib/node-esm/{chunk-RLVVOZW5.mjs → chunk-SU4D55KI.mjs} +5 -7
  19. package/dist/lib/node-esm/chunk-SU4D55KI.mjs.map +7 -0
  20. package/dist/lib/node-esm/{_deprecated → deprecated}/index.mjs +22 -22
  21. package/dist/lib/node-esm/{_deprecated → deprecated}/index.mjs.map +1 -1
  22. package/dist/lib/node-esm/index.mjs +394 -192
  23. package/dist/lib/node-esm/index.mjs.map +4 -4
  24. package/dist/lib/node-esm/meta.json +1 -1
  25. package/dist/lib/node-esm/types/index.mjs +1 -1
  26. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  28. package/dist/types/src/components/Table/DynamicTable.d.ts +19 -0
  29. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -0
  30. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/Table.d.ts +1 -0
  32. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/Table.stories.d.ts +3 -1
  34. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/index.d.ts +1 -0
  36. package/dist/types/src/components/Table/index.d.ts.map +1 -1
  37. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/testing.d.ts +7 -7
  39. package/dist/types/src/components/testing.d.ts.map +1 -1
  40. package/dist/types/src/deprecated/components/Cell/Cell.d.ts.map +1 -0
  41. package/dist/types/src/deprecated/components/Cell/CellCombobox.d.ts.map +1 -0
  42. package/dist/types/src/deprecated/components/Cell/index.d.ts.map +1 -0
  43. package/dist/types/src/deprecated/components/ColumnMenu/ColumnMenu.d.ts.map +1 -0
  44. package/dist/types/src/deprecated/components/ColumnMenu/ColumnMenu.stories.d.ts.map +1 -0
  45. package/dist/types/src/deprecated/components/ColumnMenu/ColumnSettings.d.ts.map +1 -0
  46. package/dist/types/src/deprecated/components/ColumnMenu/ColumnSettings.stories.d.ts.map +1 -0
  47. package/dist/types/src/deprecated/components/ColumnMenu/index.d.ts.map +1 -0
  48. package/dist/types/src/deprecated/components/Table/Table.d.ts.map +1 -0
  49. package/dist/types/src/deprecated/components/Table/Table.stories.d.ts.map +1 -0
  50. package/dist/types/src/deprecated/components/Table/TableBody.d.ts.map +1 -0
  51. package/dist/types/src/deprecated/components/Table/TableContext.d.ts.map +1 -0
  52. package/dist/types/src/deprecated/components/Table/TableHead.d.ts.map +1 -0
  53. package/dist/types/src/deprecated/components/Table/TableRootContext.d.ts.map +1 -0
  54. package/dist/types/src/deprecated/components/Table/index.d.ts.map +1 -0
  55. package/dist/types/src/deprecated/components/Table/props.d.ts.map +1 -0
  56. package/dist/types/src/deprecated/components/index.d.ts.map +1 -0
  57. package/dist/types/src/deprecated/helpers.d.ts.map +1 -0
  58. package/dist/types/src/deprecated/hooks/index.d.ts.map +1 -0
  59. package/dist/types/src/deprecated/hooks/useColumnSizing.d.ts.map +1 -0
  60. package/dist/types/src/deprecated/hooks/useColumnSorting.d.ts.map +1 -0
  61. package/dist/types/src/deprecated/hooks/usePinLastRow.d.ts.map +1 -0
  62. package/dist/types/src/deprecated/hooks/useRowSelection.d.ts.map +1 -0
  63. package/dist/types/src/deprecated/index.d.ts.map +1 -0
  64. package/dist/types/src/deprecated/schema/Schema.stories.d.ts.map +1 -0
  65. package/dist/types/src/deprecated/schema/column-utils.d.ts.map +1 -0
  66. package/dist/types/src/deprecated/schema/index.d.ts.map +1 -0
  67. package/dist/types/src/deprecated/schema/types.d.ts.map +1 -0
  68. package/dist/types/src/deprecated/theme.d.ts.map +1 -0
  69. package/dist/types/src/deprecated/translations.d.ts.map +1 -0
  70. package/dist/types/src/deprecated/types.d.ts.map +1 -0
  71. package/dist/types/src/hooks/useTableModel.d.ts +6 -3
  72. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  73. package/dist/types/src/model/table-model.d.ts +34 -7
  74. package/dist/types/src/model/table-model.d.ts.map +1 -1
  75. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  76. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  77. package/dist/types/src/types/table.d.ts +7 -33
  78. package/dist/types/src/types/table.d.ts.map +1 -1
  79. package/dist/types/src/util/dynamic-table.d.ts +40 -0
  80. package/dist/types/src/util/dynamic-table.d.ts.map +1 -0
  81. package/dist/types/src/util/index.d.ts +1 -0
  82. package/dist/types/src/util/index.d.ts.map +1 -1
  83. package/dist/types/src/util/table.d.ts +5 -3
  84. package/dist/types/src/util/table.d.ts.map +1 -1
  85. package/package.json +34 -34
  86. package/src/components/Table/ColumnActionsMenu.tsx +6 -4
  87. package/src/components/Table/ColumnSettings.tsx +5 -6
  88. package/src/components/Table/CreateRefPanel.tsx +3 -3
  89. package/src/components/Table/DynamicTable.tsx +86 -0
  90. package/src/components/Table/RefPanel.tsx +3 -3
  91. package/src/components/Table/RowActionsMenu.tsx +18 -3
  92. package/src/components/Table/Table.stories.tsx +71 -20
  93. package/src/components/Table/Table.tsx +30 -10
  94. package/src/components/Table/index.ts +1 -0
  95. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +9 -4
  96. package/src/hooks/useTableModel.ts +28 -7
  97. package/src/model/table-model.test.ts +1 -1
  98. package/src/model/table-model.ts +97 -21
  99. package/src/model/table-presentation.test.ts +1 -1
  100. package/src/model/table-presentation.ts +30 -12
  101. package/src/model/table-sorting.ts +1 -0
  102. package/src/types/table.ts +5 -7
  103. package/src/util/dynamic-table.ts +71 -0
  104. package/src/util/index.ts +1 -0
  105. package/src/util/table.ts +67 -65
  106. package/dist/lib/browser/chunk-3FR6NHPD.mjs.map +0 -7
  107. package/dist/lib/node/chunk-NB3SFHGA.cjs.map +0 -7
  108. package/dist/lib/node-esm/chunk-RLVVOZW5.mjs.map +0 -7
  109. package/dist/types/src/_deprecated/components/Cell/Cell.d.ts.map +0 -1
  110. package/dist/types/src/_deprecated/components/Cell/CellCombobox.d.ts.map +0 -1
  111. package/dist/types/src/_deprecated/components/Cell/index.d.ts.map +0 -1
  112. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.d.ts.map +0 -1
  113. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnMenu.stories.d.ts.map +0 -1
  114. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.d.ts.map +0 -1
  115. package/dist/types/src/_deprecated/components/ColumnMenu/ColumnSettings.stories.d.ts.map +0 -1
  116. package/dist/types/src/_deprecated/components/ColumnMenu/index.d.ts.map +0 -1
  117. package/dist/types/src/_deprecated/components/Table/Table.d.ts.map +0 -1
  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.map +0 -1
  120. package/dist/types/src/_deprecated/components/Table/TableContext.d.ts.map +0 -1
  121. package/dist/types/src/_deprecated/components/Table/TableHead.d.ts.map +0 -1
  122. package/dist/types/src/_deprecated/components/Table/TableRootContext.d.ts.map +0 -1
  123. package/dist/types/src/_deprecated/components/Table/index.d.ts.map +0 -1
  124. package/dist/types/src/_deprecated/components/Table/props.d.ts.map +0 -1
  125. package/dist/types/src/_deprecated/components/index.d.ts.map +0 -1
  126. package/dist/types/src/_deprecated/helpers.d.ts.map +0 -1
  127. package/dist/types/src/_deprecated/hooks/index.d.ts.map +0 -1
  128. package/dist/types/src/_deprecated/hooks/useColumnSizing.d.ts.map +0 -1
  129. package/dist/types/src/_deprecated/hooks/useColumnSorting.d.ts.map +0 -1
  130. package/dist/types/src/_deprecated/hooks/usePinLastRow.d.ts.map +0 -1
  131. package/dist/types/src/_deprecated/hooks/useRowSelection.d.ts.map +0 -1
  132. package/dist/types/src/_deprecated/index.d.ts.map +0 -1
  133. package/dist/types/src/_deprecated/schema/Schema.stories.d.ts.map +0 -1
  134. package/dist/types/src/_deprecated/schema/column-utils.d.ts.map +0 -1
  135. package/dist/types/src/_deprecated/schema/index.d.ts.map +0 -1
  136. package/dist/types/src/_deprecated/schema/types.d.ts.map +0 -1
  137. package/dist/types/src/_deprecated/theme.d.ts.map +0 -1
  138. package/dist/types/src/_deprecated/translations.d.ts.map +0 -1
  139. package/dist/types/src/_deprecated/types.d.ts.map +0 -1
  140. /package/dist/types/src/{_deprecated → deprecated}/components/Cell/Cell.d.ts +0 -0
  141. /package/dist/types/src/{_deprecated → deprecated}/components/Cell/CellCombobox.d.ts +0 -0
  142. /package/dist/types/src/{_deprecated → deprecated}/components/Cell/index.d.ts +0 -0
  143. /package/dist/types/src/{_deprecated → deprecated}/components/ColumnMenu/ColumnMenu.d.ts +0 -0
  144. /package/dist/types/src/{_deprecated → deprecated}/components/ColumnMenu/ColumnMenu.stories.d.ts +0 -0
  145. /package/dist/types/src/{_deprecated → deprecated}/components/ColumnMenu/ColumnSettings.d.ts +0 -0
  146. /package/dist/types/src/{_deprecated → deprecated}/components/ColumnMenu/ColumnSettings.stories.d.ts +0 -0
  147. /package/dist/types/src/{_deprecated → deprecated}/components/ColumnMenu/index.d.ts +0 -0
  148. /package/dist/types/src/{_deprecated → deprecated}/components/Table/Table.d.ts +0 -0
  149. /package/dist/types/src/{_deprecated → deprecated}/components/Table/Table.stories.d.ts +0 -0
  150. /package/dist/types/src/{_deprecated → deprecated}/components/Table/TableBody.d.ts +0 -0
  151. /package/dist/types/src/{_deprecated → deprecated}/components/Table/TableContext.d.ts +0 -0
  152. /package/dist/types/src/{_deprecated → deprecated}/components/Table/TableHead.d.ts +0 -0
  153. /package/dist/types/src/{_deprecated → deprecated}/components/Table/TableRootContext.d.ts +0 -0
  154. /package/dist/types/src/{_deprecated → deprecated}/components/Table/index.d.ts +0 -0
  155. /package/dist/types/src/{_deprecated → deprecated}/components/Table/props.d.ts +0 -0
  156. /package/dist/types/src/{_deprecated → deprecated}/components/index.d.ts +0 -0
  157. /package/dist/types/src/{_deprecated → deprecated}/helpers.d.ts +0 -0
  158. /package/dist/types/src/{_deprecated → deprecated}/hooks/index.d.ts +0 -0
  159. /package/dist/types/src/{_deprecated → deprecated}/hooks/useColumnSizing.d.ts +0 -0
  160. /package/dist/types/src/{_deprecated → deprecated}/hooks/useColumnSorting.d.ts +0 -0
  161. /package/dist/types/src/{_deprecated → deprecated}/hooks/usePinLastRow.d.ts +0 -0
  162. /package/dist/types/src/{_deprecated → deprecated}/hooks/useRowSelection.d.ts +0 -0
  163. /package/dist/types/src/{_deprecated → deprecated}/index.d.ts +0 -0
  164. /package/dist/types/src/{_deprecated → deprecated}/schema/Schema.stories.d.ts +0 -0
  165. /package/dist/types/src/{_deprecated → deprecated}/schema/column-utils.d.ts +0 -0
  166. /package/dist/types/src/{_deprecated → deprecated}/schema/index.d.ts +0 -0
  167. /package/dist/types/src/{_deprecated → deprecated}/schema/types.d.ts +0 -0
  168. /package/dist/types/src/{_deprecated → deprecated}/theme.d.ts +0 -0
  169. /package/dist/types/src/{_deprecated → deprecated}/translations.d.ts +0 -0
  170. /package/dist/types/src/{_deprecated → deprecated}/types.d.ts +0 -0
  171. /package/src/{_deprecated → deprecated}/components/Cell/Cell.tsx +0 -0
  172. /package/src/{_deprecated → deprecated}/components/Cell/CellCombobox.tsx +0 -0
  173. /package/src/{_deprecated → deprecated}/components/Cell/index.ts +0 -0
  174. /package/src/{_deprecated → deprecated}/components/ColumnMenu/ColumnMenu.stories.tsx +0 -0
  175. /package/src/{_deprecated → deprecated}/components/ColumnMenu/ColumnMenu.tsx +0 -0
  176. /package/src/{_deprecated → deprecated}/components/ColumnMenu/ColumnSettings.stories.tsx +0 -0
  177. /package/src/{_deprecated → deprecated}/components/ColumnMenu/ColumnSettings.tsx +0 -0
  178. /package/src/{_deprecated → deprecated}/components/ColumnMenu/index.ts +0 -0
  179. /package/src/{_deprecated → deprecated}/components/Table/Table.stories.tsx +0 -0
  180. /package/src/{_deprecated → deprecated}/components/Table/Table.tsx +0 -0
  181. /package/src/{_deprecated → deprecated}/components/Table/TableBody.tsx +0 -0
  182. /package/src/{_deprecated → deprecated}/components/Table/TableContext.tsx +0 -0
  183. /package/src/{_deprecated → deprecated}/components/Table/TableHead.tsx +0 -0
  184. /package/src/{_deprecated → deprecated}/components/Table/TableRootContext.ts +0 -0
  185. /package/src/{_deprecated → deprecated}/components/Table/index.ts +0 -0
  186. /package/src/{_deprecated → deprecated}/components/Table/props.ts +0 -0
  187. /package/src/{_deprecated → deprecated}/components/index.ts +0 -0
  188. /package/src/{_deprecated → deprecated}/helpers.tsx +0 -0
  189. /package/src/{_deprecated → deprecated}/hooks/index.ts +0 -0
  190. /package/src/{_deprecated → deprecated}/hooks/useColumnSizing.ts +0 -0
  191. /package/src/{_deprecated → deprecated}/hooks/useColumnSorting.ts +0 -0
  192. /package/src/{_deprecated → deprecated}/hooks/usePinLastRow.ts +0 -0
  193. /package/src/{_deprecated → deprecated}/hooks/useRowSelection.ts +0 -0
  194. /package/src/{_deprecated → deprecated}/index.ts +0 -0
  195. /package/src/{_deprecated → deprecated}/schema/Schema.stories.tsx +0 -0
  196. /package/src/{_deprecated → deprecated}/schema/column-utils.ts +0 -0
  197. /package/src/{_deprecated → deprecated}/schema/index.ts +0 -0
  198. /package/src/{_deprecated → deprecated}/schema/types.ts +0 -0
  199. /package/src/{_deprecated → deprecated}/theme.ts +0 -0
  200. /package/src/{_deprecated → deprecated}/translations.ts +0 -0
  201. /package/src/{_deprecated → deprecated}/types.ts +0 -0
@@ -0,0 +1,86 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { useRef, useMemo, useCallback } from 'react';
6
+
7
+ import { mx } from '@dxos/react-ui-theme';
8
+
9
+ import { Table, type TableController } from './Table';
10
+ import { useTableModel } from '../../hooks';
11
+ import { TablePresentation, type TableRowAction } from '../../model';
12
+ import { makeDynamicTable, type TablePropertyDefinition } from '../../util';
13
+
14
+ type DynamicTableProps = {
15
+ data: any[];
16
+ properties: TablePropertyDefinition[];
17
+ tableName?: string;
18
+ classNames?: string;
19
+ onRowClicked?: (row: any) => void;
20
+ rowActions?: TableRowAction[];
21
+ onRowAction?: (actionId: string, datum: any) => void;
22
+ };
23
+
24
+ /**
25
+ * A dynamic table component that renders data using the specified properties.
26
+ * Properties define both the schema and display characteristics of the table columns.
27
+ */
28
+ export const DynamicTable = ({
29
+ data,
30
+ properties,
31
+ classNames,
32
+ tableName = 'com.example/dynamic_table',
33
+ onRowClicked,
34
+ rowActions,
35
+ onRowAction,
36
+ }: DynamicTableProps) => {
37
+ // TODO(ZaymonFC): Consider allowing the user to supply a schema directly instead of deriving it from
38
+ // the properties array. (Both should be viable).
39
+ const { table, viewProjection } = useMemo(() => {
40
+ return makeDynamicTable(tableName, properties);
41
+ }, [tableName, properties]);
42
+
43
+ const tableRef = useRef<TableController>(null);
44
+ const handleCellUpdate = useCallback((cell: any) => {
45
+ tableRef.current?.update?.(cell);
46
+ }, []);
47
+
48
+ const handleRowOrderChanged = useCallback(() => {
49
+ tableRef.current?.update?.();
50
+ }, []);
51
+
52
+ const features = useMemo(() => ({ selection: false, dataEditable: false }), []);
53
+
54
+ const model = useTableModel({
55
+ table,
56
+ objects: data,
57
+ projection: viewProjection,
58
+ features,
59
+ onCellUpdate: handleCellUpdate,
60
+ onRowOrderChanged: handleRowOrderChanged,
61
+ rowActions,
62
+ onRowAction,
63
+ });
64
+
65
+ const presentation = useMemo(() => {
66
+ if (model) {
67
+ return new TablePresentation(model);
68
+ }
69
+ }, [model]);
70
+
71
+ return (
72
+ <div className={mx('is-full bs-full grow grid', classNames)}>
73
+ <div className='grid min-bs-0 overflow-hidden'>
74
+ <Table.Root>
75
+ <Table.Main
76
+ ref={tableRef}
77
+ model={model}
78
+ presentation={presentation}
79
+ onRowClicked={onRowClicked}
80
+ ignoreAttention
81
+ />
82
+ </Table.Root>
83
+ </div>
84
+ </div>
85
+ );
86
+ };
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { getSpace, type Space } from '@dxos/react-client/echo';
7
+ import { type Space } from '@dxos/react-client/echo';
8
8
  import { Popover } from '@dxos/react-ui';
9
9
  import { Form } from '@dxos/react-ui-form';
10
10
 
@@ -22,11 +22,11 @@ const getRefTargetObj = (space?: Space, id?: string) => {
22
22
 
23
23
  export const RefPanel = ({ model, modals }: RefPanelProps) => {
24
24
  const state = modals.state.value;
25
- const space = getSpace(model.table);
25
+ const space = model?.space;
26
26
  const targetObj = getRefTargetObj(space, state?.type === 'refPanel' ? state.targetId : undefined);
27
27
  const schema = space && state?.type === 'refPanel' ? space.db.schemaRegistry.getSchema(state.typename) : undefined;
28
28
 
29
- if (!model.table?.view || !model.projection) {
29
+ if (!model.view || !model.projection) {
30
30
  return null;
31
31
  }
32
32
 
@@ -23,9 +23,24 @@ export const RowActionsMenu = ({ model, modals }: RowActionsMenuProps) => {
23
23
  <DropdownMenu.VirtualTrigger virtualRef={modals.trigger} />
24
24
  <DropdownMenu.Content>
25
25
  <DropdownMenu.Viewport>
26
- <DropdownMenu.Item data-testid='row-menu-delete' onClick={() => model.deleteRow(state.rowIndex)}>
27
- {t(hasSelection ? 'bulk delete row label' : 'delete row label')}
28
- </DropdownMenu.Item>
26
+ {model.features.dataEditable !== false && (
27
+ <DropdownMenu.Item data-testid='row-menu-delete' onClick={() => model.deleteRow(state.rowIndex)}>
28
+ {t(hasSelection ? 'bulk delete row label' : 'delete row label')}
29
+ </DropdownMenu.Item>
30
+ )}
31
+ {/* Custom actions */}
32
+ {model.rowActions?.map((action) => (
33
+ <DropdownMenu.Item
34
+ key={action.id}
35
+ data-testid={`row-action-${action.id}`}
36
+ onClick={() => {
37
+ modals.close();
38
+ model.handleRowAction(action.id, state.rowIndex);
39
+ }}
40
+ >
41
+ {t(action.translationKey)}
42
+ </DropdownMenu.Item>
43
+ ))}
29
44
  </DropdownMenu.Viewport>
30
45
  <DropdownMenu.Arrow />
31
46
  </DropdownMenu.Content>
@@ -4,20 +4,24 @@
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, useState } 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
17
  import { useDefaultValue } from '@dxos/react-ui';
16
18
  import { ViewEditor } from '@dxos/react-ui-form';
17
19
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
18
- import { ViewProjection, ViewType } from '@dxos/schema';
20
+ import { type SchemaPropertyDefinition, ViewProjection, ViewType } from '@dxos/schema';
21
+ import { Testing, createObjectFactory } from '@dxos/schema/testing';
19
22
  import { withLayout, withTheme } from '@dxos/storybook-utils';
20
23
 
24
+ import { DynamicTable as DynamicTableComponent } from './DynamicTable';
21
25
  import { Table, type TableController } from './Table';
22
26
  import { useTableModel, type UseTableModelParams } from '../../hooks';
23
27
  import { TablePresentation } from '../../model';
@@ -35,20 +39,13 @@ faker.seed(0);
35
39
  //
36
40
 
37
41
  const DefaultStory = () => {
42
+ const client = useClient();
38
43
  const { space } = useClientProvider();
39
- invariant(space);
40
44
 
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
-
45
- useEffect(() => {
46
- if (space && tables.length && !table) {
47
- const table = tables[0];
48
- invariant(table.view);
49
- setTable(table);
50
- }
51
- }, [space, tables]);
45
+ const filter = useMemo(() => Filter.schema(TableType), []);
46
+ const tables = useQuery(space, filter);
47
+ const table = useMemo(() => tables.at(0), [tables]);
48
+ const schema = useSchema(client, space, table?.view?.target?.query.typename);
52
49
 
53
50
  const projection = useMemo(() => {
54
51
  if (schema && table?.view?.target) {
@@ -56,6 +53,11 @@ const DefaultStory = () => {
56
53
  }
57
54
  }, [schema, table?.view?.target]);
58
55
 
56
+ const features = useMemo(
57
+ () => ({ selection: true, dataEditable: true, schemaEditable: !(schema instanceof ImmutableSchema) }),
58
+ [schema],
59
+ );
60
+
59
61
  const objects = useQuery(space, schema ? Filter.schema(schema) : Filter.nothing());
60
62
  const filteredObjects = useGlobalFilteredObjects(objects);
61
63
 
@@ -68,7 +70,7 @@ const DefaultStory = () => {
68
70
  const handleDeleteRows = useCallback(
69
71
  (_: number, objects: any[]) => {
70
72
  for (const object of objects) {
71
- space.db.remove(object);
73
+ space?.db.remove(object);
72
74
  }
73
75
  },
74
76
  [space],
@@ -95,6 +97,7 @@ const DefaultStory = () => {
95
97
  const model = useTableModel({
96
98
  table,
97
99
  projection,
100
+ features,
98
101
  objects: filteredObjects,
99
102
  onInsertRow: handleInsertRow,
100
103
  onDeleteRows: handleDeleteRows,
@@ -132,11 +135,12 @@ const DefaultStory = () => {
132
135
  const onTypenameChanged = useCallback(
133
136
  (typename: string) => {
134
137
  if (table?.view?.target) {
135
- schema?.updateTypename(typename);
138
+ invariant(schema);
139
+ schema.mutable.updateTypename(typename);
136
140
  table.view.target.query.typename = typename;
137
141
  }
138
142
  },
139
- [table?.view?.target, schema],
143
+ [schema, table?.view?.target],
140
144
  );
141
145
 
142
146
  if (!schema || !table) {
@@ -170,6 +174,25 @@ const DefaultStory = () => {
170
174
  );
171
175
  };
172
176
 
177
+ const DynamicTableStory = () => {
178
+ const properties = useMemo<SchemaPropertyDefinition[]>(
179
+ () => [
180
+ { name: 'name', format: FormatEnum.String },
181
+ { name: 'age', format: FormatEnum.Number },
182
+ ],
183
+ [],
184
+ );
185
+
186
+ const [objects, _setObjects] = useState<any[]>(
187
+ Array.from({ length: 100 }, () => ({
188
+ name: faker.person.fullName(),
189
+ age: faker.number.int({ min: 18, max: 80 }),
190
+ })),
191
+ );
192
+
193
+ return <DynamicTableComponent properties={properties} data={objects} />;
194
+ };
195
+
173
196
  type StoryProps = {
174
197
  rows?: number;
175
198
  } & Pick<SimulatorProps, 'insertInterval' | 'updateInterval'>;
@@ -228,9 +251,9 @@ const meta: Meta<StoryProps> = {
228
251
  types: [TableType, ViewType],
229
252
  createIdentity: true,
230
253
  createSpace: true,
231
- onSpaceCreated: async ({ space }) => {
254
+ onSpaceCreated: async ({ client, space }) => {
232
255
  const table = space.db.add(create(TableType, {}));
233
- const schema = await initializeTable({ space, table, initialRow: false });
256
+ const schema = await initializeTable({ client, space, table, initialRow: false });
234
257
  Array.from({ length: 10 }).map(() => {
235
258
  return space.db.add(
236
259
  create(schema, {
@@ -249,6 +272,34 @@ export default meta;
249
272
 
250
273
  export const Default = {};
251
274
 
275
+ export const StaticSchema: StoryObj = {
276
+ render: DefaultStory,
277
+ parameters: { translations },
278
+ decorators: [
279
+ withClientProvider({
280
+ types: [TableType, ViewType, Testing.ContactType, Testing.OrgType],
281
+ createIdentity: true,
282
+ createSpace: true,
283
+ onSpaceCreated: async ({ client, space }) => {
284
+ const table = space.db.add(create(TableType, {}));
285
+ await initializeTable({ client, space, table, typename: Testing.ContactType.typename });
286
+
287
+ const factory = createObjectFactory(space.db, faker as any);
288
+ await factory([
289
+ { type: Testing.ContactType, count: 10 },
290
+ { type: Testing.OrgType, count: 1 },
291
+ ]);
292
+ },
293
+ }),
294
+ withTheme,
295
+ withLayout({ fullscreen: true, tooltips: true }),
296
+ ],
297
+ };
298
+
299
+ export const DynamicTable: StoryObj = {
300
+ render: DynamicTableStory,
301
+ };
302
+
252
303
  // TODO(ZaymonFC): Restore the performance stories.
253
304
  // type Story = StoryObj<StoryProps>;
254
305
  // export const Mutations: Story = {
@@ -15,12 +15,12 @@ import React, {
15
15
 
16
16
  import { getValue } from '@dxos/echo-schema';
17
17
  import { invariant } from '@dxos/invariant';
18
- import { Filter, getSpace, fullyQualifiedId } from '@dxos/react-client/echo';
18
+ import { Filter } from '@dxos/react-client/echo';
19
19
  import { useTranslation } from '@dxos/react-ui';
20
20
  import { useAttention } from '@dxos/react-ui-attention';
21
21
  import { type DxGridElement, Grid, type GridContentProps, closestCell, type DxGridPosition } from '@dxos/react-ui-grid';
22
22
  import { mx } from '@dxos/react-ui-theme';
23
- import { isNotFalsy } from '@dxos/util';
23
+ import { isNotFalsy, safeParseInt } from '@dxos/util';
24
24
 
25
25
  import { ColumnActionsMenu } from './ColumnActionsMenu';
26
26
  import { ColumnSettings } from './ColumnSettings';
@@ -39,8 +39,6 @@ const inlineEndLine =
39
39
  const blockEndLine =
40
40
  '[&>.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
41
 
42
- const frozen = { frozenRowsStart: 1, frozenColsStart: 1, frozenColsEnd: 1 };
43
-
44
42
  //
45
43
  // Root
46
44
  //
@@ -75,15 +73,21 @@ export type TableMainProps = {
75
73
  model?: TableModel;
76
74
  presentation?: TablePresentation;
77
75
  ignoreAttention?: boolean;
76
+ onRowClicked?: (row: any) => void;
78
77
  };
79
78
 
80
79
  const TableMain = forwardRef<TableController, TableMainProps>(
81
- ({ model, presentation, ignoreAttention }, forwardedRef) => {
80
+ ({ model, presentation, ignoreAttention, onRowClicked }, forwardedRef) => {
82
81
  const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
83
- const { hasAttention } = useAttention(model?.table ? fullyQualifiedId(model.table) : 'table');
82
+ const { hasAttention } = useAttention(model?.id ?? 'table');
84
83
  const { t } = useTranslation(translationKey);
85
84
  const modals = useMemo(() => new ModalController(), []);
86
85
 
86
+ const frozen = useMemo(
87
+ () => ({ frozenRowsStart: 1, frozenColsStart: model?.features.selection ? 1 : 0, frozenColsEnd: 1 }),
88
+ [model],
89
+ );
90
+
87
91
  /**
88
92
  * Provides an external controller that can be called to repaint the table.
89
93
  */
@@ -111,6 +115,14 @@ const TableMain = forwardRef<TableController, TableMainProps>(
111
115
 
112
116
  const handleGridClick = useCallback(
113
117
  (event: MouseEvent) => {
118
+ if (onRowClicked) {
119
+ const rowIndex = safeParseInt((event.target as HTMLElement).ariaRowIndex ?? '');
120
+ if (rowIndex != null) {
121
+ const row = model?.getRowAt(rowIndex);
122
+ row && onRowClicked(row);
123
+ }
124
+ }
125
+
114
126
  if (!modals) {
115
127
  return;
116
128
  }
@@ -197,6 +209,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
197
209
 
198
210
  const handleEnter = useCallback<NonNullable<TableCellEditorProps['onEnter']>>(
199
211
  (cell) => {
212
+ if (!model?.features.dataEditable) {
213
+ return;
214
+ }
215
+
200
216
  // TODO(burdon): Insert row only if bottom row isn't completely blank already.
201
217
  if (model && cell.row === model.getRowCount() - 1) {
202
218
  model.insertRow(cell.row);
@@ -213,6 +229,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
213
229
 
214
230
  const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
215
231
  (event) => {
232
+ if (!model?.features.dataEditable) {
233
+ return;
234
+ }
235
+
216
236
  const cell = closestCell(event.target);
217
237
  if (!model || !cell) {
218
238
  return;
@@ -260,7 +280,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
260
280
  const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
261
281
  async ({ field, props }, text) => {
262
282
  if (model && props.referenceSchema && field.referencePath) {
263
- const space = getSpace(model.table);
283
+ const space = model.space;
264
284
  invariant(space);
265
285
  const schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
266
286
  if (schema) {
@@ -291,7 +311,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
291
311
 
292
312
  return [];
293
313
  },
294
- [model],
314
+ [model, t],
295
315
  );
296
316
 
297
317
  if (!model || !modals) {
@@ -299,7 +319,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
299
319
  }
300
320
 
301
321
  return (
302
- <Grid.Root id={model.table.id ?? 'table-grid'}>
322
+ <Grid.Root id={model.id ?? 'table-grid'}>
303
323
  <TableCellEditor
304
324
  model={model}
305
325
  modals={modals}
@@ -313,7 +333,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
313
333
  frozen={frozen}
314
334
  columns={model.columnMeta.value}
315
335
  limitRows={model.getRowCount() ?? 0}
316
- limitColumns={model.table.view?.target?.fields?.length ?? 0}
336
+ limitColumns={model.view?.fields?.length ?? 0}
317
337
  onAxisResize={handleAxisResize}
318
338
  onClick={handleGridClick}
319
339
  onKeyDown={handleKeyDown}
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './Table';
6
+ export * from './DynamicTable';
@@ -7,7 +7,7 @@ import '@dxos-theme';
7
7
  import { type Meta, type StoryObj } from '@storybook/react';
8
8
  import React, { useEffect, useMemo, useState } from 'react';
9
9
 
10
- import { type EchoSchema } from '@dxos/echo-schema';
10
+ import { ImmutableSchema, type EchoSchema } from '@dxos/echo-schema';
11
11
  import { invariant } from '@dxos/invariant';
12
12
  import { faker } from '@dxos/random';
13
13
  import { Filter, useQuery, create } from '@dxos/react-client/echo';
@@ -48,7 +48,12 @@ const DefaultStory = ({ editing }: StoryProps) => {
48
48
  }
49
49
  }, [schema, table?.view]);
50
50
 
51
- const model = useTableModel({ table, projection });
51
+ const features = useMemo(
52
+ () => ({ selection: true, dataEditable: true, schemaEditable: !(schema instanceof ImmutableSchema) }),
53
+ [],
54
+ );
55
+
56
+ const model = useTableModel({ table, projection, features });
52
57
 
53
58
  const handleQuery: TableCellEditorProps['onQuery'] = async ({ field }) => {
54
59
  const { objects } = await space.db.query(schema).run();
@@ -84,9 +89,9 @@ const meta: Meta<StoryProps> = {
84
89
  types: [TableType, ViewType],
85
90
  createIdentity: true,
86
91
  createSpace: true,
87
- onSpaceCreated: async ({ space }) => {
92
+ onSpaceCreated: async ({ client, space }) => {
88
93
  const table = space.db.add(create(TableType, {}));
89
- const schema = await initializeTable({ space, table });
94
+ const schema = await initializeTable({ client, space, table });
90
95
  Array.from({ length: 10 }).forEach(() => {
91
96
  space.db.add(
92
97
  create(schema, {
@@ -5,24 +5,34 @@
5
5
  import { effect } from '@preact/signals-core';
6
6
  import { useEffect, useState } from 'react';
7
7
 
8
- import { type ReactiveObject } from '@dxos/react-client/echo';
8
+ import { fullyQualifiedId, getSpace, type ReactiveObject } from '@dxos/react-client/echo';
9
9
  import { useSelectionActions } from '@dxos/react-ui-attention';
10
10
  import { type ViewProjection } from '@dxos/schema';
11
11
  import { isNonNullable } from '@dxos/util';
12
12
 
13
- import { type BaseTableRow, TableModel, type TableModelProps } from '../model';
13
+ import { type BaseTableRow, TableModel, type TableModelProps, type TableRowAction } from '../model';
14
14
  import { type TableType } from '../types';
15
15
 
16
16
  export type UseTableModelParams<T extends BaseTableRow = { id: string }> = {
17
17
  table?: TableType;
18
18
  projection?: ViewProjection;
19
19
  objects?: ReactiveObject<T>[];
20
- } & Pick<TableModelProps<T>, 'onInsertRow' | 'onDeleteRows' | 'onDeleteColumn' | 'onCellUpdate' | 'onRowOrderChanged'>;
20
+ onSelectionChanged?: (selection: string[]) => void;
21
+ rowActions?: TableRowAction[];
22
+ onRowAction?: (actionId: string, data: T) => void;
23
+ } & Pick<
24
+ TableModelProps<T>,
25
+ 'features' | 'onInsertRow' | 'onDeleteRows' | 'onDeleteColumn' | 'onCellUpdate' | 'onRowOrderChanged'
26
+ >;
21
27
 
22
28
  export const useTableModel = <T extends BaseTableRow = { id: string }>({
23
29
  objects,
24
30
  table,
25
31
  projection,
32
+ features,
33
+ onSelectionChanged,
34
+ rowActions,
35
+ onRowAction,
26
36
  ...props
27
37
  }: UseTableModelParams<T>): TableModel<T> | undefined => {
28
38
  const [model, setModel] = useState<TableModel<T>>();
@@ -33,7 +43,16 @@ export const useTableModel = <T extends BaseTableRow = { id: string }>({
33
43
 
34
44
  let model: TableModel<T> | undefined;
35
45
  const t = setTimeout(async () => {
36
- model = new TableModel<T>({ table, projection, ...props });
46
+ model = new TableModel<T>({
47
+ id: fullyQualifiedId(table),
48
+ space: getSpace(table),
49
+ view: table.view?.target,
50
+ projection,
51
+ features,
52
+ rowActions,
53
+ onRowAction,
54
+ ...props,
55
+ });
37
56
  await model.open();
38
57
  setModel(model);
39
58
  });
@@ -42,7 +61,7 @@ export const useTableModel = <T extends BaseTableRow = { id: string }>({
42
61
  clearTimeout(t);
43
62
  void model?.close();
44
63
  };
45
- }, [table, projection]); // TODO(burdon): Trigger if callbacks change?
64
+ }, [table, projection, table?.view?.target, features, rowActions]); // TODO(burdon): Trigger if callbacks change?
46
65
 
47
66
  // Update data.
48
67
  useEffect(() => {
@@ -59,7 +78,9 @@ export const useTableModel = <T extends BaseTableRow = { id: string }>({
59
78
  }
60
79
 
61
80
  const unsubscribe = effect(() => {
62
- select([...model.selection.selection.value]);
81
+ const selectedItems = [...model.selection.selection.value];
82
+ select(selectedItems);
83
+ onSelectionChanged?.(selectedItems);
63
84
  });
64
85
 
65
86
  // Maybe clear the selection here?
@@ -67,7 +88,7 @@ export const useTableModel = <T extends BaseTableRow = { id: string }>({
67
88
  clear();
68
89
  unsubscribe();
69
90
  };
70
- }, [model]);
91
+ }, [model, onSelectionChanged]);
71
92
 
72
93
  return model;
73
94
  };
@@ -108,5 +108,5 @@ const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
108
108
  const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: schema.jsonSchema });
109
109
  const projection = new ViewProjection(schema, view);
110
110
  const table = create(TableType, { view: makeRef(view) });
111
- return new TableModel({ table, projection, ...props });
111
+ return new TableModel({ id: table.id, space: undefined, view, projection, ...props });
112
112
  };