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

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