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