@dxos/react-ui-table 0.7.4 → 0.7.5-labs.401163d

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 (120) hide show
  1. package/dist/lib/browser/{chunk-IYCSJDMB.mjs → chunk-3FR6NHPD.mjs} +7 -5
  2. package/dist/lib/browser/chunk-3FR6NHPD.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1488 -1112
  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-MHBMVCFZ.cjs → chunk-NB3SFHGA.cjs} +9 -7
  8. package/dist/lib/node/chunk-NB3SFHGA.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +1493 -1097
  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-BMWGPXPL.mjs → chunk-RLVVOZW5.mjs} +7 -5
  15. package/dist/lib/node-esm/chunk-RLVVOZW5.mjs.map +7 -0
  16. package/dist/lib/node-esm/index.mjs +1488 -1112
  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/_deprecated/hooks/useColumnSorting.d.ts +1 -1
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +4 -3
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/CreateRefPanel.d.ts +3 -2
  26. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/RefPanel.d.ts +4 -3
  28. package/dist/types/src/components/Table/RefPanel.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/RowActionsMenu.d.ts +3 -2
  30. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/Table.d.ts +2 -1
  32. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/Table.stories.d.ts +1 -4
  34. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -2
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.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/TableToolbar/TableToolbar.d.ts +18 -0
  39. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -0
  40. package/dist/types/src/components/TableToolbar/index.d.ts +2 -0
  41. package/dist/types/src/components/TableToolbar/index.d.ts.map +1 -0
  42. package/dist/types/src/components/index.d.ts +1 -1
  43. package/dist/types/src/components/index.d.ts.map +1 -1
  44. package/dist/types/src/components/testing.d.ts +31 -71
  45. package/dist/types/src/components/testing.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  47. package/dist/types/src/index.d.ts +1 -0
  48. package/dist/types/src/index.d.ts.map +1 -1
  49. package/dist/types/src/model/index.d.ts +1 -0
  50. package/dist/types/src/model/index.d.ts.map +1 -1
  51. package/dist/types/src/model/modal-controller.d.ts +6 -2
  52. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  53. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  54. package/dist/types/src/model/table-model.d.ts +19 -31
  55. package/dist/types/src/model/table-model.d.ts.map +1 -1
  56. package/dist/types/src/model/table-presentation.d.ts +22 -0
  57. package/dist/types/src/model/table-presentation.d.ts.map +1 -0
  58. package/dist/types/src/model/table-presentation.test.d.ts +2 -0
  59. package/dist/types/src/model/table-presentation.test.d.ts.map +1 -0
  60. package/dist/types/src/model/table-sorting.d.ts +53 -0
  61. package/dist/types/src/model/table-sorting.d.ts.map +1 -0
  62. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  63. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  64. package/dist/types/src/translations.d.ts +2 -1
  65. package/dist/types/src/translations.d.ts.map +1 -1
  66. package/dist/types/src/types/table.d.ts +46 -154
  67. package/dist/types/src/types/table.d.ts.map +1 -1
  68. package/dist/types/src/util/index.d.ts +1 -0
  69. package/dist/types/src/util/index.d.ts.map +1 -1
  70. package/dist/types/src/util/table-buttons.d.ts +63 -19
  71. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  72. package/dist/types/src/util/table-controls.d.ts +32 -9
  73. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  74. package/dist/types/src/util/table.d.ts +3 -2
  75. package/dist/types/src/util/table.d.ts.map +1 -1
  76. package/dist/types/tsconfig.tsbuildinfo +1 -0
  77. package/package.json +35 -34
  78. package/src/_deprecated/schema/Schema.stories.tsx +1 -1
  79. package/src/components/Table/ColumnActionsMenu.tsx +12 -12
  80. package/src/components/Table/ColumnSettings.tsx +11 -11
  81. package/src/components/Table/CreateRefPanel.tsx +13 -14
  82. package/src/components/Table/RefPanel.tsx +9 -9
  83. package/src/components/Table/RowActionsMenu.tsx +6 -7
  84. package/src/components/Table/Table.stories.tsx +84 -56
  85. package/src/components/Table/Table.tsx +239 -148
  86. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +13 -12
  87. package/src/components/TableCellEditor/TableCellEditor.tsx +6 -4
  88. package/src/components/TableToolbar/TableToolbar.tsx +72 -0
  89. package/src/components/{Toolbar → TableToolbar}/index.ts +1 -1
  90. package/src/components/index.ts +1 -1
  91. package/src/components/testing.ts +10 -8
  92. package/src/hooks/useTableModel.ts +19 -0
  93. package/src/index.ts +2 -0
  94. package/src/model/index.ts +1 -0
  95. package/src/model/modal-controller.ts +15 -48
  96. package/src/model/selection-model.ts +12 -1
  97. package/src/model/table-model.test.ts +4 -76
  98. package/src/model/table-model.ts +73 -304
  99. package/src/model/table-presentation.test.ts +102 -0
  100. package/src/model/table-presentation.ts +235 -0
  101. package/src/model/table-sorting.ts +215 -0
  102. package/src/playwright/TableManager.ts +5 -1
  103. package/src/playwright/smoke.spec.ts +94 -9
  104. package/src/translations.ts +2 -1
  105. package/src/types/table.ts +4 -4
  106. package/src/util/index.ts +1 -0
  107. package/src/util/table-buttons.ts +127 -48
  108. package/src/util/table-controls.ts +71 -7
  109. package/src/util/table.ts +93 -52
  110. package/dist/lib/browser/chunk-IYCSJDMB.mjs.map +0 -7
  111. package/dist/lib/node/chunk-MHBMVCFZ.cjs.map +0 -7
  112. package/dist/lib/node-esm/chunk-BMWGPXPL.mjs.map +0 -7
  113. package/dist/types/src/components/Toolbar/Toolbar.d.ts +0 -21
  114. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +0 -1
  115. package/dist/types/src/components/Toolbar/common.d.ts +0 -14
  116. package/dist/types/src/components/Toolbar/common.d.ts.map +0 -1
  117. package/dist/types/src/components/Toolbar/index.d.ts +0 -2
  118. package/dist/types/src/components/Toolbar/index.d.ts.map +0 -1
  119. package/src/components/Toolbar/Toolbar.tsx +0 -87
  120. package/src/components/Toolbar/common.tsx +0 -71
@@ -2,7 +2,7 @@ import { createRequire } from 'node:module';const require = createRequire(import
2
2
  import {
3
3
  TableSchema,
4
4
  TableType
5
- } from "./chunk-BMWGPXPL.mjs";
5
+ } from "./chunk-RLVVOZW5.mjs";
6
6
 
7
7
  // packages/ui/react-ui-table/src/translations.ts
8
8
  var translationKey = "table";
@@ -18,7 +18,7 @@ var translations_default = [
18
18
  "new schema": "New schema",
19
19
  "continue label": "Continue",
20
20
  "create stack section label": "Create table",
21
- "add row": "Add row",
21
+ "add row label": "Add row",
22
22
  "create comment": "Create thread",
23
23
  "column action sort descending": "Sort Descending",
24
24
  "column action sort ascending": "Sort Ascending",
@@ -29,55 +29,56 @@ var translations_default = [
29
29
  "bulk delete row label": "Delete selected rows",
30
30
  "column deleted label": "Column deleted",
31
31
  "new column button label": "Create column",
32
- "create new object label": "Create new object \u201C{{text}}\u201D"
32
+ "create new object label": "Create new object \u201C{{text}}\u201D",
33
+ "save view label": "Save changes to view"
33
34
  }
34
35
  }
35
36
  }
36
37
  ];
37
38
 
38
39
  // packages/ui/react-ui-table/src/components/Table/Table.tsx
39
- import React7, { forwardRef, useCallback as useCallback4, useImperativeHandle, useState as useState2 } from "react";
40
- import { getValue } from "@dxos/echo-schema";
41
- import { invariant as invariant2 } from "@dxos/invariant";
40
+ import React7, { forwardRef, useCallback as useCallback4, useImperativeHandle, useState as useState2, useMemo as useMemo4 } from "react";
41
+ import { getValue as getValue4 } from "@dxos/echo-schema";
42
+ import { invariant as invariant3 } from "@dxos/invariant";
42
43
  import { Filter, getSpace as getSpace4, fullyQualifiedId } from "@dxos/react-client/echo";
43
44
  import { useTranslation as useTranslation3 } from "@dxos/react-ui";
44
45
  import { useAttention } from "@dxos/react-ui-attention";
45
46
  import { Grid, closestCell } from "@dxos/react-ui-grid";
46
- import { mx } from "@dxos/react-ui-theme";
47
+ import { mx as mx2 } from "@dxos/react-ui-theme";
47
48
  import { isNotFalsy } from "@dxos/util";
48
49
 
49
50
  // packages/ui/react-ui-table/src/components/Table/ColumnActionsMenu.tsx
50
51
  import React from "react";
51
52
  import { DropdownMenu, useTranslation } from "@dxos/react-ui";
52
- var ColumnActionsMenu = ({ model }) => {
53
+ var ColumnActionsMenu = ({ model, modals }) => {
53
54
  const { t } = useTranslation(translationKey);
54
- const state = model?.modalController.state.value;
55
- if (!model || state?.type !== "column") {
55
+ const state = modals.state.value;
56
+ if (state?.type !== "column") {
56
57
  return null;
57
58
  }
58
- const currentSort = model.sorting;
59
+ const currentSort = model.sorting?.sorting;
59
60
  const isCurrentColumnSorted = currentSort?.fieldId === state.fieldId;
60
61
  return /* @__PURE__ */ React.createElement(DropdownMenu.Root, {
61
62
  modal: false,
62
- open: state?.type === "column",
63
- onOpenChange: model.modalController.close
63
+ open: true,
64
+ onOpenChange: modals.close
64
65
  }, /* @__PURE__ */ React.createElement(DropdownMenu.VirtualTrigger, {
65
- virtualRef: model.modalController.trigger
66
+ virtualRef: modals.trigger
66
67
  }), /* @__PURE__ */ React.createElement(DropdownMenu.Portal, null, /* @__PURE__ */ React.createElement(DropdownMenu.Content, null, /* @__PURE__ */ React.createElement(DropdownMenu.Viewport, null, (!isCurrentColumnSorted || currentSort?.direction === "asc") && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
67
68
  "data-testid": "column-sort-descending",
68
- onClick: () => model.setSort(state.fieldId, "desc")
69
+ onClick: () => model.sorting?.setSort(state.fieldId, "desc")
69
70
  }, t("column action sort descending")), (!isCurrentColumnSorted || currentSort?.direction === "desc") && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
70
71
  "data-testid": "column-sort-ascending",
71
- onClick: () => model.setSort(state.fieldId, "asc")
72
+ onClick: () => model.sorting?.setSort(state.fieldId, "asc")
72
73
  }, t("column action sort ascending")), isCurrentColumnSorted && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
73
74
  "data-testid": "column-clear-sort",
74
- onClick: () => model.clearSort()
75
+ onClick: () => model.sorting?.clearSort()
75
76
  }, t("column action clear sorting")), model.getColumnCount() > 1 && /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
76
77
  "data-testid": "column-delete",
77
78
  onClick: () => model.deleteColumn(state.fieldId)
78
79
  }, t("column action delete")), /* @__PURE__ */ React.createElement(DropdownMenu.Item, {
79
80
  "data-testid": "column-settings",
80
- onClick: () => model.modalController.openColumnSettings()
81
+ onClick: () => modals.openColumnSettings()
81
82
  }, t("column action settings"))), /* @__PURE__ */ React.createElement(DropdownMenu.Arrow, null))));
82
83
  };
83
84
 
@@ -86,9 +87,9 @@ import React2, { useCallback, useEffect, useMemo, useState } from "react";
86
87
  import { getSpace } from "@dxos/react-client/echo";
87
88
  import { DropdownMenu as DropdownMenu2 } from "@dxos/react-ui";
88
89
  import { FieldEditor } from "@dxos/react-ui-form";
89
- var ColumnSettings = ({ model, onNewColumn }) => {
90
+ var ColumnSettings = ({ model, modals, onNewColumn }) => {
90
91
  const [newField, setNewField] = useState();
91
- const state = model?.modalController.state.value;
92
+ const state = modals.state.value;
92
93
  const space = getSpace(model?.table);
93
94
  useEffect(() => {
94
95
  if (state?.type === "columnSettings" && state.mode.type === "create" && model?.projection) {
@@ -107,19 +108,19 @@ var ColumnSettings = ({ model, onNewColumn }) => {
107
108
  if (state?.type === "columnSettings") {
108
109
  const { mode } = state;
109
110
  if (mode.type === "edit") {
110
- return model?.table?.view?.fields.find((f) => f.id === mode.fieldId);
111
+ return model?.table?.view?.target?.fields.find((f) => f.id === mode.fieldId);
111
112
  }
112
113
  }
113
114
  return void 0;
114
115
  }, [
115
- model?.table?.view?.fields,
116
+ model?.table?.view?.target?.fields,
116
117
  state
117
118
  ]);
118
119
  const field = existingField ?? newField;
119
120
  const handleSave = useCallback(() => {
120
- model?.modalController.close();
121
+ modals.close();
121
122
  }, [
122
- model?.modalController
123
+ modals
123
124
  ]);
124
125
  const handleCancel = useCallback(() => {
125
126
  if (state?.type === "columnSettings" && state.mode.type === "create" && newField) {
@@ -130,18 +131,18 @@ var ColumnSettings = ({ model, onNewColumn }) => {
130
131
  state,
131
132
  newField
132
133
  ]);
133
- if (!model?.table?.view || !model.projection || !field) {
134
+ if (!model?.table?.view?.target || !model.projection || !field) {
134
135
  return null;
135
136
  }
136
137
  return /* @__PURE__ */ React2.createElement(DropdownMenu2.Root, {
137
138
  modal: false,
138
139
  open: state?.type === "columnSettings"
139
140
  }, /* @__PURE__ */ React2.createElement(DropdownMenu2.VirtualTrigger, {
140
- virtualRef: model.modalController.trigger
141
+ virtualRef: modals.trigger
141
142
  }), /* @__PURE__ */ React2.createElement(DropdownMenu2.Portal, null, /* @__PURE__ */ React2.createElement(DropdownMenu2.Content, {
142
143
  classNames: "md:is-64"
143
144
  }, /* @__PURE__ */ React2.createElement(DropdownMenu2.Viewport, null, /* @__PURE__ */ React2.createElement(FieldEditor, {
144
- view: model.table.view,
145
+ view: model.table.view.target,
145
146
  projection: model.projection,
146
147
  field,
147
148
  registry: space?.db.schemaRegistry,
@@ -157,15 +158,17 @@ import { create, getSpace as getSpace2 } from "@dxos/react-client/echo";
157
158
  import { Popover } from "@dxos/react-ui";
158
159
  import { Form } from "@dxos/react-ui-form";
159
160
  import { useGridContext } from "@dxos/react-ui-grid";
160
- var CreateRefPanel = ({ model, __gridScope }) => {
161
+ var CreateRefPanel = ({ model, modals, __gridScope }) => {
161
162
  const { id: gridId } = useGridContext("TableCellEditor", __gridScope);
162
163
  const space = getSpace2(model?.table);
163
- const state = model?.modalController.state.value;
164
+ const state = modals.state.value;
164
165
  const schema = useMemo2(() => {
165
166
  if (!space || state?.type !== "createRefPanel") {
166
167
  return;
167
168
  }
168
- const schema2 = space.db.schemaRegistry.getSchema(state.typename)?.schema;
169
+ const [schema2] = space.db.schemaRegistry.query({
170
+ typename: state.typename
171
+ }).runSync();
169
172
  if (schema2) {
170
173
  const omit = S.omit("id");
171
174
  return omit(schema2);
@@ -178,23 +181,25 @@ var CreateRefPanel = ({ model, __gridScope }) => {
178
181
  if (!model || !space || state?.type !== "createRefPanel") {
179
182
  return;
180
183
  }
181
- const schema2 = space.db.schemaRegistry.getSchema(state.typename)?.schema;
184
+ const [schema2] = space.db.schemaRegistry.query({
185
+ typename: state.typename
186
+ }).runSync();
182
187
  if (schema2) {
183
188
  const obj = space.db.add(create(schema2, values));
184
189
  state.onCreate?.(obj);
185
190
  }
186
- void model.modalController.close();
191
+ void modals.close();
187
192
  }, [
188
- model?.modalController,
193
+ modals,
189
194
  space,
190
195
  state
191
196
  ]);
192
197
  const handleCancel = useCallback2(() => {
193
198
  if (model) {
194
- model.modalController.close();
199
+ modals.close();
195
200
  }
196
201
  }, [
197
- model?.modalController
202
+ modals
198
203
  ]);
199
204
  if (!model?.table?.view || !model.projection) {
200
205
  return null;
@@ -204,11 +209,11 @@ var CreateRefPanel = ({ model, __gridScope }) => {
204
209
  open: state?.type === "createRefPanel" && !!schema,
205
210
  onOpenChange: (nextOpen) => {
206
211
  if (model && !nextOpen) {
207
- return model.modalController.close();
212
+ return modals.close();
208
213
  }
209
214
  }
210
215
  }, /* @__PURE__ */ React3.createElement(Popover.VirtualTrigger, {
211
- virtualRef: model.modalController.trigger
216
+ virtualRef: modals.trigger
212
217
  }), /* @__PURE__ */ React3.createElement(Popover.Portal, null, /* @__PURE__ */ React3.createElement(Popover.Content, {
213
218
  classNames: "md:is-64",
214
219
  "data-grid": gridId
@@ -232,24 +237,24 @@ var getRefTargetObj = (space, id) => {
232
237
  return void 0;
233
238
  }
234
239
  };
235
- var RefPanel = ({ model }) => {
236
- const state = model?.modalController.state.value;
237
- const space = getSpace3(model?.table);
240
+ var RefPanel = ({ model, modals }) => {
241
+ const state = modals.state.value;
242
+ const space = getSpace3(model.table);
238
243
  const targetObj = getRefTargetObj(space, state?.type === "refPanel" ? state.targetId : void 0);
239
244
  const schema = space && state?.type === "refPanel" ? space.db.schemaRegistry.getSchema(state.typename) : void 0;
240
- if (!model?.table?.view || !model.projection) {
245
+ if (!model.table?.view || !model.projection) {
241
246
  return null;
242
247
  }
243
248
  return /* @__PURE__ */ React4.createElement(Popover2.Root, {
244
249
  modal: false,
245
250
  open: state?.type === "refPanel" && !!targetObj && !!schema,
246
251
  onOpenChange: (nextOpen) => {
247
- if (model && !nextOpen) {
248
- return model.modalController.close();
252
+ if (!nextOpen) {
253
+ return modals.close();
249
254
  }
250
255
  }
251
256
  }, /* @__PURE__ */ React4.createElement(Popover2.VirtualTrigger, {
252
- virtualRef: model.modalController.trigger
257
+ virtualRef: modals.trigger
253
258
  }), /* @__PURE__ */ React4.createElement(Popover2.Portal, null, /* @__PURE__ */ React4.createElement(Popover2.Content, {
254
259
  classNames: "md:is-64"
255
260
  }, targetObj && schema && /* @__PURE__ */ React4.createElement(Form2, {
@@ -262,762 +267,319 @@ var RefPanel = ({ model }) => {
262
267
  // packages/ui/react-ui-table/src/components/Table/RowActionsMenu.tsx
263
268
  import React5 from "react";
264
269
  import { DropdownMenu as DropdownMenu3, useTranslation as useTranslation2 } from "@dxos/react-ui";
265
- var RowActionsMenu = ({ model }) => {
270
+ var RowActionsMenu = ({ model, modals }) => {
266
271
  const { t } = useTranslation2(translationKey);
267
272
  const hasSelection = model.selection.hasSelection.value;
268
- const state = model.modalController.state.value;
273
+ const state = modals.state.value;
269
274
  if (state?.type !== "row") {
270
275
  return null;
271
276
  }
272
277
  return /* @__PURE__ */ React5.createElement(DropdownMenu3.Root, {
273
278
  modal: false,
274
279
  open: true,
275
- onOpenChange: model.modalController.close
280
+ onOpenChange: modals.close
276
281
  }, /* @__PURE__ */ React5.createElement(DropdownMenu3.VirtualTrigger, {
277
- virtualRef: model.modalController.trigger
282
+ virtualRef: modals.trigger
278
283
  }), /* @__PURE__ */ React5.createElement(DropdownMenu3.Content, null, /* @__PURE__ */ React5.createElement(DropdownMenu3.Viewport, null, /* @__PURE__ */ React5.createElement(DropdownMenu3.Item, {
279
284
  "data-testid": "row-menu-delete",
280
285
  onClick: () => model.deleteRow(state.rowIndex)
281
286
  }, t(hasSelection ? "bulk delete row label" : "delete row label"))), /* @__PURE__ */ React5.createElement(DropdownMenu3.Arrow, null)));
282
287
  };
283
288
 
284
- // packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx
285
- import React6, { useCallback as useCallback3, useMemo as useMemo3, useRef } from "react";
286
- import { FormatEnum } from "@dxos/echo-schema";
289
+ // packages/ui/react-ui-table/src/model/table-model.ts
290
+ import { computed as computed3, effect as effect2, signal as signal3 } from "@preact/signals-core";
291
+ import { Resource as Resource2 } from "@dxos/context";
292
+ import { FormatEnum as FormatEnum3, getValue as getValue2, setValue } from "@dxos/echo-schema";
287
293
  import { invariant } from "@dxos/invariant";
288
- import { cellQuery, editorKeys, parseCellIndex, useGridContext as useGridContext2, GridCellEditor } from "@dxos/react-ui-grid";
294
+ import { isReactiveObject, makeRef as makeRef2 } from "@dxos/live-object";
295
+ import { PublicKey as PublicKey2 } from "@dxos/react-client";
296
+ import { formatForEditing, parseValue } from "@dxos/react-ui-form";
289
297
 
290
- // packages/ui/react-ui-table/src/components/TableCellEditor/extension.ts
291
- import { autocompletion, pickedCompletion } from "@codemirror/autocomplete";
292
- import { EditorView } from "@codemirror/view";
293
- var completion = ({ debug, onQuery, onMatch }) => {
294
- return [
295
- EditorView.theme({
296
- ".cm-completionDialog": {
297
- width: "var(--dx-gridCellWidth)"
298
- }
299
- }),
300
- autocompletion({
301
- icons: false,
302
- activateOnTyping: true,
303
- closeOnBlur: !debug,
304
- override: [
305
- async (context) => {
306
- const text = context.state.doc.toString();
307
- return {
308
- from: 0,
309
- options: await onQuery(text)
310
- };
311
- }
312
- ],
313
- // TODO(burdon): Consts from grid (to line-up with grid).
314
- tooltipClass: () => "cm-completionDialog !mt-[8px] !-ml-[4px] [&>ul]:!max-h-[264px]",
315
- optionClass: () => "flex h-[33px] items-center"
316
- }),
317
- // Check for accepted completion metadata in the transaction.
318
- EditorView.updateListener.of((update) => {
319
- update.transactions.forEach((tr) => {
320
- const completion2 = tr.annotation(pickedCompletion);
321
- if (completion2) {
322
- onMatch(completion2.data);
323
- }
324
- });
325
- })
326
- ];
327
- };
298
+ // packages/ui/react-ui-table/src/model/selection-model.ts
299
+ import { computed, effect, signal } from "@preact/signals-core";
300
+ import { Resource } from "@dxos/context";
328
301
 
329
- // packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx
330
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx";
331
- var newValue = Symbol.for("newValue");
332
- var createOption = (text) => ({
333
- [newValue]: true,
334
- text
335
- });
336
- var isCreateOption = (data) => typeof data === "object" && data[newValue];
337
- var TableCellEditor = ({ model, onEnter, onFocus, onQuery, __gridScope }) => {
338
- const { id: gridId, editing, setEditing } = useGridContext2("TableCellEditor", __gridScope);
339
- const suppressNextBlur = useRef(false);
340
- const fieldProjection = useMemo3(() => {
341
- if (!model || !editing) {
342
- return;
302
+ // packages/ui/react-ui-table/src/util/signals.ts
303
+ var touch = (value) => value;
304
+
305
+ // packages/ui/react-ui-table/src/util/table.ts
306
+ import { AST, Format, FormatEnum, S as S2, TypedObject, TypeEnum } from "@dxos/echo-schema";
307
+ import { PublicKey } from "@dxos/react-client";
308
+ import { create as create2, makeRef } from "@dxos/react-client/echo";
309
+ import { createFieldId, createView, getSchemaProperties, ViewProjection } from "@dxos/schema";
310
+ var initializeTable = async ({ space, table, initialRow = true, initialSchema }) => {
311
+ if (initialSchema) {
312
+ const schema = await space.db.schemaRegistry.query({
313
+ typename: initialSchema
314
+ }).firstOrUndefined();
315
+ if (!schema) {
316
+ throw new Error(`Schema not found: ${initialSchema}`);
343
317
  }
344
- const { col } = parseCellIndex(editing.index);
345
- const field = model.projection.view.fields[col];
346
- const fieldProjection2 = model.projection.getFieldProjection(field.id);
347
- invariant(fieldProjection2, void 0, {
348
- F: __dxlog_file,
349
- L: 65,
350
- S: void 0,
351
- A: [
352
- "fieldProjection",
353
- ""
318
+ const fields = getSchemaProperties(schema.ast).filter((prop) => prop.type !== "object" || prop.format === FormatEnum.Ref).map((prop) => prop.name);
319
+ table.view = makeRef(createView({
320
+ // TODO(ZaymonFC): Don't hardcode name?
321
+ name: "View",
322
+ typename: schema.typename,
323
+ jsonSchema: schema.jsonSchema,
324
+ fields
325
+ }));
326
+ return schema;
327
+ } else {
328
+ const ContactSchema = TypedObject({
329
+ typename: `example.com/type/${PublicKey.random().truncate()}`,
330
+ version: "0.1.0"
331
+ })({
332
+ name: S2.optional(S2.String).annotations({
333
+ [AST.TitleAnnotationId]: "Name"
334
+ }),
335
+ active: S2.optional(S2.Boolean),
336
+ email: S2.optional(Format.Email),
337
+ salary: S2.optional(Format.Currency()).annotations({
338
+ [AST.TitleAnnotationId]: "Salary"
339
+ })
340
+ });
341
+ const [contactSchema] = await space.db.schemaRegistry.register([
342
+ ContactSchema
343
+ ]);
344
+ table.view = makeRef(createView({
345
+ name: "View",
346
+ typename: contactSchema.typename,
347
+ jsonSchema: contactSchema.jsonSchema,
348
+ fields: [
349
+ "name",
350
+ "email",
351
+ "salary",
352
+ "active"
354
353
  ]
354
+ }));
355
+ const projection = new ViewProjection(contactSchema, table.view.target);
356
+ projection.setFieldProjection({
357
+ field: {
358
+ id: table.view.target.fields.find((f) => f.path === "salary").id,
359
+ path: "salary",
360
+ size: 150
361
+ }
355
362
  });
356
- return fieldProjection2;
357
- }, [
358
- model,
359
- editing
360
- ]);
361
- const handleEnter = useCallback3((value) => {
362
- if (!model || !editing) {
363
- return;
364
- }
365
- const cell = parseCellIndex(editing.index);
366
- model.setCellData(cell, value);
367
- onEnter?.(cell);
368
- onFocus?.();
369
- setEditing(null);
370
- }, [
371
- model,
372
- editing
373
- ]);
374
- const handleBlur = useCallback3((value) => {
375
- if (!model || !editing) {
376
- return;
377
- }
378
- if (suppressNextBlur.current) {
379
- suppressNextBlur.current = false;
380
- return;
381
- }
382
- const cell = parseCellIndex(editing.index);
383
- if (value !== void 0) {
384
- model.setCellData(cell, value);
385
- }
386
- }, [
387
- model,
388
- editing
389
- ]);
390
- const handleClose = useCallback3((value, event) => {
391
- if (!model || !editing || !fieldProjection) {
392
- return;
393
- }
394
- const cell = parseCellIndex(editing.index);
395
- onEnter?.(cell);
396
- if (event && onFocus) {
397
- onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
398
- }
399
- }, [
400
- model,
401
- editing,
402
- onFocus,
403
- onEnter,
404
- fieldProjection,
405
- determineNavigationAxis,
406
- determineNavigationDelta
407
- ]);
408
- const extension = useMemo3(() => {
409
- if (!fieldProjection) {
410
- return [];
411
- }
412
- const extension2 = [
413
- editorKeys({
414
- onClose: handleClose,
415
- ...editing?.initialContent && {
416
- onNav: handleClose
417
- }
418
- })
419
- ];
420
- if (onQuery) {
421
- switch (fieldProjection.props.format) {
422
- case FormatEnum.Ref: {
423
- extension2.push([
424
- completion({
425
- onQuery: (text) => onQuery(fieldProjection, text),
426
- onMatch: (data) => {
427
- if (model && editing) {
428
- if (isCreateOption(data)) {
429
- const { field, props } = fieldProjection;
430
- if (props.referenceSchema) {
431
- suppressNextBlur.current = true;
432
- model.modalController.openCreateRef(props.referenceSchema, document.querySelector(cellQuery(editing.index, gridId)), {
433
- [field.referencePath]: data.text
434
- }, (data2) => {
435
- handleEnter(data2);
436
- });
437
- }
438
- } else {
439
- handleEnter(data);
440
- }
441
- }
442
- }
443
- })
444
- ]);
445
- break;
446
- }
363
+ projection.setFieldProjection({
364
+ field: {
365
+ id: table.view.target.fields.find((f) => f.path === "active").id,
366
+ path: "active",
367
+ size: 100
447
368
  }
369
+ });
370
+ projection.setFieldProjection({
371
+ field: {
372
+ id: createFieldId(),
373
+ path: "manager",
374
+ referencePath: "name"
375
+ },
376
+ props: {
377
+ property: "manager",
378
+ type: TypeEnum.Ref,
379
+ format: FormatEnum.Ref,
380
+ referenceSchema: contactSchema.typename,
381
+ title: "Manager"
382
+ }
383
+ });
384
+ if (initialRow) {
385
+ space.db.add(create2(contactSchema, {}));
448
386
  }
449
- return extension2;
450
- }, [
451
- model,
452
- editing,
453
- fieldProjection,
454
- handleClose
455
- ]);
456
- const getCellContent = useCallback3(() => {
457
- if (model && editing) {
458
- const value = model.getCellData(parseCellIndex(editing.index));
459
- return value !== void 0 ? String(value) : "";
460
- }
461
- }, [
462
- model,
463
- editing
464
- ]);
465
- return /* @__PURE__ */ React6.createElement(GridCellEditor, {
466
- extension,
467
- getCellContent,
468
- onBlur: handleBlur
469
- });
470
- };
471
- var determineNavigationAxis = ({ key }) => {
472
- switch (key) {
473
- case "ArrowUp":
474
- case "ArrowDown":
475
- case "Enter": {
476
- return "row";
477
- }
478
- case "ArrowLeft":
479
- case "ArrowRight":
480
- case "Tab":
481
- return "col";
482
- }
483
- };
484
- var determineNavigationDelta = ({ key, shift }) => {
485
- switch (key) {
486
- case "ArrowUp":
487
- case "ArrowLeft":
488
- return -1;
489
- case "ArrowDown":
490
- case "ArrowRight":
491
- return 1;
387
+ return contactSchema;
492
388
  }
493
- return shift ? -1 : 1;
494
389
  };
495
390
 
496
- // packages/ui/react-ui-table/src/components/Table/Table.tsx
497
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/Table/Table.tsx";
498
- var inlineEndLine = "[&>.dx-grid]:relative [&>.dx-grid]:after:absolute [&>.dx-grid]:after:inset-block-0 [&>.dx-grid]:after:-inline-end-px [&>.dx-grid]:after:is-px [&>.dx-grid]:after:bg-separator";
499
- var blockEndLine = "[&>.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";
500
- var frozen = {
501
- frozenRowsStart: 1,
502
- frozenColsStart: 1,
503
- frozenColsEnd: 1
391
+ // packages/ui/react-ui-table/src/util/table-buttons.ts
392
+ var BUTTON_IDENTIFIERS = {
393
+ columnSettings: "data-table-column-settings-button",
394
+ newColumn: "data-table-new-column-button",
395
+ referencedCell: "data-table-ref-cell-button",
396
+ rowMenu: "data-table-row-menu-button",
397
+ sort: "data-table-sort-button"
504
398
  };
505
- var TableRoot = ({ children, role }) => {
506
- return /* @__PURE__ */ React7.createElement("div", {
507
- role: "none",
508
- className: mx("relative border-bs !border-separator", role === "section" ? "attention-surface overflow-hidden [&_.dx-grid]:max-is-[--dx-grid-content-inline-size]" : "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]")
509
- }, children);
399
+ var createButton = ({ attr, data, disabled = false, icon, testId, type = "primary" }) => {
400
+ const dataAttrs = Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
401
+ const positionClass = type === "primary" ? "inline-end-2" : "inline-end-9";
402
+ return `<button ${attr} data-testid="${testId}" class="dx-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 ${positionClass}" ${dataAttrs} ${disabled ? "disabled" : ""}><svg><use href="/icons.svg#${icon}"/></svg></button>`;
510
403
  };
511
- var TableMain = /* @__PURE__ */ forwardRef(({ model, ignoreAttention }, forwardedRef) => {
512
- const [dxGrid, setDxGrid] = useState2(null);
513
- const { hasAttention } = useAttention(model?.table ? fullyQualifiedId(model.table) : "table");
514
- const { t } = useTranslation3(translationKey);
515
- useImperativeHandle(forwardedRef, () => {
516
- if (!model || !dxGrid) {
517
- return {};
518
- }
519
- dxGrid.getCells = model.getCells.bind(model);
520
- return {
521
- update: (cell) => {
522
- if (cell) {
523
- dxGrid.updateIfWithinBounds(cell);
524
- } else {
525
- dxGrid.updateCells(true);
526
- }
527
- }
528
- };
529
- }, [
530
- model,
531
- dxGrid
532
- ]);
533
- const handleFocus = useCallback4((increment, delta) => {
534
- if (dxGrid) {
535
- dxGrid.refocus(increment, delta);
536
- }
537
- }, [
538
- dxGrid
539
- ]);
540
- const handleEnter = useCallback4((cell) => {
541
- if (model && cell.row === model.getRowCount() - 1) {
542
- model.insertRow(cell.row);
543
- if (dxGrid) {
544
- requestAnimationFrame(() => {
545
- dxGrid?.scrollToRow(cell.row + 1);
546
- dxGrid?.refocus("row", 1);
547
- });
548
- }
549
- }
550
- }, [
551
- model,
552
- dxGrid
553
- ]);
554
- const handleKeyDown = useCallback4((event) => {
555
- const cell = closestCell(event.target);
556
- if (!model || !cell) {
557
- return;
558
- }
559
- switch (event.key) {
560
- case "Backspace":
561
- case "Delete": {
562
- model.setCellData(cell, void 0);
563
- break;
564
- }
565
- }
566
- }, [
567
- model
568
- ]);
569
- const handleAxisResize = useCallback4((event) => {
570
- if (event.axis === "col") {
571
- const columnIndex = parseInt(event.index, 10);
572
- model?.setColumnWidth(columnIndex, event.size);
573
- }
574
- }, [
575
- model
576
- ]);
577
- const handleWheel = useCallback4((event) => {
578
- if (!ignoreAttention && !hasAttention) {
579
- event.stopPropagation();
580
- }
581
- }, [
582
- hasAttention,
583
- ignoreAttention
584
- ]);
585
- const handleNewColumn = useCallback4(() => {
586
- const columns = model?.getColumnCount();
587
- if (dxGrid && columns) {
588
- dxGrid.scrollToColumn(columns - 1);
589
- }
590
- }, [
591
- model,
592
- dxGrid
593
- ]);
594
- const handleQuery = useCallback4(async ({ field, props }, text) => {
595
- if (model && props.referenceSchema && field.referencePath) {
596
- const space = getSpace4(model.table);
597
- invariant2(space, void 0, {
598
- F: __dxlog_file2,
599
- L: 180,
600
- S: void 0,
601
- A: [
602
- "space",
603
- ""
604
- ]
605
- });
606
- const schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
607
- if (schema) {
608
- const { objects } = await space.db.query(Filter.schema(schema)).run();
609
- const options = objects.map((obj) => {
610
- const value = getValue(obj, field.referencePath);
611
- if (!value || typeof value !== "string") {
612
- return void 0;
613
- }
614
- return {
615
- label: value,
616
- data: obj
617
- };
618
- }).filter(isNotFalsy);
619
- return [
620
- ...options,
621
- {
622
- label: t("create new object label", {
623
- text
624
- }),
625
- data: createOption(text)
626
- }
627
- ];
628
- }
629
- }
630
- return [];
631
- }, [
632
- model
633
- ]);
634
- if (!model) {
635
- return /* @__PURE__ */ React7.createElement("span", {
636
- role: "none",
637
- className: "attention-surface"
404
+ var addColumnButton = {
405
+ attr: BUTTON_IDENTIFIERS.newColumn,
406
+ icon: "ph--plus--regular",
407
+ render: ({ disabled }) => {
408
+ return createButton({
409
+ attr: BUTTON_IDENTIFIERS.newColumn,
410
+ icon: addColumnButton.icon,
411
+ disabled,
412
+ data: {},
413
+ testId: "table-new-column-button"
638
414
  });
639
- }
640
- return /* @__PURE__ */ React7.createElement(Grid.Root, {
641
- id: model.table.id ?? "table-grid"
642
- }, /* @__PURE__ */ React7.createElement(TableCellEditor, {
643
- model,
644
- onEnter: handleEnter,
645
- onFocus: handleFocus,
646
- onQuery: handleQuery
647
- }), /* @__PURE__ */ React7.createElement(Grid.Content, {
648
- onWheelCapture: handleWheel,
649
- className: mx("[--dx-grid-base:var(--surface-bg)]", inlineEndLine, blockEndLine),
650
- frozen,
651
- columns: model.columnMeta.value,
652
- limitRows: model.getRowCount() ?? 0,
653
- limitColumns: model.table.view?.fields?.length ?? 0,
654
- onAxisResize: handleAxisResize,
655
- onClick: model?.handleGridClick,
656
- onKeyDown: handleKeyDown,
657
- overscroll: "trap",
658
- ref: setDxGrid
659
- }), /* @__PURE__ */ React7.createElement(RowActionsMenu, {
660
- model
661
- }), /* @__PURE__ */ React7.createElement(ColumnActionsMenu, {
662
- model
663
- }), /* @__PURE__ */ React7.createElement(ColumnSettings, {
664
- model,
665
- onNewColumn: handleNewColumn
666
- }), /* @__PURE__ */ React7.createElement(RefPanel, {
667
- model
668
- }), /* @__PURE__ */ React7.createElement(CreateRefPanel, {
669
- model
670
- }));
671
- });
672
- var Table = {
673
- Root: TableRoot,
674
- Main: TableMain
675
- };
676
-
677
- // packages/ui/react-ui-table/src/components/Toolbar/Toolbar.tsx
678
- import { createContext } from "@radix-ui/react-context";
679
- import React9 from "react";
680
- import { Toolbar as NaturalToolbar2, useTranslation as useTranslation4 } from "@dxos/react-ui";
681
-
682
- // packages/ui/react-ui-table/src/components/Toolbar/common.tsx
683
- import React8 from "react";
684
- import { Icon, Toolbar as NaturalToolbar, Tooltip } from "@dxos/react-ui";
685
- var buttonStyles = "min-bs-0 p-2";
686
- var tooltipProps = {
687
- side: "top",
688
- classNames: "z-10"
415
+ },
416
+ getData: (_el) => ({
417
+ type: "newColumn"
418
+ })
689
419
  };
690
- var ToolbarSeparator = () => /* @__PURE__ */ React8.createElement("div", {
691
- role: "separator",
692
- className: "grow"
693
- });
694
- var ToolbarButton = ({ icon, children, ...props }) => {
695
- return /* @__PURE__ */ React8.createElement(Tooltip.Root, null, /* @__PURE__ */ React8.createElement(Tooltip.Trigger, {
696
- asChild: true
697
- }, /* @__PURE__ */ React8.createElement(NaturalToolbar.Button, {
698
- variant: "ghost",
699
- ...props,
700
- classNames: buttonStyles
701
- }, /* @__PURE__ */ React8.createElement(Icon, {
702
- icon,
703
- size: 5
704
- }), /* @__PURE__ */ React8.createElement("span", {
705
- className: "sr-only"
706
- }, children))), /* @__PURE__ */ React8.createElement(Tooltip.Portal, null, /* @__PURE__ */ React8.createElement(Tooltip.Content, tooltipProps, children, /* @__PURE__ */ React8.createElement(Tooltip.Arrow, null))));
420
+ var columnSettingsButton = {
421
+ attr: BUTTON_IDENTIFIERS.columnSettings,
422
+ icon: "ph--caret-down--regular",
423
+ render: ({ fieldId }) => {
424
+ return createButton({
425
+ attr: BUTTON_IDENTIFIERS.columnSettings,
426
+ icon: columnSettingsButton.icon,
427
+ data: {
428
+ "data-field-id": fieldId
429
+ },
430
+ testId: "table-column-settings-button"
431
+ });
432
+ },
433
+ getData: (el) => ({
434
+ type: "columnSettings",
435
+ fieldId: el.getAttribute("data-field-id")
436
+ })
707
437
  };
708
-
709
- // packages/ui/react-ui-table/src/components/Toolbar/Toolbar.tsx
710
- var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
711
- var ToolbarRoot = ({ classNames, children, onAction }) => {
712
- return /* @__PURE__ */ React9.createElement(ToolbarContextProvider, {
713
- onAction
714
- }, /* @__PURE__ */ React9.createElement(NaturalToolbar2.Root, {
715
- classNames: [
716
- "is-full shrink-0 overflow-x-auto overflow-y-hidden p-1 attention-surface",
717
- classNames
718
- ]
719
- }, children));
438
+ var referencedCellButton = {
439
+ attr: BUTTON_IDENTIFIERS.referencedCell,
440
+ icon: "ph--link-simple-horizontal--regular",
441
+ render: ({ targetId, schemaId }) => {
442
+ return createButton({
443
+ attr: BUTTON_IDENTIFIERS.referencedCell,
444
+ icon: referencedCellButton.icon,
445
+ data: {
446
+ "data-target-id": targetId,
447
+ "data-schema-id": schemaId
448
+ },
449
+ testId: "table-ref-cell-button"
450
+ });
451
+ },
452
+ getData: (el) => ({
453
+ type: "referencedCell",
454
+ targetId: el.getAttribute("data-target-id"),
455
+ schemaId: el.getAttribute("data-schema-id")
456
+ })
720
457
  };
721
- var Editing = () => {
722
- const { onAction } = useToolbarContext("Editing");
723
- const { t } = useTranslation4(translationKey);
724
- return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(ToolbarButton, {
725
- value: "add-row",
726
- icon: "ph--plus--regular",
727
- "data-testid": "table.toolbar.add-row",
728
- onClick: () => onAction?.({
729
- type: "add-row"
730
- })
731
- }, t("add row")));
458
+ var rowMenuButton = {
459
+ attr: BUTTON_IDENTIFIERS.rowMenu,
460
+ icon: "ph--dots-three--regular",
461
+ render: ({ rowIndex }) => {
462
+ return createButton({
463
+ attr: BUTTON_IDENTIFIERS.rowMenu,
464
+ icon: rowMenuButton.icon,
465
+ data: {
466
+ "data-row-index": rowIndex.toString()
467
+ },
468
+ testId: "table-row-menu-button"
469
+ });
470
+ },
471
+ getData: (el) => ({
472
+ type: "rowMenu",
473
+ rowIndex: Number(el.getAttribute("data-row-index"))
474
+ })
732
475
  };
733
- var Actions = () => {
734
- const { onAction } = useToolbarContext("Actions");
735
- const { t } = useTranslation4(translationKey);
736
- return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(ToolbarButton, {
737
- value: "comment",
738
- icon: "ph--chat-text--regular",
739
- "data-testid": "table.toolbar.comment",
740
- onClick: () => onAction?.({
741
- type: "comment"
742
- })
743
- }, t("create comment")));
476
+ var sortButton = {
477
+ attr: BUTTON_IDENTIFIERS.sort,
478
+ icon: "ph--sort-ascending--regular",
479
+ render: ({ fieldId, direction }) => {
480
+ return createButton({
481
+ attr: BUTTON_IDENTIFIERS.sort,
482
+ icon: direction === "desc" ? "ph--sort-descending--regular" : "ph--sort-ascending--regular",
483
+ data: {
484
+ "data-field-id": fieldId,
485
+ "data-direction": direction ?? ""
486
+ },
487
+ testId: "table-sort-button",
488
+ // TODO(ZaymonFC): All buttons should be rendered into an absolutely positioned flex container.
489
+ // This is a bit hacky.
490
+ type: "secondary"
491
+ });
492
+ },
493
+ getData: (el) => ({
494
+ type: "sort",
495
+ fieldId: el.getAttribute("data-field-id"),
496
+ direction: el.getAttribute("data-direction")
497
+ })
744
498
  };
745
- var Toolbar = {
746
- Root: ToolbarRoot,
747
- Editing,
748
- Actions,
749
- Separator: ToolbarSeparator
499
+ var tableButtons = {
500
+ addColumn: addColumnButton,
501
+ columnSettings: columnSettingsButton,
502
+ referencedCell: referencedCellButton,
503
+ rowMenu: rowMenuButton,
504
+ sort: sortButton
750
505
  };
751
506
 
752
- // packages/ui/react-ui-table/src/hooks/useTableModel.ts
753
- import { useEffect as useEffect2, useState as useState3 } from "react";
754
-
755
- // packages/ui/react-ui-table/src/model/table-model.ts
756
- import { computed as computed2, effect as effect2, signal as signal3 } from "@preact/signals-core";
757
- import orderBy from "lodash.orderby";
758
- import { Resource as Resource2 } from "@dxos/context";
759
- import { getValue as getValue2, setValue, FormatEnum as FormatEnum3 } from "@dxos/echo-schema";
760
- import { invariant as invariant3 } from "@dxos/invariant";
761
- import { PublicKey as PublicKey2 } from "@dxos/react-client";
762
- import { cellClassesForFieldType, cellClassesForRowSelection, formatForDisplay, formatForEditing, parseValue } from "@dxos/react-ui-form";
763
- import { toPlaneCellIndex } from "@dxos/react-ui-grid";
764
- import { mx as mx2 } from "@dxos/react-ui-theme";
765
- import { VIEW_FIELD_LIMIT } from "@dxos/schema";
766
-
767
- // packages/ui/react-ui-table/src/model/modal-controller.ts
768
- import { signal } from "@preact/signals-core";
769
- import { log as log2 } from "@dxos/log";
770
-
771
- // packages/ui/react-ui-table/src/util/signals.ts
772
- var touch = (value) => value;
773
-
774
- // packages/ui/react-ui-table/src/util/table.ts
775
- import { AST, Format, FormatEnum as FormatEnum2, S as S2, TypedObject, TypeEnum } from "@dxos/echo-schema";
776
- import { log } from "@dxos/log";
777
- import { PublicKey } from "@dxos/react-client";
778
- import { create as create2 } from "@dxos/react-client/echo";
779
- import { createFieldId, createView, ViewProjection } from "@dxos/schema";
780
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/util/table.ts";
781
- var initializeTable = ({ space, table, initialRow = true }) => {
782
- log.info("initializeTable", {
783
- table
784
- }, {
785
- F: __dxlog_file3,
786
- L: 32,
787
- S: void 0,
788
- C: (f, a) => f(...a)
789
- });
790
- const ContactSchema = TypedObject({
791
- typename: `example.com/type/${PublicKey.random().truncate()}`,
792
- version: "0.1.0"
793
- })({
794
- name: S2.optional(S2.String).annotations({
795
- [AST.TitleAnnotationId]: "Name"
796
- }),
797
- email: S2.optional(Format.Email),
798
- salary: S2.optional(Format.Currency()).annotations({
799
- [AST.TitleAnnotationId]: "Salary"
800
- })
801
- });
802
- const contactSchema = space.db.schemaRegistry.addSchema(ContactSchema);
803
- table.view = createView({
804
- name: "Test",
805
- typename: contactSchema.typename,
806
- jsonSchema: contactSchema.jsonSchema,
807
- fields: [
808
- "name",
809
- "email",
810
- "salary"
811
- ]
812
- });
813
- const projection = new ViewProjection(contactSchema, table.view);
814
- projection.setFieldProjection({
815
- field: {
816
- id: table.view.fields[2].id,
817
- path: "salary",
818
- size: 150
819
- }
820
- });
821
- projection.setFieldProjection({
822
- field: {
823
- id: createFieldId(),
824
- path: "manager",
825
- referencePath: "name"
826
- },
827
- props: {
828
- property: "manager",
829
- type: TypeEnum.Ref,
830
- format: FormatEnum2.Ref,
831
- referenceSchema: contactSchema.typename,
832
- title: "Manager"
833
- }
834
- });
835
- if (initialRow) {
836
- space.db.add(create2(contactSchema, {}));
837
- }
838
- return contactSchema;
507
+ // packages/ui/react-ui-table/src/util/table-controls.ts
508
+ var CONTROL_IDENTIFIERS = {
509
+ checkbox: "data-table-checkbox",
510
+ switch: "data-table-switch"
839
511
  };
840
-
841
- // packages/ui/react-ui-table/src/util/table-buttons.ts
842
- var TABLE_ATTRS = {
843
- referencedCell: "data-table-ref-cell-button",
844
- columnSettings: "data-table-column-settings-button",
845
- rowMenu: "data-table-row-menu-button",
846
- newColumn: "data-table-new-column-button"
512
+ var BASE_CLASSES = {
513
+ checkbox: "absolute inset-block-[.375rem] inline-end-[.375rem] dx-checkbox",
514
+ switch: "absolute inset-block-[.25rem] inline-end-[.25rem] dx-checkbox--switch"
847
515
  };
848
- var ICONS = {
849
- referencedCell: "ph--link-simple-horizontal--regular",
850
- columnSettings: "ph--caret-down--regular",
851
- rowMenu: "ph--dots-three--regular",
852
- newColumn: "ph--plus--regular"
516
+ var renderAttributes = (data) => {
517
+ return Object.entries(data).map(([k, v]) => `${k}="${v}"`).join(" ");
853
518
  };
854
- var createButtonHtml = ({ button, value = "", disabled = false, testId }) => {
855
- const buttonClasses = "ch-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 inline-end-2";
856
- return `<button class="${buttonClasses}" ${TABLE_ATTRS[button]}="${value}" data-testid="${testId}" ${disabled ? "disabled" : ""}><svg><use href="/icons.svg#${ICONS[button]}"/></svg></button>`;
519
+ var renderInput = (baseClass, attrs, checked = false, disabled = false, preventToggle = false, testId) => {
520
+ return `<input type="checkbox" class="${baseClass}" ${renderAttributes(attrs)} ${checked ? "checked" : ""} ${preventToggle ? 'onclick="return false"' : ""} ${disabled ? "disabled" : ""} data-testid="${testId}"/>`;
857
521
  };
858
- var tableButtons = {
859
- referencedCell: {
860
- attr: TABLE_ATTRS.referencedCell,
861
- icon: ICONS.referencedCell,
862
- render: ({ targetId, schemaId }) => createButtonHtml({
863
- button: "referencedCell",
864
- value: `${schemaId}#${targetId}`,
865
- testId: "table-ref-cell-button"
866
- })
867
- },
868
- columnSettings: {
869
- attr: TABLE_ATTRS.columnSettings,
870
- icon: ICONS.columnSettings,
871
- render: ({ fieldId }) => createButtonHtml({
872
- button: "columnSettings",
873
- value: fieldId,
874
- testId: "table-column-settings-button"
875
- })
876
- },
877
- rowMenu: {
878
- attr: TABLE_ATTRS.rowMenu,
879
- icon: ICONS.rowMenu,
880
- render: ({ rowIndex }) => createButtonHtml({
881
- button: "rowMenu",
882
- value: rowIndex.toString(),
883
- testId: "table-row-menu-button"
522
+ var renderCheckbox = ({ rowIndex, header = false, checked = false, disabled = false }) => {
523
+ const attrs = {
524
+ [CONTROL_IDENTIFIERS.checkbox]: "",
525
+ "data-row-index": rowIndex.toString(),
526
+ ...header && {
527
+ "data-header": ""
528
+ }
529
+ };
530
+ return renderInput(BASE_CLASSES.checkbox, attrs, checked, disabled, true, "table-selection");
531
+ };
532
+ var renderSwitch = ({ colIndex, rowIndex, checked = false, disabled = false }) => {
533
+ const attrs = {
534
+ [CONTROL_IDENTIFIERS.switch]: "",
535
+ "data-row-index": rowIndex.toString(),
536
+ "data-col-index": colIndex.toString()
537
+ };
538
+ return renderInput(BASE_CLASSES.switch, attrs, checked, disabled, true, "table-switch");
539
+ };
540
+ var tableControls = {
541
+ checkbox: {
542
+ attr: CONTROL_IDENTIFIERS.checkbox,
543
+ render: renderCheckbox,
544
+ getData: (el) => ({
545
+ type: "checkbox",
546
+ rowIndex: Number(el.getAttribute("data-row-index")),
547
+ header: el.hasAttribute("data-header")
884
548
  })
885
549
  },
886
- addColumn: {
887
- attr: TABLE_ATTRS.newColumn,
888
- icon: ICONS.newColumn,
889
- render: ({ disabled } = {}) => createButtonHtml({
890
- button: "newColumn",
891
- disabled,
892
- testId: "table-new-column-button"
550
+ switch: {
551
+ attr: CONTROL_IDENTIFIERS.switch,
552
+ render: renderSwitch,
553
+ getData: (el) => ({
554
+ type: "switch",
555
+ rowIndex: Number(el.getAttribute("data-row-index")),
556
+ colIndex: Number(el.getAttribute("data-col-index"))
893
557
  })
894
558
  }
895
559
  };
896
560
 
897
- // packages/ui/react-ui-table/src/model/modal-controller.ts
898
- var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/modal-controller.ts";
899
- var ModalController = class {
900
- constructor() {
901
- this._state = signal({
902
- type: "closed"
903
- });
904
- this._triggerRef = {
905
- current: null
906
- };
907
- this.handleClick = (event) => {
908
- const target = event.target;
909
- const rowButton = target.closest(`button[${tableButtons.rowMenu.attr}]`);
910
- if (rowButton) {
911
- this._triggerRef.current = rowButton;
912
- this._state.value = {
913
- type: "row",
914
- rowIndex: Number(rowButton.getAttribute(tableButtons.rowMenu.attr))
915
- };
916
- return true;
917
- }
918
- const columnButton = target.closest(`button[${tableButtons.columnSettings.attr}]`);
919
- if (columnButton) {
920
- const fieldId = columnButton.getAttribute(tableButtons.columnSettings.attr);
921
- this._triggerRef.current = columnButton;
922
- this._state.value = {
923
- type: "column",
924
- fieldId
925
- };
926
- return true;
927
- }
928
- const addColumnButton = target.closest(`button[${tableButtons.addColumn.attr}]`);
929
- if (addColumnButton) {
930
- this._triggerRef.current = addColumnButton;
931
- this._state.value = {
932
- type: "columnSettings",
933
- mode: {
934
- type: "create"
935
- }
936
- };
937
- return true;
561
+ // packages/ui/react-ui-table/src/model/selection-model.ts
562
+ var SelectionModel = class extends Resource {
563
+ constructor(_rows, _onSelectionChanged) {
564
+ super();
565
+ this._rows = _rows;
566
+ this._onSelectionChanged = _onSelectionChanged;
567
+ this._selection = signal(/* @__PURE__ */ new Set());
568
+ this._validSelectedIds = computed(() => {
569
+ const rows = this._rows.value;
570
+ if (!rows) {
571
+ return /* @__PURE__ */ new Set();
938
572
  }
939
- const refButton = target.closest(`button[${tableButtons.referencedCell.attr}]`);
940
- if (refButton) {
941
- this._triggerRef.current = refButton;
942
- const [schemaId, targetId] = refButton.getAttribute(tableButtons.referencedCell.attr).split("#");
943
- this._state.value = {
944
- type: "refPanel",
945
- targetId,
946
- typename: schemaId
947
- };
948
- }
949
- return false;
950
- };
951
- this.openColumnSettings = () => {
952
- if (this._state.value.type === "column") {
953
- const fieldId = this._state.value.fieldId;
954
- requestAnimationFrame(() => {
955
- this._state.value = {
956
- type: "columnSettings",
957
- mode: {
958
- type: "edit",
959
- fieldId
960
- }
961
- };
962
- });
963
- }
964
- };
965
- this.openCreateRef = (typename, anchorCell, initialValues, onCreate) => {
966
- if (anchorCell) {
967
- this._triggerRef.current = anchorCell;
968
- this._state.value = {
969
- type: "createRefPanel",
970
- typename,
971
- initialValues,
972
- onCreate
973
- };
974
- } else {
975
- log2.warn("anchor cell not found while creating new ref", void 0, {
976
- F: __dxlog_file4,
977
- L: 119,
978
- S: this,
979
- C: (f, a) => f(...a)
980
- });
981
- }
982
- };
983
- this.close = () => {
984
- this._state.value = {
985
- type: "closed"
986
- };
987
- };
988
- }
989
- get state() {
990
- return this._state;
991
- }
992
- get trigger() {
993
- return this._triggerRef;
994
- }
995
- };
996
-
997
- // packages/ui/react-ui-table/src/model/selection-model.ts
998
- import { computed, effect, signal as signal2 } from "@preact/signals-core";
999
- import { Resource } from "@dxos/context";
1000
- var SelectionModel = class extends Resource {
1001
- constructor(_rows, _onSelectionChanged) {
1002
- super();
1003
- this._rows = _rows;
1004
- this._onSelectionChanged = _onSelectionChanged;
1005
- this._selection = signal2(/* @__PURE__ */ new Set());
1006
- this._validSelectedIds = computed(() => {
1007
- const rows = this._rows.value;
1008
- if (!rows) {
1009
- return /* @__PURE__ */ new Set();
1010
- }
1011
- const validIds = new Set(rows.map((row) => row.id));
1012
- return new Set([
1013
- ...this._selection.value
1014
- ].filter((id) => validIds.has(id)));
1015
- });
1016
- this._hasSelection = computed(() => this._validSelectedIds.value.size > 0);
1017
- this._allSelected = computed(() => {
1018
- const rows = this._rows.value;
1019
- if (rows.length === 0) {
1020
- return false;
573
+ const validIds = new Set(rows.map((row) => row.id));
574
+ return new Set([
575
+ ...this._selection.value
576
+ ].filter((id) => validIds.has(id)));
577
+ });
578
+ this._hasSelection = computed(() => this._validSelectedIds.value.size > 0);
579
+ this._allSelected = computed(() => {
580
+ const rows = this._rows.value;
581
+ if (rows.length === 0) {
582
+ return false;
1021
583
  }
1022
584
  return rows.every((row) => this._selection.value.has(row.id));
1023
585
  });
@@ -1043,7 +605,16 @@ var SelectionModel = class extends Resource {
1043
605
  this._selection.value = newSelection;
1044
606
  };
1045
607
  this.setSelection = (mode) => {
1046
- this._selection.value = mode === "all" ? new Set(this._rows.value.map((row) => row.id)) : /* @__PURE__ */ new Set();
608
+ switch (mode) {
609
+ case "all": {
610
+ this._selection.value = new Set(this._rows.value.map((row) => row.id));
611
+ break;
612
+ }
613
+ case "none": {
614
+ this._selection.value = /* @__PURE__ */ new Set();
615
+ break;
616
+ }
617
+ }
1047
618
  };
1048
619
  }
1049
620
  async _open() {
@@ -1067,25 +638,173 @@ var SelectionModel = class extends Resource {
1067
638
  }
1068
639
  };
1069
640
 
1070
- // packages/ui/react-ui-table/src/util/table-controls.ts
1071
- var checkboxAttributes = {
1072
- checkbox: "data-table-checkbox",
1073
- header: "data-table-header"
1074
- };
1075
- var renderCheckbox = ({ rowIndex, header = false, checked = false, disabled = false }) => {
1076
- const baseClasses = "absolute inset-block-[6px] inline-end-[6px] ch-checkbox";
1077
- const { attributes } = tableControls.checkbox;
1078
- return `<input type="checkbox" class="${baseClasses}" ${attributes.checkbox}="${rowIndex}" ${header ? attributes.header : ""} ${checked ? "checked" : ""} ${disabled ? "disabled" : ""} data-testid="table-selection"/>`;
1079
- };
1080
- var tableControls = {
1081
- checkbox: {
1082
- attributes: checkboxAttributes,
1083
- render: renderCheckbox
641
+ // packages/ui/react-ui-table/src/model/table-sorting.ts
642
+ import { computed as computed2, signal as signal2 } from "@preact/signals-core";
643
+ import orderBy from "lodash.orderby";
644
+ import { getValue, FormatEnum as FormatEnum2, TypeEnum as TypeEnum2 } from "@dxos/echo-schema";
645
+ import { formatForDisplay } from "@dxos/react-ui-form";
646
+ var TableSorting = class {
647
+ constructor(rows, _view, _projection) {
648
+ this._view = _view;
649
+ this._projection = _projection;
650
+ this._displayToDataIndex = /* @__PURE__ */ new Map();
651
+ this._localSort = signal2(void 0);
652
+ this._rows = rows;
653
+ this._isDirty = computed2(() => {
654
+ const local = this._localSort.value;
655
+ const viewSort = this._view?.query.sort?.[0];
656
+ if (local?.type === "cleared") {
657
+ return viewSort !== void 0;
658
+ }
659
+ if (local?.type === "active") {
660
+ if (!viewSort) {
661
+ return true;
662
+ }
663
+ return local.sort.fieldId !== viewSort.fieldId || local.sort.direction !== viewSort.direction;
664
+ }
665
+ return false;
666
+ });
667
+ this.sortedRows = this.initialiseSortedRows();
668
+ }
669
+ /**
670
+ * @reactive
671
+ * @returns Local sort if present, falls back to view sort.
672
+ */
673
+ get sorting() {
674
+ const local = this._localSort.value;
675
+ if (local?.type === "cleared") {
676
+ return void 0;
677
+ }
678
+ if (local?.type === "active") {
679
+ return local.sort;
680
+ }
681
+ return this._view?.query.sort?.[0];
682
+ }
683
+ /**
684
+ * @reactive
685
+ * @returns Whether local sort differs from view sort.
686
+ */
687
+ get isDirty() {
688
+ return this._isDirty.value;
689
+ }
690
+ getDataIndex(displayIndex) {
691
+ return this._displayToDataIndex.get(displayIndex) ?? displayIndex;
692
+ }
693
+ setSort(fieldId, direction) {
694
+ this._localSort.value = {
695
+ type: "active",
696
+ sort: {
697
+ fieldId,
698
+ direction
699
+ }
700
+ };
701
+ }
702
+ toggleSort(fieldId) {
703
+ if (!this._view || !this.sorting || this.sorting.fieldId !== fieldId) {
704
+ return;
705
+ }
706
+ switch (this.sorting.direction) {
707
+ case "asc": {
708
+ this.setSort(fieldId, "desc");
709
+ break;
710
+ }
711
+ case "desc": {
712
+ this.setSort(fieldId, "asc");
713
+ break;
714
+ }
715
+ }
716
+ }
717
+ clearSort() {
718
+ this._localSort.value = {
719
+ type: "cleared"
720
+ };
721
+ }
722
+ save() {
723
+ if (this._view && this._localSort.value !== void 0) {
724
+ if (this._localSort.value.type === "active") {
725
+ this._view.query.sort = [
726
+ this._localSort.value.sort
727
+ ];
728
+ } else {
729
+ this._view.query.sort = [];
730
+ }
731
+ this._localSort.value = void 0;
732
+ }
733
+ }
734
+ //
735
+ // Initialisation.
736
+ //
737
+ initialiseSortedRows() {
738
+ return computed2(() => {
739
+ this._displayToDataIndex.clear();
740
+ const sort = this.sorting;
741
+ if (!sort || !this._view) {
742
+ return this._rows.value;
743
+ }
744
+ const field = this._view.fields.find((f) => f.id === sort.fieldId);
745
+ if (!field) {
746
+ return this._rows.value;
747
+ }
748
+ const { props } = this._projection.getFieldProjection(field.id);
749
+ const dataWithIndices = this._rows.value.map((item, index) => {
750
+ const sortValue = this.getSortValue(props, field, item);
751
+ const isEmpty = !(props.type === TypeEnum2.Boolean) && (sortValue == null || sortValue === "");
752
+ return {
753
+ item,
754
+ index,
755
+ value: sortValue,
756
+ isEmpty
757
+ };
758
+ });
759
+ const ordered = orderBy(dataWithIndices, [
760
+ "isEmpty",
761
+ "value"
762
+ ], [
763
+ "asc",
764
+ sort.direction
765
+ ]);
766
+ ordered.forEach(({ index: dataIndex }, displayIndex) => {
767
+ if (displayIndex !== dataIndex) {
768
+ this._displayToDataIndex.set(displayIndex, dataIndex);
769
+ }
770
+ });
771
+ return ordered.map(({ item }) => item);
772
+ });
773
+ }
774
+ getSortValue(props, field, item) {
775
+ const { type, format } = props;
776
+ const value = getValue(item, field.path);
777
+ if (type === TypeEnum2.Boolean) {
778
+ return !!value;
779
+ }
780
+ if (type === TypeEnum2.Number) {
781
+ return value;
782
+ }
783
+ if (value == null) {
784
+ return value;
785
+ }
786
+ if (format === FormatEnum2.SingleSelect) {
787
+ const options = props.options;
788
+ if (!options) {
789
+ return void 0;
790
+ }
791
+ const option = options.find((option2) => option2.id === value);
792
+ return option ? options.indexOf(option) : void 0;
793
+ }
794
+ if (format === FormatEnum2.Ref && field.referencePath) {
795
+ const refValue = getValue(value.target, field.referencePath);
796
+ return refValue;
797
+ }
798
+ return formatForDisplay({
799
+ type,
800
+ format,
801
+ value
802
+ });
1084
803
  }
1085
804
  };
1086
805
 
1087
806
  // packages/ui/react-ui-table/src/model/table-model.ts
1088
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
807
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/table-model.ts";
1089
808
  var TableModel = class extends Resource2 {
1090
809
  constructor({ table, projection, sorting = [], pinnedRows = {
1091
810
  top: [],
@@ -1103,200 +822,7 @@ var TableModel = class extends Resource2 {
1103
822
  col: 0
1104
823
  }
1105
824
  });
1106
- /**
1107
- * Maps display indices to data indices.
1108
- * Used for translating between sorted/displayed order and original data order.
1109
- * Keys are display indices, values are corresponding data indices.
1110
- */
1111
- this._displayToDataIndex = /* @__PURE__ */ new Map();
1112
- this._sorting = signal3(void 0);
1113
825
  this._rows = signal3([]);
1114
- this._modalController = new ModalController();
1115
- //
1116
- // Get Cells
1117
- //
1118
- this.getCells = (range, plane) => {
1119
- switch (plane) {
1120
- case "grid": {
1121
- this._visibleRange.value = range;
1122
- return this.getMainGridCells(range);
1123
- }
1124
- case "frozenRowsStart": {
1125
- return this.getHeaderCells(range);
1126
- }
1127
- case "frozenColsStart": {
1128
- return this.getSelectionColumnCells(range);
1129
- }
1130
- case "frozenColsEnd": {
1131
- return this.getActionColumnCells(range);
1132
- }
1133
- case "fixedStartStart": {
1134
- return this.getSelectAllCell();
1135
- }
1136
- case "fixedStartEnd": {
1137
- return this.getNewColumnCell();
1138
- }
1139
- default: {
1140
- return {};
1141
- }
1142
- }
1143
- };
1144
- this.getMainGridCells = (range) => {
1145
- const cells = {};
1146
- const fields = this._table.view?.fields ?? [];
1147
- const addCell = (obj, field, colIndex, displayIndex) => {
1148
- const { props } = this._projection.getFieldProjection(field.id);
1149
- const cell = {
1150
- get value() {
1151
- const value = getValue2(obj, field.path);
1152
- if (value == null) {
1153
- return "";
1154
- }
1155
- switch (props.format) {
1156
- case FormatEnum3.Ref: {
1157
- if (!field.referencePath) {
1158
- return "";
1159
- }
1160
- return getValue2(value, field.referencePath);
1161
- }
1162
- default: {
1163
- return formatForDisplay({
1164
- type: props.type,
1165
- format: props.format,
1166
- value
1167
- });
1168
- }
1169
- }
1170
- }
1171
- };
1172
- const classes = [];
1173
- const formatClasses = cellClassesForFieldType({
1174
- type: props.type,
1175
- format: props.format
1176
- });
1177
- if (formatClasses) {
1178
- classes.push(formatClasses);
1179
- }
1180
- const rowSelectionClasses = cellClassesForRowSelection(this._selection.isObjectSelected(obj));
1181
- if (rowSelectionClasses) {
1182
- classes.push(rowSelectionClasses);
1183
- }
1184
- if (classes.length > 0) {
1185
- cell.className = mx2(classes.flat());
1186
- }
1187
- if (cell.value && props.format === FormatEnum3.Ref && props.referenceSchema) {
1188
- const targetObj = getValue2(obj, field.path);
1189
- cell.accessoryHtml = tableButtons.referencedCell.render({
1190
- targetId: targetObj.id,
1191
- schemaId: props.referenceSchema
1192
- });
1193
- }
1194
- const idx = toPlaneCellIndex({
1195
- col: colIndex,
1196
- row: displayIndex
1197
- });
1198
- cells[idx] = cell;
1199
- };
1200
- for (let row = range.start.row; row <= range.end.row && row < this._sortedRows.value.length; row++) {
1201
- for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1202
- const field = fields[col];
1203
- if (!field) {
1204
- continue;
1205
- }
1206
- addCell(this._sortedRows.value[row], field, col, row);
1207
- }
1208
- }
1209
- return cells;
1210
- };
1211
- this.getHeaderCells = (range) => {
1212
- const cells = {};
1213
- const fields = this.table.view?.fields ?? [];
1214
- for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1215
- const { field, props } = this._projection.getFieldProjection(fields[col].id);
1216
- cells[toPlaneCellIndex({
1217
- col,
1218
- row: 0
1219
- })] = {
1220
- // TODO(burdon): Use same logic as form for fallback title.
1221
- value: props.title ?? field.path,
1222
- readonly: true,
1223
- resizeHandle: "col",
1224
- accessoryHtml: tableButtons.columnSettings.render({
1225
- fieldId: field.id
1226
- })
1227
- };
1228
- }
1229
- return cells;
1230
- };
1231
- this.getSelectionColumnCells = (range) => {
1232
- const cells = {};
1233
- for (let row = range.start.row; row <= range.end.row && row < this._rows.value.length; row++) {
1234
- const isSelected = this._selection.isRowIndexSelected(row);
1235
- const classes = cellClassesForRowSelection(isSelected);
1236
- cells[toPlaneCellIndex({
1237
- col: 0,
1238
- row
1239
- })] = {
1240
- value: "",
1241
- readonly: true,
1242
- className: classes ? mx2(classes) : void 0,
1243
- accessoryHtml: tableControls.checkbox.render({
1244
- rowIndex: row,
1245
- checked: isSelected
1246
- })
1247
- };
1248
- }
1249
- return cells;
1250
- };
1251
- this.getActionColumnCells = (range) => {
1252
- const cells = {};
1253
- for (let row = range.start.row; row <= range.end.row && row < this._rows.value.length; row++) {
1254
- const isSelected = this._selection.isRowIndexSelected(row);
1255
- const classes = cellClassesForRowSelection(isSelected);
1256
- cells[toPlaneCellIndex({
1257
- col: 0,
1258
- row
1259
- })] = {
1260
- value: "",
1261
- readonly: true,
1262
- className: classes ? mx2(classes) : void 0,
1263
- accessoryHtml: tableButtons.rowMenu.render({
1264
- rowIndex: row
1265
- })
1266
- };
1267
- }
1268
- return cells;
1269
- };
1270
- this.getSelectAllCell = () => {
1271
- return {
1272
- [toPlaneCellIndex({
1273
- col: 0,
1274
- row: 0
1275
- })]: {
1276
- value: "",
1277
- accessoryHtml: tableControls.checkbox.render({
1278
- rowIndex: 0,
1279
- header: true,
1280
- checked: this._selection.allRowsSeleted.value
1281
- }),
1282
- readonly: true
1283
- }
1284
- };
1285
- };
1286
- this.getNewColumnCell = () => {
1287
- return {
1288
- [toPlaneCellIndex({
1289
- col: 0,
1290
- row: 0
1291
- })]: {
1292
- value: "",
1293
- accessoryHtml: tableButtons.addColumn.render({
1294
- disabled: (this._table.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT
1295
- }),
1296
- readonly: true
1297
- }
1298
- };
1299
- };
1300
826
  //
1301
827
  // Data
1302
828
  //
@@ -1304,13 +830,13 @@ var TableModel = class extends Resource2 {
1304
830
  this._rows.value = obj;
1305
831
  };
1306
832
  this.getRowCount = () => this._rows.value.length;
1307
- this.getColumnCount = () => this.table.view?.fields.length ?? 0;
833
+ this.getColumnCount = () => this.table.view?.target?.fields.length ?? 0;
1308
834
  this.insertRow = (rowIndex) => {
1309
- const row = rowIndex !== void 0 ? this._displayToDataIndex.get(rowIndex) ?? rowIndex : this._rows.value.length;
835
+ const row = rowIndex !== void 0 ? this._sorting.getDataIndex(rowIndex) : this._rows.value.length;
1310
836
  this._onInsertRow?.(row);
1311
837
  };
1312
838
  this.deleteRow = (rowIndex) => {
1313
- const row = this._displayToDataIndex.get(rowIndex) ?? rowIndex;
839
+ const row = this._sorting.getDataIndex(rowIndex);
1314
840
  const obj = this._rows.value[row];
1315
841
  const objectsToDelete = [];
1316
842
  if (this._selection.hasSelection.value) {
@@ -1322,12 +848,12 @@ var TableModel = class extends Resource2 {
1322
848
  this._onDeleteRows?.(row, objectsToDelete);
1323
849
  };
1324
850
  this.getCellData = ({ col, row }) => {
1325
- const fields = this.table.view?.fields ?? [];
851
+ const fields = this.table.view?.target?.fields ?? [];
1326
852
  if (col < 0 || col >= fields.length) {
1327
853
  return void 0;
1328
854
  }
1329
855
  const field = fields[col];
1330
- const dataIndex = this._displayToDataIndex.get(row) ?? row;
856
+ const dataIndex = this._sorting.getDataIndex(row);
1331
857
  const value = getValue2(this._rows.value[dataIndex], field.path);
1332
858
  if (value == null) {
1333
859
  return "";
@@ -1338,7 +864,7 @@ var TableModel = class extends Resource2 {
1338
864
  if (!field.referencePath) {
1339
865
  return "";
1340
866
  }
1341
- return getValue2(value, field.referencePath);
867
+ return getValue2(value.target, field.referencePath);
1342
868
  }
1343
869
  default: {
1344
870
  return formatForEditing({
@@ -1350,8 +876,8 @@ var TableModel = class extends Resource2 {
1350
876
  }
1351
877
  };
1352
878
  this.setCellData = ({ col, row }, value) => {
1353
- const rowIdx = this._displayToDataIndex.get(row) ?? row;
1354
- const fields = this.table.view?.fields ?? [];
879
+ const rowIdx = this._sorting.getDataIndex(row);
880
+ const fields = this.table.view?.target?.fields ?? [];
1355
881
  if (col < 0 || col >= fields.length) {
1356
882
  return;
1357
883
  }
@@ -1359,7 +885,9 @@ var TableModel = class extends Resource2 {
1359
885
  const { props } = this._projection.getFieldProjection(field.id);
1360
886
  switch (props.format) {
1361
887
  case FormatEnum3.Ref: {
1362
- setValue(this._rows.value[rowIdx], field.path, value);
888
+ if (isReactiveObject(value)) {
889
+ setValue(this._rows.value[rowIdx], field.path, makeRef2(value));
890
+ }
1363
891
  break;
1364
892
  }
1365
893
  default: {
@@ -1372,31 +900,13 @@ var TableModel = class extends Resource2 {
1372
900
  }
1373
901
  }
1374
902
  };
1375
- //
1376
- // Interactions
1377
- //
1378
- this.handleGridClick = (event) => {
1379
- if (!this._modalController.handleClick(event)) {
1380
- const target = event.target;
1381
- const selectionCheckbox = target.closest(`input[${tableControls.checkbox.attributes.checkbox}]`);
1382
- if (selectionCheckbox) {
1383
- const isHeader = selectionCheckbox.hasAttribute(tableControls.checkbox.attributes.header);
1384
- if (isHeader) {
1385
- if (this._selection.allRowsSeleted.value) {
1386
- this._selection.setSelection("none");
1387
- } else {
1388
- this._selection.setSelection("all");
1389
- }
1390
- } else {
1391
- const rowIndex = Number(selectionCheckbox.getAttribute(tableControls.checkbox.attributes.checkbox));
1392
- this._selection.toggleSelectionForRowIndex(rowIndex);
1393
- }
1394
- }
1395
- }
1396
- };
1397
903
  this._table = table;
1398
904
  this._projection = projection;
1399
- this._sorting.value = sorting.at(0);
905
+ this._sorting = new TableSorting(this._rows, table.view?.target, projection);
906
+ if (sorting.length > 0) {
907
+ const [sort] = sorting;
908
+ this._sorting.setSort(sort.fieldId, sort.direction);
909
+ }
1400
910
  this._pinnedRows = pinnedRows;
1401
911
  this._onInsertRow = onInsertRow;
1402
912
  this._onDeleteRows = onDeleteRows;
@@ -1410,13 +920,22 @@ var TableModel = class extends Resource2 {
1410
920
  get projection() {
1411
921
  return this._projection;
1412
922
  }
923
+ get rows() {
924
+ return this._sorting.sortedRows;
925
+ }
926
+ /**
927
+ * @reactive
928
+ */
929
+ get sorting() {
930
+ return this._sorting;
931
+ }
1413
932
  get pinnedRows() {
1414
933
  return this._pinnedRows;
1415
934
  }
1416
935
  get columnMeta() {
1417
- invariant3(this._columnMeta, void 0, {
1418
- F: __dxlog_file5,
1419
- L: 126,
936
+ invariant(this._columnMeta, void 0, {
937
+ F: __dxlog_file,
938
+ L: 117,
1420
939
  S: this,
1421
940
  A: [
1422
941
  "this._columnMeta",
@@ -1425,28 +944,25 @@ var TableModel = class extends Resource2 {
1425
944
  });
1426
945
  return this._columnMeta;
1427
946
  }
1428
- get sorting() {
1429
- return this._sorting.value;
1430
- }
1431
- get modalController() {
1432
- return this._modalController;
1433
- }
1434
947
  get selection() {
1435
948
  return this._selection;
1436
949
  }
950
+ /** @reactive */
951
+ get isViewDirty() {
952
+ return this._sorting.isDirty;
953
+ }
1437
954
  //
1438
955
  // Initialisation
1439
956
  //
1440
957
  async _open() {
1441
958
  this.initializeColumnMeta();
1442
- this.initializeSorting();
1443
959
  this.initializeEffects();
1444
- this._selection = new SelectionModel(this._sortedRows, () => this._onRowOrderChanged?.());
960
+ this._selection = new SelectionModel(this._sorting.sortedRows, () => this._onRowOrderChanged?.());
1445
961
  await this._selection.open(this._ctx);
1446
962
  }
1447
963
  initializeColumnMeta() {
1448
- this._columnMeta = computed2(() => {
1449
- const fields = this._table.view?.fields ?? [];
964
+ this._columnMeta = computed3(() => {
965
+ const fields = this._table.view?.target?.fields ?? [];
1450
966
  const meta = Object.fromEntries(fields.map((field, index) => [
1451
967
  index,
1452
968
  {
@@ -1468,123 +984,959 @@ var TableModel = class extends Resource2 {
1468
984
  resizeable: false
1469
985
  }
1470
986
  }
1471
- };
1472
- });
1473
- }
1474
- initializeSorting() {
1475
- this._sortedRows = computed2(() => {
1476
- this._displayToDataIndex.clear();
1477
- const sort = this._sorting.value;
1478
- if (!sort) {
1479
- return this._rows.value;
987
+ };
988
+ });
989
+ }
990
+ initializeEffects() {
991
+ const rowOrderWatcher = effect2(() => {
992
+ touch(this._sorting.sortedRows.value);
993
+ this._onRowOrderChanged?.();
994
+ });
995
+ this._ctx.onDispose(rowOrderWatcher);
996
+ const rowEffectManager = effect2(() => {
997
+ const { start, end } = touch(this._visibleRange.value);
998
+ const rowEffects = [];
999
+ for (let row = start.row; row <= end.row; row++) {
1000
+ rowEffects.push(effect2(() => {
1001
+ const obj = this._sorting.sortedRows.value[row];
1002
+ this?._table?.view?.target?.fields.forEach((field) => touch(getValue2(obj, field.path)));
1003
+ this._onCellUpdate?.({
1004
+ row,
1005
+ col: start.col,
1006
+ plane: "grid"
1007
+ });
1008
+ }));
1009
+ }
1010
+ return () => rowEffects.forEach((cleanup) => cleanup());
1011
+ });
1012
+ this._ctx.onDispose(rowEffectManager);
1013
+ }
1014
+ /**
1015
+ * Updates a cell's value using a transform function.
1016
+ * @param {DxGridPlanePosition} position - The position of the cell to update.
1017
+ * @param {(value: any) => any} update - A function that takes the current value and returns the updated value.
1018
+ */
1019
+ updateCellData({ col, row }, update) {
1020
+ const dataRow = this._sorting.getDataIndex(row);
1021
+ const fields = this.table.view?.target?.fields ?? [];
1022
+ const field = fields[col];
1023
+ const value = getValue2(this._rows.value[dataRow], field.path);
1024
+ const updatedValue = update(value);
1025
+ setValue(this._rows.value[dataRow], field.path, updatedValue);
1026
+ }
1027
+ //
1028
+ // Column Operations
1029
+ //
1030
+ deleteColumn(fieldId) {
1031
+ if (!this.table.view) {
1032
+ return;
1033
+ }
1034
+ const field = this.table.view.target?.fields.find((field2) => field2.id === fieldId);
1035
+ if (field && this._onDeleteColumn) {
1036
+ this._onDeleteColumn(field.id);
1037
+ }
1038
+ }
1039
+ //
1040
+ // Resize
1041
+ //
1042
+ setColumnWidth(columnIndex, width) {
1043
+ const fields = this.table.view?.target?.fields ?? [];
1044
+ if (columnIndex < fields.length) {
1045
+ const newWidth = Math.max(0, width);
1046
+ const field = fields[columnIndex];
1047
+ if (field) {
1048
+ field.size = newWidth;
1049
+ }
1050
+ }
1051
+ }
1052
+ //
1053
+ // Pinning
1054
+ //
1055
+ // TODO(burdon): Change to setPinned(on/off).
1056
+ pinRow(rowIndex, side) {
1057
+ this._pinnedRows[side].push(rowIndex);
1058
+ }
1059
+ unpinRow(rowIndex) {
1060
+ this._pinnedRows.top = this._pinnedRows.top.filter((index) => index !== rowIndex);
1061
+ this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index) => index !== rowIndex);
1062
+ }
1063
+ //
1064
+ // View operations
1065
+ //
1066
+ saveView() {
1067
+ this._sorting.save();
1068
+ }
1069
+ };
1070
+
1071
+ // packages/ui/react-ui-table/src/model/modal-controller.ts
1072
+ import { signal as signal4 } from "@preact/signals-core";
1073
+ import { log } from "@dxos/log";
1074
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/model/modal-controller.ts";
1075
+ var ModalController = class {
1076
+ constructor() {
1077
+ this._state = signal4({
1078
+ type: "closed"
1079
+ });
1080
+ this._triggerRef = {
1081
+ current: null
1082
+ };
1083
+ this.setTrigger = (trigger) => {
1084
+ this._triggerRef.current = trigger;
1085
+ };
1086
+ this.showRowMenu = (rowIndex) => {
1087
+ this._state.value = {
1088
+ type: "row",
1089
+ rowIndex
1090
+ };
1091
+ };
1092
+ this.showColumnMenu = (fieldId) => {
1093
+ this._state.value = {
1094
+ type: "column",
1095
+ fieldId
1096
+ };
1097
+ };
1098
+ this.showColumnCreator = () => {
1099
+ this._state.value = {
1100
+ type: "columnSettings",
1101
+ mode: {
1102
+ type: "create"
1103
+ }
1104
+ };
1105
+ };
1106
+ this.showReferencePanel = (targetId, schemaId) => {
1107
+ this._state.value = {
1108
+ type: "refPanel",
1109
+ targetId,
1110
+ typename: schemaId
1111
+ };
1112
+ };
1113
+ this.openColumnSettings = () => {
1114
+ if (this._state.value.type === "column") {
1115
+ const fieldId = this._state.value.fieldId;
1116
+ requestAnimationFrame(() => {
1117
+ this._state.value = {
1118
+ type: "columnSettings",
1119
+ mode: {
1120
+ type: "edit",
1121
+ fieldId
1122
+ }
1123
+ };
1124
+ });
1125
+ }
1126
+ };
1127
+ this.openCreateRef = (typename, anchorCell, initialValues, onCreate) => {
1128
+ if (anchorCell) {
1129
+ this._triggerRef.current = anchorCell;
1130
+ this._state.value = {
1131
+ type: "createRefPanel",
1132
+ typename,
1133
+ initialValues,
1134
+ onCreate
1135
+ };
1136
+ } else {
1137
+ log.warn("anchor cell not found while creating new ref", void 0, {
1138
+ F: __dxlog_file2,
1139
+ L: 86,
1140
+ S: this,
1141
+ C: (f, a) => f(...a)
1142
+ });
1143
+ }
1144
+ };
1145
+ this.close = () => {
1146
+ this._state.value = {
1147
+ type: "closed"
1148
+ };
1149
+ };
1150
+ }
1151
+ get state() {
1152
+ return this._state;
1153
+ }
1154
+ get trigger() {
1155
+ return this._triggerRef;
1156
+ }
1157
+ };
1158
+
1159
+ // packages/ui/react-ui-table/src/model/table-presentation.ts
1160
+ import { signal as signal5 } from "@preact/signals-core";
1161
+ import { FormatEnum as FormatEnum4, getValue as getValue3 } from "@dxos/echo-schema";
1162
+ import { cellClassesForFieldType, cellClassesForRowSelection, formatForDisplay as formatForDisplay2 } from "@dxos/react-ui-form";
1163
+ import { toPlaneCellIndex } from "@dxos/react-ui-grid";
1164
+ import { mx } from "@dxos/react-ui-theme";
1165
+ import { VIEW_FIELD_LIMIT } from "@dxos/schema";
1166
+ var TablePresentation = class {
1167
+ constructor(model, _visibleRange = signal5({
1168
+ start: {
1169
+ row: 0,
1170
+ col: 0
1171
+ },
1172
+ end: {
1173
+ row: 0,
1174
+ col: 0
1175
+ }
1176
+ })) {
1177
+ this.model = model;
1178
+ this._visibleRange = _visibleRange;
1179
+ }
1180
+ getCells(range, plane) {
1181
+ switch (plane) {
1182
+ case "grid":
1183
+ this._visibleRange.value = range;
1184
+ return this.getMainGridCells(range);
1185
+ case "frozenRowsStart":
1186
+ return this.getHeaderCells(range);
1187
+ case "frozenColsStart":
1188
+ return this.getSelectionColumnCells(range);
1189
+ case "frozenColsEnd":
1190
+ return this.getActionColumnCells(range);
1191
+ case "fixedStartStart":
1192
+ return this.getSelectAllCell();
1193
+ case "fixedStartEnd":
1194
+ return this.getNewColumnCell();
1195
+ default:
1196
+ return {};
1197
+ }
1198
+ }
1199
+ getMainGridCells(range) {
1200
+ const cells = {};
1201
+ const fields = this.model.table.view?.target?.fields ?? [];
1202
+ const addCell = (obj, field, colIndex, displayIndex) => {
1203
+ const { props } = this.model.projection.getFieldProjection(field.id);
1204
+ const cell = {
1205
+ get value() {
1206
+ const value = getValue3(obj, field.path);
1207
+ if (value == null) {
1208
+ return "";
1209
+ }
1210
+ switch (props.format) {
1211
+ case FormatEnum4.Boolean:
1212
+ case FormatEnum4.SingleSelect: {
1213
+ return "";
1214
+ }
1215
+ case FormatEnum4.Ref: {
1216
+ if (!field.referencePath) {
1217
+ return "";
1218
+ }
1219
+ return getValue3(value.target, field.referencePath);
1220
+ }
1221
+ default: {
1222
+ return formatForDisplay2({
1223
+ type: props.type,
1224
+ format: props.format,
1225
+ value
1226
+ });
1227
+ }
1228
+ }
1229
+ }
1230
+ };
1231
+ const classes = [];
1232
+ const formatClasses = cellClassesForFieldType({
1233
+ type: props.type,
1234
+ format: props.format
1235
+ });
1236
+ if (formatClasses) {
1237
+ classes.push(formatClasses);
1238
+ }
1239
+ const rowSelectionClasses = cellClassesForRowSelection(this.model.selection.isObjectSelected(obj));
1240
+ if (rowSelectionClasses) {
1241
+ classes.push(rowSelectionClasses);
1242
+ }
1243
+ if (classes.length > 0) {
1244
+ cell.className = mx(classes.flat());
1245
+ }
1246
+ if (props.format === FormatEnum4.Ref && props.referenceSchema) {
1247
+ const targetObj = getValue3(obj, field.path)?.target;
1248
+ if (targetObj) {
1249
+ cell.accessoryHtml = tableButtons.referencedCell.render({
1250
+ targetId: targetObj.id,
1251
+ schemaId: props.referenceSchema
1252
+ });
1253
+ }
1254
+ }
1255
+ if (props.format === FormatEnum4.Boolean) {
1256
+ const value = getValue3(obj, field.path);
1257
+ cell.accessoryHtml = tableControls.switch.render({
1258
+ colIndex,
1259
+ rowIndex: displayIndex,
1260
+ checked: value ?? false
1261
+ });
1262
+ cell.readonly = true;
1263
+ }
1264
+ if (props.format === FormatEnum4.SingleSelect) {
1265
+ const value = getValue3(obj, field.path);
1266
+ const options = this.model.projection.getFieldProjection(field.id).props.options;
1267
+ if (options) {
1268
+ const option = options.find((o) => o.id === value);
1269
+ if (option) {
1270
+ cell.accessoryHtml = `<span class="dx-tag" data-hue="${option.color}">${option.title}</span>`;
1271
+ }
1272
+ }
1273
+ }
1274
+ const idx = toPlaneCellIndex({
1275
+ col: colIndex,
1276
+ row: displayIndex
1277
+ });
1278
+ cells[idx] = cell;
1279
+ };
1280
+ for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1281
+ for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1282
+ const field = fields[col];
1283
+ if (!field) {
1284
+ continue;
1285
+ }
1286
+ addCell(this.model.rows.value[row], field, col, row);
1287
+ }
1288
+ }
1289
+ return cells;
1290
+ }
1291
+ getHeaderCells(range) {
1292
+ const cells = {};
1293
+ const fields = this.model.table.view?.target?.fields ?? [];
1294
+ for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
1295
+ const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
1296
+ const sorting = this.model.sorting?.sorting;
1297
+ const direction = sorting?.fieldId === field.id ? sorting.direction : void 0;
1298
+ cells[toPlaneCellIndex({
1299
+ col,
1300
+ row: 0
1301
+ })] = {
1302
+ // TODO(burdon): Use same logic as form for fallback title.
1303
+ value: props.title ?? field.path,
1304
+ readonly: true,
1305
+ resizeHandle: "col",
1306
+ accessoryHtml: `
1307
+ ${direction !== void 0 ? tableButtons.sort.render({
1308
+ fieldId: field.id,
1309
+ direction
1310
+ }) : ""}
1311
+ ${tableButtons.columnSettings.render({
1312
+ fieldId: field.id
1313
+ })}
1314
+ `,
1315
+ className: "!bg-gridHeader"
1316
+ };
1317
+ }
1318
+ return cells;
1319
+ }
1320
+ getSelectionColumnCells(range) {
1321
+ const cells = {};
1322
+ for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1323
+ const isSelected = this.model.selection.isRowIndexSelected(row);
1324
+ const classes = cellClassesForRowSelection(isSelected);
1325
+ cells[toPlaneCellIndex({
1326
+ col: 0,
1327
+ row
1328
+ })] = {
1329
+ value: "",
1330
+ readonly: true,
1331
+ className: classes ? mx(classes) : void 0,
1332
+ accessoryHtml: tableControls.checkbox.render({
1333
+ rowIndex: row,
1334
+ checked: isSelected
1335
+ })
1336
+ };
1337
+ }
1338
+ return cells;
1339
+ }
1340
+ getActionColumnCells(range) {
1341
+ const cells = {};
1342
+ for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
1343
+ const isSelected = this.model.selection.isRowIndexSelected(row);
1344
+ const classes = cellClassesForRowSelection(isSelected);
1345
+ cells[toPlaneCellIndex({
1346
+ col: 0,
1347
+ row
1348
+ })] = {
1349
+ value: "",
1350
+ readonly: true,
1351
+ className: classes ? mx(classes) : void 0,
1352
+ accessoryHtml: tableButtons.rowMenu.render({
1353
+ rowIndex: row
1354
+ })
1355
+ };
1356
+ }
1357
+ return cells;
1358
+ }
1359
+ getSelectAllCell() {
1360
+ return {
1361
+ [toPlaneCellIndex({
1362
+ col: 0,
1363
+ row: 0
1364
+ })]: {
1365
+ accessoryHtml: tableControls.checkbox.render({
1366
+ rowIndex: 0,
1367
+ header: true,
1368
+ checked: this.model.selection.allRowsSeleted.value
1369
+ }),
1370
+ className: "!bg-gridHeader",
1371
+ readonly: true,
1372
+ value: ""
1373
+ }
1374
+ };
1375
+ }
1376
+ getNewColumnCell() {
1377
+ return {
1378
+ [toPlaneCellIndex({
1379
+ col: 0,
1380
+ row: 0
1381
+ })]: {
1382
+ accessoryHtml: tableButtons.addColumn.render({
1383
+ disabled: (this.model.table.view?.target?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT
1384
+ }),
1385
+ className: "!bg-gridHeader",
1386
+ readonly: true,
1387
+ value: ""
1388
+ }
1389
+ };
1390
+ }
1391
+ };
1392
+
1393
+ // packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx
1394
+ import React6, { useCallback as useCallback3, useMemo as useMemo3, useRef } from "react";
1395
+ import { FormatEnum as FormatEnum5 } from "@dxos/echo-schema";
1396
+ import { invariant as invariant2 } from "@dxos/invariant";
1397
+ import { cellQuery, editorKeys, parseCellIndex, useGridContext as useGridContext2, GridCellEditor } from "@dxos/react-ui-grid";
1398
+
1399
+ // packages/ui/react-ui-table/src/components/TableCellEditor/extension.ts
1400
+ import { autocompletion, pickedCompletion } from "@codemirror/autocomplete";
1401
+ import { EditorView } from "@codemirror/view";
1402
+ var completion = ({ debug, onQuery, onMatch }) => {
1403
+ return [
1404
+ EditorView.theme({
1405
+ ".cm-completionDialog": {
1406
+ width: "var(--dx-gridCellWidth)"
1407
+ }
1408
+ }),
1409
+ autocompletion({
1410
+ icons: false,
1411
+ activateOnTyping: true,
1412
+ closeOnBlur: !debug,
1413
+ override: [
1414
+ async (context) => {
1415
+ const text = context.state.doc.toString();
1416
+ return {
1417
+ from: 0,
1418
+ options: await onQuery(text)
1419
+ };
1420
+ }
1421
+ ],
1422
+ // TODO(burdon): Consts from grid (to line-up with grid).
1423
+ tooltipClass: () => "cm-completionDialog !mt-[8px] !-ml-[4px] [&>ul]:!max-h-[264px]",
1424
+ optionClass: () => "flex h-[33px] items-center"
1425
+ }),
1426
+ // Check for accepted completion metadata in the transaction.
1427
+ EditorView.updateListener.of((update) => {
1428
+ update.transactions.forEach((tr) => {
1429
+ const completion2 = tr.annotation(pickedCompletion);
1430
+ if (completion2) {
1431
+ onMatch(completion2.data);
1432
+ }
1433
+ });
1434
+ })
1435
+ ];
1436
+ };
1437
+
1438
+ // packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx
1439
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/TableCellEditor/TableCellEditor.tsx";
1440
+ var newValue = Symbol.for("newValue");
1441
+ var createOption = (text) => ({
1442
+ [newValue]: true,
1443
+ text
1444
+ });
1445
+ var isCreateOption = (data) => typeof data === "object" && data[newValue];
1446
+ var TableCellEditor = ({ model, modals, onEnter, onFocus, onQuery, __gridScope }) => {
1447
+ const { id: gridId, editing, setEditing } = useGridContext2("TableCellEditor", __gridScope);
1448
+ const suppressNextBlur = useRef(false);
1449
+ const fieldProjection = useMemo3(() => {
1450
+ if (!model || !editing) {
1451
+ return;
1452
+ }
1453
+ const { col } = parseCellIndex(editing.index);
1454
+ const field = model.projection.view.fields[col];
1455
+ const fieldProjection2 = model.projection.getFieldProjection(field.id);
1456
+ invariant2(fieldProjection2, void 0, {
1457
+ F: __dxlog_file3,
1458
+ L: 67,
1459
+ S: void 0,
1460
+ A: [
1461
+ "fieldProjection",
1462
+ ""
1463
+ ]
1464
+ });
1465
+ return fieldProjection2;
1466
+ }, [
1467
+ model,
1468
+ editing
1469
+ ]);
1470
+ const handleEnter = useCallback3((value) => {
1471
+ if (!model || !editing) {
1472
+ return;
1473
+ }
1474
+ const cell = parseCellIndex(editing.index);
1475
+ model.setCellData(cell, value);
1476
+ onEnter?.(cell);
1477
+ onFocus?.();
1478
+ setEditing(null);
1479
+ }, [
1480
+ model,
1481
+ editing
1482
+ ]);
1483
+ const handleBlur = useCallback3((value) => {
1484
+ if (!model || !editing) {
1485
+ return;
1486
+ }
1487
+ if (suppressNextBlur.current) {
1488
+ suppressNextBlur.current = false;
1489
+ return;
1490
+ }
1491
+ const cell = parseCellIndex(editing.index);
1492
+ if (value !== void 0) {
1493
+ model.setCellData(cell, value);
1494
+ }
1495
+ }, [
1496
+ model,
1497
+ editing
1498
+ ]);
1499
+ const handleClose = useCallback3((value, event) => {
1500
+ if (!model || !editing || !fieldProjection) {
1501
+ return;
1502
+ }
1503
+ const cell = parseCellIndex(editing.index);
1504
+ onEnter?.(cell);
1505
+ if (event && onFocus) {
1506
+ onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
1507
+ }
1508
+ }, [
1509
+ model,
1510
+ editing,
1511
+ onFocus,
1512
+ onEnter,
1513
+ fieldProjection,
1514
+ determineNavigationAxis,
1515
+ determineNavigationDelta
1516
+ ]);
1517
+ const extension = useMemo3(() => {
1518
+ if (!fieldProjection) {
1519
+ return [];
1520
+ }
1521
+ const extension2 = [
1522
+ editorKeys({
1523
+ onClose: handleClose,
1524
+ ...editing?.initialContent && {
1525
+ onNav: handleClose
1526
+ }
1527
+ })
1528
+ ];
1529
+ if (onQuery) {
1530
+ switch (fieldProjection.props.format) {
1531
+ case FormatEnum5.Ref: {
1532
+ extension2.push([
1533
+ completion({
1534
+ onQuery: (text) => onQuery(fieldProjection, text),
1535
+ onMatch: (data) => {
1536
+ if (model && editing && modals) {
1537
+ if (isCreateOption(data)) {
1538
+ const { field, props } = fieldProjection;
1539
+ if (props.referenceSchema) {
1540
+ suppressNextBlur.current = true;
1541
+ modals.openCreateRef(props.referenceSchema, document.querySelector(cellQuery(editing.index, gridId)), {
1542
+ [field.referencePath]: data.text
1543
+ }, (data2) => {
1544
+ handleEnter(data2);
1545
+ });
1546
+ }
1547
+ } else {
1548
+ handleEnter(data);
1549
+ }
1550
+ }
1551
+ }
1552
+ })
1553
+ ]);
1554
+ break;
1555
+ }
1556
+ }
1557
+ }
1558
+ return extension2;
1559
+ }, [
1560
+ model,
1561
+ modals,
1562
+ editing,
1563
+ fieldProjection,
1564
+ handleClose
1565
+ ]);
1566
+ const getCellContent = useCallback3(() => {
1567
+ if (model && editing) {
1568
+ const value = model.getCellData(parseCellIndex(editing.index));
1569
+ return value !== void 0 ? String(value) : "";
1570
+ }
1571
+ }, [
1572
+ model,
1573
+ editing
1574
+ ]);
1575
+ return /* @__PURE__ */ React6.createElement(GridCellEditor, {
1576
+ extension,
1577
+ getCellContent,
1578
+ onBlur: handleBlur
1579
+ });
1580
+ };
1581
+ var determineNavigationAxis = ({ key }) => {
1582
+ switch (key) {
1583
+ case "ArrowUp":
1584
+ case "ArrowDown":
1585
+ case "Enter": {
1586
+ return "row";
1587
+ }
1588
+ case "ArrowLeft":
1589
+ case "ArrowRight":
1590
+ case "Tab":
1591
+ return "col";
1592
+ }
1593
+ };
1594
+ var determineNavigationDelta = ({ key, shift }) => {
1595
+ switch (key) {
1596
+ case "ArrowUp":
1597
+ case "ArrowLeft":
1598
+ return -1;
1599
+ case "ArrowDown":
1600
+ case "ArrowRight":
1601
+ return 1;
1602
+ }
1603
+ return shift ? -1 : 1;
1604
+ };
1605
+
1606
+ // packages/ui/react-ui-table/src/components/Table/Table.tsx
1607
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-table/src/components/Table/Table.tsx";
1608
+ var inlineEndLine = "[&>.dx-grid]:relative [&>.dx-grid]:after:absolute [&>.dx-grid]:after:inset-block-0 [&>.dx-grid]:after:-inline-end-px [&>.dx-grid]:after:is-px [&>.dx-grid]:after:bg-separator";
1609
+ var blockEndLine = "[&>.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";
1610
+ var frozen = {
1611
+ frozenRowsStart: 1,
1612
+ frozenColsStart: 1,
1613
+ frozenColsEnd: 1
1614
+ };
1615
+ var TableRoot = ({ children, role }) => {
1616
+ return /* @__PURE__ */ React7.createElement("div", {
1617
+ role: "none",
1618
+ className: mx2("relative border-bs !border-separator", role === "section" ? "attention-surface overflow-hidden [&_.dx-grid]:max-is-[--dx-grid-content-inline-size]" : "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]")
1619
+ }, children);
1620
+ };
1621
+ var TableMain = /* @__PURE__ */ forwardRef(({ model, presentation, ignoreAttention }, forwardedRef) => {
1622
+ const [dxGrid, setDxGrid] = useState2(null);
1623
+ const { hasAttention } = useAttention(model?.table ? fullyQualifiedId(model.table) : "table");
1624
+ const { t } = useTranslation3(translationKey);
1625
+ const modals = useMemo4(() => new ModalController(), []);
1626
+ useImperativeHandle(forwardedRef, () => {
1627
+ if (!presentation || !dxGrid) {
1628
+ return {};
1629
+ }
1630
+ dxGrid.getCells = (range, plane) => presentation.getCells(range, plane);
1631
+ return {
1632
+ update: (cell) => {
1633
+ if (cell) {
1634
+ dxGrid.updateIfWithinBounds(cell);
1635
+ } else {
1636
+ dxGrid.updateCells(true);
1637
+ dxGrid.requestUpdate();
1638
+ }
1639
+ }
1640
+ };
1641
+ }, [
1642
+ presentation,
1643
+ dxGrid
1644
+ ]);
1645
+ const handleGridClick = useCallback4((event) => {
1646
+ if (!modals) {
1647
+ return;
1648
+ }
1649
+ const buttonSelector = Object.values(tableButtons).map((button) => `button[${button.attr}]`).join(",");
1650
+ const tableButtonElement = event.target.closest(buttonSelector);
1651
+ if (tableButtonElement) {
1652
+ const button = Object.values(tableButtons).find((btn) => tableButtonElement.hasAttribute(btn.attr));
1653
+ if (!button) {
1654
+ return;
1655
+ }
1656
+ modals.setTrigger(tableButtonElement);
1657
+ const data = button.getData(tableButtonElement);
1658
+ switch (data.type) {
1659
+ case "rowMenu": {
1660
+ modals.showRowMenu(data.rowIndex);
1661
+ break;
1662
+ }
1663
+ case "columnSettings": {
1664
+ modals.showColumnMenu(data.fieldId);
1665
+ break;
1666
+ }
1667
+ case "newColumn": {
1668
+ modals.showColumnCreator();
1669
+ break;
1670
+ }
1671
+ case "referencedCell": {
1672
+ modals.showReferencePanel(data.targetId, data.schemaId);
1673
+ break;
1674
+ }
1675
+ case "sort": {
1676
+ model?.sorting?.toggleSort(data.fieldId);
1677
+ break;
1678
+ }
1480
1679
  }
1481
- const field = this._table.view?.fields.find((field2) => field2.id === sort.fieldId);
1482
- if (!field) {
1483
- return this._rows.value;
1680
+ return;
1681
+ }
1682
+ const controlSelector = Object.values(tableControls).map((control) => `input[${control.attr}]`).join(",");
1683
+ const controlElement = event.target.closest(controlSelector);
1684
+ if (controlElement) {
1685
+ const control = Object.values(tableControls).find((ctrl) => controlElement.hasAttribute(ctrl.attr));
1686
+ if (!control) {
1687
+ return;
1484
1688
  }
1485
- const dataWithIndices = this._rows.value.map((item, index) => {
1486
- const value = getValue2(item, field.path);
1487
- return {
1488
- item,
1489
- index,
1490
- value,
1491
- isEmpty: value == null || value === ""
1492
- };
1493
- });
1494
- const sorted = orderBy(dataWithIndices, [
1495
- "isEmpty",
1496
- "value"
1497
- ], [
1498
- "asc",
1499
- sort.direction
1500
- ]);
1501
- for (let displayIndex = 0; displayIndex < sorted.length; displayIndex++) {
1502
- const { index: dataIndex } = sorted[displayIndex];
1503
- if (displayIndex !== dataIndex) {
1504
- this._displayToDataIndex.set(displayIndex, dataIndex);
1689
+ const data = control.getData(controlElement);
1690
+ switch (data.type) {
1691
+ case "checkbox": {
1692
+ if (data.header) {
1693
+ model?.selection.setSelection(model.selection.allRowsSeleted.value ? "none" : "all");
1694
+ } else {
1695
+ model?.selection.toggleSelectionForRowIndex(data.rowIndex);
1696
+ }
1697
+ break;
1698
+ }
1699
+ case "switch": {
1700
+ if (model) {
1701
+ model.updateCellData({
1702
+ row: data.rowIndex,
1703
+ col: data.colIndex
1704
+ }, (value) => !value);
1705
+ }
1706
+ break;
1505
1707
  }
1506
1708
  }
1507
- return sorted.map(({ item }) => item);
1508
- });
1509
- }
1510
- initializeEffects() {
1511
- const rowOrderWatcher = effect2(() => {
1512
- touch(this._sortedRows.value);
1513
- this._onRowOrderChanged?.();
1514
- });
1515
- this._ctx.onDispose(rowOrderWatcher);
1516
- const rowEffectManager = effect2(() => {
1517
- const { start, end } = touch(this._visibleRange.value);
1518
- const rowEffects = [];
1519
- for (let row = start.row; row <= end.row; row++) {
1520
- rowEffects.push(effect2(() => {
1521
- const obj = this._sortedRows.value[row];
1522
- this?._table?.view?.fields.forEach((field) => touch(getValue2(obj, field.path)));
1523
- this._onCellUpdate?.({
1524
- row,
1525
- col: start.col,
1526
- plane: "grid"
1527
- });
1528
- }));
1709
+ }
1710
+ }, [
1711
+ model,
1712
+ modals
1713
+ ]);
1714
+ const handleFocus = useCallback4((increment, delta) => {
1715
+ if (dxGrid) {
1716
+ dxGrid.refocus(increment, delta);
1717
+ }
1718
+ }, [
1719
+ dxGrid
1720
+ ]);
1721
+ const handleEnter = useCallback4((cell) => {
1722
+ if (model && cell.row === model.getRowCount() - 1) {
1723
+ model.insertRow(cell.row);
1724
+ if (dxGrid) {
1725
+ requestAnimationFrame(() => {
1726
+ dxGrid?.scrollToRow(cell.row + 1);
1727
+ dxGrid?.refocus("row", 1);
1728
+ });
1529
1729
  }
1530
- return () => rowEffects.forEach((cleanup) => cleanup());
1531
- });
1532
- this._ctx.onDispose(rowEffectManager);
1533
- }
1534
- //
1535
- // Column Operations
1536
- //
1537
- deleteColumn(fieldId) {
1538
- if (!this.table.view) {
1730
+ }
1731
+ }, [
1732
+ model,
1733
+ dxGrid
1734
+ ]);
1735
+ const handleKeyDown = useCallback4((event) => {
1736
+ const cell = closestCell(event.target);
1737
+ if (!model || !cell) {
1539
1738
  return;
1540
1739
  }
1541
- const field = this.table.view.fields.find((field2) => field2.id === fieldId);
1542
- if (field && this._onDeleteColumn) {
1543
- if (this._sorting.value?.fieldId === fieldId) {
1544
- this.clearSort();
1740
+ switch (event.key) {
1741
+ case "Backspace":
1742
+ case "Delete": {
1743
+ model.setCellData(cell, void 0);
1744
+ break;
1545
1745
  }
1546
- this._onDeleteColumn(field.id);
1547
1746
  }
1548
- }
1549
- //
1550
- // Resize
1551
- //
1552
- setColumnWidth(columnIndex, width) {
1553
- const fields = this.table.view?.fields ?? [];
1554
- if (columnIndex < fields.length) {
1555
- const newWidth = Math.max(0, width);
1556
- const field = fields[columnIndex];
1557
- if (field) {
1558
- field.size = newWidth;
1747
+ }, [
1748
+ model
1749
+ ]);
1750
+ const handleAxisResize = useCallback4((event) => {
1751
+ if (event.axis === "col") {
1752
+ const columnIndex = parseInt(event.index, 10);
1753
+ model?.setColumnWidth(columnIndex, event.size);
1754
+ }
1755
+ }, [
1756
+ model
1757
+ ]);
1758
+ const handleWheel = useCallback4((event) => {
1759
+ if (!ignoreAttention && !hasAttention) {
1760
+ event.stopPropagation();
1761
+ }
1762
+ }, [
1763
+ hasAttention,
1764
+ ignoreAttention
1765
+ ]);
1766
+ const handleNewColumn = useCallback4(() => {
1767
+ const columns = model?.getColumnCount();
1768
+ if (dxGrid && columns) {
1769
+ dxGrid.scrollToColumn(columns - 1);
1770
+ }
1771
+ }, [
1772
+ model,
1773
+ dxGrid
1774
+ ]);
1775
+ const handleQuery = useCallback4(async ({ field, props }, text) => {
1776
+ if (model && props.referenceSchema && field.referencePath) {
1777
+ const space = getSpace4(model.table);
1778
+ invariant3(space, void 0, {
1779
+ F: __dxlog_file4,
1780
+ L: 264,
1781
+ S: void 0,
1782
+ A: [
1783
+ "space",
1784
+ ""
1785
+ ]
1786
+ });
1787
+ const schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
1788
+ if (schema) {
1789
+ const { objects } = await space.db.query(Filter.schema(schema)).run();
1790
+ const options = objects.map((obj) => {
1791
+ const value = getValue4(obj, field.referencePath);
1792
+ if (!value || typeof value !== "string") {
1793
+ return void 0;
1794
+ }
1795
+ return {
1796
+ label: value,
1797
+ data: obj
1798
+ };
1799
+ }).filter(isNotFalsy);
1800
+ return [
1801
+ ...options,
1802
+ {
1803
+ label: t("create new object label", {
1804
+ text
1805
+ }),
1806
+ data: createOption(text)
1807
+ }
1808
+ ];
1559
1809
  }
1560
1810
  }
1811
+ return [];
1812
+ }, [
1813
+ model
1814
+ ]);
1815
+ if (!model || !modals) {
1816
+ return /* @__PURE__ */ React7.createElement("span", {
1817
+ role: "none",
1818
+ className: "attention-surface"
1819
+ });
1561
1820
  }
1562
- //
1563
- // Sorting
1564
- //
1565
- setSort(fieldId, direction) {
1566
- this._sorting.value = {
1567
- fieldId,
1568
- direction
1569
- };
1570
- }
1571
- clearSort() {
1572
- this._sorting.value = void 0;
1573
- }
1574
- //
1575
- // Pinning
1576
- //
1577
- // TODO(burdon): Change to setPinned(on/off).
1578
- pinRow(rowIndex, side) {
1579
- this._pinnedRows[side].push(rowIndex);
1580
- }
1581
- unpinRow(rowIndex) {
1582
- this._pinnedRows.top = this._pinnedRows.top.filter((index) => index !== rowIndex);
1583
- this._pinnedRows.bottom = this._pinnedRows.bottom.filter((index) => index !== rowIndex);
1584
- }
1821
+ return /* @__PURE__ */ React7.createElement(Grid.Root, {
1822
+ id: model.table.id ?? "table-grid"
1823
+ }, /* @__PURE__ */ React7.createElement(TableCellEditor, {
1824
+ model,
1825
+ modals,
1826
+ onEnter: handleEnter,
1827
+ onFocus: handleFocus,
1828
+ onQuery: handleQuery
1829
+ }), /* @__PURE__ */ React7.createElement(Grid.Content, {
1830
+ onWheelCapture: handleWheel,
1831
+ className: mx2("[--dx-grid-base:var(--surface-bg)]", inlineEndLine, blockEndLine),
1832
+ frozen,
1833
+ columns: model.columnMeta.value,
1834
+ limitRows: model.getRowCount() ?? 0,
1835
+ limitColumns: model.table.view?.target?.fields?.length ?? 0,
1836
+ onAxisResize: handleAxisResize,
1837
+ onClick: handleGridClick,
1838
+ onKeyDown: handleKeyDown,
1839
+ overscroll: "trap",
1840
+ ref: setDxGrid
1841
+ }), /* @__PURE__ */ React7.createElement(RowActionsMenu, {
1842
+ model,
1843
+ modals
1844
+ }), /* @__PURE__ */ React7.createElement(ColumnActionsMenu, {
1845
+ model,
1846
+ modals
1847
+ }), /* @__PURE__ */ React7.createElement(ColumnSettings, {
1848
+ model,
1849
+ modals,
1850
+ onNewColumn: handleNewColumn
1851
+ }), /* @__PURE__ */ React7.createElement(RefPanel, {
1852
+ model,
1853
+ modals
1854
+ }), /* @__PURE__ */ React7.createElement(CreateRefPanel, {
1855
+ model,
1856
+ modals
1857
+ }));
1858
+ });
1859
+ var Table = {
1860
+ Root: TableRoot,
1861
+ Main: TableMain
1862
+ };
1863
+
1864
+ // packages/ui/react-ui-table/src/components/TableToolbar/TableToolbar.tsx
1865
+ import React8, { useCallback as useCallback5, useMemo as useMemo5 } from "react";
1866
+ import { create as create3 } from "@dxos/live-object";
1867
+ import { createGapSeparator, createMenuAction, MenuProvider, ToolbarMenu, useMenuActions } from "@dxos/react-ui-menu";
1868
+ var createTableToolbarActions = (state) => {
1869
+ const add = createMenuAction("add-row", {
1870
+ type: "add-row",
1871
+ icon: "ph--plus--regular",
1872
+ label: [
1873
+ "add row",
1874
+ {
1875
+ ns: translationKey
1876
+ }
1877
+ ],
1878
+ testId: "table.toolbar.add-row"
1879
+ });
1880
+ const save = createMenuAction("save-view", {
1881
+ type: "save-view",
1882
+ icon: "ph--floppy-disk--regular",
1883
+ label: [
1884
+ "save view label",
1885
+ {
1886
+ ns: translationKey
1887
+ }
1888
+ ],
1889
+ testId: "table.toolbar.save-view",
1890
+ iconOnly: false,
1891
+ hidden: !state.viewDirty
1892
+ });
1893
+ const gap = createGapSeparator();
1894
+ const comment = createMenuAction("comment", {
1895
+ type: "comment",
1896
+ icon: "ph--chat-text--regular",
1897
+ label: [
1898
+ "create comment",
1899
+ {
1900
+ ns: translationKey
1901
+ }
1902
+ ],
1903
+ testId: "table.toolbar.comment"
1904
+ });
1905
+ const nodes = [
1906
+ add,
1907
+ save,
1908
+ ...gap.nodes,
1909
+ comment
1910
+ ];
1911
+ return {
1912
+ nodes,
1913
+ edges: nodes.map(({ id: target }) => ({
1914
+ source: "root",
1915
+ target
1916
+ }))
1917
+ };
1918
+ };
1919
+ var TableToolbar = ({ classNames, viewDirty, attendableId, onAction }) => {
1920
+ const state = useMemo5(() => create3({
1921
+ viewDirty
1922
+ }), []);
1923
+ const actionsCreator = useCallback5(() => createTableToolbarActions(state), [
1924
+ state
1925
+ ]);
1926
+ const menu = useMenuActions(actionsCreator);
1927
+ return /* @__PURE__ */ React8.createElement(MenuProvider, {
1928
+ ...menu,
1929
+ attendableId,
1930
+ onAction
1931
+ }, /* @__PURE__ */ React8.createElement(ToolbarMenu, {
1932
+ classNames
1933
+ }));
1585
1934
  };
1586
1935
 
1587
1936
  // packages/ui/react-ui-table/src/hooks/useTableModel.ts
1937
+ import { effect as effect3 } from "@preact/signals-core";
1938
+ import { useEffect as useEffect2, useState as useState3 } from "react";
1939
+ import { useSelectionActions } from "@dxos/react-ui-attention";
1588
1940
  var useTableModel = ({ objects, table, projection, ...props }) => {
1589
1941
  const [model, setModel] = useState3();
1590
1942
  useEffect2(() => {
@@ -1617,18 +1969,42 @@ var useTableModel = ({ objects, table, projection, ...props }) => {
1617
1969
  model,
1618
1970
  objects
1619
1971
  ]);
1972
+ const { select, clear } = useSelectionActions(table?.id);
1973
+ useEffect2(() => {
1974
+ if (!model) {
1975
+ return;
1976
+ }
1977
+ const unsubscribe = effect3(() => {
1978
+ select([
1979
+ ...model.selection.selection.value
1980
+ ]);
1981
+ });
1982
+ return () => {
1983
+ clear();
1984
+ unsubscribe();
1985
+ };
1986
+ }, [
1987
+ model
1988
+ ]);
1620
1989
  return model;
1621
1990
  };
1622
1991
  export {
1992
+ BUTTON_IDENTIFIERS,
1993
+ CONTROL_IDENTIFIERS,
1994
+ ModalController,
1623
1995
  Table,
1996
+ TableModel,
1997
+ TablePresentation,
1624
1998
  TableSchema,
1999
+ TableToolbar,
1625
2000
  TableType,
1626
- Toolbar,
1627
2001
  initializeTable,
2002
+ renderCheckbox,
2003
+ renderSwitch,
1628
2004
  tableButtons,
2005
+ tableControls,
1629
2006
  touch,
1630
2007
  translations_default as translations,
1631
- useTableModel,
1632
- useToolbarContext
2008
+ useTableModel
1633
2009
  };
1634
2010
  //# sourceMappingURL=index.mjs.map