@dxos/react-ui-table 0.7.4 → 0.7.5-main.9cb18ac

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