@dxos/react-ui-table 0.7.4-staging.f7e8224 → 0.7.5-main.499c70c

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